Skip to content

Commit ce9f966

Browse files
committed
graphql tessts
1 parent 479fe5a commit ce9f966

5 files changed

Lines changed: 60 additions & 19 deletions

File tree

packages/bridge-core/src/ExecutionTree.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ export class ExecutionTree implements TreeContext {
150150
new Map();
151151
/** Promise that resolves when all critical `force` handles have settled. */
152152
private forcedExecution?: Promise<void>;
153+
/** Cached spread data for field-by-field GraphQL resolution. */
154+
private spreadCache?: Record<string, unknown>;
153155
/** Shared trace collector — present only when tracing is enabled. */
154156
tracer?: TraceCollector;
155157
/**
@@ -1280,6 +1282,7 @@ export class ExecutionTree implements TreeContext {
12801282
const result = this.resolveWires(matches);
12811283
if (!array) return result;
12821284
const resolved = await result;
1285+
if (resolved == null || !Array.isArray(resolved)) return resolved;
12831286
const arrayPathKey = path.join(".");
12841287
if (isLoopControlSignal(resolved)) {
12851288
this.recordEmptyArray(arrayPathKey);
@@ -1719,6 +1722,29 @@ export class ExecutionTree implements TreeContext {
17191722
return this;
17201723
}
17211724

1725+
// ── Lazy spread resolution ─────────────────────────────────────
1726+
// When ALL matches are spread wires, resolve them eagerly, cache
1727+
// the result, then return `this` so GraphQL sub-field resolvers
1728+
// can pick up both spread properties and explicit wires.
1729+
if (
1730+
!array &&
1731+
matches.every(
1732+
(w): boolean => "from" in w && "spread" in w && !!w.spread,
1733+
)
1734+
) {
1735+
const spreadData = await this.resolveWires(matches);
1736+
if (spreadData != null && typeof spreadData === "object") {
1737+
const prefix = cleanPath.join(".");
1738+
this.spreadCache ??= {};
1739+
if (prefix === "") {
1740+
Object.assign(this.spreadCache, spreadData as Record<string, unknown>);
1741+
} else {
1742+
(this.spreadCache as Record<string, unknown>)[prefix] = spreadData;
1743+
}
1744+
}
1745+
return this;
1746+
}
1747+
17221748
const response = this.resolveWires(matches);
17231749

17241750
if (!array) {
@@ -1727,6 +1753,7 @@ export class ExecutionTree implements TreeContext {
17271753

17281754
// Array: create shadow trees for per-element resolution
17291755
const resolved = await response;
1756+
if (resolved == null || !Array.isArray(resolved)) return resolved;
17301757
const arrayPathKey = cleanPath.join(".");
17311758
if (isLoopControlSignal(resolved)) {
17321759
this.recordEmptyArray(arrayPathKey);
@@ -1745,6 +1772,7 @@ export class ExecutionTree implements TreeContext {
17451772
const response = this.resolveWires(defineFieldWires);
17461773
if (!array) return response;
17471774
const resolved = await response;
1775+
if (resolved == null || !Array.isArray(resolved)) return resolved;
17481776
const definePathKey = cleanPath.join(".");
17491777
if (isLoopControlSignal(resolved)) {
17501778
this.recordEmptyArray(definePathKey);
@@ -1754,6 +1782,29 @@ export class ExecutionTree implements TreeContext {
17541782
}
17551783
}
17561784

1785+
// ── Spread cache fallback ─────────────────────────────────────────
1786+
// If a spread wire was resolved at a parent path, field-by-field GraphQL
1787+
// resolution consults the cached spread data for fields not covered by
1788+
// explicit wires.
1789+
if (cleanPath.length > 0 && this.spreadCache) {
1790+
// Check for a parent-level spread: e.g. cleanPath=["author"] with
1791+
// spread cached under "" (root spread), or cleanPath=["info","author"]
1792+
// with spread cached under "info".
1793+
const fieldName = cleanPath[cleanPath.length - 1]!;
1794+
const parentPrefix = cleanPath.slice(0, -1).join(".");
1795+
const parentSpread =
1796+
parentPrefix === ""
1797+
? this.spreadCache
1798+
: (this.spreadCache[parentPrefix] as Record<string, unknown> | undefined);
1799+
if (
1800+
parentSpread != null &&
1801+
typeof parentSpread === "object" &&
1802+
fieldName in parentSpread
1803+
) {
1804+
return (parentSpread as Record<string, unknown>)[fieldName];
1805+
}
1806+
}
1807+
17571808
// Fallback: if this shadow tree has stored element data, resolve the
17581809
// requested field directly from it. This handles passthrough arrays
17591810
// where the bridge maps an inner array (e.g. `.stops <- j.stops`) but

packages/bridge-graphql/src/bridge-transform.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,10 @@ export function bridgeTransform(
457457
if (scalar) {
458458
if (result instanceof ExecutionTree) {
459459
try {
460-
return result.collectOutput();
460+
const data = result.collectOutput();
461+
const forced = result.getForcedExecution();
462+
if (forced) await forced;
463+
return data;
461464
} catch (err) {
462465
throw new Error(
463466
formatBridgeError(err, {
@@ -470,11 +473,15 @@ export function bridgeTransform(
470473
}
471474
if (Array.isArray(result) && result[0] instanceof ExecutionTree) {
472475
try {
473-
return await Promise.all(
476+
const firstTree = result[0] as ExecutionTree;
477+
const forced = firstTree.getForcedExecution();
478+
const collected = await Promise.all(
474479
result.map((shadow: ExecutionTree) =>
475480
shadow.collectOutput(),
476481
),
477482
);
483+
if (forced) await forced;
484+
return collected;
478485
} catch (err) {
479486
throw new Error(
480487
formatBridgeError(err, {

packages/bridge/test/path-scoping.test.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,6 @@ regressionTest("path scoping: spread syntax", {
435435
api: () => ({ name: "Alice", age: 30 }),
436436
},
437437
assertData: { name: "Alice", age: 30, extra: "added" },
438-
disable: ["graphql"],
439438
assertTraces: 1,
440439
},
441440
},
@@ -446,7 +445,6 @@ regressionTest("path scoping: spread syntax", {
446445
api: () => ({ data: { x: 1, y: 2 } }),
447446
},
448447
assertData: { x: 1, y: 2, source: "api" },
449-
disable: ["graphql"],
450448
assertTraces: 1,
451449
},
452450
},
@@ -459,7 +457,6 @@ regressionTest("path scoping: spread syntax", {
459457
assertData: {
460458
info: { author: "Bob", year: 2024, verified: true },
461459
},
462-
disable: ["graphql"],
463460
assertTraces: 1,
464461
},
465462
},

packages/bridge/test/scheduling.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ regressionTest("scheduling: diamond dependency dedup", {
115115
},
116116
// geocode + weatherForecast + census + formatGreeting = 4
117117
assertTraces: 4,
118-
disable: ["graphql"],
119118
},
120119
},
121120
},

packages/bridge/test/shared-parity.test.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,6 @@ regressionTest("parity: array mapping", {
366366
input: {},
367367
tools: { api: () => ({ list: null }) },
368368
assertData: { items: null },
369-
disable: ["graphql"],
370369
assertTraces: 1,
371370
},
372371
"non-empty items map correctly": {
@@ -665,7 +664,6 @@ regressionTest("parity: force statements", {
665664
},
666665
},
667666
assertError: /audit failed/,
668-
disable: ["graphql"],
669667
assertTraces: 2,
670668
},
671669
},
@@ -1734,7 +1732,6 @@ regressionTest("parity: sparse fieldsets — wildcard and chains", {
17341732
},
17351733
fields: ["price"],
17361734
assertData: { price: 99 },
1737-
disable: ["graphql"],
17381735
assertTraces: 1,
17391736
},
17401737
},
@@ -1900,14 +1897,12 @@ regressionTest("parity: sparse fieldsets — nested and array paths", {
19001897
assertData: [
19011898
{ id: 1, provider: "X", price: 50, legs: [{ name: "L1" }] },
19021899
],
1903-
disable: ["graphql"],
19041900
assertTraces: 1,
19051901
},
19061902
"empty items returns empty array": {
19071903
input: { from: "A", to: "B" },
19081904
tools: { api: () => ({ items: [] }) },
19091905
assertData: [],
1910-
disable: ["graphql"],
19111906
assertTraces: 1,
19121907
},
19131908
},
@@ -1934,7 +1929,6 @@ regressionTest("parity: sparse fieldsets — nested and array paths", {
19341929
legs: [{ destination: "Zürich" }, { destination: "Basel" }],
19351930
},
19361931
],
1937-
disable: ["graphql"],
19381932
assertTraces: 1,
19391933
},
19401934
"all fields returned when no requestedFields": {
@@ -1958,14 +1952,12 @@ regressionTest("parity: sparse fieldsets — nested and array paths", {
19581952
legs: [{ trainName: "IC1", destination: "Zürich" }],
19591953
},
19601954
],
1961-
disable: ["graphql"],
19621955
assertTraces: 1,
19631956
},
19641957
"empty connections returns empty array": {
19651958
input: { from: "Bern", to: "Zürich" },
19661959
tools: { api: () => ({ connections: [] }) },
19671960
assertData: [],
1968-
disable: ["graphql"],
19691961
assertTraces: 1,
19701962
},
19711963
"connection with empty sections": {
@@ -1978,7 +1970,6 @@ regressionTest("parity: sparse fieldsets — nested and array paths", {
19781970
assertData: [
19791971
{ id: 1, provider: "SBB", departureTime: "09:00", legs: [] },
19801972
],
1981-
disable: ["graphql"],
19821973
assertTraces: 1,
19831974
},
19841975
},
@@ -2009,7 +2000,6 @@ regressionTest("parity: sparse fieldsets — nested and array paths", {
20092000
legs: [{ destination: { actualTime: "08:32" } }],
20102001
},
20112002
],
2012-
disable: ["graphql"],
20132003
assertTraces: 1,
20142004
},
20152005
"all fields returned when no requestedFields": {
@@ -2049,14 +2039,12 @@ regressionTest("parity: sparse fieldsets — nested and array paths", {
20492039
],
20502040
},
20512041
],
2052-
disable: ["graphql"],
20532042
assertTraces: 1,
20542043
},
20552044
"empty connections returns empty array": {
20562045
input: { from: "Bern" },
20572046
tools: { api: () => ({ connections: [] }) },
20582047
assertData: [],
2059-
disable: ["graphql"],
20602048
assertTraces: 1,
20612049
},
20622050
"connection with empty sections": {
@@ -2067,7 +2055,6 @@ regressionTest("parity: sparse fieldsets — nested and array paths", {
20672055
}),
20682056
},
20692057
assertData: [{ id: 1, provider: "SBB", legs: [] }],
2070-
disable: ["graphql"],
20712058
assertTraces: 1,
20722059
},
20732060
},

0 commit comments

Comments
 (0)