@soda-gql/typegen 0.10.1 → 0.11.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.
- package/dist/index.cjs +269 -154
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -71
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +87 -71
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +269 -154
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -7,6 +7,78 @@ let neverthrow = require("neverthrow");
|
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
let esbuild = require("esbuild");
|
|
9
9
|
|
|
10
|
+
//#region packages/typegen/src/errors.ts
|
|
11
|
+
/**
|
|
12
|
+
* Error constructor helpers for concise error creation.
|
|
13
|
+
*/
|
|
14
|
+
const typegenErrors = {
|
|
15
|
+
codegenRequired: (outdir) => ({
|
|
16
|
+
code: "TYPEGEN_CODEGEN_REQUIRED",
|
|
17
|
+
message: `Generated graphql-system module not found at '${outdir}'. Run 'soda-gql codegen' first.`,
|
|
18
|
+
outdir
|
|
19
|
+
}),
|
|
20
|
+
schemaLoadFailed: (schemaNames, cause) => ({
|
|
21
|
+
code: "TYPEGEN_SCHEMA_LOAD_FAILED",
|
|
22
|
+
message: `Failed to load schemas: ${schemaNames.join(", ")}`,
|
|
23
|
+
schemaNames,
|
|
24
|
+
cause
|
|
25
|
+
}),
|
|
26
|
+
buildFailed: (message, cause) => ({
|
|
27
|
+
code: "TYPEGEN_BUILD_FAILED",
|
|
28
|
+
message,
|
|
29
|
+
cause
|
|
30
|
+
}),
|
|
31
|
+
emitFailed: (path, message, cause) => ({
|
|
32
|
+
code: "TYPEGEN_EMIT_FAILED",
|
|
33
|
+
message,
|
|
34
|
+
path,
|
|
35
|
+
cause
|
|
36
|
+
}),
|
|
37
|
+
bundleFailed: (path, message, cause) => ({
|
|
38
|
+
code: "TYPEGEN_BUNDLE_FAILED",
|
|
39
|
+
message,
|
|
40
|
+
path,
|
|
41
|
+
cause
|
|
42
|
+
}),
|
|
43
|
+
fragmentMissingKey: (fragments) => ({
|
|
44
|
+
code: "TYPEGEN_FRAGMENT_MISSING_KEY",
|
|
45
|
+
message: `${fragments.length} fragment(s) missing required 'key' property for prebuilt types`,
|
|
46
|
+
fragments
|
|
47
|
+
})
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Format TypegenError for console output (human-readable).
|
|
51
|
+
*/
|
|
52
|
+
const formatTypegenError = (error) => {
|
|
53
|
+
const lines = [];
|
|
54
|
+
lines.push(`Error [${error.code}]: ${error.message}`);
|
|
55
|
+
switch (error.code) {
|
|
56
|
+
case "TYPEGEN_CODEGEN_REQUIRED":
|
|
57
|
+
lines.push(` Output directory: ${error.outdir}`);
|
|
58
|
+
lines.push(" Hint: Run 'soda-gql codegen' to generate the graphql-system module first.");
|
|
59
|
+
break;
|
|
60
|
+
case "TYPEGEN_SCHEMA_LOAD_FAILED":
|
|
61
|
+
lines.push(` Schemas: ${error.schemaNames.join(", ")}`);
|
|
62
|
+
break;
|
|
63
|
+
case "TYPEGEN_EMIT_FAILED":
|
|
64
|
+
case "TYPEGEN_BUNDLE_FAILED":
|
|
65
|
+
lines.push(` Path: ${error.path}`);
|
|
66
|
+
break;
|
|
67
|
+
case "TYPEGEN_FRAGMENT_MISSING_KEY":
|
|
68
|
+
lines.push(" Fragments missing 'key' property:");
|
|
69
|
+
for (const fragment of error.fragments) {
|
|
70
|
+
lines.push(` - ${fragment.canonicalId} (${fragment.typename} on ${fragment.schemaLabel})`);
|
|
71
|
+
}
|
|
72
|
+
lines.push(" Hint: Add a 'key' property to each fragment for prebuilt type resolution.");
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
if ("cause" in error && error.cause) {
|
|
76
|
+
lines.push(` Caused by: ${error.cause}`);
|
|
77
|
+
}
|
|
78
|
+
return lines.join("\n");
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
//#endregion
|
|
10
82
|
//#region packages/typegen/src/emitter.ts
|
|
11
83
|
/**
|
|
12
84
|
* Prebuilt types emitter.
|
|
@@ -36,11 +108,16 @@ let esbuild = require("esbuild");
|
|
|
36
108
|
* Group field selections by schema.
|
|
37
109
|
* Uses the schemaLabel from each selection to group them correctly.
|
|
38
110
|
*
|
|
111
|
+
* In strict mode, all fragments must have a 'key' property. Fragments
|
|
112
|
+
* without keys will cause an error.
|
|
113
|
+
*
|
|
39
114
|
* @returns Result containing grouped selections and warnings, or error if schema not found
|
|
115
|
+
* or fragments are missing keys
|
|
40
116
|
*/
|
|
41
117
|
const groupBySchema = (fieldSelections, schemas) => {
|
|
42
118
|
const grouped = new Map();
|
|
43
119
|
const warnings = [];
|
|
120
|
+
const missingKeyFragments = [];
|
|
44
121
|
for (const schemaName of Object.keys(schemas)) {
|
|
45
122
|
grouped.set(schemaName, {
|
|
46
123
|
fragments: [],
|
|
@@ -62,6 +139,11 @@ const groupBySchema = (fieldSelections, schemas) => {
|
|
|
62
139
|
};
|
|
63
140
|
if (selection.type === "fragment") {
|
|
64
141
|
if (!selection.key) {
|
|
142
|
+
missingKeyFragments.push({
|
|
143
|
+
canonicalId,
|
|
144
|
+
typename: selection.typename,
|
|
145
|
+
schemaLabel: selection.schemaLabel
|
|
146
|
+
});
|
|
65
147
|
continue;
|
|
66
148
|
}
|
|
67
149
|
try {
|
|
@@ -74,6 +156,7 @@ const groupBySchema = (fieldSelections, schemas) => {
|
|
|
74
156
|
const inputType = hasVariables ? (0, __soda_gql_core.generateInputTypeFromSpecifiers)(schema, selection.variableDefinitions, { formatters: inputFormatters }) : "void";
|
|
75
157
|
group.fragments.push({
|
|
76
158
|
key: selection.key,
|
|
159
|
+
typename: selection.typename,
|
|
77
160
|
inputType,
|
|
78
161
|
outputType
|
|
79
162
|
});
|
|
@@ -98,23 +181,15 @@ const groupBySchema = (fieldSelections, schemas) => {
|
|
|
98
181
|
}
|
|
99
182
|
}
|
|
100
183
|
}
|
|
184
|
+
if (missingKeyFragments.length > 0) {
|
|
185
|
+
return (0, neverthrow.err)(typegenErrors.fragmentMissingKey(missingKeyFragments));
|
|
186
|
+
}
|
|
101
187
|
return (0, neverthrow.ok)({
|
|
102
188
|
grouped,
|
|
103
189
|
warnings
|
|
104
190
|
});
|
|
105
191
|
};
|
|
106
192
|
/**
|
|
107
|
-
* Calculate relative import path from one file to another.
|
|
108
|
-
*/
|
|
109
|
-
const toImportSpecifier$1 = (from, to) => {
|
|
110
|
-
const fromDir = (0, node_path.dirname)(from);
|
|
111
|
-
let relativePath = (0, node_path.relative)(fromDir, to);
|
|
112
|
-
if (!relativePath.startsWith(".")) {
|
|
113
|
-
relativePath = `./${relativePath}`;
|
|
114
|
-
}
|
|
115
|
-
return relativePath.replace(/\.ts$/, "");
|
|
116
|
-
};
|
|
117
|
-
/**
|
|
118
193
|
* Extract input object names from a GraphQL TypeNode.
|
|
119
194
|
*/
|
|
120
195
|
const extractInputObjectsFromType = (schema, typeNode, inputObjects) => {
|
|
@@ -210,8 +285,8 @@ const generateInputObjectTypeDefinitions = (schema, schemaName, inputNames) => {
|
|
|
210
285
|
/**
|
|
211
286
|
* Generate the TypeScript code for prebuilt types.
|
|
212
287
|
*/
|
|
213
|
-
const generateTypesCode = (grouped, schemas,
|
|
214
|
-
const
|
|
288
|
+
const generateTypesCode = (grouped, schemas, injectsModulePath) => {
|
|
289
|
+
const schemaNames = Object.keys(schemas);
|
|
215
290
|
const lines = [
|
|
216
291
|
"/**",
|
|
217
292
|
" * Prebuilt type registry.",
|
|
@@ -225,12 +300,10 @@ const generateTypesCode = (grouped, schemas, injects, outdir) => {
|
|
|
225
300
|
"",
|
|
226
301
|
"import type { PrebuiltTypeRegistry } from \"@soda-gql/core\";"
|
|
227
302
|
];
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
lines.push(`import type { scalar as scalar_${schemaName} } from "${relativePath}";`);
|
|
231
|
-
}
|
|
303
|
+
const scalarImports = schemaNames.map((name) => `scalar_${name}`).join(", ");
|
|
304
|
+
lines.push(`import type { ${scalarImports} } from "${injectsModulePath}";`);
|
|
232
305
|
lines.push("");
|
|
233
|
-
for (const schemaName of
|
|
306
|
+
for (const schemaName of schemaNames) {
|
|
234
307
|
lines.push(`type ScalarInput_${schemaName}<T extends keyof typeof scalar_${schemaName}> = ` + `typeof scalar_${schemaName}[T]["$type"]["input"];`);
|
|
235
308
|
lines.push(`type ScalarOutput_${schemaName}<T extends keyof typeof scalar_${schemaName}> = ` + `typeof scalar_${schemaName}[T]["$type"]["output"];`);
|
|
236
309
|
}
|
|
@@ -243,7 +316,7 @@ const generateTypesCode = (grouped, schemas, injects, outdir) => {
|
|
|
243
316
|
lines.push(...inputTypeLines);
|
|
244
317
|
lines.push("");
|
|
245
318
|
}
|
|
246
|
-
const fragmentEntries = fragments.sort((a, b) => a.key.localeCompare(b.key)).map((f) => ` readonly "${f.key}": { readonly input: ${f.inputType}; readonly output: ${f.outputType} };`);
|
|
319
|
+
const fragmentEntries = fragments.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} };`);
|
|
247
320
|
const operationEntries = operations.sort((a, b) => a.key.localeCompare(b.key)).map((o) => ` readonly "${o.key}": { readonly input: ${o.inputType}; readonly output: ${o.outputType} };`);
|
|
248
321
|
lines.push(`export type PrebuiltTypes_${schemaName} = {`);
|
|
249
322
|
lines.push(" readonly fragments: {");
|
|
@@ -256,7 +329,7 @@ const generateTypesCode = (grouped, schemas, injects, outdir) => {
|
|
|
256
329
|
lines.push(...operationEntries);
|
|
257
330
|
}
|
|
258
331
|
lines.push(" };");
|
|
259
|
-
lines.push("}
|
|
332
|
+
lines.push("};");
|
|
260
333
|
lines.push("");
|
|
261
334
|
}
|
|
262
335
|
return lines.join("\n");
|
|
@@ -291,14 +364,14 @@ const generateTypesCode = (grouped, schemas, injects, outdir) => {
|
|
|
291
364
|
* ```
|
|
292
365
|
*/
|
|
293
366
|
const emitPrebuiltTypes = async (options) => {
|
|
294
|
-
const { schemas, fieldSelections, outdir,
|
|
367
|
+
const { schemas, fieldSelections, outdir, injectsModulePath } = options;
|
|
295
368
|
const groupResult = groupBySchema(fieldSelections, schemas);
|
|
296
369
|
if (groupResult.isErr()) {
|
|
297
370
|
return (0, neverthrow.err)(groupResult.error);
|
|
298
371
|
}
|
|
299
372
|
const { grouped, warnings } = groupResult.value;
|
|
300
|
-
const code = generateTypesCode(grouped, schemas,
|
|
301
|
-
const typesPath = (0, node_path.join)(outdir, "
|
|
373
|
+
const code = generateTypesCode(grouped, schemas, injectsModulePath);
|
|
374
|
+
const typesPath = (0, node_path.join)(outdir, "types.prebuilt.ts");
|
|
302
375
|
try {
|
|
303
376
|
await (0, node_fs_promises.writeFile)(typesPath, code, "utf-8");
|
|
304
377
|
return (0, neverthrow.ok)({
|
|
@@ -310,143 +383,193 @@ const emitPrebuiltTypes = async (options) => {
|
|
|
310
383
|
}
|
|
311
384
|
};
|
|
312
385
|
|
|
313
|
-
//#endregion
|
|
314
|
-
//#region packages/typegen/src/errors.ts
|
|
315
|
-
/**
|
|
316
|
-
* Error constructor helpers for concise error creation.
|
|
317
|
-
*/
|
|
318
|
-
const typegenErrors = {
|
|
319
|
-
codegenRequired: (outdir) => ({
|
|
320
|
-
code: "TYPEGEN_CODEGEN_REQUIRED",
|
|
321
|
-
message: `Generated graphql-system module not found at '${outdir}'. Run 'soda-gql codegen' first.`,
|
|
322
|
-
outdir
|
|
323
|
-
}),
|
|
324
|
-
schemaLoadFailed: (schemaNames, cause) => ({
|
|
325
|
-
code: "TYPEGEN_SCHEMA_LOAD_FAILED",
|
|
326
|
-
message: `Failed to load schemas: ${schemaNames.join(", ")}`,
|
|
327
|
-
schemaNames,
|
|
328
|
-
cause
|
|
329
|
-
}),
|
|
330
|
-
buildFailed: (message, cause) => ({
|
|
331
|
-
code: "TYPEGEN_BUILD_FAILED",
|
|
332
|
-
message,
|
|
333
|
-
cause
|
|
334
|
-
}),
|
|
335
|
-
emitFailed: (path, message, cause) => ({
|
|
336
|
-
code: "TYPEGEN_EMIT_FAILED",
|
|
337
|
-
message,
|
|
338
|
-
path,
|
|
339
|
-
cause
|
|
340
|
-
}),
|
|
341
|
-
bundleFailed: (path, message, cause) => ({
|
|
342
|
-
code: "TYPEGEN_BUNDLE_FAILED",
|
|
343
|
-
message,
|
|
344
|
-
path,
|
|
345
|
-
cause
|
|
346
|
-
})
|
|
347
|
-
};
|
|
348
|
-
/**
|
|
349
|
-
* Format TypegenError for console output (human-readable).
|
|
350
|
-
*/
|
|
351
|
-
const formatTypegenError = (error) => {
|
|
352
|
-
const lines = [];
|
|
353
|
-
lines.push(`Error [${error.code}]: ${error.message}`);
|
|
354
|
-
switch (error.code) {
|
|
355
|
-
case "TYPEGEN_CODEGEN_REQUIRED":
|
|
356
|
-
lines.push(` Output directory: ${error.outdir}`);
|
|
357
|
-
lines.push(" Hint: Run 'soda-gql codegen' to generate the graphql-system module first.");
|
|
358
|
-
break;
|
|
359
|
-
case "TYPEGEN_SCHEMA_LOAD_FAILED":
|
|
360
|
-
lines.push(` Schemas: ${error.schemaNames.join(", ")}`);
|
|
361
|
-
break;
|
|
362
|
-
case "TYPEGEN_EMIT_FAILED":
|
|
363
|
-
case "TYPEGEN_BUNDLE_FAILED":
|
|
364
|
-
lines.push(` Path: ${error.path}`);
|
|
365
|
-
break;
|
|
366
|
-
}
|
|
367
|
-
if ("cause" in error && error.cause) {
|
|
368
|
-
lines.push(` Caused by: ${error.cause}`);
|
|
369
|
-
}
|
|
370
|
-
return lines.join("\n");
|
|
371
|
-
};
|
|
372
|
-
|
|
373
386
|
//#endregion
|
|
374
387
|
//#region packages/typegen/src/prebuilt-generator.ts
|
|
375
388
|
/**
|
|
376
|
-
* Generate the prebuilt module code.
|
|
389
|
+
* Generate the prebuilt index module code.
|
|
377
390
|
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
391
|
+
* Generates index.prebuilt.ts with builder-level type resolution.
|
|
392
|
+
* Types are resolved at the fragment/operation builder level using TKey/TName,
|
|
393
|
+
* eliminating the need for ResolvePrebuiltElement at the composer level.
|
|
381
394
|
*/
|
|
382
395
|
const generatePrebuiltModule = (schemas, options) => {
|
|
383
396
|
const schemaNames = Array.from(schemas.keys());
|
|
384
|
-
const
|
|
385
|
-
const
|
|
386
|
-
for (const name of schemaNames) {
|
|
387
|
-
typeImports.push(`Schema_${name}`, `FragmentBuilders_${name}`, `Context_${name}`);
|
|
388
|
-
runtimeImports.push(`__schema_${name}`, `__inputTypeMethods_${name}`, `__directiveMethods_${name}`);
|
|
389
|
-
const hasAdapter = options.injection?.get(name)?.adapterImportPath !== undefined;
|
|
390
|
-
if (hasAdapter) {
|
|
391
|
-
typeImports.push(`Adapter_${name}`);
|
|
392
|
-
runtimeImports.push(`__adapter_${name}`);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
const gqlEntries = [];
|
|
397
|
+
const injection = options.injection ?? new Map();
|
|
398
|
+
const adapterImports = [];
|
|
396
399
|
for (const name of schemaNames) {
|
|
397
|
-
const
|
|
398
|
-
if (
|
|
399
|
-
|
|
400
|
-
if (hasAdapter) {
|
|
401
|
-
gqlEntries.push(` ${name}: createPrebuiltGqlElementComposer<Schema_${name}, PrebuiltTypes_${name}, FragmentBuilders_${name}, typeof __directiveMethods_${name}, Context_${name}, Adapter_${name}>(__schema_${name}, { adapter: __adapter_${name}, inputTypeMethods: __inputTypeMethods_${name}, directiveMethods: __directiveMethods_${name} })`);
|
|
402
|
-
} else {
|
|
403
|
-
gqlEntries.push(` ${name}: createPrebuiltGqlElementComposer<Schema_${name}, PrebuiltTypes_${name}, FragmentBuilders_${name}, typeof __directiveMethods_${name}, Context_${name}>(__schema_${name}, { inputTypeMethods: __inputTypeMethods_${name}, directiveMethods: __directiveMethods_${name} })`);
|
|
400
|
+
const config = injection.get(name);
|
|
401
|
+
if (config?.hasAdapter) {
|
|
402
|
+
adapterImports.push(`adapter_${name}`);
|
|
404
403
|
}
|
|
405
404
|
}
|
|
405
|
+
const internalImports = schemaNames.flatMap((name) => [
|
|
406
|
+
`__schema_${name}`,
|
|
407
|
+
`__inputTypeMethods_${name}`,
|
|
408
|
+
`__directiveMethods_${name}`
|
|
409
|
+
]);
|
|
410
|
+
const genericTypes = `
|
|
411
|
+
/**
|
|
412
|
+
* Generic field factory for type-erased field access.
|
|
413
|
+
* Returns a callable for nested field builders. Primitive fields can be spread directly.
|
|
414
|
+
* Runtime behavior differs but spread works for both: ...f.id() and ...f.user()(...)
|
|
415
|
+
*/
|
|
416
|
+
type GenericFieldFactory = (
|
|
417
|
+
...args: unknown[]
|
|
418
|
+
) => (nest: (tools: GenericFieldsBuilderTools) => AnyFields) => AnyFields;
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Generic tools for fields builder callbacks.
|
|
422
|
+
* Uses type-erased factory to allow any field access while maintaining strict mode compatibility.
|
|
423
|
+
*/
|
|
424
|
+
type GenericFieldsBuilderTools = {
|
|
425
|
+
readonly f: Record<string, GenericFieldFactory>;
|
|
426
|
+
readonly $: Record<string, unknown>;
|
|
427
|
+
};
|
|
428
|
+
`;
|
|
429
|
+
const contextTypes = schemaNames.map((name) => `
|
|
430
|
+
/**
|
|
431
|
+
* Resolve fragment types at builder level using TKey.
|
|
432
|
+
* If TKey is a known key in PrebuiltTypes, return resolved types.
|
|
433
|
+
* Otherwise, return PrebuiltEntryNotFound.
|
|
434
|
+
*/
|
|
435
|
+
type ResolveFragmentAtBuilder_${name}<
|
|
436
|
+
TKey extends string | undefined
|
|
437
|
+
> = TKey extends keyof PrebuiltTypes_${name}["fragments"]
|
|
438
|
+
? Fragment<
|
|
439
|
+
PrebuiltTypes_${name}["fragments"][TKey]["typename"],
|
|
440
|
+
PrebuiltTypes_${name}["fragments"][TKey]["input"] extends infer TInput
|
|
441
|
+
? TInput extends void ? void : Partial<TInput & object>
|
|
442
|
+
: void,
|
|
443
|
+
Partial<AnyFields>,
|
|
444
|
+
PrebuiltTypes_${name}["fragments"][TKey]["output"] & object
|
|
445
|
+
>
|
|
446
|
+
: TKey extends undefined
|
|
447
|
+
? Fragment<"(unknown)", PrebuiltEntryNotFound<"(undefined)", "fragment">, Partial<AnyFields>, PrebuiltEntryNotFound<"(undefined)", "fragment">>
|
|
448
|
+
: Fragment<"(unknown)", PrebuiltEntryNotFound<TKey & string, "fragment">, Partial<AnyFields>, PrebuiltEntryNotFound<TKey & string, "fragment">>;
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Resolve operation types at builder level using TName.
|
|
452
|
+
*/
|
|
453
|
+
type ResolveOperationAtBuilder_${name}<
|
|
454
|
+
TOperationType extends OperationType,
|
|
455
|
+
TName extends string
|
|
456
|
+
> = TName extends keyof PrebuiltTypes_${name}["operations"]
|
|
457
|
+
? Operation<
|
|
458
|
+
TOperationType,
|
|
459
|
+
TName,
|
|
460
|
+
string[],
|
|
461
|
+
PrebuiltTypes_${name}["operations"][TName]["input"] & AnyConstAssignableInput,
|
|
462
|
+
Partial<AnyFields>,
|
|
463
|
+
PrebuiltTypes_${name}["operations"][TName]["output"] & object
|
|
464
|
+
>
|
|
465
|
+
: Operation<
|
|
466
|
+
TOperationType,
|
|
467
|
+
TName,
|
|
468
|
+
string[],
|
|
469
|
+
PrebuiltEntryNotFound<TName, "operation">,
|
|
470
|
+
Partial<AnyFields>,
|
|
471
|
+
PrebuiltEntryNotFound<TName, "operation">
|
|
472
|
+
>;
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Fragment builder that resolves types at builder level using TKey.
|
|
476
|
+
*/
|
|
477
|
+
type PrebuiltFragmentBuilder_${name} = <TKey extends string | undefined = undefined>(
|
|
478
|
+
options: {
|
|
479
|
+
key?: TKey;
|
|
480
|
+
fields: (tools: GenericFieldsBuilderTools) => AnyFields;
|
|
481
|
+
variables?: Record<string, unknown>;
|
|
482
|
+
metadata?: unknown;
|
|
483
|
+
}
|
|
484
|
+
) => ResolveFragmentAtBuilder_${name}<TKey>;
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Operation builder that resolves types at builder level using TName.
|
|
488
|
+
*/
|
|
489
|
+
type PrebuiltOperationBuilder_${name}<TOperationType extends OperationType> = <TName extends string>(
|
|
490
|
+
options: {
|
|
491
|
+
name: TName;
|
|
492
|
+
fields: (tools: GenericFieldsBuilderTools) => AnyFields;
|
|
493
|
+
variables?: Record<string, unknown>;
|
|
494
|
+
metadata?: unknown;
|
|
495
|
+
}
|
|
496
|
+
) => ResolveOperationAtBuilder_${name}<TOperationType, TName>;
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Prebuilt context with builder-level type resolution for schema "${name}".
|
|
500
|
+
*/
|
|
501
|
+
type PrebuiltContext_${name} = {
|
|
502
|
+
readonly fragment: { [K: string]: PrebuiltFragmentBuilder_${name} };
|
|
503
|
+
readonly query: { readonly operation: PrebuiltOperationBuilder_${name}<"query"> };
|
|
504
|
+
readonly mutation: { readonly operation: PrebuiltOperationBuilder_${name}<"mutation"> };
|
|
505
|
+
readonly subscription: { readonly operation: PrebuiltOperationBuilder_${name}<"subscription"> };
|
|
506
|
+
readonly $var: unknown;
|
|
507
|
+
readonly $dir: StandardDirectives;
|
|
508
|
+
readonly $colocate: unknown;
|
|
509
|
+
};`).join("\n");
|
|
510
|
+
const gqlEntries = schemaNames.map((name) => {
|
|
511
|
+
const config = injection.get(name);
|
|
512
|
+
const adapterLine = config?.hasAdapter ? `,\n adapter: adapter_${name}` : "";
|
|
513
|
+
return ` ${name}: createGqlElementComposer(
|
|
514
|
+
__schema_${name} as AnyGraphqlSchema,
|
|
515
|
+
{
|
|
516
|
+
inputTypeMethods: __inputTypeMethods_${name},
|
|
517
|
+
directiveMethods: __directiveMethods_${name}${adapterLine}
|
|
518
|
+
}
|
|
519
|
+
) as unknown as GqlComposer_${name}`;
|
|
520
|
+
});
|
|
521
|
+
const injectsImportSpecifiers = adapterImports.length > 0 ? adapterImports.join(", ") : "";
|
|
522
|
+
const injectsImportLine = injectsImportSpecifiers ? `import { ${injectsImportSpecifiers} } from "${options.injectsModulePath}";` : "";
|
|
406
523
|
const indexCode = `\
|
|
407
524
|
/**
|
|
408
|
-
* Prebuilt GQL module
|
|
525
|
+
* Prebuilt GQL module with builder-level type resolution.
|
|
409
526
|
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
527
|
+
* Types are resolved at the fragment/operation builder level using TKey/TName,
|
|
528
|
+
* not at the composer level. This enables proper typing for builder arguments
|
|
529
|
+
* and eliminates the need for ResolvePrebuiltElement.
|
|
412
530
|
*
|
|
413
531
|
* @module
|
|
414
532
|
* @generated by @soda-gql/typegen
|
|
415
533
|
*/
|
|
416
534
|
|
|
417
|
-
import { createPrebuiltGqlElementComposer } from "@soda-gql/core";
|
|
418
535
|
import {
|
|
419
|
-
|
|
420
|
-
type
|
|
421
|
-
|
|
422
|
-
|
|
536
|
+
createGqlElementComposer,
|
|
537
|
+
type AnyConstAssignableInput,
|
|
538
|
+
type AnyFields,
|
|
539
|
+
type AnyGraphqlSchema,
|
|
540
|
+
type Fragment,
|
|
541
|
+
type Operation,
|
|
542
|
+
type OperationType,
|
|
543
|
+
type PrebuiltEntryNotFound,
|
|
544
|
+
type StandardDirectives,
|
|
545
|
+
} from "@soda-gql/core";
|
|
546
|
+
${injectsImportLine}
|
|
547
|
+
import { ${internalImports.join(", ")} } from "${options.internalModulePath}";
|
|
548
|
+
import type { ${schemaNames.map((name) => `PrebuiltTypes_${name}`).join(", ")} } from "./types.prebuilt";
|
|
549
|
+
${genericTypes}
|
|
550
|
+
${contextTypes}
|
|
423
551
|
|
|
424
|
-
|
|
425
|
-
${
|
|
426
|
-
|
|
552
|
+
// Export context types for explicit annotation
|
|
553
|
+
${schemaNames.map((name) => `export type { PrebuiltContext_${name} };`).join("\n")}
|
|
554
|
+
|
|
555
|
+
// Composer type - TResult already has resolved types from builders, no ResolvePrebuiltElement needed
|
|
556
|
+
${schemaNames.map((name) => `type GqlComposer_${name} = {
|
|
557
|
+
<TResult>(composeElement: (context: PrebuiltContext_${name}) => TResult): TResult;
|
|
558
|
+
readonly $schema: AnyGraphqlSchema;
|
|
559
|
+
};`).join("\n")}
|
|
427
560
|
|
|
428
|
-
// Re-export types from main module
|
|
429
|
-
export type { ${typeImports.join(", ")} };
|
|
430
|
-
`;
|
|
431
|
-
const typesCode = `\
|
|
432
561
|
/**
|
|
433
|
-
* Prebuilt type
|
|
434
|
-
*
|
|
435
|
-
* This file contains placeholder types that will be populated by typegen
|
|
436
|
-
* when running \`soda-gql typegen\` command.
|
|
562
|
+
* Prebuilt GQL composers with builder-level type resolution.
|
|
437
563
|
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
564
|
+
* These composers have the same runtime behavior as the base composers,
|
|
565
|
+
* but their return types are resolved from the prebuilt type registry
|
|
566
|
+
* at the builder level instead of using ResolvePrebuiltElement.
|
|
440
567
|
*/
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
${schemaNames.map((name) => `// Placeholder for ${name} schema - populated by typegen\nexport type PrebuiltTypes_${name} = EmptyPrebuiltTypeRegistry;`).join("\n\n")}
|
|
568
|
+
export const gql: { ${schemaNames.map((name) => `${name}: GqlComposer_${name}`).join("; ")} } = {
|
|
569
|
+
${gqlEntries.join(",\n")}
|
|
570
|
+
};
|
|
445
571
|
`;
|
|
446
|
-
return {
|
|
447
|
-
indexCode,
|
|
448
|
-
typesCode
|
|
449
|
-
};
|
|
572
|
+
return { indexCode };
|
|
450
573
|
};
|
|
451
574
|
|
|
452
575
|
//#endregion
|
|
@@ -456,10 +579,10 @@ ${schemaNames.map((name) => `// Placeholder for ${name} schema - populated by ty
|
|
|
456
579
|
*
|
|
457
580
|
* Orchestrates the prebuilt type generation process:
|
|
458
581
|
* 1. Load schemas from generated CJS bundle
|
|
459
|
-
* 2. Generate
|
|
582
|
+
* 2. Generate index.prebuilt.ts
|
|
460
583
|
* 3. Build artifact to evaluate elements
|
|
461
584
|
* 4. Extract field selections
|
|
462
|
-
* 5. Emit
|
|
585
|
+
* 5. Emit types.prebuilt.ts
|
|
463
586
|
* 6. Bundle prebuilt module
|
|
464
587
|
*
|
|
465
588
|
* @module
|
|
@@ -549,10 +672,10 @@ const loadSchemaDocuments = (schemasConfig) => {
|
|
|
549
672
|
*
|
|
550
673
|
* This function:
|
|
551
674
|
* 1. Loads schemas from the generated CJS bundle
|
|
552
|
-
* 2. Generates
|
|
675
|
+
* 2. Generates index.prebuilt.ts using generatePrebuiltModule
|
|
553
676
|
* 3. Creates a BuilderService and builds the artifact
|
|
554
677
|
* 4. Extracts field selections from the artifact
|
|
555
|
-
* 5. Emits
|
|
678
|
+
* 5. Emits types.prebuilt.ts using emitPrebuiltTypes
|
|
556
679
|
* 6. Bundles the prebuilt module
|
|
557
680
|
*
|
|
558
681
|
* @param options - Typegen options including config
|
|
@@ -572,23 +695,19 @@ const runTypegen = async (options) => {
|
|
|
572
695
|
return (0, neverthrow.err)(typegenErrors.schemaLoadFailed(schemaNames, schemasResult.error));
|
|
573
696
|
}
|
|
574
697
|
const schemas = schemasResult.value;
|
|
575
|
-
const prebuiltDir = (0, node_path.join)(outdir, "prebuilt");
|
|
576
|
-
await (0, node_fs_promises.mkdir)(prebuiltDir, { recursive: true });
|
|
577
698
|
const schemaDocuments = loadSchemaDocuments(config.schemas);
|
|
578
|
-
const
|
|
699
|
+
const prebuiltIndexPath = (0, node_path.join)(outdir, "index.prebuilt.ts");
|
|
700
|
+
const internalModulePath = toImportSpecifier(prebuiltIndexPath, (0, node_path.join)(outdir, "_internal.ts"), importSpecifierOptions);
|
|
701
|
+
const injectsModulePath = toImportSpecifier(prebuiltIndexPath, (0, node_path.join)(outdir, "_internal-injects.ts"), importSpecifierOptions);
|
|
579
702
|
const injection = new Map();
|
|
580
703
|
for (const [schemaName, schemaConfig] of Object.entries(config.schemas)) {
|
|
581
|
-
|
|
582
|
-
injection.set(schemaName, { adapterImportPath: toImportSpecifier((0, node_path.join)(outdir, "index.ts"), schemaConfig.inject.adapter, importSpecifierOptions) });
|
|
583
|
-
} else {
|
|
584
|
-
injection.set(schemaName, {});
|
|
585
|
-
}
|
|
704
|
+
injection.set(schemaName, { hasAdapter: !!schemaConfig.inject.adapter });
|
|
586
705
|
}
|
|
587
706
|
const prebuilt = generatePrebuiltModule(schemaDocuments, {
|
|
588
|
-
|
|
707
|
+
internalModulePath,
|
|
708
|
+
injectsModulePath,
|
|
589
709
|
injection
|
|
590
710
|
});
|
|
591
|
-
const prebuiltIndexPath = (0, node_path.join)(prebuiltDir, "index.ts");
|
|
592
711
|
try {
|
|
593
712
|
await writeModule(prebuiltIndexPath, prebuilt.indexCode);
|
|
594
713
|
} catch (error) {
|
|
@@ -605,15 +724,11 @@ const runTypegen = async (options) => {
|
|
|
605
724
|
}
|
|
606
725
|
const fieldSelectionsResult = (0, __soda_gql_builder.extractFieldSelections)(intermediateElements);
|
|
607
726
|
const { selections: fieldSelections, warnings: extractWarnings } = fieldSelectionsResult;
|
|
608
|
-
const injects = {};
|
|
609
|
-
for (const [schemaName, schemaConfig] of Object.entries(config.schemas)) {
|
|
610
|
-
injects[schemaName] = { scalars: schemaConfig.inject.scalars };
|
|
611
|
-
}
|
|
612
727
|
const emitResult = await emitPrebuiltTypes({
|
|
613
728
|
schemas,
|
|
614
729
|
fieldSelections,
|
|
615
730
|
outdir,
|
|
616
|
-
|
|
731
|
+
injectsModulePath
|
|
617
732
|
});
|
|
618
733
|
if (emitResult.isErr()) {
|
|
619
734
|
return (0, neverthrow.err)(emitResult.error);
|