@soda-gql/tools 0.13.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.
Files changed (49) hide show
  1. package/README.md +72 -0
  2. package/codegen.d.ts +2 -0
  3. package/codegen.js +1 -0
  4. package/dist/bin.cjs +15509 -0
  5. package/dist/bin.cjs.map +1 -0
  6. package/dist/bin.d.cts +839 -0
  7. package/dist/bin.d.cts.map +1 -0
  8. package/dist/chunk-BrXtsOCC.cjs +41 -0
  9. package/dist/codegen.cjs +4704 -0
  10. package/dist/codegen.cjs.map +1 -0
  11. package/dist/codegen.d.cts +416 -0
  12. package/dist/codegen.d.cts.map +1 -0
  13. package/dist/codegen.d.mts +416 -0
  14. package/dist/codegen.d.mts.map +1 -0
  15. package/dist/codegen.mjs +4712 -0
  16. package/dist/codegen.mjs.map +1 -0
  17. package/dist/formatter-Glj5a663.cjs +510 -0
  18. package/dist/formatter-Glj5a663.cjs.map +1 -0
  19. package/dist/formatter.cjs +509 -0
  20. package/dist/formatter.cjs.map +1 -0
  21. package/dist/formatter.d.cts +33 -0
  22. package/dist/formatter.d.cts.map +1 -0
  23. package/dist/formatter.d.mts +33 -0
  24. package/dist/formatter.d.mts.map +1 -0
  25. package/dist/formatter.mjs +507 -0
  26. package/dist/formatter.mjs.map +1 -0
  27. package/dist/index.cjs +13 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +11 -0
  30. package/dist/index.d.cts.map +1 -0
  31. package/dist/index.d.mts +11 -0
  32. package/dist/index.d.mts.map +1 -0
  33. package/dist/index.mjs +12 -0
  34. package/dist/index.mjs.map +1 -0
  35. package/dist/typegen.cjs +864 -0
  36. package/dist/typegen.cjs.map +1 -0
  37. package/dist/typegen.d.cts +205 -0
  38. package/dist/typegen.d.cts.map +1 -0
  39. package/dist/typegen.d.mts +205 -0
  40. package/dist/typegen.d.mts.map +1 -0
  41. package/dist/typegen.mjs +859 -0
  42. package/dist/typegen.mjs.map +1 -0
  43. package/formatter.d.ts +2 -0
  44. package/formatter.js +1 -0
  45. package/index.d.ts +2 -0
  46. package/index.js +1 -0
  47. package/package.json +102 -0
  48. package/typegen.d.ts +2 -0
  49. package/typegen.js +1 -0
