feat(speckl): emit TransitionNode and OneofNode in proto generator (cycle 64)
The .proto generator now recognizes two new top-level speck members
and emits real proto3 syntax for them:
oneof Name { oneof is NOT emitted at top level
variant_field: VariantType (it lives inside the transition's
} payload block — see below)
transition Name { emitted as:
type: <EnumName> message Name {
payload: <OneofName> <regular fields>
hlc: Bytes oneof payload {
} VariantType variant_field = 10;
...
}
}
The and fields are special-cased: type records the
discriminator enum reference (so future type-aware code can find it),
payload records the sibling oneof block to inline. Neither is emitted
as a regular proto field.
The oneof variants reference the suffixed message name when the
speck has 'event_suffix: "Payload"' (or any suffix). This makes the
generated variants match the consumer's Go types in the federated-
meetup case: 'CreateGroupPayload create_group = 10' is the type
binding the consumer's apply switch expects.
End-to-end verified: a speck with
event_suffix: "Payload"
oneof TransitionPayload {
create_group: CreateGroup
add_steward: AddSteward
}
transition Transition {
type: TransitionType
payload: TransitionPayload
hlc: Bytes
}
event CreateGroup { name: String }
event AddSteward { new_steward: String }
emits the right proto3:
message CreateGroupPayload { ... }
message AddStewardPayload { ... }
message Transition {
bytes hlc = 1;
oneof payload {
CreateGroupPayload create_group = 10;
AddStewardPayload add_steward = 11;
}
}
All 55 tests pass. tsc build clean.
The federated-meetup speckdl (specs/FederatedMeetup.speckdl) does
not yet declare the Transition envelope — it uses 13 separate
event blocks. Adding the transition+oneof envelope to the real
spec is the next step (cycle 65).
This commit is contained in:
parent
4d95a2b975
commit
f37c0ccc57
1 changed files with 37 additions and 3 deletions
|
|
@ -22,7 +22,7 @@
|
|||
* existing determinism claim.
|
||||
*/
|
||||
|
||||
import { AST, SpeckNode, MemberNode, StateNode, EventNode, InterfaceNode, TypeExpr, ServiceNode } from '../parser.js';
|
||||
import { AST, SpeckNode, MemberNode, StateNode, EventNode, InterfaceNode, TypeExpr, ServiceNode, TransitionNode, OneofNode } from '../parser.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
|
|
@ -270,8 +270,8 @@ function emitSpeck(speck: SpeckNode, packageName?: string): string {
|
|||
let protoType = speckTypeToProto(v.typeExpr.name || '');
|
||||
if (v.typeExpr.type === 'map' && v.typeExpr.keyType && v.typeExpr.valueType) {
|
||||
const k = speckTypeToProto(v.typeExpr.keyType.name || 'string');
|
||||
const val = speckTypeToProto(v.typeExpr.valueType.name || 'bytes');
|
||||
protoType = `map<${k}, ${val}>`;
|
||||
const v2 = speckTypeToProto(v.typeExpr.valueType.name || 'bytes');
|
||||
protoType = `map<${k}, ${v2}>`;
|
||||
} else if (v.typeExpr.type === 'list' && v.typeExpr.elementType) {
|
||||
const inner = speckTypeToProto(v.typeExpr.elementType.name || 'string');
|
||||
protoType = `repeated ${inner}`;
|
||||
|
|
@ -282,6 +282,40 @@ function emitSpeck(speck: SpeckNode, packageName?: string): string {
|
|||
lines.push('');
|
||||
}
|
||||
|
||||
// Cycle 64: emit TransitionNode messages. A `transition Name { ... }`
|
||||
// block produces a proto message with the regular fields plus an inline
|
||||
// `oneof payload { ... }` block, whose variants are pulled from a
|
||||
// sibling `oneof <Name> { ... }` block referenced by `payload: <Name>`.
|
||||
// Field numbers are assigned starting at 1; the oneof variants use
|
||||
// 10, 11, 12, ... (proto convention: leave 1-15 for the most stable
|
||||
// fields so the wire format is forward-compatible).
|
||||
const transitionNodes = members.filter(m => m.type === 'transition') as TransitionNode[];
|
||||
const oneofNodes = members.filter(m => m.type === 'oneof') as OneofNode[];
|
||||
for (const t of transitionNodes) {
|
||||
lines.push(`message ${t.name} {`);
|
||||
const fieldNums = assignFieldNumbers(t.fields);
|
||||
for (const f of t.fields) {
|
||||
const num = fieldNums.get(f.name) || 1;
|
||||
const protoType = speckTypeToProto(f.type.name || '');
|
||||
lines.push(` ${protoType} ${snakeCase(f.name)} = ${num};`);
|
||||
}
|
||||
if (t.payloadOneof) {
|
||||
const oneof = oneofNodes.find(o => o.name === t.payloadOneof);
|
||||
if (oneof) {
|
||||
lines.push(' oneof payload {');
|
||||
oneof.variants.forEach((v, i) => {
|
||||
// Reference the suffixed message name if event_suffix is set,
|
||||
// so the oneof variants match what the consumer Go code expects.
|
||||
const variantType = speck.eventSuffix ? v.typeName + speck.eventSuffix : v.typeName;
|
||||
lines.push(` ${variantType} ${v.fieldName} = ${i + 10};`);
|
||||
});
|
||||
lines.push(' }');
|
||||
}
|
||||
}
|
||||
lines.push('}');
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// ─── Service (RPC surface) ──────────────────────────────────
|
||||
// ServiceNode members emit proto3 service blocks. These map directly to
|
||||
// gRPC and ConnectRPC service definitions; buf generate + protoc-gen-go
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue