Skip to content

Commit 0857b7e

Browse files
committed
initial version of current to legacy format transformer
1 parent 76c0e90 commit 0857b7e

File tree

10 files changed

+6280
-0
lines changed

10 files changed

+6280
-0
lines changed

src/transform/__tests__/defer-test.ts

Lines changed: 2370 additions & 0 deletions
Large diffs are not rendered by default.

src/transform/__tests__/stream-test.ts

Lines changed: 2439 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
import { invariant } from '../jsutils/invariant.js';
2+
import { mapValue } from '../jsutils/mapValue.js';
3+
import type { ObjMap } from '../jsutils/ObjMap.js';
4+
5+
import type {
6+
ArgumentNode,
7+
DirectiveNode,
8+
SelectionNode,
9+
SelectionSetNode,
10+
} from '../language/ast.js';
11+
import { Kind } from '../language/kinds.js';
12+
13+
import {
14+
GraphQLDeferDirective,
15+
GraphQLStreamDirective,
16+
} from '../type/directives.js';
17+
import { TypeNameMetaFieldDef } from '../type/introspection.js';
18+
19+
import { collectSubfields as _collectSubfields } from '../execution/collectFields.js';
20+
import type { ValidatedExecutionArgs } from '../execution/execute.js';
21+
import type { PendingResult } from '../execution/types.js';
22+
23+
type SelectionSetNodeOrFragmentName =
24+
| { node: SelectionSetNode; fragmentName?: never }
25+
| { node?: never; fragmentName: string };
26+
27+
interface DeferUsageContext {
28+
originalLabel: string | undefined;
29+
selectionSet: SelectionSetNodeOrFragmentName;
30+
}
31+
32+
interface StreamUsageContext {
33+
originalLabel: string | undefined;
34+
selectionSet: SelectionSetNode | undefined;
35+
}
36+
37+
export interface TransformationContext {
38+
transformedArgs: ValidatedExecutionArgs;
39+
deferUsageMap: Map<string, DeferUsageContext>;
40+
streamUsageMap: Map<string, StreamUsageContext>;
41+
prefix: string;
42+
pendingResultsById: Map<string, PendingResult>;
43+
pendingLabelsByPath: Map<string, Set<string>>;
44+
mergedResult: ObjMap<unknown>;
45+
}
46+
47+
interface RequestTransformationContext {
48+
prefix: string;
49+
incrementalCounter: number;
50+
deferUsageMap: Map<string, DeferUsageContext>;
51+
streamUsageMap: Map<string, StreamUsageContext>;
52+
}
53+
54+
export function buildTransformationContext(
55+
originalArgs: ValidatedExecutionArgs,
56+
prefix: string,
57+
): TransformationContext {
58+
const { operation, fragments } = originalArgs;
59+
60+
const context: RequestTransformationContext = {
61+
prefix,
62+
incrementalCounter: 0,
63+
deferUsageMap: new Map(),
64+
streamUsageMap: new Map(),
65+
};
66+
67+
const transformedFragments = mapValue(fragments, (details) => ({
68+
...details,
69+
definition: {
70+
...details.definition,
71+
selectionSet: transformRootSelectionSet(
72+
context,
73+
details.definition.selectionSet,
74+
),
75+
},
76+
}));
77+
78+
const transformedArgs: ValidatedExecutionArgs = {
79+
...originalArgs,
80+
operation: {
81+
...operation,
82+
selectionSet: transformRootSelectionSet(context, operation.selectionSet),
83+
},
84+
fragmentDefinitions: mapValue(
85+
transformedFragments,
86+
({ definition }) => definition,
87+
),
88+
fragments: transformedFragments,
89+
};
90+
91+
return {
92+
transformedArgs,
93+
deferUsageMap: context.deferUsageMap,
94+
streamUsageMap: context.streamUsageMap,
95+
prefix,
96+
pendingResultsById: new Map(),
97+
pendingLabelsByPath: new Map(),
98+
mergedResult: {},
99+
};
100+
}
101+
102+
function transformRootSelectionSet(
103+
context: RequestTransformationContext,
104+
selectionSet: SelectionSetNode,
105+
): SelectionSetNode {
106+
return {
107+
...selectionSet,
108+
selections: [
109+
...selectionSet.selections.map((node) =>
110+
transformSelection(context, node),
111+
),
112+
],
113+
};
114+
}
115+
116+
function transformNestedSelectionSet(
117+
context: RequestTransformationContext,
118+
selectionSet: SelectionSetNode,
119+
): SelectionSetNode {
120+
return {
121+
...selectionSet,
122+
selections: [
123+
...selectionSet.selections.map((node) =>
124+
transformSelection(context, node),
125+
),
126+
{
127+
kind: Kind.FIELD,
128+
name: {
129+
kind: Kind.NAME,
130+
value: TypeNameMetaFieldDef.name,
131+
},
132+
alias: {
133+
kind: Kind.NAME,
134+
value: context.prefix,
135+
},
136+
},
137+
],
138+
};
139+
}
140+
141+
function transformSelection(
142+
context: RequestTransformationContext,
143+
selection: SelectionNode,
144+
): SelectionNode {
145+
if (selection.kind === Kind.FIELD) {
146+
const selectionSet = selection.selectionSet;
147+
if (selectionSet) {
148+
const transformedSelectionSet = transformNestedSelectionSet(
149+
context,
150+
selectionSet,
151+
);
152+
return {
153+
...selection,
154+
selectionSet: transformedSelectionSet,
155+
directives: selection.directives?.map((directive) =>
156+
transformMaybeStreamDirective(
157+
context,
158+
directive,
159+
transformedSelectionSet,
160+
),
161+
),
162+
};
163+
}
164+
return {
165+
...selection,
166+
directives: selection.directives?.map((directive) =>
167+
transformMaybeStreamDirective(context, directive, undefined),
168+
),
169+
};
170+
} else if (selection.kind === Kind.INLINE_FRAGMENT) {
171+
const transformedSelectionSet = transformRootSelectionSet(
172+
context,
173+
selection.selectionSet,
174+
);
175+
176+
return {
177+
...selection,
178+
selectionSet: transformedSelectionSet,
179+
directives: selection.directives?.map((directive) =>
180+
transformMaybeDeferDirective(context, directive, {
181+
node: transformedSelectionSet,
182+
}),
183+
),
184+
};
185+
}
186+
187+
return {
188+
...selection,
189+
directives: selection.directives?.map((directive) =>
190+
transformMaybeDeferDirective(context, directive, {
191+
fragmentName: selection.name.value,
192+
}),
193+
),
194+
};
195+
}
196+
197+
function transformMaybeDeferDirective(
198+
context: RequestTransformationContext,
199+
directive: DirectiveNode,
200+
selectionSet: SelectionSetNodeOrFragmentName,
201+
): DirectiveNode {
202+
const name = directive.name.value;
203+
204+
if (name !== GraphQLDeferDirective.name) {
205+
return directive;
206+
}
207+
208+
let foundLabel = false;
209+
const newArgs: Array<ArgumentNode> = [];
210+
const args = directive.arguments;
211+
if (args) {
212+
for (const arg of args) {
213+
if (arg.name.value === 'label') {
214+
foundLabel = true;
215+
const value = arg.value;
216+
217+
invariant(value.kind === Kind.STRING);
218+
219+
const originalLabel = value.value;
220+
const prefixedLabel = `${context.prefix}defer${context.incrementalCounter++}__${originalLabel}`;
221+
context.deferUsageMap.set(prefixedLabel, {
222+
originalLabel,
223+
selectionSet,
224+
});
225+
newArgs.push({
226+
...arg,
227+
value: {
228+
...value,
229+
value: prefixedLabel,
230+
},
231+
});
232+
} else {
233+
newArgs.push(arg);
234+
}
235+
}
236+
}
237+
238+
if (!foundLabel) {
239+
const newLabel = `${context.prefix}defer${context.incrementalCounter++}`;
240+
context.deferUsageMap.set(newLabel, {
241+
originalLabel: undefined,
242+
selectionSet,
243+
});
244+
newArgs.push({
245+
kind: Kind.ARGUMENT,
246+
name: {
247+
kind: Kind.NAME,
248+
value: 'label',
249+
},
250+
value: {
251+
kind: Kind.STRING,
252+
value: newLabel,
253+
},
254+
});
255+
}
256+
257+
return {
258+
...directive,
259+
arguments: newArgs,
260+
};
261+
}
262+
263+
function transformMaybeStreamDirective(
264+
context: RequestTransformationContext,
265+
directive: DirectiveNode,
266+
selectionSet: SelectionSetNode | undefined,
267+
): DirectiveNode {
268+
const name = directive.name.value;
269+
270+
if (name !== GraphQLStreamDirective.name) {
271+
return directive;
272+
}
273+
274+
let foundLabel = false;
275+
const newArgs: Array<ArgumentNode> = [];
276+
const args = directive.arguments;
277+
if (args) {
278+
for (const arg of args) {
279+
if (arg.name.value === 'label') {
280+
foundLabel = true;
281+
const value = arg.value;
282+
283+
invariant(value.kind === Kind.STRING);
284+
285+
const originalLabel = value.value;
286+
const prefixedLabel = `${context.prefix}stream${context.incrementalCounter++}__${originalLabel}`;
287+
context.streamUsageMap.set(prefixedLabel, {
288+
originalLabel,
289+
selectionSet,
290+
});
291+
newArgs.push({
292+
...arg,
293+
value: {
294+
...value,
295+
value: prefixedLabel,
296+
},
297+
});
298+
} else {
299+
newArgs.push(arg);
300+
}
301+
}
302+
}
303+
304+
if (!foundLabel) {
305+
const newLabel = `${context.prefix}stream${context.incrementalCounter++}`;
306+
context.streamUsageMap.set(newLabel, {
307+
originalLabel: undefined,
308+
selectionSet,
309+
});
310+
newArgs.push({
311+
kind: Kind.ARGUMENT,
312+
name: {
313+
kind: Kind.NAME,
314+
value: 'label',
315+
},
316+
value: {
317+
kind: Kind.STRING,
318+
value: newLabel,
319+
},
320+
});
321+
}
322+
323+
return {
324+
...directive,
325+
arguments: newArgs,
326+
};
327+
}

0 commit comments

Comments
 (0)