@@ -0,0 +1,864 @@
1
+ const require_chunk = require('./chunk-BrXtsOCC.cjs');
2
+ let neverthrow = require("neverthrow");
3
+ let __soda_gql_core = require("@soda-gql/core");
4
+ let graphql = require("graphql");
5
+ let node_fs = require("node:fs");
6
+ let node_path = require("node:path");
7
+ let __soda_gql_common_template_extraction = require("@soda-gql/common/template-extraction");
8
+ let __swc_core = require("@swc/core");
9
+ let node_fs_promises = require("node:fs/promises");
10
+ let __soda_gql_builder = require("@soda-gql/builder");
11
+ let fast_glob = require("fast-glob");
12
+ fast_glob = require_chunk.__toESM(fast_glob);
13
+
14
+ //#region packages/tools/src/typegen/emitter.ts
15
+ /**
16
+ * Prebuilt types emitter.
17
+ *
18
+ * Generates TypeScript type definitions for PrebuiltTypes registry
19
+ * from field selection data and schema.
20
+ *
21
+ * ## Error Handling Strategy
22
+ *
23
+ * The emitter uses a partial failure approach for type calculation errors:
24
+ *
25
+ * **Recoverable errors** (result in warnings, element skipped):
26
+ * - Type calculation failures (e.g., `calculateFieldsType` throws)
27
+ * - Input type generation failures (e.g., `generateInputType` throws)
28
+ * - These are caught per-element, logged as warnings, and the element is omitted
29
+ *
30
+ * **Fatal errors** (result in error result):
31
+ * - `SCHEMA_NOT_FOUND`: Selection references non-existent schema
32
+ * - `WRITE_FAILED`: Cannot write output file to disk
33
+ *
34
+ * This allows builds to succeed with partial type coverage when some elements
35
+ * have issues, while providing visibility into problems via warnings.
36
+ *
37
+ * @module
38
+ */
39
+ /**
40
+ * Group field selections by schema.
41
+ * Uses the schemaLabel from each selection to group them correctly.
42
+ *
43
+ * Fragments without a 'key' property are skipped (not included in PrebuiltTypes)
44
+ * and a warning is added. This allows projects to use fragments without keys
45
+ * while still generating prebuilt types for those that have keys.
46
+ *
47
+ * @returns Result containing grouped selections and warnings, or error if schema not found
48
+ */
49
+ const groupBySchema = (fieldSelections, schemas) => {
50
+ const grouped = new Map();
51
+ const warnings = [];
52
+ let skippedFragmentCount = 0;
53
+ for (const schemaName of Object.keys(schemas)) {
54
+ grouped.set(schemaName, {
55
+ fragments: [],
56
+ operations: [],
57
+ inputObjects: new Set()
58
+ });
59
+ }
60
+ for (const [canonicalId, selection] of fieldSelections) {
61
+ const schemaName = selection.schemaLabel;
62
+ const schema = schemas[schemaName];
63
+ const group = grouped.get(schemaName);
64
+ if (!schema || !group) {
65
+ return (0, neverthrow.err)(__soda_gql_builder.builderErrors.schemaNotFound(schemaName, canonicalId));
66
+ }
67
+ const outputFormatters = { scalarOutput: (name) => `ScalarOutput_${schemaName}<"${name}">` };
68
+ const inputFormatters = {
69
+ scalarInput: (name) => `ScalarInput_${schemaName}<"${name}">`,
70
+ inputObject: (name) => `Input_${schemaName}_${name}`
71
+ };
72
+ if (selection.type === "fragment") {
73
+ if (!selection.key) {
74
+ skippedFragmentCount++;
75
+ warnings.push(`[prebuilt] Fragment "${canonicalId}" skipped: missing 'key' property. ` + `Use tagged template syntax fragment("Name", "Type")\`{ ... }\` to auto-assign a key, ` + `or set 'key' explicitly in the callback builder.`);
76
+ continue;
77
+ }
78
+ try {
79
+ const usedInputObjects = collectUsedInputObjectsFromVarDefs(schema, selection.variableDefinitions);
80
+ for (const inputName of usedInputObjects) {
81
+ group.inputObjects.add(inputName);
82
+ }
83
+ const outputType = (0, __soda_gql_core.calculateFieldsType)(schema, selection.fields, outputFormatters, selection.typename);
84
+ const hasVariables = Object.keys(selection.variableDefinitions).length > 0;
85
+ const inputType = hasVariables ? (0, __soda_gql_core.generateInputTypeFromVarDefs)(schema, selection.variableDefinitions, { formatters: inputFormatters }) : "void";
86
+ group.fragments.push({
87
+ key: selection.key,
88
+ typename: selection.typename,
89
+ inputType,
90
+ outputType
91
+ });
92
+ } catch (error) {
93
+ warnings.push(`[prebuilt] Failed to calculate type for fragment "${selection.key}": ${error instanceof Error ? error.message : String(error)}`);
94
+ }
95
+ } else if (selection.type === "operation") {
96
+ try {
97
+ const usedInputObjects = collectUsedInputObjects(schema, selection.variableDefinitions);
98
+ for (const inputName of usedInputObjects) {
99
+ group.inputObjects.add(inputName);
100
+ }
101
+ const rootTypeName = schema.operations[selection.operationType];
102
+ const outputType = (0, __soda_gql_core.calculateFieldsType)(schema, selection.fields, outputFormatters, rootTypeName ?? undefined);
103
+ const inputType = (0, __soda_gql_core.generateInputType)(schema, selection.variableDefinitions, inputFormatters);
104
+ group.operations.push({
105
+ key: selection.operationName,
106
+ inputType,
107
+ outputType
108
+ });
109
+ } catch (error) {
110
+ warnings.push(`[prebuilt] Failed to calculate type for operation "${selection.operationName}": ${error instanceof Error ? error.message : String(error)}`);
111
+ }
112
+ }
113
+ }
114
+ return (0, neverthrow.ok)({
115
+ grouped,
116
+ warnings,
117
+ skippedFragmentCount
118
+ });
119
+ };
120
+ /**
121
+ * Extract input object names from a GraphQL TypeNode.
122
+ */
123
+ const extractInputObjectsFromType = (schema, typeNode, inputObjects) => {
124
+ switch (typeNode.kind) {
125
+ case graphql.Kind.NON_NULL_TYPE:
126
+ extractInputObjectsFromType(schema, typeNode.type, inputObjects);
127
+ break;
128
+ case graphql.Kind.LIST_TYPE:
129
+ extractInputObjectsFromType(schema, typeNode.type, inputObjects);
130
+ break;
131
+ case graphql.Kind.NAMED_TYPE: {
132
+ const name = typeNode.name.value;
133
+ if (!schema.scalar[name] && !schema.enum[name] && schema.input[name]) {
134
+ inputObjects.add(name);
135
+ }
136
+ break;
137
+ }
138
+ }
139
+ };
140
+ /**
141
+ * Recursively collect nested input objects from schema definitions.
142
+ * Takes a set of initial input names and expands to include all nested inputs.
143
+ */
144
+ const collectNestedInputObjects = (schema, initialInputNames) => {
145
+ const inputObjects = new Set(initialInputNames);
146
+ const collectNested = (inputName, seen) => {
147
+ if (seen.has(inputName)) {
148
+ return;
149
+ }
150
+ seen.add(inputName);
151
+ const inputDef = schema.input[inputName];
152
+ if (!inputDef) {
153
+ return;
154
+ }
155
+ for (const fieldSpec of Object.values(inputDef.fields)) {
156
+ const parsed = (0, __soda_gql_core.parseInputSpecifier)(fieldSpec);
157
+ if (parsed.kind === "input" && !inputObjects.has(parsed.name)) {
158
+ inputObjects.add(parsed.name);
159
+ collectNested(parsed.name, seen);
160
+ }
161
+ }
162
+ };
163
+ for (const inputName of Array.from(initialInputNames)) {
164
+ collectNested(inputName, new Set());
165
+ }
166
+ return inputObjects;
167
+ };
168
+ /**
169
+ * Collect all input object types used in variable definitions.
170
+ * Recursively collects nested input objects from the schema.
171
+ */
172
+ const collectUsedInputObjects = (schema, variableDefinitions) => {
173
+ const directInputs = new Set();
174
+ for (const varDef of variableDefinitions) {
175
+ extractInputObjectsFromType(schema, varDef.type, directInputs);
176
+ }
177
+ return collectNestedInputObjects(schema, directInputs);
178
+ };
179
+ /**
180
+ * Collect all input object types used in InputTypeSpecifiers.
181
+ * Recursively collects nested input objects from the schema.
182
+ */
183
+ const _collectUsedInputObjectsFromSpecifiers = (schema, specifiers) => {
184
+ const directInputs = new Set();
185
+ for (const specStr of Object.values(specifiers)) {
186
+ const parsed = (0, __soda_gql_core.parseInputSpecifier)(specStr);
187
+ if (parsed.kind === "input" && schema.input[parsed.name]) {
188
+ directInputs.add(parsed.name);
189
+ }
190
+ }
191
+ return collectNestedInputObjects(schema, directInputs);
192
+ };
193
+ /**
194
+ * Collect all input object types used in VariableDefinitions (VarSpecifier objects).
195
+ * Used for fragment variable definitions.
196
+ */
197
+ const collectUsedInputObjectsFromVarDefs = (schema, varDefs) => {
198
+ const directInputs = new Set();
199
+ for (const varSpec of Object.values(varDefs)) {
200
+ if (varSpec.kind === "input" && schema.input[varSpec.name]) {
201
+ directInputs.add(varSpec.name);
202
+ }
203
+ }
204
+ return collectNestedInputObjects(schema, directInputs);
205
+ };
206
+ /**
207
+ * Generate type definitions for input objects.
208
+ */
209
+ const generateInputObjectTypeDefinitions = (schema, schemaName, inputNames) => {
210
+ const lines = [];
211
+ const defaultDepth = schema.__defaultInputDepth ?? 3;
212
+ const depthOverrides = schema.__inputDepthOverrides ?? {};
213
+ const formatters = {
214
+ scalarInput: (name) => `ScalarInput_${schemaName}<"${name}">`,
215
+ inputObject: (name) => `Input_${schemaName}_${name}`
216
+ };
217
+ const sortedNames = Array.from(inputNames).sort();
218
+ for (const inputName of sortedNames) {
219
+ const typeString = (0, __soda_gql_core.generateInputObjectType)(schema, inputName, {
220
+ defaultDepth,
221
+ depthOverrides,
222
+ formatters
223
+ });
224
+ lines.push(`export type Input_${schemaName}_${inputName} = ${typeString};`);
225
+ }
226
+ return lines;
227
+ };
228
+ /**
229
+ * Generate the TypeScript code for prebuilt types.
230
+ */
231
+ const generateTypesCode = (grouped, schemas, injectsModulePath) => {
232
+ const schemaNames = Object.keys(schemas);
233
+ const lines = [
234
+ "/**",
235
+ " * Prebuilt type registry.",
236
+ " *",
237
+ " * This file is auto-generated by @soda-gql/tools/typegen.",
238
+ " * Do not edit manually.",
239
+ " *",
240
+ " * @module",
241
+ " * @generated",
242
+ " */",
243
+ "",
244
+ "import type { AssertExtends, PrebuiltTypeRegistry } from \"@soda-gql/core\";"
245
+ ];
246
+ const scalarImports = schemaNames.map((name) => `scalar_${name}`).join(", ");
247
+ lines.push(`import type { ${scalarImports} } from "${injectsModulePath}";`);
248
+ lines.push("");
249
+ for (const schemaName of schemaNames) {
250
+ lines.push(`type ScalarInput_${schemaName}<T extends keyof typeof scalar_${schemaName}> = ` + `typeof scalar_${schemaName}[T]["$type"]["input"];`);
251
+ lines.push(`type ScalarOutput_${schemaName}<T extends keyof typeof scalar_${schemaName}> = ` + `typeof scalar_${schemaName}[T]["$type"]["output"];`);
252
+ }
253
+ lines.push("");
254
+ for (const [schemaName, { fragments, operations, inputObjects }] of grouped) {
255
+ const schema = schemas[schemaName];
256
+ if (inputObjects.size > 0 && schema) {
257
+ lines.push("// Input object types");
258
+ const inputTypeLines = generateInputObjectTypeDefinitions(schema, schemaName, inputObjects);
259
+ lines.push(...inputTypeLines);
260
+ lines.push("");
261
+ }
262
+ const deduplicatedFragments = new Map();
263
+ for (const f of fragments) {
264
+ deduplicatedFragments.set(f.key, f);
265
+ }
266
+ const fragmentEntries = Array.from(deduplicatedFragments.values()).sort((a, b) => a.key.localeCompare(b.key)).map((f) => ` readonly "${f.key}": { readonly typename: "${f.typename}"; readonly input: ${f.inputType}; readonly output: ${f.outputType} };`);
267
+ const deduplicatedOperations = new Map();
268
+ for (const o of operations) {
269
+ deduplicatedOperations.set(o.key, o);
270
+ }
271
+ const operationEntries = Array.from(deduplicatedOperations.values()).sort((a, b) => a.key.localeCompare(b.key)).map((o) => ` readonly "${o.key}": { readonly input: ${o.inputType}; readonly output: ${o.outputType} };`);
272
+ lines.push(`export type PrebuiltTypes_${schemaName} = {`);
273
+ lines.push(" readonly fragments: {");
274
+ if (fragmentEntries.length > 0) {
275
+ lines.push(...fragmentEntries);
276
+ }
277
+ lines.push(" };");
278
+ lines.push(" readonly operations: {");
279
+ if (operationEntries.length > 0) {
280
+ lines.push(...operationEntries);
281
+ }
282
+ lines.push(" };");
283
+ lines.push("};");
284
+ lines.push(`type _AssertPrebuiltTypes_${schemaName} = AssertExtends<PrebuiltTypes_${schemaName}, PrebuiltTypeRegistry>;`);
285
+ lines.push("");
286
+ }
287
+ return lines.join("\n");
288
+ };
289
+ /**
290
+ * Emit prebuilt types to the types.prebuilt.ts file.
291
+ *
292
+ * This function uses a partial failure strategy: if type calculation fails for
293
+ * individual elements (e.g., due to invalid field selections or missing schema
294
+ * types), those elements are skipped and warnings are collected rather than
295
+ * failing the entire emission. This allows builds to succeed even when some
296
+ * elements have issues, while still reporting problems via warnings.
297
+ *
298
+ * @param options - Emitter options including schemas, field selections, and output directory
299
+ * @returns Result containing output path and warnings, or error if a hard failure occurs
300
+ *
301
+ * @example
302
+ * ```typescript
303
+ * const result = await emitPrebuiltTypes({
304
+ * schemas: { mySchema: schema },
305
+ * fieldSelections,
306
+ * outdir: "./generated",
307
+ * injects: { mySchema: { scalars: "./scalars.ts" } },
308
+ * });
309
+ *
310
+ * if (result.isOk()) {
311
+ * console.log(`Generated: ${result.value.path}`);
312
+ * if (result.value.warnings.length > 0) {
313
+ * console.warn("Warnings:", result.value.warnings);
314
+ * }
315
+ * }
316
+ * ```
317
+ */
318
+ const emitPrebuiltTypes = async (options) => {
319
+ const { schemas, fieldSelections, outdir, injectsModulePath } = options;
320
+ const groupResult = groupBySchema(fieldSelections, schemas);
321
+ if (groupResult.isErr()) {
322
+ return (0, neverthrow.err)(groupResult.error);
323
+ }
324
+ const { grouped, warnings, skippedFragmentCount } = groupResult.value;
325
+ const code = generateTypesCode(grouped, schemas, injectsModulePath);
326
+ const typesPath = (0, node_path.join)(outdir, "types.prebuilt.ts");
327
+ try {
328
+ await (0, node_fs_promises.writeFile)(typesPath, code, "utf-8");
329
+ return (0, neverthrow.ok)({
330
+ path: typesPath,
331
+ warnings,
332
+ skippedFragmentCount
333
+ });
334
+ } catch (error) {
335
+ return (0, neverthrow.err)(__soda_gql_builder.builderErrors.writeFailed(typesPath, `Failed to write prebuilt types: ${error instanceof Error ? error.message : String(error)}`, error));
336
+ }
337
+ };
338
+
339
+ //#endregion
340
+ //#region packages/tools/src/typegen/errors.ts
341
+ /**
342
+ * Error constructor helpers for concise error creation.
343
+ */
344
+ const typegenErrors = {
345
+ codegenRequired: (outdir) => ({
346
+ code: "TYPEGEN_CODEGEN_REQUIRED",
347
+ message: `Generated graphql-system module not found at '${outdir}'. Run 'soda-gql codegen' first.`,
348
+ outdir
349
+ }),
350
+ schemaLoadFailed: (schemaNames, cause) => ({
351
+ code: "TYPEGEN_SCHEMA_LOAD_FAILED",
352
+ message: `Failed to load schemas: ${schemaNames.join(", ")}`,
353
+ schemaNames,
354
+ cause
355
+ }),
356
+ buildFailed: (message, cause) => ({
357
+ code: "TYPEGEN_BUILD_FAILED",
358
+ message,
359
+ cause
360
+ })
361
+ };
362
+ /**
363
+ * Format TypegenError for console output (human-readable).
364
+ */
365
+ const formatTypegenError = (error) => {
366
+ const lines = [];
367
+ lines.push(`Error [${error.code}]: ${error.message}`);
368
+ switch (error.code) {
369
+ case "TYPEGEN_CODEGEN_REQUIRED":
370
+ lines.push(` Output directory: ${error.outdir}`);
371
+ lines.push(" Hint: Run 'soda-gql codegen' to generate the graphql-system module first.");
372
+ break;
373
+ case "TYPEGEN_SCHEMA_LOAD_FAILED":
374
+ lines.push(` Schemas: ${error.schemaNames.join(", ")}`);
375
+ break;
376
+ }
377
+ if ("cause" in error && error.cause) {
378
+ lines.push(` Caused by: ${error.cause}`);
379
+ }
380
+ return lines.join("\n");
381
+ };
382
+
383
+ //#endregion
384
+ //#region packages/tools/src/typegen/template-extractor.ts
385
+ /**
386
+ * Parse TypeScript source with SWC, returning null on failure.
387
+ */
388
+ const safeParseSync = (source, tsx) => {
389
+ try {
390
+ return (0, __swc_core.parseSync)(source, {
391
+ syntax: "typescript",
392
+ tsx,
393
+ decorators: false,
394
+ dynamicImport: true
395
+ });
396
+ } catch {
397
+ return null;
398
+ }
399
+ };
400
+ /**
401
+ * Collect gql identifiers from import declarations.
402
+ * Finds imports like `import { gql } from "./graphql-system"`.
403
+ */
404
+ const collectGqlIdentifiers = (module$1, filePath, helper) => {
405
+ const identifiers = new Set();
406
+ for (const item of module$1.body) {
407
+ let declaration = null;
408
+ if (item.type === "ImportDeclaration") {
409
+ declaration = item;
410
+ } else if ("declaration" in item && item.declaration && item.declaration.type === "ImportDeclaration") {
411
+ declaration = item.declaration;
412
+ }
413
+ if (!declaration) {
414
+ continue;
415
+ }
416
+ if (!helper.isGraphqlSystemImportSpecifier({
417
+ filePath,
418
+ specifier: declaration.source.value
419
+ })) {
420
+ continue;
421
+ }
422
+ for (const specifier of declaration.specifiers ?? []) {
423
+ if (specifier.type === "ImportSpecifier") {
424
+ const imported = specifier.imported ? specifier.imported.value : specifier.local.value;
425
+ if (imported === "gql" && !specifier.imported) {
426
+ identifiers.add(specifier.local.value);
427
+ }
428
+ }
429
+ }
430
+ }
431
+ return identifiers;
432
+ };
433
+ /**
434
+ * Extract all tagged templates from a TypeScript source file.
435
+ *
436
+ * @param filePath - Absolute path to the source file (used for import resolution)
437
+ * @param source - TypeScript source code
438
+ * @param helper - GraphQL system identifier for resolving gql imports
439
+ * @returns Extracted templates and any warnings
440
+ */
441
+ const extractTemplatesFromSource = (filePath, source, helper) => {
442
+ const warnings = [];
443
+ const isTsx = filePath.endsWith(".tsx");
444
+ const program = safeParseSync(source, isTsx);
445
+ if (!program || program.type !== "Module") {
446
+ if (source.includes("gql")) {
447
+ warnings.push(`[typegen-extract] Failed to parse ${filePath}`);
448
+ }
449
+ return {
450
+ templates: [],
451
+ warnings
452
+ };
453
+ }
454
+ const gqlIdentifiers = collectGqlIdentifiers(program, filePath, helper);
455
+ if (gqlIdentifiers.size === 0) {
456
+ return {
457
+ templates: [],
458
+ warnings
459
+ };
460
+ }
461
+ return {
462
+ templates: (0, __soda_gql_common_template_extraction.walkAndExtract)(program, gqlIdentifiers),
463
+ warnings
464
+ };
465
+ };
466
+
467
+ //#endregion
468
+ //#region packages/tools/src/typegen/template-scanner.ts
469
+ /**
470
+ * Source file scanner for tagged template extraction.
471
+ *
472
+ * Discovers source files from config include/exclude patterns,
473
+ * reads them, and extracts tagged templates using the template extractor.
474
+ *
475
+ * @module
476
+ */
477
+ /**
478
+ * Scan source files for tagged templates.
479
+ *
480
+ * Uses fast-glob to discover files matching include/exclude patterns,
481
+ * then extracts tagged templates from each file.
482
+ */
483
+ const scanSourceFiles = (options) => {
484
+ const { include, exclude, baseDir, helper } = options;
485
+ const warnings = [];
486
+ const ignorePatterns = exclude.map((pattern) => pattern.startsWith("!") ? pattern.slice(1) : pattern);
487
+ const matchedFiles = fast_glob.default.sync(include, {
488
+ cwd: baseDir,
489
+ ignore: ignorePatterns,
490
+ onlyFiles: true,
491
+ absolute: true
492
+ });
493
+ const templates = new Map();
494
+ for (const filePath of matchedFiles) {
495
+ const normalizedPath = (0, node_path.normalize)((0, node_path.resolve)(filePath)).replace(/\\/g, "/");
496
+ try {
497
+ const source = (0, node_fs.readFileSync)(normalizedPath, "utf-8");
498
+ const { templates: extracted, warnings: extractionWarnings } = extractTemplatesFromSource(normalizedPath, source, helper);
499
+ warnings.push(...extractionWarnings);
500
+ if (extracted.length > 0) {
501
+ templates.set(normalizedPath, extracted);
502
+ }
503
+ } catch (error) {
504
+ const message = error instanceof Error ? error.message : String(error);
505
+ warnings.push(`[typegen-scan] Failed to read ${normalizedPath}: ${message}`);
506
+ }
507
+ }
508
+ return {
509
+ templates,
510
+ warnings
511
+ };
512
+ };
513
+
514
+ //#endregion
515
+ //#region packages/tools/src/typegen/template-to-selections.ts
516
+ /**
517
+ * Convert extracted templates into field selections for the emitter.
518
+ *
519
+ * @param templates - Templates extracted from source files, keyed by file path
520
+ * @param schemas - Loaded schema objects keyed by schema name
521
+ * @returns Map of canonical IDs to field selection data, plus any warnings
522
+ */
523
+ const convertTemplatesToSelections = (templates, schemas) => {
524
+ const selections = new Map();
525
+ const warnings = [];
526
+ const schemaIndexes = new Map(Object.entries(schemas).map(([name, schema]) => [name, (0, __soda_gql_core.createSchemaIndexFromSchema)(schema)]));
527
+ for (const [filePath, fileTemplates] of templates) {
528
+ for (const template of fileTemplates) {
529
+ const schema = schemas[template.schemaName];
530
+ if (!schema) {
531
+ warnings.push(`[typegen-template] Unknown schema "${template.schemaName}" in ${filePath}`);
532
+ continue;
533
+ }
534
+ const schemaIndex = schemaIndexes.get(template.schemaName);
535
+ if (!schemaIndex) {
536
+ continue;
537
+ }
538
+ try {
539
+ if (template.kind === "fragment") {
540
+ const selection = convertFragmentTemplate(template, schema, filePath);
541
+ if (selection) {
542
+ selections.set(selection.id, selection.data);
543
+ }
544
+ } else {
545
+ const selection = convertOperationTemplate(template, schema, filePath);
546
+ if (selection) {
547
+ selections.set(selection.id, selection.data);
548
+ }
549
+ }
550
+ } catch (error) {
551
+ const message = error instanceof Error ? error.message : String(error);
552
+ warnings.push(`[typegen-template] Failed to process ${template.kind} in ${filePath}: ${message}`);
553
+ }
554
+ }
555
+ }
556
+ return {
557
+ selections,
558
+ warnings
559
+ };
560
+ };
561
+ /**
562
+ * Recursively filter out __FRAG_SPREAD_ placeholder nodes from a selection set.
563
+ * These placeholders are created by template-extractor for interpolated fragment references.
564
+ * buildFieldsFromSelectionSet would throw on them since no interpolationMap is available.
565
+ */
566
+ const filterPlaceholderSpreads = (selectionSet) => ({
567
+ ...selectionSet,
568
+ selections: selectionSet.selections.filter((sel) => !(sel.kind === graphql.Kind.FRAGMENT_SPREAD && sel.name.value.startsWith("__FRAG_SPREAD_"))).map((sel) => {
569
+ if (sel.kind === graphql.Kind.FIELD && sel.selectionSet) {
570
+ return {
571
+ ...sel,
572
+ selectionSet: filterPlaceholderSpreads(sel.selectionSet)
573
+ };
574
+ }
575
+ if (sel.kind === graphql.Kind.INLINE_FRAGMENT && sel.selectionSet) {
576
+ return {
577
+ ...sel,
578
+ selectionSet: filterPlaceholderSpreads(sel.selectionSet)
579
+ };
580
+ }
581
+ return sel;
582
+ })
583
+ });
584
+ /** Simple matching-paren finder for template content (no comments/strings to handle). */
585
+ const findClosingParen = (source, openIndex) => {
586
+ let depth = 0;
587
+ for (let i = openIndex; i < source.length; i++) {
588
+ if (source[i] === "(") depth++;
589
+ else if (source[i] === ")") {
590
+ depth--;
591
+ if (depth === 0) return i;
592
+ }
593
+ }
594
+ return -1;
595
+ };
596
+ /**
597
+ * Reconstruct full GraphQL source from an extracted template.
598
+ * For curried syntax (new), prepends the definition header from tag call arguments.
599
+ * For curried fragments with Fragment Arguments, repositions variable declarations
600
+ * before the on-clause to produce RFC-compliant syntax.
601
+ * For old syntax, returns content as-is.
602
+ */
603
+ const reconstructGraphql = (template) => {
604
+ if (template.elementName) {
605
+ if (template.kind === "fragment" && template.typeName) {
606
+ const trimmed = template.content.trim();
607
+ if (trimmed.startsWith("(")) {
608
+ const closeIdx = findClosingParen(trimmed, 0);
609
+ if (closeIdx !== -1) {
610
+ const varDecls = trimmed.slice(0, closeIdx + 1);
611
+ const selectionSet = trimmed.slice(closeIdx + 1).trim();
612
+ return `fragment ${template.elementName}${varDecls} on ${template.typeName} ${selectionSet}`;
613
+ }
614
+ }
615
+ return `fragment ${template.elementName} on ${template.typeName} ${trimmed}`;
616
+ }
617
+ return `${template.kind} ${template.elementName} ${template.content}`;
618
+ }
619
+ return template.content;
620
+ };
621
+ /**
622
+ * Convert a fragment template into FieldSelectionData.
623
+ */
624
+ const convertFragmentTemplate = (template, schema, filePath) => {
625
+ const schemaIndex = (0, __soda_gql_core.createSchemaIndexFromSchema)(schema);
626
+ const graphqlSource = reconstructGraphql(template);
627
+ const variableDefinitions = (0, __soda_gql_core.extractFragmentVariables)(graphqlSource, schemaIndex);
628
+ const { preprocessed } = (0, __soda_gql_core.preprocessFragmentArgs)(graphqlSource);
629
+ const document = (0, graphql.parse)(preprocessed);
630
+ const fragDef = document.definitions.find((d) => d.kind === graphql.Kind.FRAGMENT_DEFINITION);
631
+ if (!fragDef || fragDef.kind !== graphql.Kind.FRAGMENT_DEFINITION) {
632
+ return null;
633
+ }
634
+ const fragmentName = fragDef.name.value;
635
+ const onType = fragDef.typeCondition.name.value;
636
+ const fields = (0, __soda_gql_core.buildFieldsFromSelectionSet)(filterPlaceholderSpreads(fragDef.selectionSet), schema, onType);
637
+ const id = `${filePath}::${fragmentName}`;
638
+ return {
639
+ id,
640
+ data: {
641
+ type: "fragment",
642
+ schemaLabel: schema.label,
643
+ key: fragmentName,
644
+ typename: onType,
645
+ fields,
646
+ variableDefinitions
647
+ }
648
+ };
649
+ };
650
+ /**
651
+ * Convert an operation template into FieldSelectionData.
652
+ */
653
+ const convertOperationTemplate = (template, schema, filePath) => {
654
+ const graphqlSource = reconstructGraphql(template);
655
+ const document = (0, graphql.parse)(graphqlSource);
656
+ const opDef = document.definitions.find((d) => d.kind === graphql.Kind.OPERATION_DEFINITION);
657
+ if (!opDef || opDef.kind !== graphql.Kind.OPERATION_DEFINITION) {
658
+ return null;
659
+ }
660
+ const operationName = opDef.name?.value ?? "Anonymous";
661
+ const operationType = opDef.operation;
662
+ const rootTypeName = getRootTypeName(schema, operationType);
663
+ const fields = (0, __soda_gql_core.buildFieldsFromSelectionSet)(filterPlaceholderSpreads(opDef.selectionSet), schema, rootTypeName);
664
+ const variableDefinitions = opDef.variableDefinitions ?? [];
665
+ const id = `${filePath}::${operationName}`;
666
+ return {
667
+ id,
668
+ data: {
669
+ type: "operation",
670
+ schemaLabel: schema.label,
671
+ operationName,
672
+ operationType,
673
+ fields,
674
+ variableDefinitions: [...variableDefinitions]
675
+ }
676
+ };
677
+ };
678
+ /**
679
+ * Get the root type name for an operation type from the schema.
680
+ */
681
+ const getRootTypeName = (schema, operationType) => {
682
+ switch (operationType) {
683
+ case "query": return schema.operations.query ?? "Query";
684
+ case "mutation": return schema.operations.mutation ?? "Mutation";
685
+ case "subscription": return schema.operations.subscription ?? "Subscription";
686
+ default: return "Query";
687
+ }
688
+ };
689
+
690
+ //#endregion
691
+ //#region packages/tools/src/typegen/runner.ts
692
+ /**
693
+ * Main typegen runner.
694
+ *
695
+ * Orchestrates the prebuilt type generation process:
696
+ * 1. Load schemas from generated CJS bundle
697
+ * 2. Build artifact to evaluate elements
698
+ * 3. Extract field selections from builder
699
+ * 4. Scan source files for tagged templates and merge selections
700
+ * 5. Emit types.prebuilt.ts
701
+ *
702
+ * @module
703
+ */
704
+ const extensionMap = {
705
+ ".ts": ".js",
706
+ ".tsx": ".js",
707
+ ".mts": ".mjs",
708
+ ".cts": ".cjs",
709
+ ".js": ".js",
710
+ ".mjs": ".mjs",
711
+ ".cjs": ".cjs"
712
+ };
713
+ const toImportSpecifier = (fromPath, targetPath, options) => {
714
+ const fromDir = (0, node_path.dirname)(fromPath);
715
+ const normalized = (0, node_path.relative)(fromDir, targetPath).replace(/\\/g, "/");
716
+ const sourceExt = (0, node_path.extname)(targetPath);
717
+ if (!options?.includeExtension) {
718
+ if (normalized.length === 0) {
719
+ return `./${targetPath.slice(0, -sourceExt.length).split("/").pop()}`;
720
+ }
721
+ const withPrefix$1 = normalized.startsWith(".") ? normalized : `./${normalized}`;
722
+ const currentExt$1 = (0, node_path.extname)(withPrefix$1);
723
+ return currentExt$1 ? withPrefix$1.slice(0, -currentExt$1.length) : withPrefix$1;
724
+ }
725
+ const runtimeExt = extensionMap[sourceExt] ?? sourceExt;
726
+ if (normalized.length === 0) {
727
+ const base = runtimeExt !== sourceExt ? targetPath.slice(0, -sourceExt.length).split("/").pop() : targetPath.split("/").pop();
728
+ return `./${base}${runtimeExt}`;
729
+ }
730
+ const withPrefix = normalized.startsWith(".") ? normalized : `./${normalized}`;
731
+ if (!runtimeExt) {
732
+ return withPrefix;
733
+ }
734
+ if (withPrefix.endsWith(runtimeExt)) {
735
+ return withPrefix;
736
+ }
737
+ const currentExt = (0, node_path.extname)(withPrefix);
738
+ const withoutExt = currentExt ? withPrefix.slice(0, -currentExt.length) : withPrefix;
739
+ return `${withoutExt}${runtimeExt}`;
740
+ };
741
+ /**
742
+ * Run the typegen process.
743
+ *
744
+ * This function:
745
+ * 1. Loads schemas from the generated CJS bundle
746
+ * 2. Creates a BuilderService and builds the artifact
747
+ * 3. Extracts field selections from the artifact
748
+ * 4. Scans source files for tagged templates and merges selections
749
+ * 5. Emits types.prebuilt.ts using emitPrebuiltTypes
750
+ *
751
+ * @param options - Typegen options including config
752
+ * @returns Result containing success data or error
753
+ */
754
+ const runTypegen = async (options) => {
755
+ const { config } = options;
756
+ const outdir = (0, node_path.resolve)(config.outdir);
757
+ const cjsPath = (0, node_path.join)(outdir, "index.cjs");
758
+ const importSpecifierOptions = { includeExtension: config.styles.importExtension };
759
+ if (!(0, node_fs.existsSync)(cjsPath)) {
760
+ return (0, neverthrow.err)(typegenErrors.codegenRequired(outdir));
761
+ }
762
+ const schemaNames = Object.keys(config.schemas);
763
+ const schemasResult = (0, __soda_gql_builder.loadSchemasFromBundle)(cjsPath, schemaNames);
764
+ if (schemasResult.isErr()) {
765
+ return (0, neverthrow.err)(typegenErrors.schemaLoadFailed(schemaNames, schemasResult.error));
766
+ }
767
+ const schemas = schemasResult.value;
768
+ const prebuiltTypesPath = (0, node_path.join)(outdir, "types.prebuilt.ts");
769
+ const injectsModulePath = toImportSpecifier(prebuiltTypesPath, (0, node_path.join)(outdir, "_internal-injects.ts"), importSpecifierOptions);
770
+ const builderService = (0, __soda_gql_builder.createBuilderService)({ config });
771
+ const artifactResult = await builderService.buildAsync();
772
+ if (artifactResult.isErr()) {
773
+ return (0, neverthrow.err)(typegenErrors.buildFailed(`Builder failed: ${artifactResult.error.message}`, artifactResult.error));
774
+ }
775
+ const intermediateElements = builderService.getIntermediateElements();
776
+ if (!intermediateElements) {
777
+ return (0, neverthrow.err)(typegenErrors.buildFailed("No intermediate elements available after build", undefined));
778
+ }
779
+ const fieldSelectionsResult = (0, __soda_gql_builder.extractFieldSelections)(intermediateElements);
780
+ const { selections: builderSelections, warnings: extractWarnings } = fieldSelectionsResult;
781
+ const graphqlHelper = (0, __soda_gql_builder.createGraphqlSystemIdentifyHelper)(config);
782
+ const scanResult = scanSourceFiles({
783
+ include: [...config.include],
784
+ exclude: [...config.exclude],
785
+ baseDir: config.baseDir,
786
+ helper: graphqlHelper
787
+ });
788
+ const templateSelections = convertTemplatesToSelections(scanResult.templates, schemas);
789
+ const fieldSelections = mergeSelections(builderSelections, templateSelections.selections, config.baseDir);
790
+ const scanWarnings = [...scanResult.warnings, ...templateSelections.warnings];
791
+ const emitResult = await emitPrebuiltTypes({
792
+ schemas,
793
+ fieldSelections,
794
+ outdir,
795
+ injectsModulePath
796
+ });
797
+ if (emitResult.isErr()) {
798
+ return (0, neverthrow.err)(emitResult.error);
799
+ }
800
+ const { warnings: emitWarnings, skippedFragmentCount } = emitResult.value;
801
+ let fragmentCount = 0;
802
+ let operationCount = 0;
803
+ for (const selection of fieldSelections.values()) {
804
+ if (selection.type === "fragment" && selection.key) {
805
+ fragmentCount++;
806
+ } else if (selection.type === "operation") {
807
+ operationCount++;
808
+ }
809
+ }
810
+ const allWarnings = [
811
+ ...extractWarnings,
812
+ ...scanWarnings,
813
+ ...emitWarnings
814
+ ];
815
+ return (0, neverthrow.ok)({
816
+ prebuiltTypesPath,
817
+ fragmentCount,
818
+ operationCount,
819
+ skippedFragmentCount,
820
+ warnings: allWarnings
821
+ });
822
+ };
823
+ const extractElementName = (data) => data.type === "fragment" ? data.key : data.operationName;
824
+ /**
825
+ * Merge builder and template selections into a combined map.
826
+ *
827
+ * Builder selections are authoritative — VM evaluation correctly resolves
828
+ * fragment spreads that static template analysis cannot handle.
829
+ * Template selections serve as fallback for elements only found by the scanner.
830
+ *
831
+ * Deduplication is per-element (file + GraphQL name), not per-file, so that
832
+ * callback-builder operations in files that also contain tagged templates are preserved.
833
+ */
834
+ const mergeSelections = (builderSelections, templateSelections, baseDir) => {
835
+ const extractFilePart = (id) => {
836
+ const filePart = id.split("::")[0] ?? "";
837
+ if (filePart.startsWith("/")) {
838
+ return (0, node_path.relative)(baseDir, filePart);
839
+ }
840
+ return filePart;
841
+ };
842
+ const builderElements = new Set();
843
+ for (const [id, data] of builderSelections) {
844
+ const name = extractElementName(data);
845
+ if (name) builderElements.add(`${extractFilePart(id)}::${name}`);
846
+ }
847
+ const fieldSelections = new Map();
848
+ for (const [id, data] of builderSelections) {
849
+ fieldSelections.set(id, data);
850
+ }
851
+ for (const [id, data] of templateSelections) {
852
+ const name = extractElementName(data);
853
+ if (name && builderElements.has(`${extractFilePart(id)}::${name}`)) continue;
854
+ fieldSelections.set(id, data);
855
+ }
856
+ return fieldSelections;
857
+ };
858
+
859
+ //#endregion
860
+ exports.emitPrebuiltTypes = emitPrebuiltTypes;
861
+ exports.formatTypegenError = formatTypegenError;
862
+ exports.runTypegen = runTypegen;
863
+ exports.typegenErrors = typegenErrors;
864
+ //# sourceMappingURL=typegen.cjs.map