behavior-contracts 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/authoring.d.ts +215 -3
- package/dist/authoring.d.ts.map +1 -1
- package/dist/authoring.js +338 -8
- package/dist/authoring.js.map +1 -1
- package/dist/behavior.d.ts +101 -3
- package/dist/behavior.d.ts.map +1 -1
- package/dist/behavior.js +126 -4
- package/dist/behavior.js.map +1 -1
- package/dist/bind.d.ts +64 -0
- package/dist/bind.d.ts.map +1 -0
- package/dist/bind.js +131 -0
- package/dist/bind.js.map +1 -0
- package/dist/builder.d.ts +30 -88
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +114 -59
- package/dist/builder.js.map +1 -1
- package/dist/expr-operator-types.d.ts +89 -0
- package/dist/expr-operator-types.d.ts.map +1 -0
- package/dist/expr-operator-types.js +134 -0
- package/dist/expr-operator-types.js.map +1 -0
- package/dist/generator/async-plan.d.ts.map +1 -1
- package/dist/generator/async-plan.js +4 -0
- package/dist/generator/async-plan.js.map +1 -1
- package/dist/generator/core.d.ts +15 -0
- package/dist/generator/core.d.ts.map +1 -1
- package/dist/generator/core.js +2 -0
- package/dist/generator/core.js.map +1 -1
- package/dist/generator/emit-php.d.ts.map +1 -1
- package/dist/generator/emit-php.js +1 -0
- package/dist/generator/emit-php.js.map +1 -1
- package/dist/generator/emit-python.d.ts.map +1 -1
- package/dist/generator/emit-python.js +1 -0
- package/dist/generator/emit-python.js.map +1 -1
- package/dist/generator/emit-straightline-typed-typescript.d.ts.map +1 -1
- package/dist/generator/emit-straightline-typed-typescript.js +1 -0
- package/dist/generator/emit-straightline-typed-typescript.js.map +1 -1
- package/dist/generator/emit-typed-native-go.d.ts.map +1 -1
- package/dist/generator/emit-typed-native-go.js +364 -14
- package/dist/generator/emit-typed-native-go.js.map +1 -1
- package/dist/generator/emit-typed-native-rust.d.ts.map +1 -1
- package/dist/generator/emit-typed-native-rust.js +347 -21
- package/dist/generator/emit-typed-native-rust.js.map +1 -1
- package/dist/generator/emit-typescript.d.ts.map +1 -1
- package/dist/generator/emit-typescript.js +24 -8
- package/dist/generator/emit-typescript.js.map +1 -1
- package/dist/generator/index.d.ts +1 -1
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.js +7 -5
- package/dist/generator/index.js.map +1 -1
- package/dist/generator/straightline.d.ts.map +1 -1
- package/dist/generator/straightline.js +21 -1
- package/dist/generator/straightline.js.map +1 -1
- package/dist/guard.js +20 -1
- package/dist/guard.js.map +1 -1
- package/dist/index.d.ts +42 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -7
- package/dist/index.js.map +1 -1
- package/dist/provenance.d.ts +102 -0
- package/dist/provenance.d.ts.map +1 -0
- package/dist/provenance.js +128 -0
- package/dist/provenance.js.map +1 -0
- package/dist/type-gate.d.ts +63 -0
- package/dist/type-gate.d.ts.map +1 -0
- package/dist/type-gate.js +295 -0
- package/dist/type-gate.js.map +1 -0
- package/package.json +4 -3
|
@@ -119,13 +119,17 @@ function staticStringArrElems(node) {
|
|
|
119
119
|
* Value-typed input) FAILS CLOSED — the covered read plane admits NO boxed escape (there is no
|
|
120
120
|
* `dslcontracts.Value` fallback on the native module). `where` names the emission site for the error.
|
|
121
121
|
*/
|
|
122
|
-
function portFieldGoType(node, inputPorts, where = "port", asBinding, plan) {
|
|
122
|
+
function portFieldGoType(node, inputPorts, where = "port", asBinding, plan, typedNodes) {
|
|
123
123
|
if (staticStringArrElems(node) !== null)
|
|
124
124
|
return "[]string";
|
|
125
125
|
// #110: a componentRef port bound to a runtime array input port (with a resolvable elemType) → []ElemT.
|
|
126
126
|
const arrElem = portArrayElemRef(node, inputPorts, plan);
|
|
127
127
|
if (arrElem !== null)
|
|
128
128
|
return `[]${renderTypeRef(arrElem)}`;
|
|
129
|
+
// #129: a leaf port that is a ref into a PRIOR NODE's typed arr outType (node→leaf array dataflow) → []ElemT.
|
|
130
|
+
const priorArr = priorNodeArrElemInfo(node, typedNodes, plan);
|
|
131
|
+
if (priorArr !== null)
|
|
132
|
+
return `[]${renderTypeRef(priorArr.elemRef)}`;
|
|
129
133
|
const num = numLiteralGoExpr(node);
|
|
130
134
|
if (num !== null)
|
|
131
135
|
return Number.isInteger(node) ? "int64" : "float64";
|
|
@@ -322,6 +326,39 @@ function coveredMapNode(n, bodyIds, comp) {
|
|
|
322
326
|
return false;
|
|
323
327
|
return true;
|
|
324
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* coveredFanoutNode (v3) — a FANOUT node (first-class `@refs` id-list fan-out) is covered when:
|
|
331
|
+
* - over is a static ref to a PRIOR BODY NODE's typed arr field OR an INPUT array port with elemType,
|
|
332
|
+
* - every element port is static (literal / ref into the $as binding / concat / static arr),
|
|
333
|
+
* - default 'fail' policy, relationKind connection (guard-checked by the guard/builder).
|
|
334
|
+
* The fanout de-boxes to: one deduped batched dispatch → first-seen dedupe (by the elem's dedupeKey
|
|
335
|
+
* field) + dangling drop + implicitSource strip + connection wrap {items, cursor:nil} — NO boxed Value.
|
|
336
|
+
*/
|
|
337
|
+
function coveredFanoutNode(n, bodyIds, comp) {
|
|
338
|
+
if (n === null || typeof n !== "object" || !("fanout" in n))
|
|
339
|
+
return false;
|
|
340
|
+
const f = n.fanout;
|
|
341
|
+
if (f.policy !== undefined && f.policy !== "fail")
|
|
342
|
+
return false;
|
|
343
|
+
if (f.relationKind !== "connection")
|
|
344
|
+
return false;
|
|
345
|
+
const overE = classifyExpr(f.over);
|
|
346
|
+
if (overE.kind !== "ref")
|
|
347
|
+
return false;
|
|
348
|
+
const head = overE.path[0];
|
|
349
|
+
if (!bodyIds.has(head)) {
|
|
350
|
+
if (overE.path.length !== 1)
|
|
351
|
+
return false;
|
|
352
|
+
const schema = comp.inputPorts?.[head];
|
|
353
|
+
if (schema === undefined || schema.elemType === undefined)
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
const ports = f.ports;
|
|
357
|
+
for (const p of Object.keys(ports))
|
|
358
|
+
if (!portIsStatic(ports[p]))
|
|
359
|
+
return false;
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
325
362
|
/** A covered read SHAPE (typing-independent) — sequential componentRef reads + covered map...into,
|
|
326
363
|
* OR a real-concurrency staged plan (bc#87) whose parallel-stage members are plain componentRef point
|
|
327
364
|
* reads. A real-concurrency plan whose parallel members include a map / bindField-relation child is
|
|
@@ -366,6 +403,13 @@ function coverageRejectReason(comp) {
|
|
|
366
403
|
return `node '${n.id}' cond has no outType (required to de-box the branch join)`;
|
|
367
404
|
continue;
|
|
368
405
|
}
|
|
406
|
+
if ("fanout" in n) {
|
|
407
|
+
// v3: a first-class fanout node (deduped connection list-in fan-out) IS covered when its over is a
|
|
408
|
+
// covered ref (prior-node arr / input array with elemType) and its element ports are static.
|
|
409
|
+
if (!coveredFanoutNode(n, bodyIds, comp))
|
|
410
|
+
return `node '${n.id}' is a non-covered fanout shape (over an input array without elemType, or a dynamic element port)`;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
369
413
|
if ("map" in n) {
|
|
370
414
|
// #86 pt2 batched map...into / #93 shape 2/3 / #108 guarded map (no into) / map-over-input (with
|
|
371
415
|
// elemType) ARE covered. A non-covered map shape (guard+into heterogeneous / over-input without
|
|
@@ -412,13 +456,13 @@ function assertTypedCoverage(comp) {
|
|
|
412
456
|
throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go combined read (bc#77): component '${comp.name}' is a sequential componentRef read but lacks outType/outputType on every node — the combined emitter de-boxes the RESULT into the node's outType struct, so a covered read MUST be fully typed (bc#45). Add outType annotations, or regenerate on go-straightline-native (boxed result).`);
|
|
413
457
|
}
|
|
414
458
|
// ── native ports struct + PortReader ──────────────────────────────────────────────────
|
|
415
|
-
function emitPortsStruct(comp, node, asBinding, plan) {
|
|
459
|
+
function emitPortsStruct(comp, node, asBinding, plan, typedNodes) {
|
|
416
460
|
const structName = portsStructName(comp.name, node.id);
|
|
417
461
|
const portNames = Object.keys(node.ports);
|
|
418
462
|
if (portNames.length === 0) {
|
|
419
463
|
return `// ${structName} — native ports for node '${node.id}' (${node.component}); no ports.\ntype ${structName} struct{}`;
|
|
420
464
|
}
|
|
421
|
-
const fieldTypes = portNames.map((p) => portFieldGoType(node.ports[p], comp.inputPorts, `port '${p}' of node '${node.id}'`, asBinding, plan));
|
|
465
|
+
const fieldTypes = portNames.map((p) => portFieldGoType(node.ports[p], comp.inputPorts, `port '${p}' of node '${node.id}'`, asBinding, plan, typedNodes));
|
|
422
466
|
const fieldNames = portNames.map(goPortFieldName);
|
|
423
467
|
const fields = portNames
|
|
424
468
|
.map((p, i) => `\t${fieldNames[i]} ${fieldTypes[i]} // ${JSON.stringify(p)}`)
|
|
@@ -486,6 +530,15 @@ function emitRawRowStructs(comp, typedNodes, plan) {
|
|
|
486
530
|
if ("cond" in n)
|
|
487
531
|
continue;
|
|
488
532
|
const ref = typedNodes.get(n.id);
|
|
533
|
+
if ("fanout" in n) {
|
|
534
|
+
// a covered fanout node: RawElem_ is the connection items element (the per-body row the batched
|
|
535
|
+
// handler yields, aligned to the deduped id list); the batch RawRow_ carries the aligned []RawElem_.
|
|
536
|
+
const elemRowRef = fanoutItemsElemRef(n, ref, plan);
|
|
537
|
+
parts.push(emitOneRowStruct(rawElemStructName(comp.name, n.id), elemRowRef, plan));
|
|
538
|
+
parts.push(`// ${rawRowStructName(comp.name, n.id)} — the BATCH row for fanout '${n.id}': the aligned per-body rows.\n` +
|
|
539
|
+
`type ${rawRowStructName(comp.name, n.id)} struct {\n\tIsError bool\n\tErr string\n\tRows []${rawElemStructName(comp.name, n.id)}\n}`);
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
489
542
|
if ("map" in n) {
|
|
490
543
|
// a covered map node: RawElem_<comp>_<node> is the HANDLER'S per-element return shape:
|
|
491
544
|
// - into: the `into` field's type (the fetched item merged onto every over element);
|
|
@@ -583,6 +636,14 @@ function emitHandlerInterface(comp, typedNodes, plan) {
|
|
|
583
636
|
continue; // #108: a cond node has no handler method (pure Expression).
|
|
584
637
|
const method = nodeMethodName(comp.name, n.id);
|
|
585
638
|
const bt = boundGoType(n, comp, typedNodes, plan);
|
|
639
|
+
if ("fanout" in n) {
|
|
640
|
+
// fanout (v3): one batched dispatch of the deduped id list. Ports = the batch struct; returns the
|
|
641
|
+
// aligned batch row (per-body rows) + resolved. The runner applies dedupe/drop/wrap after.
|
|
642
|
+
const portsT = `${portsStructName(comp.name, n.id)}_batch`;
|
|
643
|
+
const retT = rawRowStructName(comp.name, n.id);
|
|
644
|
+
lines.push(`\t${method}(ports ${portsT}, bound ${bt}) (${retT}, bool)`);
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
586
647
|
if ("map" in n) {
|
|
587
648
|
const m = n.map;
|
|
588
649
|
const batched = m.batched === true;
|
|
@@ -626,6 +687,59 @@ type ${batchStruct} struct {
|
|
|
626
687
|
Items []${structName}
|
|
627
688
|
}`;
|
|
628
689
|
}
|
|
690
|
+
/** emitFanoutPortsStructs (v3) — the per-body element ports struct + the batch struct for a covered
|
|
691
|
+
* fanout node (identical shape to a batched map's ports). The element ports read the `$as` id binding. */
|
|
692
|
+
function emitFanoutPortsStructs(comp, node, typedNodes, plan) {
|
|
693
|
+
const f = node.fanout;
|
|
694
|
+
const structName = portsStructName(comp.name, node.id);
|
|
695
|
+
const { elemRef } = fanoutOverElemInfo(comp, node, typedNodes, plan);
|
|
696
|
+
const asBinding = { name: f.as, ref: elemRef, plan };
|
|
697
|
+
const elemStruct = emitPortsStruct(comp, { id: node.id, component: f.component, ports: f.ports }, asBinding, plan);
|
|
698
|
+
const batchStruct = `${structName}_batch`;
|
|
699
|
+
return `${elemStruct}
|
|
700
|
+
|
|
701
|
+
// ${batchStruct} — NATIVE batched ports for fanout '${node.id}': Items is a native slice of the concrete
|
|
702
|
+
// per-id ports structs (${structName}). The consumer reads Items[i].<Field> DIRECTLY (bc#90 / runtime-free).
|
|
703
|
+
type ${batchStruct} struct {
|
|
704
|
+
Items []${structName}
|
|
705
|
+
}`;
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* fanoutItemsElemRef (v3) — the connection items ELEMENT TypeRef for a covered fanout node. The fanout
|
|
709
|
+
* outType is a connection struct `{items:[]Elem, cursor:*string}`; this returns Elem (the per-body row
|
|
710
|
+
* the batched handler yields, aligned to the deduped id list, before dedupe/drop). Fails closed if the
|
|
711
|
+
* outType is not that connection shape (named struct with an `items` arr field).
|
|
712
|
+
*/
|
|
713
|
+
function fanoutItemsElemRef(node, connRef, plan) {
|
|
714
|
+
if (connRef.kind !== "named") {
|
|
715
|
+
throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go fanout (v3): node '${node.id}' outType must be a connection struct {items,cursor} (got ${JSON.stringify(connRef.kind)}).`);
|
|
716
|
+
}
|
|
717
|
+
const decl = findDecl(plan, connRef.name);
|
|
718
|
+
const itemsField = decl.fields.find((fd) => fd.name === "items");
|
|
719
|
+
if (!itemsField || itemsField.type.kind !== "arr") {
|
|
720
|
+
throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go fanout (v3): node '${node.id}' connection struct '${connRef.name}' has no 'items' array field.`);
|
|
721
|
+
}
|
|
722
|
+
return itemsField.type.elem;
|
|
723
|
+
}
|
|
724
|
+
/** fanoutOverElemInfo (v3) — the over id-list element TypeRef + the native Go over-slice expression.
|
|
725
|
+
* Mirrors mapOverElemInfo but reads the FanoutNode.fanout spec (prior-node arr OR input array w/ elemType). */
|
|
726
|
+
function fanoutOverElemInfo(comp, node, typedNodes, plan) {
|
|
727
|
+
const f = node.fanout;
|
|
728
|
+
const e = classifyExpr(f.over);
|
|
729
|
+
if (e.kind === "ref" && e.path.length >= 1 && typedNodes.has(e.path[0])) {
|
|
730
|
+
const baseRef = typedNodes.get(e.path[0]);
|
|
731
|
+
const { expr, ref } = goTypedInternals.typedFieldAccess(typedLocal(e.path[0]), baseRef, e.path.slice(1), plan);
|
|
732
|
+
if (ref.kind === "arr")
|
|
733
|
+
return { elemRef: ref.elem, overExpr: expr };
|
|
734
|
+
}
|
|
735
|
+
if (e.kind === "ref" && e.path.length === 1) {
|
|
736
|
+
const schema = comp.inputPorts?.[e.path[0]];
|
|
737
|
+
const et = schema?.elemType;
|
|
738
|
+
if (et !== undefined)
|
|
739
|
+
return { elemRef: deriveTypeRef(et, plan), overExpr: `in.${goFieldName(e.path[0])}` };
|
|
740
|
+
}
|
|
741
|
+
throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go fanout (v3): fanout '${node.id}' 'over' must be a ref to a prior node's typed arr field OR an input array port with a declared elemType (got ${JSON.stringify(f.over)}).`);
|
|
742
|
+
}
|
|
629
743
|
/** mapElemRowRef — the handler's per-element RETURN shape for a covered map node: the `into` field's
|
|
630
744
|
* type (into) or the element outType directly (no-into). This is the type of RawElem_<comp>_<node>. */
|
|
631
745
|
function mapElemRowRef(node, arrRef, plan) {
|
|
@@ -714,6 +828,43 @@ function mapOverElemInfo(comp, node, typedNodes, plan) {
|
|
|
714
828
|
function mapOverElemType(comp, node, typedNodes, plan) {
|
|
715
829
|
return renderTypeRef(mapOverElemInfo(comp, node, typedNodes, plan).elemRef);
|
|
716
830
|
}
|
|
831
|
+
/**
|
|
832
|
+
* priorNodeArrElemInfo (#129) — resolve a leaf port that is a `ref` into a PRIOR BODY NODE whose
|
|
833
|
+
* annotated `outType` is (or reaches, through a field path) a native array, to its element TypeRef +
|
|
834
|
+
* the native Go slice expression that yields it. This GENERALIZES the `map…over` prior-node-arr
|
|
835
|
+
* dataflow (mapOverElemInfo's first branch) to ANY leaf input port: a fold/dedup leaf whose `items`
|
|
836
|
+
* port is `{ref:["<priorNode>"]}` (or `.items` of a prior connection node) now lowers to `[]ElemT`
|
|
837
|
+
* fed straight from the prior node's typed local — ZERO boxed Value on the read hot path.
|
|
838
|
+
*
|
|
839
|
+
* The element type comes STRICTLY from the prior node's EXPLICIT `outType` annotation walked through
|
|
840
|
+
* typedFieldAccess (BC does NOT infer — consumer-interface C3 / [[graphddb-typed-native-static-link]]):
|
|
841
|
+
* if the head is not a prior body node, or its outType (through the field path) is not an `arr`, this
|
|
842
|
+
* returns null and the caller keeps its existing fail-closed behaviour (never a silent box). A
|
|
843
|
+
* single-segment INPUT-array port (elemType) is a DIFFERENT covered shape (portArrayElemRef) — handled
|
|
844
|
+
* ahead of this. Requires the typedNodes map (prior-node outType TypeRefs) and a TypePlan.
|
|
845
|
+
*/
|
|
846
|
+
function priorNodeArrElemInfo(node, typedNodes, plan) {
|
|
847
|
+
if (typedNodes === undefined || plan === undefined)
|
|
848
|
+
return null;
|
|
849
|
+
if (staticArrElems(node) !== null)
|
|
850
|
+
return null; // a static array literal — not a prior-node ref.
|
|
851
|
+
const e = classifyExpr(node);
|
|
852
|
+
if (e.kind !== "ref" || e.opt || e.path.length < 1)
|
|
853
|
+
return null;
|
|
854
|
+
if (!typedNodes.has(e.path[0]))
|
|
855
|
+
return null; // head is not a prior body node → not this shape.
|
|
856
|
+
const baseRef = typedNodes.get(e.path[0]);
|
|
857
|
+
let acc;
|
|
858
|
+
try {
|
|
859
|
+
acc = goTypedInternals.typedFieldAccess(typedLocal(e.path[0]), baseRef, e.path.slice(1), plan);
|
|
860
|
+
}
|
|
861
|
+
catch {
|
|
862
|
+
return null; // the field path does not resolve on the prior node's outType → fail-closed at caller.
|
|
863
|
+
}
|
|
864
|
+
if (acc.ref.kind !== "arr")
|
|
865
|
+
return null; // the prior-node result (through the path) is not an array.
|
|
866
|
+
return { elemRef: acc.ref.elem, overExpr: acc.expr };
|
|
867
|
+
}
|
|
717
868
|
function classifyPort(node) {
|
|
718
869
|
if (staticArrElems(node) !== null)
|
|
719
870
|
return "static";
|
|
@@ -945,7 +1096,7 @@ function emitNativeScalar(node, want, resolveRef) {
|
|
|
945
1096
|
* plane — a boxed escape would re-introduce the bc-runtime coupling this module removes). Returns the
|
|
946
1097
|
* struct field initializer (`Field: <expr>`). `resolveRef` resolves a ref head for the scalar path.
|
|
947
1098
|
*/
|
|
948
|
-
function emitPortInit(pn, expr, fieldGoType, resolveRef, inputPorts, plan) {
|
|
1099
|
+
function emitPortInit(pn, expr, fieldGoType, resolveRef, inputPorts, plan, typedNodes) {
|
|
949
1100
|
const fieldName = goPortFieldName(pn);
|
|
950
1101
|
// #110: a componentRef port bound to a runtime array input port (with elemType) → the native []ElemT,
|
|
951
1102
|
// read straight from the concrete input struct (`in.<Field>`) — ZERO boxed Value on the read hot path.
|
|
@@ -957,6 +1108,13 @@ function emitPortInit(pn, expr, fieldGoType, resolveRef, inputPorts, plan) {
|
|
|
957
1108
|
return `${fieldName}: in.${goFieldName(e.path[0])}`;
|
|
958
1109
|
}
|
|
959
1110
|
}
|
|
1111
|
+
// #129: a leaf port that is a ref into a PRIOR NODE's typed arr outType (node→leaf array dataflow) →
|
|
1112
|
+
// the native []ElemT read straight off the prior node's typed local (`t_<node>.Field…`) — ZERO boxed
|
|
1113
|
+
// Value on the read hot path. The over-source case (map…over) resolves via mapOverElemInfo; this is
|
|
1114
|
+
// the plain leaf array input port (a fold/dedup leaf whose `items` = a prior node's array result).
|
|
1115
|
+
const priorArr = priorNodeArrElemInfo(expr, typedNodes, plan);
|
|
1116
|
+
if (priorArr !== null)
|
|
1117
|
+
return `${fieldName}: ${priorArr.overExpr}`;
|
|
960
1118
|
// change #2: a static string array (projection) → a native `[]string{...}` literal.
|
|
961
1119
|
const strArr = staticStringArrElems(expr);
|
|
962
1120
|
if (strArr !== null) {
|
|
@@ -1066,8 +1224,8 @@ function emitParallelStageArm(comp, stage, atPos, concurrency, metas, typedNodes
|
|
|
1066
1224
|
const resolveRef = resolveRefAt(atPos);
|
|
1067
1225
|
for (const pn of Object.keys(node.ports)) {
|
|
1068
1226
|
const expr = node.ports[pn];
|
|
1069
|
-
const fieldGoType = portFieldGoType(expr, comp.inputPorts, `port '${pn}' of node '${node.id}'`, undefined, plan);
|
|
1070
|
-
inits.push(emitPortInit(pn, expr, fieldGoType, resolveRef, comp.inputPorts, plan));
|
|
1227
|
+
const fieldGoType = portFieldGoType(expr, comp.inputPorts, `port '${pn}' of node '${node.id}'`, undefined, plan, typedNodes);
|
|
1228
|
+
inits.push(emitPortInit(pn, expr, fieldGoType, resolveRef, comp.inputPorts, plan, typedNodes));
|
|
1071
1229
|
}
|
|
1072
1230
|
lines.push(`${ind}ps_${s} = ${structName}{${inits.join(", ")}}`);
|
|
1073
1231
|
lines.push(`${ind}run_${s} = true`);
|
|
@@ -1220,6 +1378,12 @@ function emitNativeRawRunner(comp, plan, flags) {
|
|
|
1220
1378
|
k += stage.length - 1;
|
|
1221
1379
|
continue;
|
|
1222
1380
|
}
|
|
1381
|
+
// v3: a covered fanout node — deduped batched dispatch + first-seen dedupe + dangling drop +
|
|
1382
|
+
// connection wrap (the SAME definition as run_behavior's fanoutDedupDrop).
|
|
1383
|
+
if ("fanout" in comp.body[i]) {
|
|
1384
|
+
lines.push(emitFanoutArm(comp, comp.body[i], k, typedNodes, plan, priorNodeAt, zeroOut));
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1223
1387
|
// #86 pt2: a covered batched map...into node — struct-native element collection + into enrichment.
|
|
1224
1388
|
if ("map" in comp.body[i]) {
|
|
1225
1389
|
lines.push(emitMapArm(comp, comp.body[i], k, typedNodes, plan, priorNodeAt, zeroOut, flags));
|
|
@@ -1276,8 +1440,8 @@ function emitNativeRawRunner(comp, plan, flags) {
|
|
|
1276
1440
|
const resolveRef = resolveRefAt(k);
|
|
1277
1441
|
for (const pn of portNames) {
|
|
1278
1442
|
const expr = node.ports[pn];
|
|
1279
|
-
const fieldGoType = portFieldGoType(expr, comp.inputPorts, `port '${pn}' of node '${node.id}'`, undefined, plan);
|
|
1280
|
-
inits.push(emitPortInit(pn, expr, fieldGoType, resolveRef, comp.inputPorts, plan));
|
|
1443
|
+
const fieldGoType = portFieldGoType(expr, comp.inputPorts, `port '${pn}' of node '${node.id}'`, undefined, plan, typedNodes);
|
|
1444
|
+
inits.push(emitPortInit(pn, expr, fieldGoType, resolveRef, comp.inputPorts, plan, typedNodes));
|
|
1281
1445
|
}
|
|
1282
1446
|
lines.push(`${indent}ports_${s} := ${structName}{${inits.join(", ")}}`);
|
|
1283
1447
|
const oc = `row_${s}`;
|
|
@@ -1536,6 +1700,132 @@ function emitMapArm(comp, node, atPos, typedNodes, plan, priorNodeAt, zeroOut, f
|
|
|
1536
1700
|
function elemLocal(s) {
|
|
1537
1701
|
return `oel_${s}`;
|
|
1538
1702
|
}
|
|
1703
|
+
/**
|
|
1704
|
+
* emitFanoutArm (v3) — the struct-native exec of a covered FANOUT node. This is the genuinely new
|
|
1705
|
+
* native list-in fan-out handler: build the per-id native ports (all over ids), dispatch the batched
|
|
1706
|
+
* handler ONCE (aligned per-body rows), then apply THE SAME dedupe/drop as run_behavior's
|
|
1707
|
+
* fanoutDedupDrop — first-seen dedupe by the elem's dedupeKey field, drop dangling (a body whose
|
|
1708
|
+
* dedupeKey field is the zero value ≡ the interpreter's null/absent-key body), implicitSource strip
|
|
1709
|
+
* (STRUCTURAL — the connection items elem type already omits the implicitSource field), and wrap into
|
|
1710
|
+
* the connection struct {items, cursor:nil}. NO boxed Value on any plane. Byte-equal to run_behavior.
|
|
1711
|
+
*/
|
|
1712
|
+
function emitFanoutArm(comp, node, atPos, typedNodes, plan, priorNodeAt, zeroOut) {
|
|
1713
|
+
const f = node.fanout;
|
|
1714
|
+
const s = sanitize(node.id);
|
|
1715
|
+
const structName = portsStructName(comp.name, node.id);
|
|
1716
|
+
const batchStruct = `${structName}_batch`;
|
|
1717
|
+
const connRef = typedNodes.get(node.id);
|
|
1718
|
+
const elemRef = fanoutItemsElemRef(node, connRef, plan);
|
|
1719
|
+
const elemGo = renderTypeRef(elemRef);
|
|
1720
|
+
const { elemRef: overElemRef, overExpr } = fanoutOverElemInfo(comp, node, typedNodes, plan);
|
|
1721
|
+
const asName = f.as;
|
|
1722
|
+
const priorHere = (head) => priorNodeAt(head, atPos);
|
|
1723
|
+
const portNames = Object.keys(f.ports);
|
|
1724
|
+
// the dedupeKey Go field on the connection items elem (must be a string scalar for the seen-set key).
|
|
1725
|
+
if (elemRef.kind !== "named")
|
|
1726
|
+
throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go fanout (v3): node '${node.id}' connection items element must be a named struct (got ${JSON.stringify(elemRef.kind)}).`);
|
|
1727
|
+
const elemDecl = findDecl(plan, elemRef.name);
|
|
1728
|
+
const dkField = elemDecl.fields.find((fd) => fd.name === f.dedupeKey);
|
|
1729
|
+
if (!dkField || dkField.type.kind !== "scalar" || dkField.type.scalar !== "string")
|
|
1730
|
+
throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go fanout (v3): node '${node.id}' dedupeKey '${f.dedupeKey}' must be a string field of the connection items element type '${elemRef.name}'.`);
|
|
1731
|
+
const dkGo = goFieldName(f.dedupeKey);
|
|
1732
|
+
// per-id native ports build (same as a batched map's per-element ports). $as → the over id value.
|
|
1733
|
+
const buildPorts = (il, elemVar) => {
|
|
1734
|
+
const inits = [];
|
|
1735
|
+
const resolveRef = (head, restPath, opt) => {
|
|
1736
|
+
if (opt)
|
|
1737
|
+
return null;
|
|
1738
|
+
let baseExpr;
|
|
1739
|
+
let baseRef;
|
|
1740
|
+
if (head === asName) {
|
|
1741
|
+
baseExpr = elemVar;
|
|
1742
|
+
baseRef = overElemRef;
|
|
1743
|
+
}
|
|
1744
|
+
else if (priorHere(head)) {
|
|
1745
|
+
baseExpr = typedLocal(head);
|
|
1746
|
+
baseRef = typedNodes.get(head);
|
|
1747
|
+
}
|
|
1748
|
+
else {
|
|
1749
|
+
if (restPath.length !== 0)
|
|
1750
|
+
return null;
|
|
1751
|
+
const scalar = inputScalarKind(comp.inputPorts?.[head]);
|
|
1752
|
+
if (scalar === undefined)
|
|
1753
|
+
return null;
|
|
1754
|
+
return { expr: `in.${goFieldName(head)}`, scalar };
|
|
1755
|
+
}
|
|
1756
|
+
let acc;
|
|
1757
|
+
let expr;
|
|
1758
|
+
try {
|
|
1759
|
+
const r = goTypedInternals.typedFieldAccess(baseExpr, baseRef, restPath, plan);
|
|
1760
|
+
expr = r.expr;
|
|
1761
|
+
acc = r.ref;
|
|
1762
|
+
}
|
|
1763
|
+
catch {
|
|
1764
|
+
return null;
|
|
1765
|
+
}
|
|
1766
|
+
if (acc.kind !== "scalar" || acc.scalar === "null")
|
|
1767
|
+
return null;
|
|
1768
|
+
return { expr, scalar: goScalar(acc.scalar) };
|
|
1769
|
+
};
|
|
1770
|
+
const asBinding = { name: asName, ref: overElemRef, plan };
|
|
1771
|
+
for (const pn of portNames) {
|
|
1772
|
+
const fieldGoType = portFieldGoType(f.ports[pn], comp.inputPorts, `fanout port '${pn}' of node '${node.id}'`, asBinding, plan);
|
|
1773
|
+
inits.push(emitPortInit(pn, f.ports[pn], fieldGoType, resolveRef, comp.inputPorts, plan));
|
|
1774
|
+
}
|
|
1775
|
+
return [`${il}ep_${s} := ${structName}{${inits.join(", ")}}`];
|
|
1776
|
+
};
|
|
1777
|
+
const lines = [];
|
|
1778
|
+
const closers = [];
|
|
1779
|
+
lines.push(`\t// ── fanout '${node.id}' (${f.component}, dedupeKey:${JSON.stringify(f.dedupeKey)}, drop:${JSON.stringify(f.drop)}${f.implicitSource ? ", implicitSource:" + JSON.stringify(f.implicitSource) : ""}${f.parent ? ", parent:" + f.parent : ""}) ──`);
|
|
1780
|
+
let indent = "\t";
|
|
1781
|
+
if (f.parent !== undefined && priorNodeAt(f.parent, atPos)) {
|
|
1782
|
+
lines.push(`${indent}if produced_${sanitize(f.parent)} {`);
|
|
1783
|
+
closers.unshift(`${indent}}`);
|
|
1784
|
+
indent += "\t";
|
|
1785
|
+
}
|
|
1786
|
+
// over id-list (typed field access — no serialize) → per-id ports (all ids; dedupe is on the result).
|
|
1787
|
+
lines.push(`${indent}over_${s} := ${overExpr}`);
|
|
1788
|
+
lines.push(`${indent}var items_${s} []${elemGo}`);
|
|
1789
|
+
lines.push(`${indent}pi_${s} := make([]${structName}, 0, len(over_${s}))`);
|
|
1790
|
+
lines.push(`${indent}for _, ${elemLocal(s)} := range over_${s} {`);
|
|
1791
|
+
for (const l of buildPorts(indent + "\t", elemLocal(s)))
|
|
1792
|
+
lines.push(l);
|
|
1793
|
+
lines.push(`${indent}\tpi_${s} = append(pi_${s}, ep_${s})`);
|
|
1794
|
+
lines.push(`${indent}}`);
|
|
1795
|
+
// empty id-list => empty connection (handler not called), matching run_behavior.
|
|
1796
|
+
lines.push(`${indent}if len(pi_${s}) > 0 {`);
|
|
1797
|
+
lines.push(`${indent}\tbp_${s} := ${batchStruct}{Items: pi_${s}}`);
|
|
1798
|
+
lines.push(`${indent}\tfo_${s}, fo_${s}Resolved := handlers.${nodeMethodName(comp.name, node.id)}(bp_${s}, nil)`);
|
|
1799
|
+
lines.push(`${indent}\tif !fo_${s}Resolved {`, `${indent}\t\treturn ${zeroOut}, ${goErr("UNKNOWN_COMPONENT", `"component '${f.component}' has no handler (fail-closed)"`)}`, `${indent}\t}`);
|
|
1800
|
+
lines.push(`${indent}\tif fo_${s}.IsError {`, `${indent}\t\treturn ${zeroOut}, ${goErr("OP_FAILED", `"operation '${node.id}' failed under 'fail' policy: "+fo_${s}.Err`)}`, `${indent}\t}`);
|
|
1801
|
+
lines.push(`${indent}\tif len(fo_${s}.Rows) != len(pi_${s}) {`, `${indent}\t\treturn ${zeroOut}, ${goErr("FANOUT_BATCH_RESULT_MISMATCH", `fmt.Sprintf("fanout '${node.id}': batched handler must return a list aligned to the deduped id list (want %d)", len(pi_${s}))`)}`, `${indent}\t}`);
|
|
1802
|
+
// ── THE ONE dedupe/drop (mirrors fanoutDedupDrop verbatim): first-seen dedupe by dedupeKey field;
|
|
1803
|
+
// drop dangling (zero dedupeKey ≡ interpreter's null/absent-key body); implicitSource strip is
|
|
1804
|
+
// structural (the elem type already omits it). cursor is always nil (connection wrap).
|
|
1805
|
+
lines.push(`${indent}\tseen_${s} := make(map[string]struct{}, len(fo_${s}.Rows))`);
|
|
1806
|
+
lines.push(`${indent}\titems_${s} = make([]${elemGo}, 0, len(fo_${s}.Rows))`);
|
|
1807
|
+
lines.push(`${indent}\tfor fi_${s} := range fo_${s}.Rows {`);
|
|
1808
|
+
lines.push(`${indent}\t\tbody_${s} := fo_${s}.Rows[fi_${s}]`);
|
|
1809
|
+
lines.push(`${indent}\t\tvar el_${s} ${elemGo}`);
|
|
1810
|
+
lines.push(emitRowCopy(`el_${s}`, `body_${s}`, elemRef, plan, indent.length + 2));
|
|
1811
|
+
lines.push(`${indent}\t\tkey_${s} := el_${s}.${dkGo}`);
|
|
1812
|
+
if (f.drop === "dangling") {
|
|
1813
|
+
// dangling: a body whose dedupeKey is the zero value (empty string) is dropped.
|
|
1814
|
+
lines.push(`${indent}\t\tif key_${s} == "" {`, `${indent}\t\t\tcontinue`, `${indent}\t\t}`);
|
|
1815
|
+
}
|
|
1816
|
+
lines.push(`${indent}\t\tif _, dup_${s} := seen_${s}[key_${s}]; dup_${s} {`, `${indent}\t\t\tcontinue`, `${indent}\t\t}`);
|
|
1817
|
+
lines.push(`${indent}\t\tseen_${s}[key_${s}] = struct{}{}`);
|
|
1818
|
+
lines.push(`${indent}\t\titems_${s} = append(items_${s}, el_${s})`);
|
|
1819
|
+
lines.push(`${indent}\t}`);
|
|
1820
|
+
lines.push(`${indent}}`);
|
|
1821
|
+
// wrap into the connection struct {Items, Cursor:nil}. A nil items slice serializes to [] (the
|
|
1822
|
+
// connection serializer normalizes it) — matching {items:[],cursor:null} for an empty fan-out.
|
|
1823
|
+
lines.push(`${indent}${typedLocal(node.id)} = ${connRef.name}{${goFieldName("items")}: items_${s}}`);
|
|
1824
|
+
lines.push(`${indent}produced_${s} = true`);
|
|
1825
|
+
for (const c of closers)
|
|
1826
|
+
lines.push(c);
|
|
1827
|
+
return lines.join("\n");
|
|
1828
|
+
}
|
|
1539
1829
|
/** #108: resolve an INPUT port head for a native-expr ref (cond `if` / map `when` / branches). A scalar
|
|
1540
1830
|
* port → `in.<Field>` of the scalar type; an array port WITH elemType → `in.<Field>` of `[]ElemT` (so
|
|
1541
1831
|
* `len(in.<Field>)` works); anything else → null (fail-closed). */
|
|
@@ -1974,7 +2264,11 @@ function emit(ctx) {
|
|
|
1974
2264
|
for (const n of c.body)
|
|
1975
2265
|
typedNodes.set(n.id, deriveTypeRef(n.outType, plan));
|
|
1976
2266
|
for (const n of c.body) {
|
|
1977
|
-
if ("
|
|
2267
|
+
if ("fanout" in n) {
|
|
2268
|
+
// v3: a fanout node emits a per-id element ports struct + a batch struct (like a batched map).
|
|
2269
|
+
structs.push(emitFanoutPortsStructs(c, n, typedNodes, plan));
|
|
2270
|
+
}
|
|
2271
|
+
else if ("map" in n) {
|
|
1978
2272
|
// #86 pt2 / #108: a map node emits an element ports struct + a batch struct. The element ports
|
|
1979
2273
|
// may read the `$as` binding — resolved via the over-element type (prior node / input array).
|
|
1980
2274
|
structs.push(emitMapPortsStructs(c, n, typedNodes, plan));
|
|
@@ -1983,7 +2277,8 @@ function emit(ctx) {
|
|
|
1983
2277
|
// #108: a cond node has NO ports struct (it is a pure Expression — no handler dispatch).
|
|
1984
2278
|
}
|
|
1985
2279
|
else {
|
|
1986
|
-
|
|
2280
|
+
// #129: pass typedNodes so a leaf array port fed by a prior node's arr outType types as []ElemT.
|
|
2281
|
+
structs.push(emitPortsStruct(c, n, undefined, plan, typedNodes));
|
|
1987
2282
|
}
|
|
1988
2283
|
}
|
|
1989
2284
|
// CONCRETE per-node row structs + the per-component handler interface (the fully-concrete seam).
|
|
@@ -2002,7 +2297,7 @@ function emit(ctx) {
|
|
|
2002
2297
|
const needSync = native.some((c) => concurrencyPlan(c) !== null);
|
|
2003
2298
|
// bc#90: the ONLY std-lib `fmt.Sprintf` site on the covered plane is the batched-map result-count
|
|
2004
2299
|
// mismatch error — so `fmt` is imported iff a covered component has a batched map node.
|
|
2005
|
-
const needFmt = native.some((c) => c.body.some((n) => "map" in n && n.map.batched === true));
|
|
2300
|
+
const needFmt = native.some((c) => c.body.some((n) => ("map" in n && n.map.batched === true) || "fanout" in n));
|
|
2006
2301
|
// The FULLY-covered module (no non-native components) gets a MINIMAL, RUNTIME-FREE header — NO
|
|
2007
2302
|
// bc-runtime import at all (bc#90).
|
|
2008
2303
|
// FAIL CLOSED (bc#86/#89): the --typed-native (native codegen) surface is native-only. If ANY
|
|
@@ -2114,6 +2409,39 @@ ${native.map((c) => `\t${handlerIfaceName(c.name)}`).join("\n")}
|
|
|
2114
2409
|
if ("cond" in n)
|
|
2115
2410
|
continue; // #108: cond has no handler method (pure Expression) — no scripted impl.
|
|
2116
2411
|
const ref = typedNodes.get(n.id);
|
|
2412
|
+
if ("fanout" in n) {
|
|
2413
|
+
// v3: scripted fanout impl — drain one batched outcome and decode the aligned []Value into the
|
|
2414
|
+
// per-body elem rows (a null array element materializes to the zero elem row = dangling).
|
|
2415
|
+
const f = n.fanout;
|
|
2416
|
+
const elemRowRef = fanoutItemsElemRef(n, ref, plan);
|
|
2417
|
+
const elemT = rawElemStructName(c.name, n.id);
|
|
2418
|
+
const batchT = rawRowStructName(c.name, n.id);
|
|
2419
|
+
const elemDecode = emitDecodeRow(c.name, n.id, elemT, elemRowRef, plan, emittedDecode, decodeFns, "Elem");
|
|
2420
|
+
const portsT = `${portsStructName(c.name, n.id)}_batch`;
|
|
2421
|
+
const foBoundT = boundGoType(n, c, typedNodes, plan);
|
|
2422
|
+
methodImpls.push(`func (s *ScriptedNativeRaw) ${nodeMethodName(c.name, n.id)}(_ ${portsT}, _ ${foBoundT}) (${batchT}, bool) {
|
|
2423
|
+
src, ok := s.next(${JSON.stringify(f.component)})
|
|
2424
|
+
if !ok {
|
|
2425
|
+
return ${batchT}{}, false
|
|
2426
|
+
}
|
|
2427
|
+
if src.isErr {
|
|
2428
|
+
return ${batchT}{IsError: true, Err: src.err}, true
|
|
2429
|
+
}
|
|
2430
|
+
arr, _ := src.ok.([]${PKG}.Value)
|
|
2431
|
+
rows := make([]${elemT}, 0, len(arr))
|
|
2432
|
+
for _, ev := range arr {
|
|
2433
|
+
// v3: a null aligned body is a DANGLING id — decode it to the zero elem row (empty dedupeKey
|
|
2434
|
+
// field), so the runner's dangling-drop excludes it (byte-equal to fanoutDedupDrop).
|
|
2435
|
+
if ev == nil {
|
|
2436
|
+
rows = append(rows, ${elemT}{})
|
|
2437
|
+
continue
|
|
2438
|
+
}
|
|
2439
|
+
rows = append(rows, ${elemDecode}(ev))
|
|
2440
|
+
}
|
|
2441
|
+
return ${batchT}{Rows: rows}, true
|
|
2442
|
+
}`);
|
|
2443
|
+
continue;
|
|
2444
|
+
}
|
|
2117
2445
|
if ("map" in n) {
|
|
2118
2446
|
const m = n.map;
|
|
2119
2447
|
const batched = m.batched === true;
|
|
@@ -2161,7 +2489,18 @@ ${native.map((c) => `\t${handlerIfaceName(c.name)}`).join("\n")}
|
|
|
2161
2489
|
const rowT = rawRowStructName(c.name, node.id);
|
|
2162
2490
|
const decode = emitDecodeRow(c.name, node.id, rowT, ref, plan, emittedDecode, decodeFns, "");
|
|
2163
2491
|
const nodeBoundT = boundGoType(node, c, typedNodes, plan);
|
|
2164
|
-
|
|
2492
|
+
// #129 (TEST glue): if this node has an `items` port and its outType is a bare arr, support the
|
|
2493
|
+
// reference scriptedHandlers' `echo === "items"` — the scripted handler ECHOES the NATIVE `items`
|
|
2494
|
+
// port back as its result. This makes the node→leaf array dataflow test non-vacuous: the leaf's
|
|
2495
|
+
// observed result is exactly the native array that flowed into its ports struct, so a dropped or
|
|
2496
|
+
// boxed native array would diverge from run_behavior. `ports` is named only when the echo path
|
|
2497
|
+
// uses it (else the param stays `_` so a non-echo node compiles clean / unused-param-free).
|
|
2498
|
+
const canEcho = ref.kind === "arr" && Object.prototype.hasOwnProperty.call(node.ports, "items");
|
|
2499
|
+
const portsParam = canEcho ? "ports" : "_";
|
|
2500
|
+
const echoBranch = canEcho
|
|
2501
|
+
? `\tif src.echo == "items" {\n\t\treturn ${rowT}{Val: ports.${goPortFieldName("items")}}, true\n\t}\n`
|
|
2502
|
+
: "";
|
|
2503
|
+
methodImpls.push(`func (s *ScriptedNativeRaw) ${nodeMethodName(c.name, node.id)}(${portsParam} ${portsStructName(c.name, node.id)}, _ ${nodeBoundT}) (${rowT}, bool) {
|
|
2165
2504
|
src, ok := s.next(${JSON.stringify(node.component)})
|
|
2166
2505
|
if !ok {
|
|
2167
2506
|
return ${rowT}{}, false
|
|
@@ -2169,14 +2508,18 @@ ${native.map((c) => `\t${handlerIfaceName(c.name)}`).join("\n")}
|
|
|
2169
2508
|
if src.isErr {
|
|
2170
2509
|
return ${rowT}{IsError: true, Err: src.err}, true
|
|
2171
2510
|
}
|
|
2172
|
-
return ${decode}(src.ok), true
|
|
2511
|
+
${echoBranch} return ${decode}(src.ok), true
|
|
2173
2512
|
}`);
|
|
2174
2513
|
}
|
|
2175
2514
|
}
|
|
2176
2515
|
const adapter = `// scriptSrc — a scripted per-component outcome (an ok Value or an error), drained in order.
|
|
2516
|
+
// #129: \`echo\` carries the reference scriptedHandlers' \`echo\` directive (e.g. "items") so a scripted
|
|
2517
|
+
// node can ECHO a named input PORT back as its result — used to make the node→leaf array dataflow test
|
|
2518
|
+
// non-vacuous (the leaf handler returns its \`items\` port, so a dropped/boxed native array diverges).
|
|
2177
2519
|
type scriptSrc struct {
|
|
2178
2520
|
isErr bool
|
|
2179
2521
|
err string
|
|
2522
|
+
echo string
|
|
2180
2523
|
ok ${PKG}.Value
|
|
2181
2524
|
}
|
|
2182
2525
|
|
|
@@ -2207,12 +2550,18 @@ func NewScriptedNativeRaw(spec *${PKG}.JObj) (*ScriptedNativeRaw, error) {
|
|
|
2207
2550
|
}
|
|
2208
2551
|
for _, e := range entries {
|
|
2209
2552
|
eo, _ := e.(*${PKG}.JObj)
|
|
2553
|
+
// #129: an \`echo\` directive names an input port to echo back (mirrors the reference
|
|
2554
|
+
// scriptedHandlers' \`echo === "items"\`). It is drained together with the ok payload.
|
|
2555
|
+
echo := ""
|
|
2556
|
+
if echoNode, has := eo.Get("echo"); has {
|
|
2557
|
+
echo, _ = echoNode.(string)
|
|
2558
|
+
}
|
|
2210
2559
|
if okNode, has := eo.Get("ok"); has {
|
|
2211
2560
|
val, derr := ${PKG}.DecodeValue(okNode)
|
|
2212
2561
|
if derr != nil {
|
|
2213
2562
|
return nil, derr
|
|
2214
2563
|
}
|
|
2215
|
-
queues[comp] = append(queues[comp], scriptSrc{ok: val})
|
|
2564
|
+
queues[comp] = append(queues[comp], scriptSrc{ok: val, echo: echo})
|
|
2216
2565
|
continue
|
|
2217
2566
|
}
|
|
2218
2567
|
ev, _ := eo.Get("error")
|
|
@@ -2324,6 +2673,7 @@ function emitDecodeRow(compName, nodeId, rowT, ref, plan, emitted, out, suffix)
|
|
|
2324
2673
|
*/
|
|
2325
2674
|
export const goTypedNativeEmitter = {
|
|
2326
2675
|
language: "go-typed-native",
|
|
2676
|
+
classification: "codegen", // native: 型付きフラットコード生成・IR/dict 非在(#128/A6)
|
|
2327
2677
|
fileExtension: ".go",
|
|
2328
2678
|
defaultRuntimeImport: "github.com/foo-ogawa/behavior-contracts/go/coderuntime",
|
|
2329
2679
|
filenameHint: "behaviors.typed.native.generated.go",
|