@stackables/bridge 1.4.2 → 1.5.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.
@@ -1 +1 @@
1
- {"version":3,"file":"ExecutionTree.d.ts","sourceRoot":"","sources":["../src/ExecutionTree.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,MAAM,EAEN,WAAW,EACX,OAAO,EAGP,OAAO,EAER,MAAM,YAAY,CAAC;AAGpB,gFAAgF;AAChF,UAAU,IAAI;IACZ,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,CAAC;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAED,KAAK,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAsChF,qBAAa,aAAa;IAQf,KAAK,EAAE,KAAK;IACnB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,OAAO,CAAC;IAChB,OAAO,CAAC,OAAO,CAAC;IAChB,OAAO,CAAC,MAAM,CAAC;IAXjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAM;IAChC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,CAAC,YAAY,CAAwC;IAC5D,OAAO,CAAC,YAAY,CAA0C;IAC9D,OAAO,CAAC,aAAa,CAAsE;gBAGlF,KAAK,EAAE,KAAK,EACX,YAAY,EAAE,WAAW,EAAE,EAC3B,OAAO,CAAC,EAAE,OAAO,YAAA,EACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,YAAA,EAC7B,MAAM,CAAC,EAAE,aAAa,YAAA;IA8BhC,oCAAoC;IACpC,OAAO,CAAC,WAAW;IAKnB;uGACmG;IACnG,OAAO,CAAC,YAAY;IAsBpB,oEAAoE;IACpE,OAAO,CAAC,oBAAoB;IA8D5B,mEAAmE;YACrD,gBAAgB;IA0B9B,2EAA2E;YAC7D,iBAAiB;IAgC/B,kDAAkD;IAClD,OAAO,CAAC,cAAc;IAgCtB,QAAQ,CAAC,MAAM,EAAE,KAAK,GAAG,GAAG;IAmF5B,MAAM,IAAI,aAAa;YAUT,UAAU;IA4BlB,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC;IAqCzC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAI9B,6DAA6D;IAC7D,aAAa,IAAI,IAAI;IAqBrB;;oFAEgF;IAChF,OAAO,CAAC,YAAY;IA8Cd,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC;CAsD1D"}
1
+ {"version":3,"file":"ExecutionTree.d.ts","sourceRoot":"","sources":["../src/ExecutionTree.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,MAAM,EAEN,WAAW,EACX,OAAO,EAGP,OAAO,EAER,MAAM,YAAY,CAAC;AAGpB,gFAAgF;AAChF,UAAU,IAAI;IACZ,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,CAAC;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAED,KAAK,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAsChF,qBAAa,aAAa;IAQf,KAAK,EAAE,KAAK;IACnB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,OAAO,CAAC;IAChB,OAAO,CAAC,OAAO,CAAC;IAChB,OAAO,CAAC,MAAM,CAAC;IAXjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAM;IAChC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,CAAC,YAAY,CAAwC;IAC5D,OAAO,CAAC,YAAY,CAA0C;IAC9D,OAAO,CAAC,aAAa,CAAsE;gBAGlF,KAAK,EAAE,KAAK,EACX,YAAY,EAAE,WAAW,EAAE,EAC3B,OAAO,CAAC,EAAE,OAAO,YAAA,EACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,YAAA,EAC7B,MAAM,CAAC,EAAE,aAAa,YAAA;IA8BhC,oCAAoC;IACpC,OAAO,CAAC,WAAW;IAKnB;uGACmG;IACnG,OAAO,CAAC,YAAY;IAsBpB,oEAAoE;IACpE,OAAO,CAAC,oBAAoB;IA8D5B,mEAAmE;YACrD,gBAAgB;IA0B9B,2EAA2E;YAC7D,iBAAiB;IAyC/B,kDAAkD;IAClD,OAAO,CAAC,cAAc;IAgCtB,QAAQ,CAAC,MAAM,EAAE,KAAK,GAAG,GAAG;IA8F5B,MAAM,IAAI,aAAa;YAUT,UAAU;IAmClB,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC;IAqCzC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAI9B,6DAA6D;IAC7D,aAAa,IAAI,IAAI;IAqBrB;;oFAEgF;IAChF,OAAO,CAAC,YAAY;IA8Cd,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC;CA+E1D"}
@@ -186,10 +186,23 @@ export class ExecutionTree {
186
186
  throw new Error(`Unknown source "${handle}" in tool "${toolDef.name}"`);
187
187
  let value;
188
188
  if (dep.kind === "context") {
189
- value = this.context ?? this.parent?.context;
189
+ // Walk the full parent chain for context
190
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
191
+ let cursor = this;
192
+ while (cursor && value === undefined) {
193
+ value = cursor.context;
194
+ cursor = cursor.parent;
195
+ }
190
196
  }
191
197
  else if (dep.kind === "const") {
192
- value = this.state[trunkKey({ module: SELF_MODULE, type: "Const", field: "const" })] ?? this.parent?.state[trunkKey({ module: SELF_MODULE, type: "Const", field: "const" })];
198
+ // Walk the full parent chain for const state
199
+ const constKey = trunkKey({ module: SELF_MODULE, type: "Const", field: "const" });
200
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
201
+ let cursor = this;
202
+ while (cursor && value === undefined) {
203
+ value = cursor.state[constKey];
204
+ cursor = cursor.parent;
205
+ }
193
206
  }
194
207
  else if (dep.kind === "tool") {
195
208
  value = await this.resolveToolDep(dep.tool);
@@ -258,10 +271,23 @@ export class ExecutionTree {
258
271
  if (toolDef) {
259
272
  await this.resolveToolWires(toolDef, input);
260
273
  }
261
- // Resolve bridge wires and apply on top
262
- const resolved = await Promise.all(bridgeWires.map(async (w) => {
263
- const value = "value" in w ? w.value : await this.pullSingle(w.from);
264
- return [w.to.path, value];
274
+ // Resolve bridge wires and apply on top.
275
+ // Group wires by target path so that || (null-fallback) and ??
276
+ // (error-fallback) semantics are honoured via resolveWires().
277
+ const wireGroups = new Map();
278
+ for (const w of bridgeWires) {
279
+ const key = w.to.path.join(".");
280
+ let group = wireGroups.get(key);
281
+ if (!group) {
282
+ group = [];
283
+ wireGroups.set(key, group);
284
+ }
285
+ group.push(w);
286
+ }
287
+ const groupEntries = Array.from(wireGroups.entries());
288
+ const resolved = await Promise.all(groupEntries.map(async ([, group]) => {
289
+ const value = await this.resolveWires(group);
290
+ return [group[0].to.path, value];
265
291
  }));
266
292
  for (const [path, value] of resolved) {
267
293
  if (path.length === 0 && value != null && typeof value === "object") {
@@ -307,7 +333,14 @@ export class ExecutionTree {
307
333
  }
308
334
  async pullSingle(ref) {
309
335
  const key = trunkKey(ref);
310
- let value = this.state[key] ?? this.parent?.state[key];
336
+ // Walk the full parent chain — shadow trees may be nested multiple levels
337
+ let value = undefined;
338
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
339
+ let cursor = this;
340
+ while (cursor && value === undefined) {
341
+ value = cursor.state[key];
342
+ cursor = cursor.parent;
343
+ }
311
344
  if (value === undefined) {
312
345
  this.state[key] = this.schedule(ref);
313
346
  value = this.state[key];
@@ -458,6 +491,30 @@ export class ExecutionTree {
458
491
  return s;
459
492
  });
460
493
  }
494
+ // Fallback: if this shadow tree has stored element data, resolve the
495
+ // requested field directly from it. This handles passthrough arrays
496
+ // where the bridge maps an inner array (e.g. `.stops <- j.stops`) but
497
+ // doesn't explicitly wire each scalar field on the element type.
498
+ if (this.parent) {
499
+ const elementKey = trunkKey({ ...this.trunk, element: true });
500
+ const elementData = this.state[elementKey];
501
+ if (elementData != null && typeof elementData === "object" && !Array.isArray(elementData)) {
502
+ const fieldName = cleanPath[cleanPath.length - 1];
503
+ if (fieldName !== undefined && fieldName in elementData) {
504
+ const value = elementData[fieldName];
505
+ if (array && Array.isArray(value)) {
506
+ // Nested array: wrap items in shadow trees so they can
507
+ // resolve their own fields via this same fallback path.
508
+ return value.map((item) => {
509
+ const s = this.shadow();
510
+ s.state[elementKey] = item;
511
+ return s;
512
+ });
513
+ }
514
+ return value;
515
+ }
516
+ }
517
+ }
461
518
  // Return self to trigger downstream resolvers
462
519
  return this;
463
520
  }
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-format.d.ts","sourceRoot":"","sources":["../src/bridge-format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKR,WAAW,EAMd,MAAM,YAAY,CAAC;AA8CpB,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CA0FvD;AAqwCD;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAchD;AAID;;GAEG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,MAAM,CA8BnE"}
1
+ {"version":3,"file":"bridge-format.d.ts","sourceRoot":"","sources":["../src/bridge-format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKR,WAAW,EAMd,MAAM,YAAY,CAAC;AA8CpB,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CA0FvD;AAmxCD;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAchD;AAID;;GAEG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,MAAM,CA8BnE"}
@@ -409,6 +409,7 @@ function parseBridgeBlock(block, lineOffset, previousInstructions) {
409
409
  const [, targetStr, quotedValue, unquotedValue] = constantMatch;
410
410
  const value = quotedValue ?? unquotedValue;
411
411
  const toRef = resolveAddress(targetStr, handleRes, bridgeType, bridgeField, ln(i));
412
+ assertNoTargetIndices(toRef, ln(i));
412
413
  wires.push({ value, to: toRef });
413
414
  continue;
414
415
  }
@@ -427,6 +428,7 @@ function parseBridgeBlock(block, lineOffset, previousInstructions) {
427
428
  assertNotReserved(iterHandle, ln(i), "iterator handle");
428
429
  const fromRef = resolveAddress(fromClean, handleRes, bridgeType, bridgeField, ln(i));
429
430
  const toRef = resolveAddress(targetStr, handleRes, bridgeType, bridgeField, ln(i));
431
+ assertNoTargetIndices(toRef, ln(i));
430
432
  wires.push({ from: fromRef, to: toRef });
431
433
  currentArrayToPath = toRef.path;
432
434
  currentIterHandle = iterHandle;
@@ -469,6 +471,7 @@ function parseBridgeBlock(block, lineOffset, previousInstructions) {
469
471
  fallbackInternalWires = wires.splice(preLen);
470
472
  }
471
473
  const toRef = resolveAddress(targetStr, handleRes, bridgeType, bridgeField, ln(i));
474
+ assertNoTargetIndices(toRef, ln(i));
472
475
  for (let ci = 0; ci < sourceParts.length; ci++) {
473
476
  const isFirst = ci === 0;
474
477
  const isLast = ci === sourceParts.length - 1;
@@ -927,6 +930,14 @@ function resolveAddress(address, handles, bridgeType, bridgeField, lineNum) {
927
930
  throw new Error(`${lineNum != null ? `Line ${lineNum}: ` : ""}Undeclared handle "${prefix}". ` +
928
931
  `Add 'with ${prefix}' or 'with ${prefix} as ${prefix}' to the bridge header.`);
929
932
  }
933
+ /** Reject explicit array indices on the target (LHS) of a wire. */
934
+ function assertNoTargetIndices(ref, lineNum) {
935
+ if (ref.path.some((seg) => /^\d+$/.test(seg))) {
936
+ throw new Error(`${lineNum != null ? `Line ${lineNum}: ` : ""}` +
937
+ `Explicit array index in wire target is not supported. ` +
938
+ `Use array mapping (\`[] as iter { }\`) instead.`);
939
+ }
940
+ }
930
941
  // ── Const block parser ──────────────────────────────────────────────────────
931
942
  /**
932
943
  * Parse `const` declarations into ConstDef instructions.
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-transform.d.ts","sourceRoot":"","sources":["../src/bridge-transform.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,aAAa,EAEnB,MAAM,SAAS,CAAC;AAGjB,OAAO,KAAK,EAAE,WAAW,EAAc,OAAO,EAAE,MAAM,YAAY,CAAC;AAGnE,MAAM,MAAM,aAAa,GAAG;IAC1B;;;kDAG8C;IAC9C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;qEACiE;IACjE,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACvD,CAAC;AAEF,gFAAgF;AAChF,MAAM,MAAM,iBAAiB,GACzB,WAAW,EAAE,GACb,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,WAAW,EAAE,CAAC,CAAC;AAEtC,wBAAgB,eAAe,CAC7B,MAAM,EAAE,aAAa,EACrB,YAAY,EAAE,iBAAiB,EAC/B,OAAO,CAAC,EAAE,aAAa,GACtB,aAAa,CA0Ef"}
1
+ {"version":3,"file":"bridge-transform.d.ts","sourceRoot":"","sources":["../src/bridge-transform.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,aAAa,EAEnB,MAAM,SAAS,CAAC;AAGjB,OAAO,KAAK,EAAE,WAAW,EAAc,OAAO,EAAE,MAAM,YAAY,CAAC;AAGnE,MAAM,MAAM,aAAa,GAAG;IAC1B;;;kDAG8C;IAC9C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;qEACiE;IACjE,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACvD,CAAC;AAEF,gFAAgF;AAChF,MAAM,MAAM,iBAAiB,GACzB,WAAW,EAAE,GACb,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,WAAW,EAAE,CAAC,CAAC;AAEtC,wBAAgB,eAAe,CAC7B,MAAM,EAAE,aAAa,EACrB,YAAY,EAAE,iBAAiB,EAC/B,OAAO,CAAC,EAAE,aAAa,GACtB,aAAa,CA+Ef"}
@@ -44,6 +44,11 @@ export function bridgeTransform(schema, instructions, options) {
44
44
  }
45
45
  // Kick off forced wires (<-!) at the root entry point
46
46
  if (source instanceof ExecutionTree && !info.path.prev) {
47
+ // Ensure input state exists even with no args (prevents
48
+ // recursive scheduling of the input trunk → stack overflow).
49
+ if (!args || Object.keys(args).length === 0) {
50
+ source.push({});
51
+ }
47
52
  source.executeForced();
48
53
  }
49
54
  if (source instanceof ExecutionTree) {
package/package.json CHANGED
@@ -1,10 +1,17 @@
1
1
  {
2
2
  "name": "@stackables/bridge",
3
- "version": "1.4.2",
3
+ "version": "1.5.0",
4
4
  "description": "Declarative dataflow for GraphQL",
5
5
  "main": "./build/index.js",
6
6
  "type": "module",
7
7
  "types": "./build/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "development": "./src/index.ts",
11
+ "import": "./build/index.js",
12
+ "types": "./build/index.d.ts"
13
+ }
14
+ },
8
15
  "files": [
9
16
  "build",
10
17
  "README.md"