@soda-gql/codegen 0.11.19 → 0.11.21
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/dist/index.cjs +448 -278
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -7
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +46 -7
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +445 -279
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -6,271 +6,6 @@ let node_path = require("node:path");
|
|
|
6
6
|
let esbuild = require("esbuild");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
|
|
9
|
-
//#region packages/codegen/src/graphql-compat/emitter.ts
|
|
10
|
-
/**
|
|
11
|
-
* Emit TypeScript code for an operation.
|
|
12
|
-
*/
|
|
13
|
-
const emitOperation = (operation, options) => {
|
|
14
|
-
const lines = [];
|
|
15
|
-
const schema = options.schemaDocument ? require_generator.createSchemaIndex(options.schemaDocument) : null;
|
|
16
|
-
const exportName = `${operation.name}Compat`;
|
|
17
|
-
const operationType = operation.kind;
|
|
18
|
-
lines.push(`export const ${exportName} = gql.${options.schemaName}(({ ${operationType}, $var }) =>`);
|
|
19
|
-
lines.push(` ${operationType}.compat({`);
|
|
20
|
-
lines.push(` name: ${JSON.stringify(operation.name)},`);
|
|
21
|
-
if (operation.variables.length > 0) {
|
|
22
|
-
lines.push(` variables: { ${emitVariables(operation.variables)} },`);
|
|
23
|
-
}
|
|
24
|
-
lines.push(` fields: ({ f, $ }) => ({`);
|
|
25
|
-
const fieldLinesResult = emitSelections(operation.selections, 3, operation.variables, schema);
|
|
26
|
-
if (fieldLinesResult.isErr()) {
|
|
27
|
-
return (0, neverthrow.err)(fieldLinesResult.error);
|
|
28
|
-
}
|
|
29
|
-
lines.push(fieldLinesResult.value);
|
|
30
|
-
lines.push(` }),`);
|
|
31
|
-
lines.push(` }),`);
|
|
32
|
-
lines.push(`);`);
|
|
33
|
-
return (0, neverthrow.ok)(lines.join("\n"));
|
|
34
|
-
};
|
|
35
|
-
/**
|
|
36
|
-
* Emit TypeScript code for a fragment.
|
|
37
|
-
*/
|
|
38
|
-
const emitFragment = (fragment, options) => {
|
|
39
|
-
const lines = [];
|
|
40
|
-
const schema = options.schemaDocument ? require_generator.createSchemaIndex(options.schemaDocument) : null;
|
|
41
|
-
const hasVariables = fragment.variables.length > 0;
|
|
42
|
-
const exportName = `${fragment.name}Fragment`;
|
|
43
|
-
const destructure = hasVariables ? "fragment, $var" : "fragment";
|
|
44
|
-
lines.push(`export const ${exportName} = gql.${options.schemaName}(({ ${destructure} }) =>`);
|
|
45
|
-
lines.push(` fragment.${fragment.onType}({`);
|
|
46
|
-
if (hasVariables) {
|
|
47
|
-
lines.push(` variables: { ${emitVariables(fragment.variables)} },`);
|
|
48
|
-
}
|
|
49
|
-
const fieldsContext = hasVariables ? "{ f, $ }" : "{ f }";
|
|
50
|
-
lines.push(` fields: (${fieldsContext}) => ({`);
|
|
51
|
-
const fieldLinesResult = emitSelections(fragment.selections, 3, fragment.variables, schema);
|
|
52
|
-
if (fieldLinesResult.isErr()) {
|
|
53
|
-
return (0, neverthrow.err)(fieldLinesResult.error);
|
|
54
|
-
}
|
|
55
|
-
lines.push(fieldLinesResult.value);
|
|
56
|
-
lines.push(` }),`);
|
|
57
|
-
lines.push(` }),`);
|
|
58
|
-
lines.push(`);`);
|
|
59
|
-
return (0, neverthrow.ok)(lines.join("\n"));
|
|
60
|
-
};
|
|
61
|
-
/**
|
|
62
|
-
* Emit variable definitions.
|
|
63
|
-
*/
|
|
64
|
-
const emitVariables = (variables) => {
|
|
65
|
-
return variables.map((v) => `...$var(${JSON.stringify(v.name)}).${v.typeName}(${JSON.stringify(v.modifier)})`).join(", ");
|
|
66
|
-
};
|
|
67
|
-
/**
|
|
68
|
-
* Emit field selections (public API).
|
|
69
|
-
* Converts variable array to Set<string> and delegates to internal implementation.
|
|
70
|
-
*/
|
|
71
|
-
const emitSelections = (selections, indent, variables, schema) => {
|
|
72
|
-
const variableNames = new Set(variables.map((v) => v.name));
|
|
73
|
-
return emitSelectionsInternal(selections, indent, variableNames, schema);
|
|
74
|
-
};
|
|
75
|
-
/**
|
|
76
|
-
* Internal implementation for emitting field selections.
|
|
77
|
-
* Takes variableNames as Set<string> for recursive calls.
|
|
78
|
-
*/
|
|
79
|
-
const emitSelectionsInternal = (selections, indent, variableNames, schema) => {
|
|
80
|
-
const lines = [];
|
|
81
|
-
const inlineFragments = [];
|
|
82
|
-
const otherSelections = [];
|
|
83
|
-
for (const sel of selections) {
|
|
84
|
-
if (sel.kind === "inlineFragment") {
|
|
85
|
-
inlineFragments.push(sel);
|
|
86
|
-
} else {
|
|
87
|
-
otherSelections.push(sel);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
for (const sel of otherSelections) {
|
|
91
|
-
const result = emitSingleSelection(sel, indent, variableNames, schema);
|
|
92
|
-
if (result.isErr()) {
|
|
93
|
-
return (0, neverthrow.err)(result.error);
|
|
94
|
-
}
|
|
95
|
-
lines.push(result.value);
|
|
96
|
-
}
|
|
97
|
-
if (inlineFragments.length > 0) {
|
|
98
|
-
const unionResult = emitInlineFragmentsAsUnion(inlineFragments, indent, variableNames, schema);
|
|
99
|
-
if (unionResult.isErr()) {
|
|
100
|
-
return (0, neverthrow.err)(unionResult.error);
|
|
101
|
-
}
|
|
102
|
-
lines.push(unionResult.value);
|
|
103
|
-
}
|
|
104
|
-
return (0, neverthrow.ok)(lines.join("\n"));
|
|
105
|
-
};
|
|
106
|
-
/**
|
|
107
|
-
* Emit a single selection (field or fragment spread).
|
|
108
|
-
*/
|
|
109
|
-
const emitSingleSelection = (sel, indent, variableNames, schema) => {
|
|
110
|
-
const padding = " ".repeat(indent);
|
|
111
|
-
switch (sel.kind) {
|
|
112
|
-
case "field": return emitFieldSelection(sel, indent, variableNames, schema);
|
|
113
|
-
case "fragmentSpread": return (0, neverthrow.ok)(`${padding}...${sel.name}Fragment.spread(),`);
|
|
114
|
-
case "inlineFragment": return (0, neverthrow.ok)("");
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
/**
|
|
118
|
-
* Emit inline fragments grouped as a union selection.
|
|
119
|
-
* Format: { TypeA: ({ f }) => ({ ...fields }), TypeB: ({ f }) => ({ ...fields }) }
|
|
120
|
-
*/
|
|
121
|
-
const emitInlineFragmentsAsUnion = (inlineFragments, indent, variableNames, schema) => {
|
|
122
|
-
const padding = " ".repeat(indent);
|
|
123
|
-
for (const frag of inlineFragments) {
|
|
124
|
-
if (frag.onType === "") {
|
|
125
|
-
return (0, neverthrow.err)({
|
|
126
|
-
code: "GRAPHQL_INLINE_FRAGMENT_WITHOUT_TYPE",
|
|
127
|
-
message: "Inline fragments without type condition are not supported. Use `... on TypeName { }` syntax."
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
for (const frag of inlineFragments) {
|
|
132
|
-
if (schema && !schema.objects.has(frag.onType)) {
|
|
133
|
-
let isUnionMember = false;
|
|
134
|
-
for (const [, unionDef] of schema.unions) {
|
|
135
|
-
if (unionDef.members.has(frag.onType)) {
|
|
136
|
-
isUnionMember = true;
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
if (!isUnionMember) {
|
|
141
|
-
return (0, neverthrow.err)({
|
|
142
|
-
code: "GRAPHQL_INLINE_FRAGMENT_ON_INTERFACE",
|
|
143
|
-
message: `Inline fragments on interface type "${frag.onType}" are not supported. Use union types instead.`,
|
|
144
|
-
onType: frag.onType
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
const entries = [];
|
|
150
|
-
for (const frag of inlineFragments) {
|
|
151
|
-
const innerPadding = " ".repeat(indent + 1);
|
|
152
|
-
const fieldsResult = emitSelectionsInternal(frag.selections, indent + 2, variableNames, schema);
|
|
153
|
-
if (fieldsResult.isErr()) {
|
|
154
|
-
return (0, neverthrow.err)(fieldsResult.error);
|
|
155
|
-
}
|
|
156
|
-
entries.push(`${innerPadding}${frag.onType}: ({ f }) => ({
|
|
157
|
-
${fieldsResult.value}
|
|
158
|
-
${innerPadding}}),`);
|
|
159
|
-
}
|
|
160
|
-
return (0, neverthrow.ok)(`${padding}...({
|
|
161
|
-
${entries.join("\n")}
|
|
162
|
-
${padding}}),`);
|
|
163
|
-
};
|
|
164
|
-
/**
|
|
165
|
-
* Emit a single field selection.
|
|
166
|
-
*/
|
|
167
|
-
const emitFieldSelection = (field, indent, variableNames, schema) => {
|
|
168
|
-
const padding = " ".repeat(indent);
|
|
169
|
-
const args = field.arguments;
|
|
170
|
-
const selections = field.selections;
|
|
171
|
-
const hasArgs = args && args.length > 0;
|
|
172
|
-
const hasSelections = selections && selections.length > 0;
|
|
173
|
-
if (!hasArgs && !hasSelections) {
|
|
174
|
-
return (0, neverthrow.ok)(`${padding}${field.name}: true,`);
|
|
175
|
-
}
|
|
176
|
-
let line = `${padding}...f.${field.name}(`;
|
|
177
|
-
if (hasArgs) {
|
|
178
|
-
const argsResult = emitArguments(args, variableNames);
|
|
179
|
-
if (argsResult.isErr()) {
|
|
180
|
-
return (0, neverthrow.err)(argsResult.error);
|
|
181
|
-
}
|
|
182
|
-
line += argsResult.value;
|
|
183
|
-
}
|
|
184
|
-
line += ")";
|
|
185
|
-
if (hasSelections) {
|
|
186
|
-
const hasInlineFragments = selections.some((s) => s.kind === "inlineFragment");
|
|
187
|
-
if (hasInlineFragments) {
|
|
188
|
-
const nestedResult = emitSelectionsInternal(selections, indent + 1, variableNames, schema);
|
|
189
|
-
if (nestedResult.isErr()) {
|
|
190
|
-
return (0, neverthrow.err)(nestedResult.error);
|
|
191
|
-
}
|
|
192
|
-
line += "({\n";
|
|
193
|
-
line += `${nestedResult.value}\n`;
|
|
194
|
-
line += `${padding}})`;
|
|
195
|
-
} else {
|
|
196
|
-
line += "(({ f }) => ({\n";
|
|
197
|
-
const nestedResult = emitSelectionsInternal(selections, indent + 1, variableNames, schema);
|
|
198
|
-
if (nestedResult.isErr()) {
|
|
199
|
-
return (0, neverthrow.err)(nestedResult.error);
|
|
200
|
-
}
|
|
201
|
-
line += `${nestedResult.value}\n`;
|
|
202
|
-
line += `${padding}}))`;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
line += ",";
|
|
206
|
-
return (0, neverthrow.ok)(line);
|
|
207
|
-
};
|
|
208
|
-
/**
|
|
209
|
-
* Emit field arguments.
|
|
210
|
-
*/
|
|
211
|
-
const emitArguments = (args, variableNames) => {
|
|
212
|
-
if (args.length === 0) {
|
|
213
|
-
return (0, neverthrow.ok)("");
|
|
214
|
-
}
|
|
215
|
-
const argEntries = [];
|
|
216
|
-
for (const arg of args) {
|
|
217
|
-
const result = emitValue(arg.value, variableNames);
|
|
218
|
-
if (result.isErr()) {
|
|
219
|
-
return (0, neverthrow.err)(result.error);
|
|
220
|
-
}
|
|
221
|
-
argEntries.push(`${arg.name}: ${result.value}`);
|
|
222
|
-
}
|
|
223
|
-
return (0, neverthrow.ok)(`{ ${argEntries.join(", ")} }`);
|
|
224
|
-
};
|
|
225
|
-
/**
|
|
226
|
-
* Emit a value (literal or variable reference).
|
|
227
|
-
*/
|
|
228
|
-
const emitValue = (value, variableNames) => {
|
|
229
|
-
switch (value.kind) {
|
|
230
|
-
case "variable":
|
|
231
|
-
if (variableNames.has(value.name)) {
|
|
232
|
-
return (0, neverthrow.ok)(`$.${value.name}`);
|
|
233
|
-
}
|
|
234
|
-
return (0, neverthrow.err)({
|
|
235
|
-
code: "GRAPHQL_UNDECLARED_VARIABLE",
|
|
236
|
-
message: `Variable "$${value.name}" is not declared in the operation`,
|
|
237
|
-
variableName: value.name
|
|
238
|
-
});
|
|
239
|
-
case "int":
|
|
240
|
-
case "float": return (0, neverthrow.ok)(value.value);
|
|
241
|
-
case "string": return (0, neverthrow.ok)(JSON.stringify(value.value));
|
|
242
|
-
case "boolean": return (0, neverthrow.ok)(value.value ? "true" : "false");
|
|
243
|
-
case "null": return (0, neverthrow.ok)("null");
|
|
244
|
-
case "enum": return (0, neverthrow.ok)(JSON.stringify(value.value));
|
|
245
|
-
case "list": {
|
|
246
|
-
const values = [];
|
|
247
|
-
for (const v of value.values) {
|
|
248
|
-
const result = emitValue(v, variableNames);
|
|
249
|
-
if (result.isErr()) {
|
|
250
|
-
return (0, neverthrow.err)(result.error);
|
|
251
|
-
}
|
|
252
|
-
values.push(result.value);
|
|
253
|
-
}
|
|
254
|
-
return (0, neverthrow.ok)(`[${values.join(", ")}]`);
|
|
255
|
-
}
|
|
256
|
-
case "object": {
|
|
257
|
-
if (value.fields.length === 0) {
|
|
258
|
-
return (0, neverthrow.ok)("{}");
|
|
259
|
-
}
|
|
260
|
-
const entries = [];
|
|
261
|
-
for (const f of value.fields) {
|
|
262
|
-
const result = emitValue(f.value, variableNames);
|
|
263
|
-
if (result.isErr()) {
|
|
264
|
-
return (0, neverthrow.err)(result.error);
|
|
265
|
-
}
|
|
266
|
-
entries.push(`${f.name}: ${result.value}`);
|
|
267
|
-
}
|
|
268
|
-
return (0, neverthrow.ok)(`{ ${entries.join(", ")} }`);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
//#endregion
|
|
274
9
|
//#region packages/codegen/src/graphql-compat/parser.ts
|
|
275
10
|
/**
|
|
276
11
|
* Parser for .graphql operation files.
|
|
@@ -569,13 +304,59 @@ const buildModifier = (structure) => {
|
|
|
569
304
|
return structure.inner + structure.lists.join("");
|
|
570
305
|
};
|
|
571
306
|
/**
|
|
572
|
-
*
|
|
573
|
-
*
|
|
574
|
-
* - List depths must match
|
|
307
|
+
* Check if source modifier can be assigned to target modifier.
|
|
308
|
+
* Implements GraphQL List Coercion: depth difference of 0 or 1 is allowed.
|
|
575
309
|
*
|
|
576
|
-
*
|
|
577
|
-
*
|
|
578
|
-
*
|
|
310
|
+
* Rules:
|
|
311
|
+
* - A single value can be coerced into a list (depth diff = 1)
|
|
312
|
+
* - At each level, non-null can be assigned to nullable (but not vice versa)
|
|
313
|
+
*
|
|
314
|
+
* @param source - The modifier of the value being assigned (variable's type)
|
|
315
|
+
* @param target - The modifier expected by the position (field argument's type)
|
|
316
|
+
* @returns true if assignment is valid
|
|
317
|
+
*/
|
|
318
|
+
const isModifierAssignable = (source, target) => {
|
|
319
|
+
const srcStruct = parseModifierStructure(source);
|
|
320
|
+
const tgtStruct = parseModifierStructure(target);
|
|
321
|
+
const depthDiff = tgtStruct.lists.length - srcStruct.lists.length;
|
|
322
|
+
if (depthDiff < 0 || depthDiff > 1) return false;
|
|
323
|
+
const tgtListsToCompare = depthDiff === 1 ? tgtStruct.lists.slice(1) : tgtStruct.lists;
|
|
324
|
+
if (depthDiff === 1 && srcStruct.lists.length === 0 && srcStruct.inner === "?" && tgtStruct.lists[0] === "[]!") {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
if (srcStruct.inner === "?" && tgtStruct.inner === "!") return false;
|
|
328
|
+
for (let i = 0; i < srcStruct.lists.length; i++) {
|
|
329
|
+
const srcList = srcStruct.lists[i];
|
|
330
|
+
const tgtList = tgtListsToCompare[i];
|
|
331
|
+
if (srcList === "[]?" && tgtList === "[]!") return false;
|
|
332
|
+
}
|
|
333
|
+
return true;
|
|
334
|
+
};
|
|
335
|
+
/**
|
|
336
|
+
* Derive minimum modifier needed to satisfy expected modifier.
|
|
337
|
+
* When List Coercion can apply, returns one level shallower.
|
|
338
|
+
*
|
|
339
|
+
* @param expectedModifier - The modifier expected by the field argument
|
|
340
|
+
* @returns The minimum modifier the variable must have
|
|
341
|
+
*/
|
|
342
|
+
const deriveMinimumModifier = (expectedModifier) => {
|
|
343
|
+
const struct = parseModifierStructure(expectedModifier);
|
|
344
|
+
if (struct.lists.length > 0) {
|
|
345
|
+
return buildModifier({
|
|
346
|
+
inner: struct.inner,
|
|
347
|
+
lists: struct.lists.slice(1)
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
return expectedModifier;
|
|
351
|
+
};
|
|
352
|
+
/**
|
|
353
|
+
* Merge two modifiers by taking the stricter constraint at each level.
|
|
354
|
+
* - Non-null (!) is stricter than nullable (?)
|
|
355
|
+
* - List depths must match
|
|
356
|
+
*
|
|
357
|
+
* @param a - First modifier
|
|
358
|
+
* @param b - Second modifier
|
|
359
|
+
* @returns Merged modifier or error if incompatible
|
|
579
360
|
*/
|
|
580
361
|
const mergeModifiers = (a, b) => {
|
|
581
362
|
const structA = parseModifierStructure(a);
|
|
@@ -650,7 +431,8 @@ const collectVariablesFromValue = (value, expectedTypeName, expectedModifier, sc
|
|
|
650
431
|
usages.push({
|
|
651
432
|
name: value.name,
|
|
652
433
|
typeName: expectedTypeName,
|
|
653
|
-
|
|
434
|
+
expectedModifier,
|
|
435
|
+
minimumModifier: deriveMinimumModifier(expectedModifier),
|
|
654
436
|
typeKind
|
|
655
437
|
});
|
|
656
438
|
return null;
|
|
@@ -760,7 +542,12 @@ const getFieldReturnType = (schema, parentTypeName, fieldName) => {
|
|
|
760
542
|
};
|
|
761
543
|
/**
|
|
762
544
|
* Merge multiple variable usages into a single InferredVariable.
|
|
763
|
-
* Validates type compatibility and merges modifiers using
|
|
545
|
+
* Validates type compatibility and merges modifiers using List Coercion rules.
|
|
546
|
+
*
|
|
547
|
+
* The algorithm:
|
|
548
|
+
* 1. Validate all usages have the same type name
|
|
549
|
+
* 2. Merge minimumModifiers to find the candidate (shallowest type that could work)
|
|
550
|
+
* 3. Verify the candidate can satisfy ALL expected modifiers via isModifierAssignable
|
|
764
551
|
*/
|
|
765
552
|
const mergeVariableUsages = (variableName, usages) => {
|
|
766
553
|
if (usages.length === 0) {
|
|
@@ -780,9 +567,9 @@ const mergeVariableUsages = (variableName, usages) => {
|
|
|
780
567
|
});
|
|
781
568
|
}
|
|
782
569
|
}
|
|
783
|
-
let
|
|
570
|
+
let candidateModifier = first.minimumModifier;
|
|
784
571
|
for (let i = 1; i < usages.length; i++) {
|
|
785
|
-
const result = mergeModifiers(
|
|
572
|
+
const result = mergeModifiers(candidateModifier, usages[i].minimumModifier);
|
|
786
573
|
if (!result.ok) {
|
|
787
574
|
return (0, neverthrow.err)({
|
|
788
575
|
code: "GRAPHQL_VARIABLE_MODIFIER_INCOMPATIBLE",
|
|
@@ -790,12 +577,21 @@ const mergeVariableUsages = (variableName, usages) => {
|
|
|
790
577
|
variableName
|
|
791
578
|
});
|
|
792
579
|
}
|
|
793
|
-
|
|
580
|
+
candidateModifier = result.value;
|
|
581
|
+
}
|
|
582
|
+
for (const usage of usages) {
|
|
583
|
+
if (!isModifierAssignable(candidateModifier, usage.expectedModifier)) {
|
|
584
|
+
return (0, neverthrow.err)({
|
|
585
|
+
code: "GRAPHQL_VARIABLE_MODIFIER_INCOMPATIBLE",
|
|
586
|
+
message: `Variable "$${variableName}" with modifier "${candidateModifier}" cannot satisfy expected "${usage.expectedModifier}"`,
|
|
587
|
+
variableName
|
|
588
|
+
});
|
|
589
|
+
}
|
|
794
590
|
}
|
|
795
591
|
return (0, neverthrow.ok)({
|
|
796
592
|
name: variableName,
|
|
797
593
|
typeName: first.typeName,
|
|
798
|
-
modifier:
|
|
594
|
+
modifier: candidateModifier,
|
|
799
595
|
typeKind: first.typeKind
|
|
800
596
|
});
|
|
801
597
|
};
|
|
@@ -991,7 +787,8 @@ const transformFragment = (frag, schema, resolvedFragmentVariables) => {
|
|
|
991
787
|
const allUsages = [...directUsages, ...spreadVariables.map((v) => ({
|
|
992
788
|
name: v.name,
|
|
993
789
|
typeName: v.typeName,
|
|
994
|
-
|
|
790
|
+
expectedModifier: v.modifier,
|
|
791
|
+
minimumModifier: v.modifier,
|
|
995
792
|
typeKind: v.typeKind
|
|
996
793
|
}))];
|
|
997
794
|
const variablesResult = inferVariablesFromUsages(allUsages);
|
|
@@ -1045,6 +842,375 @@ const collectFragmentDependencies = (selections) => {
|
|
|
1045
842
|
return [...fragments];
|
|
1046
843
|
};
|
|
1047
844
|
|
|
845
|
+
//#endregion
|
|
846
|
+
//#region packages/codegen/src/graphql-compat/emitter.ts
|
|
847
|
+
/**
|
|
848
|
+
* Map operation kind to root type name.
|
|
849
|
+
*/
|
|
850
|
+
const getRootTypeName = (kind) => {
|
|
851
|
+
switch (kind) {
|
|
852
|
+
case "query": return "Query";
|
|
853
|
+
case "mutation": return "Mutation";
|
|
854
|
+
case "subscription": return "Subscription";
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
/**
|
|
858
|
+
* Emit TypeScript code for an operation.
|
|
859
|
+
*/
|
|
860
|
+
const emitOperation = (operation, options) => {
|
|
861
|
+
const lines = [];
|
|
862
|
+
const schema = options.schemaDocument ? require_generator.createSchemaIndex(options.schemaDocument) : null;
|
|
863
|
+
const exportName = `${operation.name}Compat`;
|
|
864
|
+
const operationType = operation.kind;
|
|
865
|
+
lines.push(`export const ${exportName} = gql.${options.schemaName}(({ ${operationType}, $var }) =>`);
|
|
866
|
+
lines.push(` ${operationType}.compat({`);
|
|
867
|
+
lines.push(` name: ${JSON.stringify(operation.name)},`);
|
|
868
|
+
if (operation.variables.length > 0) {
|
|
869
|
+
lines.push(` variables: { ${emitVariables(operation.variables)} },`);
|
|
870
|
+
}
|
|
871
|
+
const rootTypeName = getRootTypeName(operation.kind);
|
|
872
|
+
lines.push(` fields: ({ f, $ }) => ({`);
|
|
873
|
+
const fieldLinesResult = emitSelections(operation.selections, 3, operation.variables, schema, rootTypeName);
|
|
874
|
+
if (fieldLinesResult.isErr()) {
|
|
875
|
+
return (0, neverthrow.err)(fieldLinesResult.error);
|
|
876
|
+
}
|
|
877
|
+
lines.push(fieldLinesResult.value);
|
|
878
|
+
lines.push(` }),`);
|
|
879
|
+
lines.push(` }),`);
|
|
880
|
+
lines.push(`);`);
|
|
881
|
+
return (0, neverthrow.ok)(lines.join("\n"));
|
|
882
|
+
};
|
|
883
|
+
/**
|
|
884
|
+
* Emit TypeScript code for a fragment.
|
|
885
|
+
*/
|
|
886
|
+
const emitFragment = (fragment, options) => {
|
|
887
|
+
const lines = [];
|
|
888
|
+
const schema = options.schemaDocument ? require_generator.createSchemaIndex(options.schemaDocument) : null;
|
|
889
|
+
const hasVariables = fragment.variables.length > 0;
|
|
890
|
+
const exportName = `${fragment.name}Fragment`;
|
|
891
|
+
const destructure = hasVariables ? "fragment, $var" : "fragment";
|
|
892
|
+
lines.push(`export const ${exportName} = gql.${options.schemaName}(({ ${destructure} }) =>`);
|
|
893
|
+
lines.push(` fragment.${fragment.onType}({`);
|
|
894
|
+
if (hasVariables) {
|
|
895
|
+
lines.push(` variables: { ${emitVariables(fragment.variables)} },`);
|
|
896
|
+
}
|
|
897
|
+
const fieldsContext = hasVariables ? "{ f, $ }" : "{ f }";
|
|
898
|
+
lines.push(` fields: (${fieldsContext}) => ({`);
|
|
899
|
+
const fieldLinesResult = emitSelections(fragment.selections, 3, fragment.variables, schema, fragment.onType);
|
|
900
|
+
if (fieldLinesResult.isErr()) {
|
|
901
|
+
return (0, neverthrow.err)(fieldLinesResult.error);
|
|
902
|
+
}
|
|
903
|
+
lines.push(fieldLinesResult.value);
|
|
904
|
+
lines.push(` }),`);
|
|
905
|
+
lines.push(` }),`);
|
|
906
|
+
lines.push(`);`);
|
|
907
|
+
return (0, neverthrow.ok)(lines.join("\n"));
|
|
908
|
+
};
|
|
909
|
+
/**
|
|
910
|
+
* Emit variable definitions.
|
|
911
|
+
*/
|
|
912
|
+
const emitVariables = (variables) => {
|
|
913
|
+
return variables.map((v) => `...$var(${JSON.stringify(v.name)}).${v.typeName}(${JSON.stringify(v.modifier)})`).join(", ");
|
|
914
|
+
};
|
|
915
|
+
/**
|
|
916
|
+
* Emit field selections (public API).
|
|
917
|
+
* Converts variable array to Set<string> and delegates to internal implementation.
|
|
918
|
+
*/
|
|
919
|
+
const emitSelections = (selections, indent, variables, schema, parentTypeName) => {
|
|
920
|
+
const variableNames = new Set(variables.map((v) => v.name));
|
|
921
|
+
return emitSelectionsInternal(selections, indent, variableNames, schema, parentTypeName);
|
|
922
|
+
};
|
|
923
|
+
/**
|
|
924
|
+
* Internal implementation for emitting field selections.
|
|
925
|
+
* Takes variableNames as Set<string> for recursive calls.
|
|
926
|
+
*/
|
|
927
|
+
const emitSelectionsInternal = (selections, indent, variableNames, schema, parentTypeName) => {
|
|
928
|
+
const lines = [];
|
|
929
|
+
const inlineFragments = [];
|
|
930
|
+
const otherSelections = [];
|
|
931
|
+
for (const sel of selections) {
|
|
932
|
+
if (sel.kind === "inlineFragment") {
|
|
933
|
+
inlineFragments.push(sel);
|
|
934
|
+
} else {
|
|
935
|
+
otherSelections.push(sel);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
for (const sel of otherSelections) {
|
|
939
|
+
const result = emitSingleSelection(sel, indent, variableNames, schema, parentTypeName);
|
|
940
|
+
if (result.isErr()) {
|
|
941
|
+
return (0, neverthrow.err)(result.error);
|
|
942
|
+
}
|
|
943
|
+
lines.push(result.value);
|
|
944
|
+
}
|
|
945
|
+
if (inlineFragments.length > 0) {
|
|
946
|
+
const unionResult = emitInlineFragmentsAsUnion(inlineFragments, indent, variableNames, schema);
|
|
947
|
+
if (unionResult.isErr()) {
|
|
948
|
+
return (0, neverthrow.err)(unionResult.error);
|
|
949
|
+
}
|
|
950
|
+
lines.push(unionResult.value);
|
|
951
|
+
}
|
|
952
|
+
return (0, neverthrow.ok)(lines.join("\n"));
|
|
953
|
+
};
|
|
954
|
+
/**
|
|
955
|
+
* Emit a single selection (field or fragment spread).
|
|
956
|
+
*/
|
|
957
|
+
const emitSingleSelection = (sel, indent, variableNames, schema, parentTypeName) => {
|
|
958
|
+
const padding = " ".repeat(indent);
|
|
959
|
+
switch (sel.kind) {
|
|
960
|
+
case "field": return emitFieldSelection(sel, indent, variableNames, schema, parentTypeName);
|
|
961
|
+
case "fragmentSpread": return (0, neverthrow.ok)(`${padding}...${sel.name}Fragment.spread(),`);
|
|
962
|
+
case "inlineFragment": return (0, neverthrow.ok)("");
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
/**
|
|
966
|
+
* Emit inline fragments grouped as a union selection.
|
|
967
|
+
* Format: { TypeA: ({ f }) => ({ ...fields }), TypeB: ({ f }) => ({ ...fields }) }
|
|
968
|
+
*/
|
|
969
|
+
const emitInlineFragmentsAsUnion = (inlineFragments, indent, variableNames, schema) => {
|
|
970
|
+
const padding = " ".repeat(indent);
|
|
971
|
+
for (const frag of inlineFragments) {
|
|
972
|
+
if (frag.onType === "") {
|
|
973
|
+
return (0, neverthrow.err)({
|
|
974
|
+
code: "GRAPHQL_INLINE_FRAGMENT_WITHOUT_TYPE",
|
|
975
|
+
message: "Inline fragments without type condition are not supported. Use `... on TypeName { }` syntax."
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
for (const frag of inlineFragments) {
|
|
980
|
+
if (schema && !schema.objects.has(frag.onType)) {
|
|
981
|
+
let isUnionMember = false;
|
|
982
|
+
for (const [, unionDef] of schema.unions) {
|
|
983
|
+
if (unionDef.members.has(frag.onType)) {
|
|
984
|
+
isUnionMember = true;
|
|
985
|
+
break;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
if (!isUnionMember) {
|
|
989
|
+
return (0, neverthrow.err)({
|
|
990
|
+
code: "GRAPHQL_INLINE_FRAGMENT_ON_INTERFACE",
|
|
991
|
+
message: `Inline fragments on interface type "${frag.onType}" are not supported. Use union types instead.`,
|
|
992
|
+
onType: frag.onType
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
const entries = [];
|
|
998
|
+
for (const frag of inlineFragments) {
|
|
999
|
+
const innerPadding = " ".repeat(indent + 1);
|
|
1000
|
+
const fieldsResult = emitSelectionsInternal(frag.selections, indent + 2, variableNames, schema, frag.onType);
|
|
1001
|
+
if (fieldsResult.isErr()) {
|
|
1002
|
+
return (0, neverthrow.err)(fieldsResult.error);
|
|
1003
|
+
}
|
|
1004
|
+
entries.push(`${innerPadding}${frag.onType}: ({ f }) => ({
|
|
1005
|
+
${fieldsResult.value}
|
|
1006
|
+
${innerPadding}}),`);
|
|
1007
|
+
}
|
|
1008
|
+
return (0, neverthrow.ok)(`${padding}...({
|
|
1009
|
+
${entries.join("\n")}
|
|
1010
|
+
${padding}}),`);
|
|
1011
|
+
};
|
|
1012
|
+
/**
|
|
1013
|
+
* Emit a single field selection.
|
|
1014
|
+
*/
|
|
1015
|
+
const emitFieldSelection = (field, indent, variableNames, schema, parentTypeName) => {
|
|
1016
|
+
const padding = " ".repeat(indent);
|
|
1017
|
+
const args = field.arguments;
|
|
1018
|
+
const selections = field.selections;
|
|
1019
|
+
const hasArgs = args && args.length > 0;
|
|
1020
|
+
const hasSelections = selections && selections.length > 0;
|
|
1021
|
+
if (!hasArgs && !hasSelections) {
|
|
1022
|
+
return (0, neverthrow.ok)(`${padding}${field.name}: true,`);
|
|
1023
|
+
}
|
|
1024
|
+
let line = `${padding}...f.${field.name}(`;
|
|
1025
|
+
if (hasArgs) {
|
|
1026
|
+
const argsResult = emitArguments(args, variableNames, schema, parentTypeName, field.name);
|
|
1027
|
+
if (argsResult.isErr()) {
|
|
1028
|
+
return (0, neverthrow.err)(argsResult.error);
|
|
1029
|
+
}
|
|
1030
|
+
line += argsResult.value;
|
|
1031
|
+
}
|
|
1032
|
+
line += ")";
|
|
1033
|
+
if (hasSelections) {
|
|
1034
|
+
const hasInlineFragments = selections.some((s) => s.kind === "inlineFragment");
|
|
1035
|
+
const nestedParentType = schema && parentTypeName ? getFieldReturnType(schema, parentTypeName, field.name) ?? undefined : undefined;
|
|
1036
|
+
if (hasInlineFragments) {
|
|
1037
|
+
const nestedResult = emitSelectionsInternal(selections, indent + 1, variableNames, schema, nestedParentType);
|
|
1038
|
+
if (nestedResult.isErr()) {
|
|
1039
|
+
return (0, neverthrow.err)(nestedResult.error);
|
|
1040
|
+
}
|
|
1041
|
+
line += "({\n";
|
|
1042
|
+
line += `${nestedResult.value}\n`;
|
|
1043
|
+
line += `${padding}})`;
|
|
1044
|
+
} else {
|
|
1045
|
+
line += "(({ f }) => ({\n";
|
|
1046
|
+
const nestedResult = emitSelectionsInternal(selections, indent + 1, variableNames, schema, nestedParentType);
|
|
1047
|
+
if (nestedResult.isErr()) {
|
|
1048
|
+
return (0, neverthrow.err)(nestedResult.error);
|
|
1049
|
+
}
|
|
1050
|
+
line += `${nestedResult.value}\n`;
|
|
1051
|
+
line += `${padding}}))`;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
line += ",";
|
|
1055
|
+
return (0, neverthrow.ok)(line);
|
|
1056
|
+
};
|
|
1057
|
+
/**
|
|
1058
|
+
* Check if a modifier represents a list type (contains []).
|
|
1059
|
+
*/
|
|
1060
|
+
const isListModifier = (modifier) => {
|
|
1061
|
+
return modifier.includes("[]");
|
|
1062
|
+
};
|
|
1063
|
+
/**
|
|
1064
|
+
* Determine if a value needs to be wrapped in an array for list coercion.
|
|
1065
|
+
* Returns true if:
|
|
1066
|
+
* - Expected type is a list
|
|
1067
|
+
* - Value is NOT already a list
|
|
1068
|
+
* - Value is NOT a variable (runtime handles coercion)
|
|
1069
|
+
* - Value is NOT null
|
|
1070
|
+
*/
|
|
1071
|
+
const needsListCoercion = (value, expectedModifier) => {
|
|
1072
|
+
if (!expectedModifier) return false;
|
|
1073
|
+
if (!isListModifier(expectedModifier)) return false;
|
|
1074
|
+
if (value.kind === "variable") return false;
|
|
1075
|
+
if (value.kind === "null") return false;
|
|
1076
|
+
if (value.kind === "list") return false;
|
|
1077
|
+
return true;
|
|
1078
|
+
};
|
|
1079
|
+
/**
|
|
1080
|
+
* Extract the element type from a list type by removing the outermost list modifier.
|
|
1081
|
+
* For example: "![]!" (non-null list of non-null) → "!" (non-null element)
|
|
1082
|
+
* "?[]![]!" (nested lists) → "?[]!" (inner list type)
|
|
1083
|
+
* Returns null if the modifier doesn't represent a list type.
|
|
1084
|
+
*/
|
|
1085
|
+
const getListElementType = (expectedType) => {
|
|
1086
|
+
const { modifier, typeName } = expectedType;
|
|
1087
|
+
const listMatch = modifier.match(/^(.+?)(\[\][!?])$/);
|
|
1088
|
+
if (!listMatch || !listMatch[1]) return null;
|
|
1089
|
+
return {
|
|
1090
|
+
typeName,
|
|
1091
|
+
modifier: listMatch[1]
|
|
1092
|
+
};
|
|
1093
|
+
};
|
|
1094
|
+
/**
|
|
1095
|
+
* Emit a value with type context for list coercion.
|
|
1096
|
+
*/
|
|
1097
|
+
const emitValueWithType = (value, expectedType, variableNames, schema) => {
|
|
1098
|
+
const shouldCoerce = needsListCoercion(value, expectedType?.modifier);
|
|
1099
|
+
if (value.kind === "object" && expectedType && schema) {
|
|
1100
|
+
return emitObjectWithType(value, expectedType.typeName, variableNames, schema, shouldCoerce);
|
|
1101
|
+
}
|
|
1102
|
+
if (value.kind === "list" && expectedType && schema) {
|
|
1103
|
+
const elementType = getListElementType(expectedType);
|
|
1104
|
+
if (elementType) {
|
|
1105
|
+
const values = [];
|
|
1106
|
+
for (const v of value.values) {
|
|
1107
|
+
const result$1 = emitValueWithType(v, elementType, variableNames, schema);
|
|
1108
|
+
if (result$1.isErr()) return result$1;
|
|
1109
|
+
values.push(result$1.value);
|
|
1110
|
+
}
|
|
1111
|
+
return (0, neverthrow.ok)(`[${values.join(", ")}]`);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
const result = emitValue(value, variableNames);
|
|
1115
|
+
if (result.isErr()) return result;
|
|
1116
|
+
if (shouldCoerce) {
|
|
1117
|
+
return (0, neverthrow.ok)(`[${result.value}]`);
|
|
1118
|
+
}
|
|
1119
|
+
return result;
|
|
1120
|
+
};
|
|
1121
|
+
/**
|
|
1122
|
+
* Emit an object value with type context for recursive list coercion.
|
|
1123
|
+
*/
|
|
1124
|
+
const emitObjectWithType = (value, inputTypeName, variableNames, schema, wrapInArray) => {
|
|
1125
|
+
if (value.fields.length === 0) {
|
|
1126
|
+
return (0, neverthrow.ok)(wrapInArray ? "[{}]" : "{}");
|
|
1127
|
+
}
|
|
1128
|
+
const entries = [];
|
|
1129
|
+
for (const f of value.fields) {
|
|
1130
|
+
const fieldType = getInputFieldType(schema, inputTypeName, f.name);
|
|
1131
|
+
if (fieldType === null) {
|
|
1132
|
+
return (0, neverthrow.err)({
|
|
1133
|
+
code: "GRAPHQL_UNKNOWN_FIELD",
|
|
1134
|
+
message: `Unknown field "${f.name}" on input type "${inputTypeName}"`,
|
|
1135
|
+
typeName: inputTypeName,
|
|
1136
|
+
fieldName: f.name
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
const result = emitValueWithType(f.value, fieldType, variableNames, schema);
|
|
1140
|
+
if (result.isErr()) {
|
|
1141
|
+
return (0, neverthrow.err)(result.error);
|
|
1142
|
+
}
|
|
1143
|
+
entries.push(`${f.name}: ${result.value}`);
|
|
1144
|
+
}
|
|
1145
|
+
const objectStr = `{ ${entries.join(", ")} }`;
|
|
1146
|
+
return (0, neverthrow.ok)(wrapInArray ? `[${objectStr}]` : objectStr);
|
|
1147
|
+
};
|
|
1148
|
+
/**
|
|
1149
|
+
* Emit field arguments with type context for list coercion.
|
|
1150
|
+
*/
|
|
1151
|
+
const emitArguments = (args, variableNames, schema, parentTypeName, fieldName) => {
|
|
1152
|
+
if (args.length === 0) {
|
|
1153
|
+
return (0, neverthrow.ok)("");
|
|
1154
|
+
}
|
|
1155
|
+
const argEntries = [];
|
|
1156
|
+
for (const arg of args) {
|
|
1157
|
+
const expectedType = schema && parentTypeName && fieldName ? getArgumentType(schema, parentTypeName, fieldName, arg.name) : null;
|
|
1158
|
+
const result = emitValueWithType(arg.value, expectedType, variableNames, schema);
|
|
1159
|
+
if (result.isErr()) {
|
|
1160
|
+
return (0, neverthrow.err)(result.error);
|
|
1161
|
+
}
|
|
1162
|
+
argEntries.push(`${arg.name}: ${result.value}`);
|
|
1163
|
+
}
|
|
1164
|
+
return (0, neverthrow.ok)(`{ ${argEntries.join(", ")} }`);
|
|
1165
|
+
};
|
|
1166
|
+
/**
|
|
1167
|
+
* Emit a value (literal or variable reference).
|
|
1168
|
+
*/
|
|
1169
|
+
const emitValue = (value, variableNames) => {
|
|
1170
|
+
switch (value.kind) {
|
|
1171
|
+
case "variable":
|
|
1172
|
+
if (variableNames.has(value.name)) {
|
|
1173
|
+
return (0, neverthrow.ok)(`$.${value.name}`);
|
|
1174
|
+
}
|
|
1175
|
+
return (0, neverthrow.err)({
|
|
1176
|
+
code: "GRAPHQL_UNDECLARED_VARIABLE",
|
|
1177
|
+
message: `Variable "$${value.name}" is not declared in the operation`,
|
|
1178
|
+
variableName: value.name
|
|
1179
|
+
});
|
|
1180
|
+
case "int":
|
|
1181
|
+
case "float": return (0, neverthrow.ok)(value.value);
|
|
1182
|
+
case "string": return (0, neverthrow.ok)(JSON.stringify(value.value));
|
|
1183
|
+
case "boolean": return (0, neverthrow.ok)(value.value ? "true" : "false");
|
|
1184
|
+
case "null": return (0, neverthrow.ok)("null");
|
|
1185
|
+
case "enum": return (0, neverthrow.ok)(JSON.stringify(value.value));
|
|
1186
|
+
case "list": {
|
|
1187
|
+
const values = [];
|
|
1188
|
+
for (const v of value.values) {
|
|
1189
|
+
const result = emitValue(v, variableNames);
|
|
1190
|
+
if (result.isErr()) {
|
|
1191
|
+
return (0, neverthrow.err)(result.error);
|
|
1192
|
+
}
|
|
1193
|
+
values.push(result.value);
|
|
1194
|
+
}
|
|
1195
|
+
return (0, neverthrow.ok)(`[${values.join(", ")}]`);
|
|
1196
|
+
}
|
|
1197
|
+
case "object": {
|
|
1198
|
+
if (value.fields.length === 0) {
|
|
1199
|
+
return (0, neverthrow.ok)("{}");
|
|
1200
|
+
}
|
|
1201
|
+
const entries = [];
|
|
1202
|
+
for (const f of value.fields) {
|
|
1203
|
+
const result = emitValue(f.value, variableNames);
|
|
1204
|
+
if (result.isErr()) {
|
|
1205
|
+
return (0, neverthrow.err)(result.error);
|
|
1206
|
+
}
|
|
1207
|
+
entries.push(`${f.name}: ${result.value}`);
|
|
1208
|
+
}
|
|
1209
|
+
return (0, neverthrow.ok)(`{ ${entries.join(", ")} }`);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
|
|
1048
1214
|
//#endregion
|
|
1049
1215
|
//#region packages/codegen/src/inject-template.ts
|
|
1050
1216
|
const templateContents = `\
|
|
@@ -1552,8 +1718,12 @@ export * from "./_internal";
|
|
|
1552
1718
|
exports.collectVariableUsages = collectVariableUsages;
|
|
1553
1719
|
exports.emitFragment = emitFragment;
|
|
1554
1720
|
exports.emitOperation = emitOperation;
|
|
1721
|
+
exports.getArgumentType = getArgumentType;
|
|
1722
|
+
exports.getFieldReturnType = getFieldReturnType;
|
|
1723
|
+
exports.getInputFieldType = getInputFieldType;
|
|
1555
1724
|
exports.hashSchema = hashSchema;
|
|
1556
1725
|
exports.inferVariablesFromUsages = inferVariablesFromUsages;
|
|
1726
|
+
exports.isModifierAssignable = isModifierAssignable;
|
|
1557
1727
|
exports.loadSchema = loadSchema;
|
|
1558
1728
|
exports.mergeModifiers = mergeModifiers;
|
|
1559
1729
|
exports.mergeVariableUsages = mergeVariableUsages;
|