@soda-gql/typegen 0.10.1
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 +652 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +236 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +236 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +648 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +61 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
let node_fs_promises = require("node:fs/promises");
|
|
2
|
+
let node_path = require("node:path");
|
|
3
|
+
let __soda_gql_builder = require("@soda-gql/builder");
|
|
4
|
+
let __soda_gql_core = require("@soda-gql/core");
|
|
5
|
+
let graphql = require("graphql");
|
|
6
|
+
let neverthrow = require("neverthrow");
|
|
7
|
+
let node_fs = require("node:fs");
|
|
8
|
+
let esbuild = require("esbuild");
|
|
9
|
+
|
|
10
|
+
//#region packages/typegen/src/emitter.ts
|
|
11
|
+
/**
|
|
12
|
+
* Prebuilt types emitter.
|
|
13
|
+
*
|
|
14
|
+
* Generates TypeScript type definitions for PrebuiltTypes registry
|
|
15
|
+
* from field selection data and schema.
|
|
16
|
+
*
|
|
17
|
+
* ## Error Handling Strategy
|
|
18
|
+
*
|
|
19
|
+
* The emitter uses a partial failure approach for type calculation errors:
|
|
20
|
+
*
|
|
21
|
+
* **Recoverable errors** (result in warnings, element skipped):
|
|
22
|
+
* - Type calculation failures (e.g., `calculateFieldsType` throws)
|
|
23
|
+
* - Input type generation failures (e.g., `generateInputType` throws)
|
|
24
|
+
* - These are caught per-element, logged as warnings, and the element is omitted
|
|
25
|
+
*
|
|
26
|
+
* **Fatal errors** (result in error result):
|
|
27
|
+
* - `SCHEMA_NOT_FOUND`: Selection references non-existent schema
|
|
28
|
+
* - `WRITE_FAILED`: Cannot write output file to disk
|
|
29
|
+
*
|
|
30
|
+
* This allows builds to succeed with partial type coverage when some elements
|
|
31
|
+
* have issues, while providing visibility into problems via warnings.
|
|
32
|
+
*
|
|
33
|
+
* @module
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Group field selections by schema.
|
|
37
|
+
* Uses the schemaLabel from each selection to group them correctly.
|
|
38
|
+
*
|
|
39
|
+
* @returns Result containing grouped selections and warnings, or error if schema not found
|
|
40
|
+
*/
|
|
41
|
+
const groupBySchema = (fieldSelections, schemas) => {
|
|
42
|
+
const grouped = new Map();
|
|
43
|
+
const warnings = [];
|
|
44
|
+
for (const schemaName of Object.keys(schemas)) {
|
|
45
|
+
grouped.set(schemaName, {
|
|
46
|
+
fragments: [],
|
|
47
|
+
operations: [],
|
|
48
|
+
inputObjects: new Set()
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
for (const [canonicalId, selection] of fieldSelections) {
|
|
52
|
+
const schemaName = selection.schemaLabel;
|
|
53
|
+
const schema = schemas[schemaName];
|
|
54
|
+
const group = grouped.get(schemaName);
|
|
55
|
+
if (!schema || !group) {
|
|
56
|
+
return (0, neverthrow.err)(__soda_gql_builder.builderErrors.schemaNotFound(schemaName, canonicalId));
|
|
57
|
+
}
|
|
58
|
+
const outputFormatters = { scalarOutput: (name) => `ScalarOutput_${schemaName}<"${name}">` };
|
|
59
|
+
const inputFormatters = {
|
|
60
|
+
scalarInput: (name) => `ScalarInput_${schemaName}<"${name}">`,
|
|
61
|
+
inputObject: (name) => `Input_${schemaName}_${name}`
|
|
62
|
+
};
|
|
63
|
+
if (selection.type === "fragment") {
|
|
64
|
+
if (!selection.key) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const usedInputObjects = collectUsedInputObjectsFromSpecifiers(schema, selection.variableDefinitions);
|
|
69
|
+
for (const inputName of usedInputObjects) {
|
|
70
|
+
group.inputObjects.add(inputName);
|
|
71
|
+
}
|
|
72
|
+
const outputType = (0, __soda_gql_core.calculateFieldsType)(schema, selection.fields, outputFormatters);
|
|
73
|
+
const hasVariables = Object.keys(selection.variableDefinitions).length > 0;
|
|
74
|
+
const inputType = hasVariables ? (0, __soda_gql_core.generateInputTypeFromSpecifiers)(schema, selection.variableDefinitions, { formatters: inputFormatters }) : "void";
|
|
75
|
+
group.fragments.push({
|
|
76
|
+
key: selection.key,
|
|
77
|
+
inputType,
|
|
78
|
+
outputType
|
|
79
|
+
});
|
|
80
|
+
} catch (error) {
|
|
81
|
+
warnings.push(`[prebuilt] Failed to calculate type for fragment "${selection.key}": ${error instanceof Error ? error.message : String(error)}`);
|
|
82
|
+
}
|
|
83
|
+
} else if (selection.type === "operation") {
|
|
84
|
+
try {
|
|
85
|
+
const usedInputObjects = collectUsedInputObjects(schema, selection.variableDefinitions);
|
|
86
|
+
for (const inputName of usedInputObjects) {
|
|
87
|
+
group.inputObjects.add(inputName);
|
|
88
|
+
}
|
|
89
|
+
const outputType = (0, __soda_gql_core.calculateFieldsType)(schema, selection.fields, outputFormatters);
|
|
90
|
+
const inputType = (0, __soda_gql_core.generateInputType)(schema, selection.variableDefinitions, inputFormatters);
|
|
91
|
+
group.operations.push({
|
|
92
|
+
key: selection.operationName,
|
|
93
|
+
inputType,
|
|
94
|
+
outputType
|
|
95
|
+
});
|
|
96
|
+
} catch (error) {
|
|
97
|
+
warnings.push(`[prebuilt] Failed to calculate type for operation "${selection.operationName}": ${error instanceof Error ? error.message : String(error)}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return (0, neverthrow.ok)({
|
|
102
|
+
grouped,
|
|
103
|
+
warnings
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
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
|
+
* Extract input object names from a GraphQL TypeNode.
|
|
119
|
+
*/
|
|
120
|
+
const extractInputObjectsFromType = (schema, typeNode, inputObjects) => {
|
|
121
|
+
switch (typeNode.kind) {
|
|
122
|
+
case graphql.Kind.NON_NULL_TYPE:
|
|
123
|
+
extractInputObjectsFromType(schema, typeNode.type, inputObjects);
|
|
124
|
+
break;
|
|
125
|
+
case graphql.Kind.LIST_TYPE:
|
|
126
|
+
extractInputObjectsFromType(schema, typeNode.type, inputObjects);
|
|
127
|
+
break;
|
|
128
|
+
case graphql.Kind.NAMED_TYPE: {
|
|
129
|
+
const name = typeNode.name.value;
|
|
130
|
+
if (!schema.scalar[name] && !schema.enum[name] && schema.input[name]) {
|
|
131
|
+
inputObjects.add(name);
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Recursively collect nested input objects from schema definitions.
|
|
139
|
+
* Takes a set of initial input names and expands to include all nested inputs.
|
|
140
|
+
*/
|
|
141
|
+
const collectNestedInputObjects = (schema, initialInputNames) => {
|
|
142
|
+
const inputObjects = new Set(initialInputNames);
|
|
143
|
+
const collectNested = (inputName, seen) => {
|
|
144
|
+
if (seen.has(inputName)) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
seen.add(inputName);
|
|
148
|
+
const inputDef = schema.input[inputName];
|
|
149
|
+
if (!inputDef) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
for (const field of Object.values(inputDef.fields)) {
|
|
153
|
+
if (field.kind === "input" && !inputObjects.has(field.name)) {
|
|
154
|
+
inputObjects.add(field.name);
|
|
155
|
+
collectNested(field.name, seen);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
for (const inputName of Array.from(initialInputNames)) {
|
|
160
|
+
collectNested(inputName, new Set());
|
|
161
|
+
}
|
|
162
|
+
return inputObjects;
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* Collect all input object types used in variable definitions.
|
|
166
|
+
* Recursively collects nested input objects from the schema.
|
|
167
|
+
*/
|
|
168
|
+
const collectUsedInputObjects = (schema, variableDefinitions) => {
|
|
169
|
+
const directInputs = new Set();
|
|
170
|
+
for (const varDef of variableDefinitions) {
|
|
171
|
+
extractInputObjectsFromType(schema, varDef.type, directInputs);
|
|
172
|
+
}
|
|
173
|
+
return collectNestedInputObjects(schema, directInputs);
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Collect all input object types used in InputTypeSpecifiers.
|
|
177
|
+
* Recursively collects nested input objects from the schema.
|
|
178
|
+
*/
|
|
179
|
+
const collectUsedInputObjectsFromSpecifiers = (schema, specifiers) => {
|
|
180
|
+
const directInputs = new Set();
|
|
181
|
+
for (const specifier of Object.values(specifiers)) {
|
|
182
|
+
if (specifier.kind === "input" && schema.input[specifier.name]) {
|
|
183
|
+
directInputs.add(specifier.name);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return collectNestedInputObjects(schema, directInputs);
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* Generate type definitions for input objects.
|
|
190
|
+
*/
|
|
191
|
+
const generateInputObjectTypeDefinitions = (schema, schemaName, inputNames) => {
|
|
192
|
+
const lines = [];
|
|
193
|
+
const defaultDepth = schema.__defaultInputDepth ?? 3;
|
|
194
|
+
const depthOverrides = schema.__inputDepthOverrides ?? {};
|
|
195
|
+
const formatters = {
|
|
196
|
+
scalarInput: (name) => `ScalarInput_${schemaName}<"${name}">`,
|
|
197
|
+
inputObject: (name) => `Input_${schemaName}_${name}`
|
|
198
|
+
};
|
|
199
|
+
const sortedNames = Array.from(inputNames).sort();
|
|
200
|
+
for (const inputName of sortedNames) {
|
|
201
|
+
const typeString = (0, __soda_gql_core.generateInputObjectType)(schema, inputName, {
|
|
202
|
+
defaultDepth,
|
|
203
|
+
depthOverrides,
|
|
204
|
+
formatters
|
|
205
|
+
});
|
|
206
|
+
lines.push(`type Input_${schemaName}_${inputName} = ${typeString};`);
|
|
207
|
+
}
|
|
208
|
+
return lines;
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* Generate the TypeScript code for prebuilt types.
|
|
212
|
+
*/
|
|
213
|
+
const generateTypesCode = (grouped, schemas, injects, outdir) => {
|
|
214
|
+
const typesFilePath = (0, node_path.join)(outdir, "prebuilt", "types.ts");
|
|
215
|
+
const lines = [
|
|
216
|
+
"/**",
|
|
217
|
+
" * Prebuilt type registry.",
|
|
218
|
+
" *",
|
|
219
|
+
" * This file is auto-generated by @soda-gql/typegen.",
|
|
220
|
+
" * Do not edit manually.",
|
|
221
|
+
" *",
|
|
222
|
+
" * @module",
|
|
223
|
+
" * @generated",
|
|
224
|
+
" */",
|
|
225
|
+
"",
|
|
226
|
+
"import type { PrebuiltTypeRegistry } from \"@soda-gql/core\";"
|
|
227
|
+
];
|
|
228
|
+
for (const [schemaName, inject] of Object.entries(injects)) {
|
|
229
|
+
const relativePath = toImportSpecifier$1(typesFilePath, inject.scalars);
|
|
230
|
+
lines.push(`import type { scalar as scalar_${schemaName} } from "${relativePath}";`);
|
|
231
|
+
}
|
|
232
|
+
lines.push("");
|
|
233
|
+
for (const schemaName of Object.keys(injects)) {
|
|
234
|
+
lines.push(`type ScalarInput_${schemaName}<T extends keyof typeof scalar_${schemaName}> = ` + `typeof scalar_${schemaName}[T]["$type"]["input"];`);
|
|
235
|
+
lines.push(`type ScalarOutput_${schemaName}<T extends keyof typeof scalar_${schemaName}> = ` + `typeof scalar_${schemaName}[T]["$type"]["output"];`);
|
|
236
|
+
}
|
|
237
|
+
lines.push("");
|
|
238
|
+
for (const [schemaName, { fragments, operations, inputObjects }] of grouped) {
|
|
239
|
+
const schema = schemas[schemaName];
|
|
240
|
+
if (inputObjects.size > 0 && schema) {
|
|
241
|
+
lines.push("// Input object types");
|
|
242
|
+
const inputTypeLines = generateInputObjectTypeDefinitions(schema, schemaName, inputObjects);
|
|
243
|
+
lines.push(...inputTypeLines);
|
|
244
|
+
lines.push("");
|
|
245
|
+
}
|
|
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} };`);
|
|
247
|
+
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
|
+
lines.push(`export type PrebuiltTypes_${schemaName} = {`);
|
|
249
|
+
lines.push(" readonly fragments: {");
|
|
250
|
+
if (fragmentEntries.length > 0) {
|
|
251
|
+
lines.push(...fragmentEntries);
|
|
252
|
+
}
|
|
253
|
+
lines.push(" };");
|
|
254
|
+
lines.push(" readonly operations: {");
|
|
255
|
+
if (operationEntries.length > 0) {
|
|
256
|
+
lines.push(...operationEntries);
|
|
257
|
+
}
|
|
258
|
+
lines.push(" };");
|
|
259
|
+
lines.push("} satisfies PrebuiltTypeRegistry;");
|
|
260
|
+
lines.push("");
|
|
261
|
+
}
|
|
262
|
+
return lines.join("\n");
|
|
263
|
+
};
|
|
264
|
+
/**
|
|
265
|
+
* Emit prebuilt types to the prebuilt/types.ts file.
|
|
266
|
+
*
|
|
267
|
+
* This function uses a partial failure strategy: if type calculation fails for
|
|
268
|
+
* individual elements (e.g., due to invalid field selections or missing schema
|
|
269
|
+
* types), those elements are skipped and warnings are collected rather than
|
|
270
|
+
* failing the entire emission. This allows builds to succeed even when some
|
|
271
|
+
* elements have issues, while still reporting problems via warnings.
|
|
272
|
+
*
|
|
273
|
+
* @param options - Emitter options including schemas, field selections, and output directory
|
|
274
|
+
* @returns Result containing output path and warnings, or error if a hard failure occurs
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```typescript
|
|
278
|
+
* const result = await emitPrebuiltTypes({
|
|
279
|
+
* schemas: { mySchema: schema },
|
|
280
|
+
* fieldSelections,
|
|
281
|
+
* outdir: "./generated",
|
|
282
|
+
* injects: { mySchema: { scalars: "./scalars.ts" } },
|
|
283
|
+
* });
|
|
284
|
+
*
|
|
285
|
+
* if (result.isOk()) {
|
|
286
|
+
* console.log(`Generated: ${result.value.path}`);
|
|
287
|
+
* if (result.value.warnings.length > 0) {
|
|
288
|
+
* console.warn("Warnings:", result.value.warnings);
|
|
289
|
+
* }
|
|
290
|
+
* }
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
const emitPrebuiltTypes = async (options) => {
|
|
294
|
+
const { schemas, fieldSelections, outdir, injects } = options;
|
|
295
|
+
const groupResult = groupBySchema(fieldSelections, schemas);
|
|
296
|
+
if (groupResult.isErr()) {
|
|
297
|
+
return (0, neverthrow.err)(groupResult.error);
|
|
298
|
+
}
|
|
299
|
+
const { grouped, warnings } = groupResult.value;
|
|
300
|
+
const code = generateTypesCode(grouped, schemas, injects, outdir);
|
|
301
|
+
const typesPath = (0, node_path.join)(outdir, "prebuilt", "types.ts");
|
|
302
|
+
try {
|
|
303
|
+
await (0, node_fs_promises.writeFile)(typesPath, code, "utf-8");
|
|
304
|
+
return (0, neverthrow.ok)({
|
|
305
|
+
path: typesPath,
|
|
306
|
+
warnings
|
|
307
|
+
});
|
|
308
|
+
} catch (error) {
|
|
309
|
+
return (0, neverthrow.err)(__soda_gql_builder.builderErrors.writeFailed(typesPath, `Failed to write prebuilt types: ${error instanceof Error ? error.message : String(error)}`, error));
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
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
|
+
//#endregion
|
|
374
|
+
//#region packages/typegen/src/prebuilt-generator.ts
|
|
375
|
+
/**
|
|
376
|
+
* Generate the prebuilt module code.
|
|
377
|
+
*
|
|
378
|
+
* This generates:
|
|
379
|
+
* - prebuilt/index.ts: Uses createPrebuiltGqlElementComposer with types from PrebuiltTypes
|
|
380
|
+
* - prebuilt/types.ts: Placeholder types that typegen will populate
|
|
381
|
+
*/
|
|
382
|
+
const generatePrebuiltModule = (schemas, options) => {
|
|
383
|
+
const schemaNames = Array.from(schemas.keys());
|
|
384
|
+
const typeImports = [];
|
|
385
|
+
const runtimeImports = [];
|
|
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 = [];
|
|
396
|
+
for (const name of schemaNames) {
|
|
397
|
+
const document = schemas.get(name);
|
|
398
|
+
if (!document) continue;
|
|
399
|
+
const hasAdapter = options.injection?.get(name)?.adapterImportPath !== undefined;
|
|
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} })`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const indexCode = `\
|
|
407
|
+
/**
|
|
408
|
+
* Prebuilt GQL module for bundler-compatible type resolution.
|
|
409
|
+
*
|
|
410
|
+
* This module uses createPrebuiltGqlElementComposer which looks up types
|
|
411
|
+
* from PrebuiltTypes instead of using complex type inference.
|
|
412
|
+
*
|
|
413
|
+
* @module
|
|
414
|
+
* @generated by @soda-gql/typegen
|
|
415
|
+
*/
|
|
416
|
+
|
|
417
|
+
import { createPrebuiltGqlElementComposer } from "@soda-gql/core";
|
|
418
|
+
import {
|
|
419
|
+
${runtimeImports.join(",\n ")},
|
|
420
|
+
type ${typeImports.join(",\n type ")},
|
|
421
|
+
} from "${options.mainModulePath}";
|
|
422
|
+
import type { ${schemaNames.map((name) => `PrebuiltTypes_${name}`).join(", ")} } from "./types";
|
|
423
|
+
|
|
424
|
+
export const gql = {
|
|
425
|
+
${gqlEntries.join(",\n")}
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
// Re-export types from main module
|
|
429
|
+
export type { ${typeImports.join(", ")} };
|
|
430
|
+
`;
|
|
431
|
+
const typesCode = `\
|
|
432
|
+
/**
|
|
433
|
+
* Prebuilt type registry.
|
|
434
|
+
*
|
|
435
|
+
* This file contains placeholder types that will be populated by typegen
|
|
436
|
+
* when running \`soda-gql typegen\` command.
|
|
437
|
+
*
|
|
438
|
+
* @module
|
|
439
|
+
* @generated by @soda-gql/typegen
|
|
440
|
+
*/
|
|
441
|
+
|
|
442
|
+
import type { EmptyPrebuiltTypeRegistry } from "@soda-gql/core";
|
|
443
|
+
|
|
444
|
+
${schemaNames.map((name) => `// Placeholder for ${name} schema - populated by typegen\nexport type PrebuiltTypes_${name} = EmptyPrebuiltTypeRegistry;`).join("\n\n")}
|
|
445
|
+
`;
|
|
446
|
+
return {
|
|
447
|
+
indexCode,
|
|
448
|
+
typesCode
|
|
449
|
+
};
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
//#endregion
|
|
453
|
+
//#region packages/typegen/src/runner.ts
|
|
454
|
+
/**
|
|
455
|
+
* Main typegen runner.
|
|
456
|
+
*
|
|
457
|
+
* Orchestrates the prebuilt type generation process:
|
|
458
|
+
* 1. Load schemas from generated CJS bundle
|
|
459
|
+
* 2. Generate prebuilt/index.ts
|
|
460
|
+
* 3. Build artifact to evaluate elements
|
|
461
|
+
* 4. Extract field selections
|
|
462
|
+
* 5. Emit prebuilt/types.ts
|
|
463
|
+
* 6. Bundle prebuilt module
|
|
464
|
+
*
|
|
465
|
+
* @module
|
|
466
|
+
*/
|
|
467
|
+
const extensionMap = {
|
|
468
|
+
".ts": ".js",
|
|
469
|
+
".tsx": ".js",
|
|
470
|
+
".mts": ".mjs",
|
|
471
|
+
".cts": ".cjs",
|
|
472
|
+
".js": ".js",
|
|
473
|
+
".mjs": ".mjs",
|
|
474
|
+
".cjs": ".cjs"
|
|
475
|
+
};
|
|
476
|
+
const toImportSpecifier = (fromPath, targetPath, options) => {
|
|
477
|
+
const fromDir = (0, node_path.dirname)(fromPath);
|
|
478
|
+
const normalized = (0, node_path.relative)(fromDir, targetPath).replace(/\\/g, "/");
|
|
479
|
+
const sourceExt = (0, node_path.extname)(targetPath);
|
|
480
|
+
if (!options?.includeExtension) {
|
|
481
|
+
if (normalized.length === 0) {
|
|
482
|
+
return `./${targetPath.slice(0, -sourceExt.length).split("/").pop()}`;
|
|
483
|
+
}
|
|
484
|
+
const withPrefix$1 = normalized.startsWith(".") ? normalized : `./${normalized}`;
|
|
485
|
+
const currentExt$1 = (0, node_path.extname)(withPrefix$1);
|
|
486
|
+
return currentExt$1 ? withPrefix$1.slice(0, -currentExt$1.length) : withPrefix$1;
|
|
487
|
+
}
|
|
488
|
+
const runtimeExt = extensionMap[sourceExt] ?? sourceExt;
|
|
489
|
+
if (normalized.length === 0) {
|
|
490
|
+
const base = runtimeExt !== sourceExt ? targetPath.slice(0, -sourceExt.length).split("/").pop() : targetPath.split("/").pop();
|
|
491
|
+
return `./${base}${runtimeExt}`;
|
|
492
|
+
}
|
|
493
|
+
const withPrefix = normalized.startsWith(".") ? normalized : `./${normalized}`;
|
|
494
|
+
if (!runtimeExt) {
|
|
495
|
+
return withPrefix;
|
|
496
|
+
}
|
|
497
|
+
if (withPrefix.endsWith(runtimeExt)) {
|
|
498
|
+
return withPrefix;
|
|
499
|
+
}
|
|
500
|
+
const currentExt = (0, node_path.extname)(withPrefix);
|
|
501
|
+
const withoutExt = currentExt ? withPrefix.slice(0, -currentExt.length) : withPrefix;
|
|
502
|
+
return `${withoutExt}${runtimeExt}`;
|
|
503
|
+
};
|
|
504
|
+
/**
|
|
505
|
+
* Bundle the prebuilt module to CJS format.
|
|
506
|
+
*/
|
|
507
|
+
const bundlePrebuiltModule = async (sourcePath) => {
|
|
508
|
+
const sourceExt = (0, node_path.extname)(sourcePath);
|
|
509
|
+
const baseName = sourcePath.slice(0, -sourceExt.length);
|
|
510
|
+
const cjsPath = `${baseName}.cjs`;
|
|
511
|
+
await (0, esbuild.build)({
|
|
512
|
+
entryPoints: [sourcePath],
|
|
513
|
+
outfile: cjsPath,
|
|
514
|
+
format: "cjs",
|
|
515
|
+
platform: "node",
|
|
516
|
+
bundle: true,
|
|
517
|
+
external: ["@soda-gql/core", "@soda-gql/runtime"],
|
|
518
|
+
sourcemap: false,
|
|
519
|
+
minify: false,
|
|
520
|
+
treeShaking: false
|
|
521
|
+
});
|
|
522
|
+
return { cjsPath };
|
|
523
|
+
};
|
|
524
|
+
/**
|
|
525
|
+
* Write a TypeScript module to disk.
|
|
526
|
+
*/
|
|
527
|
+
const writeModule = async (path, content) => {
|
|
528
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(path), { recursive: true });
|
|
529
|
+
await (0, node_fs_promises.writeFile)(path, content, "utf-8");
|
|
530
|
+
};
|
|
531
|
+
/**
|
|
532
|
+
* Load GraphQL schema documents from schema paths.
|
|
533
|
+
* This is needed for generatePrebuiltModule which expects DocumentNode.
|
|
534
|
+
*/
|
|
535
|
+
const loadSchemaDocuments = (schemasConfig) => {
|
|
536
|
+
const documents = new Map();
|
|
537
|
+
for (const [name, schemaConfig] of Object.entries(schemasConfig)) {
|
|
538
|
+
const schemaPaths = Array.isArray(schemaConfig.schema) ? schemaConfig.schema : [schemaConfig.schema];
|
|
539
|
+
let combinedSource = "";
|
|
540
|
+
for (const schemaPath of schemaPaths) {
|
|
541
|
+
combinedSource += `${(0, node_fs.readFileSync)(schemaPath, "utf-8")}\n`;
|
|
542
|
+
}
|
|
543
|
+
documents.set(name, (0, graphql.parse)(combinedSource));
|
|
544
|
+
}
|
|
545
|
+
return documents;
|
|
546
|
+
};
|
|
547
|
+
/**
|
|
548
|
+
* Run the typegen process.
|
|
549
|
+
*
|
|
550
|
+
* This function:
|
|
551
|
+
* 1. Loads schemas from the generated CJS bundle
|
|
552
|
+
* 2. Generates prebuilt/index.ts using generatePrebuiltModule
|
|
553
|
+
* 3. Creates a BuilderService and builds the artifact
|
|
554
|
+
* 4. Extracts field selections from the artifact
|
|
555
|
+
* 5. Emits prebuilt/types.ts using emitPrebuiltTypes
|
|
556
|
+
* 6. Bundles the prebuilt module
|
|
557
|
+
*
|
|
558
|
+
* @param options - Typegen options including config
|
|
559
|
+
* @returns Result containing success data or error
|
|
560
|
+
*/
|
|
561
|
+
const runTypegen = async (options) => {
|
|
562
|
+
const { config } = options;
|
|
563
|
+
const outdir = (0, node_path.resolve)(config.outdir);
|
|
564
|
+
const cjsPath = (0, node_path.join)(outdir, "index.cjs");
|
|
565
|
+
const importSpecifierOptions = { includeExtension: config.styles.importExtension };
|
|
566
|
+
if (!(0, node_fs.existsSync)(cjsPath)) {
|
|
567
|
+
return (0, neverthrow.err)(typegenErrors.codegenRequired(outdir));
|
|
568
|
+
}
|
|
569
|
+
const schemaNames = Object.keys(config.schemas);
|
|
570
|
+
const schemasResult = (0, __soda_gql_builder.loadSchemasFromBundle)(cjsPath, schemaNames);
|
|
571
|
+
if (schemasResult.isErr()) {
|
|
572
|
+
return (0, neverthrow.err)(typegenErrors.schemaLoadFailed(schemaNames, schemasResult.error));
|
|
573
|
+
}
|
|
574
|
+
const schemas = schemasResult.value;
|
|
575
|
+
const prebuiltDir = (0, node_path.join)(outdir, "prebuilt");
|
|
576
|
+
await (0, node_fs_promises.mkdir)(prebuiltDir, { recursive: true });
|
|
577
|
+
const schemaDocuments = loadSchemaDocuments(config.schemas);
|
|
578
|
+
const mainModulePath = toImportSpecifier((0, node_path.join)(prebuiltDir, "index.ts"), (0, node_path.join)(outdir, "index.ts"), importSpecifierOptions);
|
|
579
|
+
const injection = new Map();
|
|
580
|
+
for (const [schemaName, schemaConfig] of Object.entries(config.schemas)) {
|
|
581
|
+
if (schemaConfig.inject.adapter) {
|
|
582
|
+
injection.set(schemaName, { adapterImportPath: toImportSpecifier((0, node_path.join)(outdir, "index.ts"), schemaConfig.inject.adapter, importSpecifierOptions) });
|
|
583
|
+
} else {
|
|
584
|
+
injection.set(schemaName, {});
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
const prebuilt = generatePrebuiltModule(schemaDocuments, {
|
|
588
|
+
mainModulePath,
|
|
589
|
+
injection
|
|
590
|
+
});
|
|
591
|
+
const prebuiltIndexPath = (0, node_path.join)(prebuiltDir, "index.ts");
|
|
592
|
+
try {
|
|
593
|
+
await writeModule(prebuiltIndexPath, prebuilt.indexCode);
|
|
594
|
+
} catch (error) {
|
|
595
|
+
return (0, neverthrow.err)(typegenErrors.emitFailed(prebuiltIndexPath, `Failed to write prebuilt index: ${error instanceof Error ? error.message : String(error)}`, error));
|
|
596
|
+
}
|
|
597
|
+
const builderService = (0, __soda_gql_builder.createBuilderService)({ config });
|
|
598
|
+
const artifactResult = await builderService.buildAsync();
|
|
599
|
+
if (artifactResult.isErr()) {
|
|
600
|
+
return (0, neverthrow.err)(typegenErrors.buildFailed(`Builder failed: ${artifactResult.error.message}`, artifactResult.error));
|
|
601
|
+
}
|
|
602
|
+
const intermediateElements = builderService.getIntermediateElements();
|
|
603
|
+
if (!intermediateElements) {
|
|
604
|
+
return (0, neverthrow.err)(typegenErrors.buildFailed("No intermediate elements available after build", undefined));
|
|
605
|
+
}
|
|
606
|
+
const fieldSelectionsResult = (0, __soda_gql_builder.extractFieldSelections)(intermediateElements);
|
|
607
|
+
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
|
+
const emitResult = await emitPrebuiltTypes({
|
|
613
|
+
schemas,
|
|
614
|
+
fieldSelections,
|
|
615
|
+
outdir,
|
|
616
|
+
injects
|
|
617
|
+
});
|
|
618
|
+
if (emitResult.isErr()) {
|
|
619
|
+
return (0, neverthrow.err)(emitResult.error);
|
|
620
|
+
}
|
|
621
|
+
const { path: prebuiltTypesPath, warnings: emitWarnings } = emitResult.value;
|
|
622
|
+
try {
|
|
623
|
+
await bundlePrebuiltModule(prebuiltIndexPath);
|
|
624
|
+
} catch (error) {
|
|
625
|
+
return (0, neverthrow.err)(typegenErrors.bundleFailed(prebuiltIndexPath, `Failed to bundle prebuilt module: ${error instanceof Error ? error.message : String(error)}`, error));
|
|
626
|
+
}
|
|
627
|
+
let fragmentCount = 0;
|
|
628
|
+
let operationCount = 0;
|
|
629
|
+
for (const selection of fieldSelections.values()) {
|
|
630
|
+
if (selection.type === "fragment" && selection.key) {
|
|
631
|
+
fragmentCount++;
|
|
632
|
+
} else if (selection.type === "operation") {
|
|
633
|
+
operationCount++;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
const allWarnings = [...extractWarnings, ...emitWarnings];
|
|
637
|
+
return (0, neverthrow.ok)({
|
|
638
|
+
prebuiltIndexPath,
|
|
639
|
+
prebuiltTypesPath,
|
|
640
|
+
fragmentCount,
|
|
641
|
+
operationCount,
|
|
642
|
+
warnings: allWarnings
|
|
643
|
+
});
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
//#endregion
|
|
647
|
+
exports.emitPrebuiltTypes = emitPrebuiltTypes;
|
|
648
|
+
exports.formatTypegenError = formatTypegenError;
|
|
649
|
+
exports.generatePrebuiltModule = generatePrebuiltModule;
|
|
650
|
+
exports.runTypegen = runTypegen;
|
|
651
|
+
exports.typegenErrors = typegenErrors;
|
|
652
|
+
//# sourceMappingURL=index.cjs.map
|