@soda-gql/codegen 0.11.11 → 0.11.13
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 +672 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +284 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +288 -6
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +668 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,675 @@
|
|
|
1
|
-
import { n as generateMultiSchemaModule } from "./generator-BJlFKC6z.mjs";
|
|
1
|
+
import { n as generateMultiSchemaModule, t as createSchemaIndex } from "./generator-BJlFKC6z.mjs";
|
|
2
|
+
import { err, ok } from "neverthrow";
|
|
3
|
+
import { Kind, concatAST, parse, print } from "graphql";
|
|
2
4
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
5
|
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
4
|
-
import { err, ok } from "neverthrow";
|
|
5
6
|
import { build } from "esbuild";
|
|
6
|
-
import { concatAST, parse, print } from "graphql";
|
|
7
7
|
import { createHash } from "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 ? createSchemaIndex(options.schemaDocument) : null;
|
|
16
|
+
lines.push(`import { gql } from "${options.graphqlSystemPath}";`);
|
|
17
|
+
if (operation.fragmentDependencies.length > 0 && options.fragmentImports) {
|
|
18
|
+
for (const fragName of operation.fragmentDependencies) {
|
|
19
|
+
const importPath = options.fragmentImports.get(fragName);
|
|
20
|
+
if (importPath) {
|
|
21
|
+
lines.push(`import { ${fragName}Fragment } from "${importPath}";`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
lines.push("");
|
|
26
|
+
const exportName = `${operation.name}Compat`;
|
|
27
|
+
const operationType = operation.kind;
|
|
28
|
+
lines.push(`export const ${exportName} = gql.${options.schemaName}(({ ${operationType}, $var }) =>`);
|
|
29
|
+
lines.push(` ${operationType}.compat({`);
|
|
30
|
+
lines.push(` name: ${JSON.stringify(operation.name)},`);
|
|
31
|
+
if (operation.variables.length > 0) {
|
|
32
|
+
lines.push(` variables: { ${emitVariables(operation.variables)} },`);
|
|
33
|
+
}
|
|
34
|
+
lines.push(` fields: ({ f, $ }) => ({`);
|
|
35
|
+
const fieldLinesResult = emitSelections(operation.selections, 3, operation.variables, schema);
|
|
36
|
+
if (fieldLinesResult.isErr()) {
|
|
37
|
+
return err(fieldLinesResult.error);
|
|
38
|
+
}
|
|
39
|
+
lines.push(fieldLinesResult.value);
|
|
40
|
+
lines.push(` }),`);
|
|
41
|
+
lines.push(` }),`);
|
|
42
|
+
lines.push(`);`);
|
|
43
|
+
return ok(lines.join("\n"));
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Emit TypeScript code for a fragment.
|
|
47
|
+
*/
|
|
48
|
+
const emitFragment = (fragment, options) => {
|
|
49
|
+
const lines = [];
|
|
50
|
+
const schema = options.schemaDocument ? createSchemaIndex(options.schemaDocument) : null;
|
|
51
|
+
lines.push(`import { gql } from "${options.graphqlSystemPath}";`);
|
|
52
|
+
if (fragment.fragmentDependencies.length > 0 && options.fragmentImports) {
|
|
53
|
+
for (const fragName of fragment.fragmentDependencies) {
|
|
54
|
+
const importPath = options.fragmentImports.get(fragName);
|
|
55
|
+
if (importPath) {
|
|
56
|
+
lines.push(`import { ${fragName}Fragment } from "${importPath}";`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
lines.push("");
|
|
61
|
+
const exportName = `${fragment.name}Fragment`;
|
|
62
|
+
lines.push(`export const ${exportName} = gql.${options.schemaName}(({ fragment }) =>`);
|
|
63
|
+
lines.push(` fragment.${fragment.onType}({`);
|
|
64
|
+
lines.push(` fields: ({ f }) => ({`);
|
|
65
|
+
const fieldLinesResult = emitSelections(fragment.selections, 3, [], schema);
|
|
66
|
+
if (fieldLinesResult.isErr()) {
|
|
67
|
+
return err(fieldLinesResult.error);
|
|
68
|
+
}
|
|
69
|
+
lines.push(fieldLinesResult.value);
|
|
70
|
+
lines.push(` }),`);
|
|
71
|
+
lines.push(` }),`);
|
|
72
|
+
lines.push(`);`);
|
|
73
|
+
return ok(lines.join("\n"));
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Emit variable definitions.
|
|
77
|
+
*/
|
|
78
|
+
const emitVariables = (variables) => {
|
|
79
|
+
return variables.map((v) => `...$var(${JSON.stringify(v.name)}).${v.typeName}(${JSON.stringify(v.modifier)})`).join(", ");
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Emit field selections (public API).
|
|
83
|
+
* Converts EnrichedVariable[] to Set<string> and delegates to internal implementation.
|
|
84
|
+
*/
|
|
85
|
+
const emitSelections = (selections, indent, variables, schema) => {
|
|
86
|
+
const variableNames = new Set(variables.map((v) => v.name));
|
|
87
|
+
return emitSelectionsInternal(selections, indent, variableNames, schema);
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Internal implementation for emitting field selections.
|
|
91
|
+
* Takes variableNames as Set<string> for recursive calls.
|
|
92
|
+
*/
|
|
93
|
+
const emitSelectionsInternal = (selections, indent, variableNames, schema) => {
|
|
94
|
+
const lines = [];
|
|
95
|
+
const inlineFragments = [];
|
|
96
|
+
const otherSelections = [];
|
|
97
|
+
for (const sel of selections) {
|
|
98
|
+
if (sel.kind === "inlineFragment") {
|
|
99
|
+
inlineFragments.push(sel);
|
|
100
|
+
} else {
|
|
101
|
+
otherSelections.push(sel);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const sel of otherSelections) {
|
|
105
|
+
const result = emitSingleSelection(sel, indent, variableNames, schema);
|
|
106
|
+
if (result.isErr()) {
|
|
107
|
+
return err(result.error);
|
|
108
|
+
}
|
|
109
|
+
lines.push(result.value);
|
|
110
|
+
}
|
|
111
|
+
if (inlineFragments.length > 0) {
|
|
112
|
+
const unionResult = emitInlineFragmentsAsUnion(inlineFragments, indent, variableNames, schema);
|
|
113
|
+
if (unionResult.isErr()) {
|
|
114
|
+
return err(unionResult.error);
|
|
115
|
+
}
|
|
116
|
+
lines.push(unionResult.value);
|
|
117
|
+
}
|
|
118
|
+
return ok(lines.join("\n"));
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Emit a single selection (field or fragment spread).
|
|
122
|
+
*/
|
|
123
|
+
const emitSingleSelection = (sel, indent, variableNames, schema) => {
|
|
124
|
+
const padding = " ".repeat(indent);
|
|
125
|
+
switch (sel.kind) {
|
|
126
|
+
case "field": return emitFieldSelection(sel, indent, variableNames, schema);
|
|
127
|
+
case "fragmentSpread": return ok(`${padding}...${sel.name}Fragment.spread(),`);
|
|
128
|
+
case "inlineFragment": return ok("");
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Emit inline fragments grouped as a union selection.
|
|
133
|
+
* Format: { TypeA: ({ f }) => ({ ...fields }), TypeB: ({ f }) => ({ ...fields }) }
|
|
134
|
+
*/
|
|
135
|
+
const emitInlineFragmentsAsUnion = (inlineFragments, indent, variableNames, schema) => {
|
|
136
|
+
const padding = " ".repeat(indent);
|
|
137
|
+
for (const frag of inlineFragments) {
|
|
138
|
+
if (frag.onType === "") {
|
|
139
|
+
return err({
|
|
140
|
+
code: "GRAPHQL_INLINE_FRAGMENT_WITHOUT_TYPE",
|
|
141
|
+
message: "Inline fragments without type condition are not supported. Use `... on TypeName { }` syntax."
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const frag of inlineFragments) {
|
|
146
|
+
if (schema && !schema.objects.has(frag.onType)) {
|
|
147
|
+
let isUnionMember = false;
|
|
148
|
+
for (const [, unionDef] of schema.unions) {
|
|
149
|
+
if (unionDef.members.has(frag.onType)) {
|
|
150
|
+
isUnionMember = true;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (!isUnionMember) {
|
|
155
|
+
return err({
|
|
156
|
+
code: "GRAPHQL_INLINE_FRAGMENT_ON_INTERFACE",
|
|
157
|
+
message: `Inline fragments on interface type "${frag.onType}" are not supported. Use union types instead.`,
|
|
158
|
+
onType: frag.onType
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const entries = [];
|
|
164
|
+
for (const frag of inlineFragments) {
|
|
165
|
+
const innerPadding = " ".repeat(indent + 1);
|
|
166
|
+
const fieldsResult = emitSelectionsInternal(frag.selections, indent + 2, variableNames, schema);
|
|
167
|
+
if (fieldsResult.isErr()) {
|
|
168
|
+
return err(fieldsResult.error);
|
|
169
|
+
}
|
|
170
|
+
entries.push(`${innerPadding}${frag.onType}: ({ f }) => ({
|
|
171
|
+
${fieldsResult.value}
|
|
172
|
+
${innerPadding}}),`);
|
|
173
|
+
}
|
|
174
|
+
return ok(`${padding}...({
|
|
175
|
+
${entries.join("\n")}
|
|
176
|
+
${padding}}),`);
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Emit a single field selection.
|
|
180
|
+
*/
|
|
181
|
+
const emitFieldSelection = (field, indent, variableNames, schema) => {
|
|
182
|
+
const padding = " ".repeat(indent);
|
|
183
|
+
const args = field.arguments;
|
|
184
|
+
const selections = field.selections;
|
|
185
|
+
const hasArgs = args && args.length > 0;
|
|
186
|
+
const hasSelections = selections && selections.length > 0;
|
|
187
|
+
let line = `${padding}...f.${field.name}(`;
|
|
188
|
+
if (hasArgs) {
|
|
189
|
+
const argsResult = emitArguments(args, variableNames);
|
|
190
|
+
if (argsResult.isErr()) {
|
|
191
|
+
return err(argsResult.error);
|
|
192
|
+
}
|
|
193
|
+
line += argsResult.value;
|
|
194
|
+
}
|
|
195
|
+
line += ")";
|
|
196
|
+
if (hasSelections) {
|
|
197
|
+
const hasInlineFragments = selections.some((s) => s.kind === "inlineFragment");
|
|
198
|
+
if (hasInlineFragments) {
|
|
199
|
+
const nestedResult = emitSelectionsInternal(selections, indent + 1, variableNames, schema);
|
|
200
|
+
if (nestedResult.isErr()) {
|
|
201
|
+
return err(nestedResult.error);
|
|
202
|
+
}
|
|
203
|
+
line += "({\n";
|
|
204
|
+
line += `${nestedResult.value}\n`;
|
|
205
|
+
line += `${padding}})`;
|
|
206
|
+
} else {
|
|
207
|
+
line += "(({ f }) => ({\n";
|
|
208
|
+
const nestedResult = emitSelectionsInternal(selections, indent + 1, variableNames, schema);
|
|
209
|
+
if (nestedResult.isErr()) {
|
|
210
|
+
return err(nestedResult.error);
|
|
211
|
+
}
|
|
212
|
+
line += `${nestedResult.value}\n`;
|
|
213
|
+
line += `${padding}}))`;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
line += ",";
|
|
217
|
+
return ok(line);
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* Emit field arguments.
|
|
221
|
+
*/
|
|
222
|
+
const emitArguments = (args, variableNames) => {
|
|
223
|
+
if (args.length === 0) {
|
|
224
|
+
return ok("");
|
|
225
|
+
}
|
|
226
|
+
const argEntries = [];
|
|
227
|
+
for (const arg of args) {
|
|
228
|
+
const result = emitValue(arg.value, variableNames);
|
|
229
|
+
if (result.isErr()) {
|
|
230
|
+
return err(result.error);
|
|
231
|
+
}
|
|
232
|
+
argEntries.push(`${arg.name}: ${result.value}`);
|
|
233
|
+
}
|
|
234
|
+
return ok(`{ ${argEntries.join(", ")} }`);
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* Emit a value (literal or variable reference).
|
|
238
|
+
*/
|
|
239
|
+
const emitValue = (value, variableNames) => {
|
|
240
|
+
switch (value.kind) {
|
|
241
|
+
case "variable":
|
|
242
|
+
if (variableNames.has(value.name)) {
|
|
243
|
+
return ok(`$.${value.name}`);
|
|
244
|
+
}
|
|
245
|
+
return err({
|
|
246
|
+
code: "GRAPHQL_UNDECLARED_VARIABLE",
|
|
247
|
+
message: `Variable "$${value.name}" is not declared in the operation`,
|
|
248
|
+
variableName: value.name
|
|
249
|
+
});
|
|
250
|
+
case "int":
|
|
251
|
+
case "float": return ok(value.value);
|
|
252
|
+
case "string": return ok(JSON.stringify(value.value));
|
|
253
|
+
case "boolean": return ok(value.value ? "true" : "false");
|
|
254
|
+
case "null": return ok("null");
|
|
255
|
+
case "enum": return ok(JSON.stringify(value.value));
|
|
256
|
+
case "list": {
|
|
257
|
+
const values = [];
|
|
258
|
+
for (const v of value.values) {
|
|
259
|
+
const result = emitValue(v, variableNames);
|
|
260
|
+
if (result.isErr()) {
|
|
261
|
+
return err(result.error);
|
|
262
|
+
}
|
|
263
|
+
values.push(result.value);
|
|
264
|
+
}
|
|
265
|
+
return ok(`[${values.join(", ")}]`);
|
|
266
|
+
}
|
|
267
|
+
case "object": {
|
|
268
|
+
if (value.fields.length === 0) {
|
|
269
|
+
return ok("{}");
|
|
270
|
+
}
|
|
271
|
+
const entries = [];
|
|
272
|
+
for (const f of value.fields) {
|
|
273
|
+
const result = emitValue(f.value, variableNames);
|
|
274
|
+
if (result.isErr()) {
|
|
275
|
+
return err(result.error);
|
|
276
|
+
}
|
|
277
|
+
entries.push(`${f.name}: ${result.value}`);
|
|
278
|
+
}
|
|
279
|
+
return ok(`{ ${entries.join(", ")} }`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region packages/codegen/src/graphql-compat/parser.ts
|
|
286
|
+
/**
|
|
287
|
+
* Parser for .graphql operation files.
|
|
288
|
+
* Extracts operations and fragments from GraphQL documents.
|
|
289
|
+
* @module
|
|
290
|
+
*/
|
|
291
|
+
/**
|
|
292
|
+
* Parse a single .graphql file and extract operations and fragments.
|
|
293
|
+
*/
|
|
294
|
+
const parseGraphqlFile = (filePath) => {
|
|
295
|
+
const resolvedPath = resolve(filePath);
|
|
296
|
+
if (!existsSync(resolvedPath)) {
|
|
297
|
+
return err({
|
|
298
|
+
code: "GRAPHQL_FILE_NOT_FOUND",
|
|
299
|
+
message: `GraphQL file not found at ${resolvedPath}`,
|
|
300
|
+
filePath: resolvedPath
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
const source = readFileSync(resolvedPath, "utf8");
|
|
305
|
+
const document = parse(source);
|
|
306
|
+
return ok(extractFromDocument(document, resolvedPath));
|
|
307
|
+
} catch (error) {
|
|
308
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
309
|
+
return err({
|
|
310
|
+
code: "GRAPHQL_PARSE_ERROR",
|
|
311
|
+
message: `GraphQL parse error: ${message}`,
|
|
312
|
+
filePath: resolvedPath
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
/**
|
|
317
|
+
* Parse GraphQL source string directly.
|
|
318
|
+
*/
|
|
319
|
+
const parseGraphqlSource = (source, sourceFile) => {
|
|
320
|
+
try {
|
|
321
|
+
const document = parse(source);
|
|
322
|
+
return ok(extractFromDocument(document, sourceFile));
|
|
323
|
+
} catch (error) {
|
|
324
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
325
|
+
return err({
|
|
326
|
+
code: "GRAPHQL_PARSE_ERROR",
|
|
327
|
+
message: `GraphQL parse error: ${message}`,
|
|
328
|
+
filePath: sourceFile
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
/**
|
|
333
|
+
* Extract operations and fragments from a parsed GraphQL document.
|
|
334
|
+
*/
|
|
335
|
+
const extractFromDocument = (document, sourceFile) => {
|
|
336
|
+
const operations = [];
|
|
337
|
+
const fragments = [];
|
|
338
|
+
for (const definition of document.definitions) {
|
|
339
|
+
if (definition.kind === Kind.OPERATION_DEFINITION) {
|
|
340
|
+
const operation = extractOperation(definition, sourceFile);
|
|
341
|
+
if (operation) {
|
|
342
|
+
operations.push(operation);
|
|
343
|
+
}
|
|
344
|
+
} else if (definition.kind === Kind.FRAGMENT_DEFINITION) {
|
|
345
|
+
fragments.push(extractFragment(definition, sourceFile));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
operations,
|
|
350
|
+
fragments
|
|
351
|
+
};
|
|
352
|
+
};
|
|
353
|
+
/**
|
|
354
|
+
* Extract a single operation from an OperationDefinitionNode.
|
|
355
|
+
*/
|
|
356
|
+
const extractOperation = (node, sourceFile) => {
|
|
357
|
+
if (!node.name) {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
const variables = (node.variableDefinitions ?? []).map(extractVariable);
|
|
361
|
+
const selections = extractSelections(node.selectionSet.selections);
|
|
362
|
+
return {
|
|
363
|
+
kind: node.operation,
|
|
364
|
+
name: node.name.value,
|
|
365
|
+
variables,
|
|
366
|
+
selections,
|
|
367
|
+
sourceFile
|
|
368
|
+
};
|
|
369
|
+
};
|
|
370
|
+
/**
|
|
371
|
+
* Extract a fragment from a FragmentDefinitionNode.
|
|
372
|
+
*/
|
|
373
|
+
const extractFragment = (node, sourceFile) => {
|
|
374
|
+
const selections = extractSelections(node.selectionSet.selections);
|
|
375
|
+
return {
|
|
376
|
+
name: node.name.value,
|
|
377
|
+
onType: node.typeCondition.name.value,
|
|
378
|
+
selections,
|
|
379
|
+
sourceFile
|
|
380
|
+
};
|
|
381
|
+
};
|
|
382
|
+
/**
|
|
383
|
+
* Extract a variable definition.
|
|
384
|
+
*/
|
|
385
|
+
const extractVariable = (node) => {
|
|
386
|
+
const { typeName, modifier } = parseTypeNode(node.type);
|
|
387
|
+
const defaultValue = node.defaultValue ? extractValue(node.defaultValue) : undefined;
|
|
388
|
+
return {
|
|
389
|
+
name: node.variable.name.value,
|
|
390
|
+
typeName,
|
|
391
|
+
modifier,
|
|
392
|
+
typeKind: "scalar",
|
|
393
|
+
defaultValue
|
|
394
|
+
};
|
|
395
|
+
};
|
|
396
|
+
/**
|
|
397
|
+
* Parse a GraphQL TypeNode into type name and modifier.
|
|
398
|
+
*
|
|
399
|
+
* Format: inner nullability + list modifiers
|
|
400
|
+
* - Inner: `!` (non-null) or `?` (nullable)
|
|
401
|
+
* - List: `[]!` (non-null list) or `[]?` (nullable list)
|
|
402
|
+
*/
|
|
403
|
+
const parseTypeNode = (node) => {
|
|
404
|
+
const levels = [];
|
|
405
|
+
const collect = (n, nonNull) => {
|
|
406
|
+
if (n.kind === Kind.NON_NULL_TYPE) {
|
|
407
|
+
return collect(n.type, true);
|
|
408
|
+
}
|
|
409
|
+
if (n.kind === Kind.LIST_TYPE) {
|
|
410
|
+
levels.push({
|
|
411
|
+
kind: "list",
|
|
412
|
+
nonNull
|
|
413
|
+
});
|
|
414
|
+
return collect(n.type, false);
|
|
415
|
+
}
|
|
416
|
+
levels.push({
|
|
417
|
+
kind: "named",
|
|
418
|
+
nonNull
|
|
419
|
+
});
|
|
420
|
+
return n.name.value;
|
|
421
|
+
};
|
|
422
|
+
const typeName = collect(node, false);
|
|
423
|
+
let modifier = "?";
|
|
424
|
+
for (const level of levels.slice().reverse()) {
|
|
425
|
+
if (level.kind === "named") {
|
|
426
|
+
modifier = level.nonNull ? "!" : "?";
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
const listSuffix = level.nonNull ? "[]!" : "[]?";
|
|
430
|
+
modifier = `${modifier}${listSuffix}`;
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
typeName,
|
|
434
|
+
modifier
|
|
435
|
+
};
|
|
436
|
+
};
|
|
437
|
+
/**
|
|
438
|
+
* Extract selections from a SelectionSet.
|
|
439
|
+
*/
|
|
440
|
+
const extractSelections = (selections) => {
|
|
441
|
+
return selections.map(extractSelection);
|
|
442
|
+
};
|
|
443
|
+
/**
|
|
444
|
+
* Extract a single selection.
|
|
445
|
+
*/
|
|
446
|
+
const extractSelection = (node) => {
|
|
447
|
+
switch (node.kind) {
|
|
448
|
+
case Kind.FIELD: return extractFieldSelection(node);
|
|
449
|
+
case Kind.FRAGMENT_SPREAD: return extractFragmentSpread(node);
|
|
450
|
+
case Kind.INLINE_FRAGMENT: return extractInlineFragment(node);
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
/**
|
|
454
|
+
* Extract a field selection.
|
|
455
|
+
*/
|
|
456
|
+
const extractFieldSelection = (node) => {
|
|
457
|
+
const args = node.arguments?.length ? node.arguments.map(extractArgument) : undefined;
|
|
458
|
+
const selections = node.selectionSet ? extractSelections(node.selectionSet.selections) : undefined;
|
|
459
|
+
return {
|
|
460
|
+
kind: "field",
|
|
461
|
+
name: node.name.value,
|
|
462
|
+
alias: node.alias?.value,
|
|
463
|
+
arguments: args,
|
|
464
|
+
selections
|
|
465
|
+
};
|
|
466
|
+
};
|
|
467
|
+
/**
|
|
468
|
+
* Extract a fragment spread.
|
|
469
|
+
*/
|
|
470
|
+
const extractFragmentSpread = (node) => {
|
|
471
|
+
return {
|
|
472
|
+
kind: "fragmentSpread",
|
|
473
|
+
name: node.name.value
|
|
474
|
+
};
|
|
475
|
+
};
|
|
476
|
+
/**
|
|
477
|
+
* Extract an inline fragment.
|
|
478
|
+
*/
|
|
479
|
+
const extractInlineFragment = (node) => {
|
|
480
|
+
return {
|
|
481
|
+
kind: "inlineFragment",
|
|
482
|
+
onType: node.typeCondition?.name.value ?? "",
|
|
483
|
+
selections: extractSelections(node.selectionSet.selections)
|
|
484
|
+
};
|
|
485
|
+
};
|
|
486
|
+
/**
|
|
487
|
+
* Extract an argument.
|
|
488
|
+
*/
|
|
489
|
+
const extractArgument = (node) => {
|
|
490
|
+
return {
|
|
491
|
+
name: node.name.value,
|
|
492
|
+
value: extractValue(node.value)
|
|
493
|
+
};
|
|
494
|
+
};
|
|
495
|
+
/**
|
|
496
|
+
* Assert unreachable code path (for exhaustiveness checks).
|
|
497
|
+
*/
|
|
498
|
+
const assertUnreachable = (value) => {
|
|
499
|
+
throw new Error(`Unexpected value: ${JSON.stringify(value)}`);
|
|
500
|
+
};
|
|
501
|
+
/**
|
|
502
|
+
* Extract a value (literal or variable reference).
|
|
503
|
+
*/
|
|
504
|
+
const extractValue = (node) => {
|
|
505
|
+
switch (node.kind) {
|
|
506
|
+
case Kind.VARIABLE: return {
|
|
507
|
+
kind: "variable",
|
|
508
|
+
name: node.name.value
|
|
509
|
+
};
|
|
510
|
+
case Kind.INT: return {
|
|
511
|
+
kind: "int",
|
|
512
|
+
value: node.value
|
|
513
|
+
};
|
|
514
|
+
case Kind.FLOAT: return {
|
|
515
|
+
kind: "float",
|
|
516
|
+
value: node.value
|
|
517
|
+
};
|
|
518
|
+
case Kind.STRING: return {
|
|
519
|
+
kind: "string",
|
|
520
|
+
value: node.value
|
|
521
|
+
};
|
|
522
|
+
case Kind.BOOLEAN: return {
|
|
523
|
+
kind: "boolean",
|
|
524
|
+
value: node.value
|
|
525
|
+
};
|
|
526
|
+
case Kind.NULL: return { kind: "null" };
|
|
527
|
+
case Kind.ENUM: return {
|
|
528
|
+
kind: "enum",
|
|
529
|
+
value: node.value
|
|
530
|
+
};
|
|
531
|
+
case Kind.LIST: return {
|
|
532
|
+
kind: "list",
|
|
533
|
+
values: node.values.map(extractValue)
|
|
534
|
+
};
|
|
535
|
+
case Kind.OBJECT: return {
|
|
536
|
+
kind: "object",
|
|
537
|
+
fields: node.fields.map((field) => ({
|
|
538
|
+
name: field.name.value,
|
|
539
|
+
value: extractValue(field.value)
|
|
540
|
+
}))
|
|
541
|
+
};
|
|
542
|
+
default: return assertUnreachable(node);
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
//#endregion
|
|
547
|
+
//#region packages/codegen/src/graphql-compat/transformer.ts
|
|
548
|
+
/**
|
|
549
|
+
* Built-in GraphQL scalar types.
|
|
550
|
+
*/
|
|
551
|
+
const builtinScalarTypes = new Set([
|
|
552
|
+
"ID",
|
|
553
|
+
"String",
|
|
554
|
+
"Int",
|
|
555
|
+
"Float",
|
|
556
|
+
"Boolean"
|
|
557
|
+
]);
|
|
558
|
+
/**
|
|
559
|
+
* Check if a type name is a scalar type.
|
|
560
|
+
*/
|
|
561
|
+
const isScalarName = (schema, name) => builtinScalarTypes.has(name) || schema.scalars.has(name);
|
|
562
|
+
/**
|
|
563
|
+
* Check if a type name is an enum type.
|
|
564
|
+
*/
|
|
565
|
+
const isEnumName = (schema, name) => schema.enums.has(name);
|
|
566
|
+
/**
|
|
567
|
+
* Transform parsed operations/fragments by enriching them with schema information.
|
|
568
|
+
*
|
|
569
|
+
* This resolves variable type kinds (scalar, enum, input) and collects
|
|
570
|
+
* fragment dependencies.
|
|
571
|
+
*/
|
|
572
|
+
const transformParsedGraphql = (parsed, options) => {
|
|
573
|
+
const schema = createSchemaIndex(options.schemaDocument);
|
|
574
|
+
const operations = [];
|
|
575
|
+
for (const op of parsed.operations) {
|
|
576
|
+
const result = transformOperation(op, schema);
|
|
577
|
+
if (result.isErr()) {
|
|
578
|
+
return err(result.error);
|
|
579
|
+
}
|
|
580
|
+
operations.push(result.value);
|
|
581
|
+
}
|
|
582
|
+
const fragments = [];
|
|
583
|
+
for (const frag of parsed.fragments) {
|
|
584
|
+
const result = transformFragment(frag, schema);
|
|
585
|
+
if (result.isErr()) {
|
|
586
|
+
return err(result.error);
|
|
587
|
+
}
|
|
588
|
+
fragments.push(result.value);
|
|
589
|
+
}
|
|
590
|
+
return ok({
|
|
591
|
+
operations,
|
|
592
|
+
fragments
|
|
593
|
+
});
|
|
594
|
+
};
|
|
595
|
+
/**
|
|
596
|
+
* Transform a single operation.
|
|
597
|
+
*/
|
|
598
|
+
const transformOperation = (op, schema) => {
|
|
599
|
+
const variables = [];
|
|
600
|
+
for (const v of op.variables) {
|
|
601
|
+
const typeKind = resolveTypeKind(schema, v.typeName);
|
|
602
|
+
if (typeKind === null) {
|
|
603
|
+
return err({
|
|
604
|
+
code: "GRAPHQL_UNKNOWN_TYPE",
|
|
605
|
+
message: `Unknown type "${v.typeName}" in variable "${v.name}"`,
|
|
606
|
+
typeName: v.typeName
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
variables.push({
|
|
610
|
+
...v,
|
|
611
|
+
typeKind
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
const fragmentDependencies = collectFragmentDependencies(op.selections);
|
|
615
|
+
return ok({
|
|
616
|
+
...op,
|
|
617
|
+
variables,
|
|
618
|
+
fragmentDependencies
|
|
619
|
+
});
|
|
620
|
+
};
|
|
621
|
+
/**
|
|
622
|
+
* Transform a single fragment.
|
|
623
|
+
*/
|
|
624
|
+
const transformFragment = (frag, _schema) => {
|
|
625
|
+
const fragmentDependencies = collectFragmentDependencies(frag.selections);
|
|
626
|
+
return ok({
|
|
627
|
+
...frag,
|
|
628
|
+
fragmentDependencies
|
|
629
|
+
});
|
|
630
|
+
};
|
|
631
|
+
/**
|
|
632
|
+
* Resolve the type kind for a type name.
|
|
633
|
+
*/
|
|
634
|
+
const resolveTypeKind = (schema, typeName) => {
|
|
635
|
+
if (isScalarName(schema, typeName)) {
|
|
636
|
+
return "scalar";
|
|
637
|
+
}
|
|
638
|
+
if (isEnumName(schema, typeName)) {
|
|
639
|
+
return "enum";
|
|
640
|
+
}
|
|
641
|
+
if (schema.inputs.has(typeName)) {
|
|
642
|
+
return "input";
|
|
643
|
+
}
|
|
644
|
+
return null;
|
|
645
|
+
};
|
|
646
|
+
/**
|
|
647
|
+
* Collect fragment names used in selections (recursively).
|
|
648
|
+
*/
|
|
649
|
+
const collectFragmentDependencies = (selections) => {
|
|
650
|
+
const fragments = new Set();
|
|
651
|
+
const collect = (sels) => {
|
|
652
|
+
for (const sel of sels) {
|
|
653
|
+
switch (sel.kind) {
|
|
654
|
+
case "fragmentSpread":
|
|
655
|
+
fragments.add(sel.name);
|
|
656
|
+
break;
|
|
657
|
+
case "field":
|
|
658
|
+
if (sel.selections) {
|
|
659
|
+
collect(sel.selections);
|
|
660
|
+
}
|
|
661
|
+
break;
|
|
662
|
+
case "inlineFragment":
|
|
663
|
+
collect(sel.selections);
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
collect(selections);
|
|
669
|
+
return [...fragments];
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
//#endregion
|
|
9
673
|
//#region packages/codegen/src/inject-template.ts
|
|
10
674
|
const templateContents = `\
|
|
11
675
|
import { defineScalar } from "@soda-gql/core";
|
|
@@ -478,5 +1142,5 @@ export * from "./_internal";
|
|
|
478
1142
|
};
|
|
479
1143
|
|
|
480
1144
|
//#endregion
|
|
481
|
-
export { hashSchema, loadSchema, runCodegen, writeInjectTemplate };
|
|
1145
|
+
export { emitFragment, emitOperation, hashSchema, loadSchema, parseGraphqlFile, parseGraphqlSource, parseTypeNode, runCodegen, transformParsedGraphql, writeInjectTemplate };
|
|
482
1146
|
//# sourceMappingURL=index.mjs.map
|