diff --git a/compiler/src/generators/protobuf.ts b/compiler/src/generators/protobuf.ts index 4fb895a..18e5246 100644 --- a/compiler/src/generators/protobuf.ts +++ b/compiler/src/generators/protobuf.ts @@ -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 { ... }` block referenced by `payload: `. + // 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