@soda-gql/builder 0.10.0 → 0.10.2
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 -425
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -66
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +45 -66
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +271 -428
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -33,6 +33,7 @@ let node_crypto = require("node:crypto");
|
|
|
33
33
|
let node_path = require("node:path");
|
|
34
34
|
let node_vm = require("node:vm");
|
|
35
35
|
let __soda_gql_common = require("@soda-gql/common");
|
|
36
|
+
let __swc_core = require("@swc/core");
|
|
36
37
|
let __soda_gql_core = require("@soda-gql/core");
|
|
37
38
|
__soda_gql_core = __toESM(__soda_gql_core);
|
|
38
39
|
let __soda_gql_core_adapter = require("@soda-gql/core/adapter");
|
|
@@ -41,7 +42,6 @@ let __soda_gql_core_runtime = require("@soda-gql/core/runtime");
|
|
|
41
42
|
__soda_gql_core_runtime = __toESM(__soda_gql_core_runtime);
|
|
42
43
|
let __soda_gql_runtime = require("@soda-gql/runtime");
|
|
43
44
|
__soda_gql_runtime = __toESM(__soda_gql_runtime);
|
|
44
|
-
let __swc_core = require("@swc/core");
|
|
45
45
|
let graphql = require("graphql");
|
|
46
46
|
let typescript = require("typescript");
|
|
47
47
|
typescript = __toESM(typescript);
|
|
@@ -195,98 +195,6 @@ function parseAndValidateArtifact(content, filePath) {
|
|
|
195
195
|
return (0, neverthrow.ok)(validated.data);
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
//#endregion
|
|
199
|
-
//#region packages/builder/src/errors/formatter.ts
|
|
200
|
-
/**
|
|
201
|
-
* Hints for each error code to help users understand and fix issues.
|
|
202
|
-
*/
|
|
203
|
-
const errorHints = {
|
|
204
|
-
ELEMENT_EVALUATION_FAILED: "Check if all imported fragments are properly exported and included in entry patterns.",
|
|
205
|
-
GRAPH_CIRCULAR_DEPENDENCY: "Break the circular import by extracting shared types to a common module.",
|
|
206
|
-
GRAPH_MISSING_IMPORT: "Verify the import path exists and the module is included in entry patterns.",
|
|
207
|
-
RUNTIME_MODULE_LOAD_FAILED: "Ensure the module can be imported and all dependencies are installed.",
|
|
208
|
-
CONFIG_NOT_FOUND: "Create a soda-gql.config.ts file in your project root.",
|
|
209
|
-
CONFIG_INVALID: "Check your configuration file for syntax errors or invalid options.",
|
|
210
|
-
ENTRY_NOT_FOUND: "Verify the entry pattern matches your file structure.",
|
|
211
|
-
INTERNAL_INVARIANT: "This is an internal error. Please report it at https://github.com/soda-gql/soda-gql/issues"
|
|
212
|
-
};
|
|
213
|
-
/**
|
|
214
|
-
* Format a BuilderError into a structured FormattedError object.
|
|
215
|
-
*/
|
|
216
|
-
const formatBuilderErrorStructured = (error) => {
|
|
217
|
-
const base = {
|
|
218
|
-
code: error.code,
|
|
219
|
-
message: error.message,
|
|
220
|
-
hint: errorHints[error.code],
|
|
221
|
-
cause: "cause" in error ? error.cause : undefined
|
|
222
|
-
};
|
|
223
|
-
switch (error.code) {
|
|
224
|
-
case "ELEMENT_EVALUATION_FAILED": return {
|
|
225
|
-
...base,
|
|
226
|
-
location: {
|
|
227
|
-
modulePath: error.modulePath,
|
|
228
|
-
astPath: error.astPath || undefined
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
case "RUNTIME_MODULE_LOAD_FAILED": return {
|
|
232
|
-
...base,
|
|
233
|
-
location: {
|
|
234
|
-
modulePath: error.filePath,
|
|
235
|
-
astPath: error.astPath
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
case "GRAPH_MISSING_IMPORT": return {
|
|
239
|
-
...base,
|
|
240
|
-
relatedFiles: [error.importer, error.importee]
|
|
241
|
-
};
|
|
242
|
-
case "GRAPH_CIRCULAR_DEPENDENCY": return {
|
|
243
|
-
...base,
|
|
244
|
-
relatedFiles: error.chain
|
|
245
|
-
};
|
|
246
|
-
case "CONFIG_NOT_FOUND":
|
|
247
|
-
case "CONFIG_INVALID": return {
|
|
248
|
-
...base,
|
|
249
|
-
location: { modulePath: error.path }
|
|
250
|
-
};
|
|
251
|
-
case "FINGERPRINT_FAILED": return {
|
|
252
|
-
...base,
|
|
253
|
-
location: { modulePath: error.filePath }
|
|
254
|
-
};
|
|
255
|
-
case "DISCOVERY_IO_ERROR": return {
|
|
256
|
-
...base,
|
|
257
|
-
location: { modulePath: error.path }
|
|
258
|
-
};
|
|
259
|
-
default: return base;
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
/**
|
|
263
|
-
* Format a BuilderError for CLI/stderr output with human-readable formatting.
|
|
264
|
-
* Includes location, hint, and related files when available.
|
|
265
|
-
*/
|
|
266
|
-
const formatBuilderErrorForCLI = (error) => {
|
|
267
|
-
const formatted = formatBuilderErrorStructured(error);
|
|
268
|
-
const lines = [];
|
|
269
|
-
lines.push(`Error [${formatted.code}]: ${formatted.message}`);
|
|
270
|
-
if (formatted.location) {
|
|
271
|
-
lines.push(` at ${formatted.location.modulePath}`);
|
|
272
|
-
if (formatted.location.astPath) {
|
|
273
|
-
lines.push(` in ${formatted.location.astPath}`);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
if (formatted.hint) {
|
|
277
|
-
lines.push("");
|
|
278
|
-
lines.push(` Hint: ${formatted.hint}`);
|
|
279
|
-
}
|
|
280
|
-
if (formatted.relatedFiles && formatted.relatedFiles.length > 0) {
|
|
281
|
-
lines.push("");
|
|
282
|
-
lines.push(" Related files:");
|
|
283
|
-
for (const file of formatted.relatedFiles) {
|
|
284
|
-
lines.push(` - ${file}`);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
return lines.join("\n");
|
|
288
|
-
};
|
|
289
|
-
|
|
290
198
|
//#endregion
|
|
291
199
|
//#region packages/builder/src/errors.ts
|
|
292
200
|
/**
|
|
@@ -503,6 +411,98 @@ const assertUnreachable = (value, context) => {
|
|
|
503
411
|
throw new Error(`Unreachable code path${context ? ` in ${context}` : ""}: received ${JSON.stringify(value)}`);
|
|
504
412
|
};
|
|
505
413
|
|
|
414
|
+
//#endregion
|
|
415
|
+
//#region packages/builder/src/errors/formatter.ts
|
|
416
|
+
/**
|
|
417
|
+
* Hints for each error code to help users understand and fix issues.
|
|
418
|
+
*/
|
|
419
|
+
const errorHints = {
|
|
420
|
+
ELEMENT_EVALUATION_FAILED: "Check if all imported fragments are properly exported and included in entry patterns.",
|
|
421
|
+
GRAPH_CIRCULAR_DEPENDENCY: "Break the circular import by extracting shared types to a common module.",
|
|
422
|
+
GRAPH_MISSING_IMPORT: "Verify the import path exists and the module is included in entry patterns.",
|
|
423
|
+
RUNTIME_MODULE_LOAD_FAILED: "Ensure the module can be imported and all dependencies are installed.",
|
|
424
|
+
CONFIG_NOT_FOUND: "Create a soda-gql.config.ts file in your project root.",
|
|
425
|
+
CONFIG_INVALID: "Check your configuration file for syntax errors or invalid options.",
|
|
426
|
+
ENTRY_NOT_FOUND: "Verify the entry pattern matches your file structure.",
|
|
427
|
+
INTERNAL_INVARIANT: "This is an internal error. Please report it at https://github.com/soda-gql/soda-gql/issues"
|
|
428
|
+
};
|
|
429
|
+
/**
|
|
430
|
+
* Format a BuilderError into a structured FormattedError object.
|
|
431
|
+
*/
|
|
432
|
+
const formatBuilderErrorStructured = (error) => {
|
|
433
|
+
const base = {
|
|
434
|
+
code: error.code,
|
|
435
|
+
message: error.message,
|
|
436
|
+
hint: errorHints[error.code],
|
|
437
|
+
cause: "cause" in error ? error.cause : undefined
|
|
438
|
+
};
|
|
439
|
+
switch (error.code) {
|
|
440
|
+
case "ELEMENT_EVALUATION_FAILED": return {
|
|
441
|
+
...base,
|
|
442
|
+
location: {
|
|
443
|
+
modulePath: error.modulePath,
|
|
444
|
+
astPath: error.astPath || undefined
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
case "RUNTIME_MODULE_LOAD_FAILED": return {
|
|
448
|
+
...base,
|
|
449
|
+
location: {
|
|
450
|
+
modulePath: error.filePath,
|
|
451
|
+
astPath: error.astPath
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
case "GRAPH_MISSING_IMPORT": return {
|
|
455
|
+
...base,
|
|
456
|
+
relatedFiles: [error.importer, error.importee]
|
|
457
|
+
};
|
|
458
|
+
case "GRAPH_CIRCULAR_DEPENDENCY": return {
|
|
459
|
+
...base,
|
|
460
|
+
relatedFiles: error.chain
|
|
461
|
+
};
|
|
462
|
+
case "CONFIG_NOT_FOUND":
|
|
463
|
+
case "CONFIG_INVALID": return {
|
|
464
|
+
...base,
|
|
465
|
+
location: { modulePath: error.path }
|
|
466
|
+
};
|
|
467
|
+
case "FINGERPRINT_FAILED": return {
|
|
468
|
+
...base,
|
|
469
|
+
location: { modulePath: error.filePath }
|
|
470
|
+
};
|
|
471
|
+
case "DISCOVERY_IO_ERROR": return {
|
|
472
|
+
...base,
|
|
473
|
+
location: { modulePath: error.path }
|
|
474
|
+
};
|
|
475
|
+
default: return base;
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
/**
|
|
479
|
+
* Format a BuilderError for CLI/stderr output with human-readable formatting.
|
|
480
|
+
* Includes location, hint, and related files when available.
|
|
481
|
+
*/
|
|
482
|
+
const formatBuilderErrorForCLI = (error) => {
|
|
483
|
+
const formatted = formatBuilderErrorStructured(error);
|
|
484
|
+
const lines = [];
|
|
485
|
+
lines.push(`Error [${formatted.code}]: ${formatted.message}`);
|
|
486
|
+
if (formatted.location) {
|
|
487
|
+
lines.push(` at ${formatted.location.modulePath}`);
|
|
488
|
+
if (formatted.location.astPath) {
|
|
489
|
+
lines.push(` in ${formatted.location.astPath}`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (formatted.hint) {
|
|
493
|
+
lines.push("");
|
|
494
|
+
lines.push(` Hint: ${formatted.hint}`);
|
|
495
|
+
}
|
|
496
|
+
if (formatted.relatedFiles && formatted.relatedFiles.length > 0) {
|
|
497
|
+
lines.push("");
|
|
498
|
+
lines.push(" Related files:");
|
|
499
|
+
for (const file of formatted.relatedFiles) {
|
|
500
|
+
lines.push(` - ${file}`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return lines.join("\n");
|
|
504
|
+
};
|
|
505
|
+
|
|
506
506
|
//#endregion
|
|
507
507
|
//#region packages/builder/src/scheduler/effects.ts
|
|
508
508
|
/**
|
|
@@ -687,6 +687,74 @@ const BuilderEffects = {
|
|
|
687
687
|
evaluateElement: (element) => new ElementEvaluationEffect(element)
|
|
688
688
|
};
|
|
689
689
|
|
|
690
|
+
//#endregion
|
|
691
|
+
//#region packages/builder/src/vm/sandbox.ts
|
|
692
|
+
/**
|
|
693
|
+
* VM sandbox utilities for CJS bundle evaluation.
|
|
694
|
+
*
|
|
695
|
+
* Provides shared infrastructure for executing CommonJS modules
|
|
696
|
+
* in a sandboxed VM context with @soda-gql package mocking.
|
|
697
|
+
*
|
|
698
|
+
* @module
|
|
699
|
+
*/
|
|
700
|
+
/**
|
|
701
|
+
* Create a require function for the sandbox.
|
|
702
|
+
* Maps @soda-gql package imports to their actual modules.
|
|
703
|
+
*/
|
|
704
|
+
const createSandboxRequire = () => (path) => {
|
|
705
|
+
if (path === "@soda-gql/core") return __soda_gql_core;
|
|
706
|
+
if (path === "@soda-gql/core/adapter") return __soda_gql_core_adapter;
|
|
707
|
+
if (path === "@soda-gql/core/runtime") return __soda_gql_core_runtime;
|
|
708
|
+
if (path === "@soda-gql/runtime") return __soda_gql_runtime;
|
|
709
|
+
throw new Error(`Unknown module: ${path}`);
|
|
710
|
+
};
|
|
711
|
+
/**
|
|
712
|
+
* Create a VM sandbox for executing CJS bundles.
|
|
713
|
+
*
|
|
714
|
+
* Sets up:
|
|
715
|
+
* - require() handler for @soda-gql packages
|
|
716
|
+
* - module.exports and exports pointing to the same object
|
|
717
|
+
* - __dirname, __filename for path resolution
|
|
718
|
+
* - global and globalThis pointing to the sandbox itself
|
|
719
|
+
*
|
|
720
|
+
* @param modulePath - Absolute path to the module being executed
|
|
721
|
+
* @param additionalContext - Optional additional context properties
|
|
722
|
+
* @returns Configured sandbox object
|
|
723
|
+
*/
|
|
724
|
+
const createSandbox = (modulePath, additionalContext) => {
|
|
725
|
+
const moduleExports = {};
|
|
726
|
+
const sandbox = {
|
|
727
|
+
require: createSandboxRequire(),
|
|
728
|
+
module: { exports: moduleExports },
|
|
729
|
+
exports: moduleExports,
|
|
730
|
+
__dirname: (0, node_path.resolve)(modulePath, ".."),
|
|
731
|
+
__filename: modulePath,
|
|
732
|
+
global: undefined,
|
|
733
|
+
globalThis: undefined,
|
|
734
|
+
...additionalContext
|
|
735
|
+
};
|
|
736
|
+
sandbox.global = sandbox;
|
|
737
|
+
sandbox.globalThis = sandbox;
|
|
738
|
+
return sandbox;
|
|
739
|
+
};
|
|
740
|
+
/**
|
|
741
|
+
* Execute CJS code in a sandbox and return the exports.
|
|
742
|
+
*
|
|
743
|
+
* Note: Reads from sandbox.module.exports because esbuild CJS output
|
|
744
|
+
* reassigns module.exports via __toCommonJS(), replacing the original object.
|
|
745
|
+
*
|
|
746
|
+
* @param code - The CJS code to execute
|
|
747
|
+
* @param modulePath - Absolute path to the module (for error messages)
|
|
748
|
+
* @param additionalContext - Optional additional context properties
|
|
749
|
+
* @returns The module's exports object
|
|
750
|
+
*/
|
|
751
|
+
const executeSandbox = (code, modulePath, additionalContext) => {
|
|
752
|
+
const sandbox = createSandbox(modulePath, additionalContext);
|
|
753
|
+
const context = (0, node_vm.createContext)(sandbox);
|
|
754
|
+
new node_vm.Script(code, { filename: modulePath }).runInContext(context);
|
|
755
|
+
return sandbox.module.exports;
|
|
756
|
+
};
|
|
757
|
+
|
|
690
758
|
//#endregion
|
|
691
759
|
//#region packages/builder/src/intermediate-module/codegen.ts
|
|
692
760
|
const formatFactory = (expression) => {
|
|
@@ -1186,32 +1254,7 @@ function executeGraphqlSystemModule(modulePath) {
|
|
|
1186
1254
|
return { gql: cachedGql };
|
|
1187
1255
|
}
|
|
1188
1256
|
const bundledCode = (0, node_fs.readFileSync)(modulePath, "utf-8");
|
|
1189
|
-
const
|
|
1190
|
-
const sandbox = {
|
|
1191
|
-
require: (path) => {
|
|
1192
|
-
if (path === "@soda-gql/core") {
|
|
1193
|
-
return __soda_gql_core;
|
|
1194
|
-
}
|
|
1195
|
-
if (path === "@soda-gql/core/adapter") {
|
|
1196
|
-
return __soda_gql_core_adapter;
|
|
1197
|
-
}
|
|
1198
|
-
if (path === "@soda-gql/core/runtime") {
|
|
1199
|
-
return __soda_gql_core_runtime;
|
|
1200
|
-
}
|
|
1201
|
-
if (path === "@soda-gql/runtime") {
|
|
1202
|
-
return __soda_gql_runtime;
|
|
1203
|
-
}
|
|
1204
|
-
throw new Error(`Unknown module: ${path}`);
|
|
1205
|
-
},
|
|
1206
|
-
module: { exports: moduleExports },
|
|
1207
|
-
exports: moduleExports,
|
|
1208
|
-
__dirname: (0, node_path.resolve)(modulePath, ".."),
|
|
1209
|
-
__filename: modulePath,
|
|
1210
|
-
global: undefined,
|
|
1211
|
-
globalThis: undefined
|
|
1212
|
-
};
|
|
1213
|
-
sandbox.global = sandbox;
|
|
1214
|
-
sandbox.globalThis = sandbox;
|
|
1257
|
+
const sandbox = createSandbox(modulePath);
|
|
1215
1258
|
new node_vm.Script(bundledCode, { filename: modulePath }).runInNewContext(sandbox);
|
|
1216
1259
|
const finalExports = sandbox.module.exports;
|
|
1217
1260
|
const exportedGql = finalExports.gql ?? finalExports.default;
|
|
@@ -1394,310 +1437,6 @@ const createGraphqlSystemIdentifyHelper = (config) => {
|
|
|
1394
1437
|
};
|
|
1395
1438
|
};
|
|
1396
1439
|
|
|
1397
|
-
//#endregion
|
|
1398
|
-
//#region packages/builder/src/prebuilt/emitter.ts
|
|
1399
|
-
/**
|
|
1400
|
-
* Prebuilt types emitter.
|
|
1401
|
-
*
|
|
1402
|
-
* Generates TypeScript type definitions for PrebuiltTypes registry
|
|
1403
|
-
* from field selection data and schema.
|
|
1404
|
-
*
|
|
1405
|
-
* ## Error Handling Strategy
|
|
1406
|
-
*
|
|
1407
|
-
* The emitter uses a partial failure approach for type calculation errors:
|
|
1408
|
-
*
|
|
1409
|
-
* **Recoverable errors** (result in warnings, element skipped):
|
|
1410
|
-
* - Type calculation failures (e.g., `calculateFieldsType` throws)
|
|
1411
|
-
* - Input type generation failures (e.g., `generateInputType` throws)
|
|
1412
|
-
* - These are caught per-element, logged as warnings, and the element is omitted
|
|
1413
|
-
*
|
|
1414
|
-
* **Fatal errors** (result in error result):
|
|
1415
|
-
* - `SCHEMA_NOT_FOUND`: Selection references non-existent schema
|
|
1416
|
-
* - `WRITE_FAILED`: Cannot write output file to disk
|
|
1417
|
-
*
|
|
1418
|
-
* This allows builds to succeed with partial type coverage when some elements
|
|
1419
|
-
* have issues, while providing visibility into problems via warnings.
|
|
1420
|
-
*
|
|
1421
|
-
* @module
|
|
1422
|
-
*/
|
|
1423
|
-
/**
|
|
1424
|
-
* Group field selections by schema.
|
|
1425
|
-
* Uses the schemaLabel from each selection to group them correctly.
|
|
1426
|
-
*
|
|
1427
|
-
* @returns Result containing grouped selections and warnings, or error if schema not found
|
|
1428
|
-
*/
|
|
1429
|
-
const groupBySchema = (fieldSelections, schemas) => {
|
|
1430
|
-
const grouped = new Map();
|
|
1431
|
-
const warnings = [];
|
|
1432
|
-
for (const schemaName of Object.keys(schemas)) {
|
|
1433
|
-
grouped.set(schemaName, {
|
|
1434
|
-
fragments: [],
|
|
1435
|
-
operations: [],
|
|
1436
|
-
inputObjects: new Set()
|
|
1437
|
-
});
|
|
1438
|
-
}
|
|
1439
|
-
for (const [canonicalId, selection] of fieldSelections) {
|
|
1440
|
-
const schemaName = selection.schemaLabel;
|
|
1441
|
-
const schema = schemas[schemaName];
|
|
1442
|
-
const group = grouped.get(schemaName);
|
|
1443
|
-
if (!schema || !group) {
|
|
1444
|
-
return (0, neverthrow.err)(builderErrors.schemaNotFound(schemaName, canonicalId));
|
|
1445
|
-
}
|
|
1446
|
-
const outputFormatters = { scalarOutput: (name) => `ScalarOutput_${schemaName}<"${name}">` };
|
|
1447
|
-
const inputFormatters = {
|
|
1448
|
-
scalarInput: (name) => `ScalarInput_${schemaName}<"${name}">`,
|
|
1449
|
-
inputObject: (name) => `Input_${schemaName}_${name}`
|
|
1450
|
-
};
|
|
1451
|
-
if (selection.type === "fragment") {
|
|
1452
|
-
if (!selection.key) {
|
|
1453
|
-
continue;
|
|
1454
|
-
}
|
|
1455
|
-
try {
|
|
1456
|
-
const usedInputObjects = collectUsedInputObjectsFromSpecifiers(schema, selection.variableDefinitions);
|
|
1457
|
-
for (const inputName of usedInputObjects) {
|
|
1458
|
-
group.inputObjects.add(inputName);
|
|
1459
|
-
}
|
|
1460
|
-
const outputType = (0, __soda_gql_core.calculateFieldsType)(schema, selection.fields, outputFormatters);
|
|
1461
|
-
const hasVariables = Object.keys(selection.variableDefinitions).length > 0;
|
|
1462
|
-
const inputType = hasVariables ? (0, __soda_gql_core.generateInputTypeFromSpecifiers)(schema, selection.variableDefinitions, { formatters: inputFormatters }) : "void";
|
|
1463
|
-
group.fragments.push({
|
|
1464
|
-
key: selection.key,
|
|
1465
|
-
inputType,
|
|
1466
|
-
outputType
|
|
1467
|
-
});
|
|
1468
|
-
} catch (error) {
|
|
1469
|
-
warnings.push(`[prebuilt] Failed to calculate type for fragment "${selection.key}": ${error instanceof Error ? error.message : String(error)}`);
|
|
1470
|
-
}
|
|
1471
|
-
} else if (selection.type === "operation") {
|
|
1472
|
-
try {
|
|
1473
|
-
const usedInputObjects = collectUsedInputObjects(schema, selection.variableDefinitions);
|
|
1474
|
-
for (const inputName of usedInputObjects) {
|
|
1475
|
-
group.inputObjects.add(inputName);
|
|
1476
|
-
}
|
|
1477
|
-
const outputType = (0, __soda_gql_core.calculateFieldsType)(schema, selection.fields, outputFormatters);
|
|
1478
|
-
const inputType = (0, __soda_gql_core.generateInputType)(schema, selection.variableDefinitions, inputFormatters);
|
|
1479
|
-
group.operations.push({
|
|
1480
|
-
key: selection.operationName,
|
|
1481
|
-
inputType,
|
|
1482
|
-
outputType
|
|
1483
|
-
});
|
|
1484
|
-
} catch (error) {
|
|
1485
|
-
warnings.push(`[prebuilt] Failed to calculate type for operation "${selection.operationName}": ${error instanceof Error ? error.message : String(error)}`);
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
return (0, neverthrow.ok)({
|
|
1490
|
-
grouped,
|
|
1491
|
-
warnings
|
|
1492
|
-
});
|
|
1493
|
-
};
|
|
1494
|
-
/**
|
|
1495
|
-
* Calculate relative import path from one file to another.
|
|
1496
|
-
*/
|
|
1497
|
-
const toImportSpecifier = (from, to) => {
|
|
1498
|
-
const fromDir = (0, node_path.dirname)(from);
|
|
1499
|
-
let relativePath = (0, node_path.relative)(fromDir, to);
|
|
1500
|
-
if (!relativePath.startsWith(".")) {
|
|
1501
|
-
relativePath = `./${relativePath}`;
|
|
1502
|
-
}
|
|
1503
|
-
return relativePath.replace(/\.ts$/, "");
|
|
1504
|
-
};
|
|
1505
|
-
/**
|
|
1506
|
-
* Extract input object names from a GraphQL TypeNode.
|
|
1507
|
-
*/
|
|
1508
|
-
const extractInputObjectsFromType = (schema, typeNode, inputObjects) => {
|
|
1509
|
-
switch (typeNode.kind) {
|
|
1510
|
-
case graphql.Kind.NON_NULL_TYPE:
|
|
1511
|
-
extractInputObjectsFromType(schema, typeNode.type, inputObjects);
|
|
1512
|
-
break;
|
|
1513
|
-
case graphql.Kind.LIST_TYPE:
|
|
1514
|
-
extractInputObjectsFromType(schema, typeNode.type, inputObjects);
|
|
1515
|
-
break;
|
|
1516
|
-
case graphql.Kind.NAMED_TYPE: {
|
|
1517
|
-
const name = typeNode.name.value;
|
|
1518
|
-
if (!schema.scalar[name] && !schema.enum[name] && schema.input[name]) {
|
|
1519
|
-
inputObjects.add(name);
|
|
1520
|
-
}
|
|
1521
|
-
break;
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
};
|
|
1525
|
-
/**
|
|
1526
|
-
* Recursively collect nested input objects from schema definitions.
|
|
1527
|
-
* Takes a set of initial input names and expands to include all nested inputs.
|
|
1528
|
-
*/
|
|
1529
|
-
const collectNestedInputObjects = (schema, initialInputNames) => {
|
|
1530
|
-
const inputObjects = new Set(initialInputNames);
|
|
1531
|
-
const collectNested = (inputName, seen) => {
|
|
1532
|
-
if (seen.has(inputName)) {
|
|
1533
|
-
return;
|
|
1534
|
-
}
|
|
1535
|
-
seen.add(inputName);
|
|
1536
|
-
const inputDef = schema.input[inputName];
|
|
1537
|
-
if (!inputDef) {
|
|
1538
|
-
return;
|
|
1539
|
-
}
|
|
1540
|
-
for (const field of Object.values(inputDef.fields)) {
|
|
1541
|
-
if (field.kind === "input" && !inputObjects.has(field.name)) {
|
|
1542
|
-
inputObjects.add(field.name);
|
|
1543
|
-
collectNested(field.name, seen);
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
};
|
|
1547
|
-
for (const inputName of Array.from(initialInputNames)) {
|
|
1548
|
-
collectNested(inputName, new Set());
|
|
1549
|
-
}
|
|
1550
|
-
return inputObjects;
|
|
1551
|
-
};
|
|
1552
|
-
/**
|
|
1553
|
-
* Collect all input object types used in variable definitions.
|
|
1554
|
-
* Recursively collects nested input objects from the schema.
|
|
1555
|
-
*/
|
|
1556
|
-
const collectUsedInputObjects = (schema, variableDefinitions) => {
|
|
1557
|
-
const directInputs = new Set();
|
|
1558
|
-
for (const varDef of variableDefinitions) {
|
|
1559
|
-
extractInputObjectsFromType(schema, varDef.type, directInputs);
|
|
1560
|
-
}
|
|
1561
|
-
return collectNestedInputObjects(schema, directInputs);
|
|
1562
|
-
};
|
|
1563
|
-
/**
|
|
1564
|
-
* Collect all input object types used in InputTypeSpecifiers.
|
|
1565
|
-
* Recursively collects nested input objects from the schema.
|
|
1566
|
-
*/
|
|
1567
|
-
const collectUsedInputObjectsFromSpecifiers = (schema, specifiers) => {
|
|
1568
|
-
const directInputs = new Set();
|
|
1569
|
-
for (const specifier of Object.values(specifiers)) {
|
|
1570
|
-
if (specifier.kind === "input" && schema.input[specifier.name]) {
|
|
1571
|
-
directInputs.add(specifier.name);
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
return collectNestedInputObjects(schema, directInputs);
|
|
1575
|
-
};
|
|
1576
|
-
/**
|
|
1577
|
-
* Generate type definitions for input objects.
|
|
1578
|
-
*/
|
|
1579
|
-
const generateInputObjectTypeDefinitions = (schema, schemaName, inputNames) => {
|
|
1580
|
-
const lines = [];
|
|
1581
|
-
const defaultDepth = schema.__defaultInputDepth ?? 3;
|
|
1582
|
-
const depthOverrides = schema.__inputDepthOverrides ?? {};
|
|
1583
|
-
const formatters = {
|
|
1584
|
-
scalarInput: (name) => `ScalarInput_${schemaName}<"${name}">`,
|
|
1585
|
-
inputObject: (name) => `Input_${schemaName}_${name}`
|
|
1586
|
-
};
|
|
1587
|
-
const sortedNames = Array.from(inputNames).sort();
|
|
1588
|
-
for (const inputName of sortedNames) {
|
|
1589
|
-
const typeString = (0, __soda_gql_core.generateInputObjectType)(schema, inputName, {
|
|
1590
|
-
defaultDepth,
|
|
1591
|
-
depthOverrides,
|
|
1592
|
-
formatters
|
|
1593
|
-
});
|
|
1594
|
-
lines.push(`type Input_${schemaName}_${inputName} = ${typeString};`);
|
|
1595
|
-
}
|
|
1596
|
-
return lines;
|
|
1597
|
-
};
|
|
1598
|
-
/**
|
|
1599
|
-
* Generate the TypeScript code for prebuilt types.
|
|
1600
|
-
*/
|
|
1601
|
-
const generateTypesCode = (grouped, schemas, injects, outdir) => {
|
|
1602
|
-
const typesFilePath = (0, node_path.join)(outdir, "prebuilt", "types.ts");
|
|
1603
|
-
const lines = [
|
|
1604
|
-
"/**",
|
|
1605
|
-
" * Prebuilt type registry.",
|
|
1606
|
-
" *",
|
|
1607
|
-
" * This file is auto-generated by @soda-gql/builder.",
|
|
1608
|
-
" * Do not edit manually.",
|
|
1609
|
-
" *",
|
|
1610
|
-
" * @module",
|
|
1611
|
-
" * @generated",
|
|
1612
|
-
" */",
|
|
1613
|
-
"",
|
|
1614
|
-
"import type { PrebuiltTypeRegistry } from \"@soda-gql/core\";"
|
|
1615
|
-
];
|
|
1616
|
-
for (const [schemaName, inject] of Object.entries(injects)) {
|
|
1617
|
-
const relativePath = toImportSpecifier(typesFilePath, inject.scalars);
|
|
1618
|
-
lines.push(`import type { scalar as scalar_${schemaName} } from "${relativePath}";`);
|
|
1619
|
-
}
|
|
1620
|
-
lines.push("");
|
|
1621
|
-
for (const schemaName of Object.keys(injects)) {
|
|
1622
|
-
lines.push(`type ScalarInput_${schemaName}<T extends keyof typeof scalar_${schemaName}> = ` + `typeof scalar_${schemaName}[T]["$type"]["input"];`);
|
|
1623
|
-
lines.push(`type ScalarOutput_${schemaName}<T extends keyof typeof scalar_${schemaName}> = ` + `typeof scalar_${schemaName}[T]["$type"]["output"];`);
|
|
1624
|
-
}
|
|
1625
|
-
lines.push("");
|
|
1626
|
-
for (const [schemaName, { fragments, operations, inputObjects }] of grouped) {
|
|
1627
|
-
const schema = schemas[schemaName];
|
|
1628
|
-
if (inputObjects.size > 0 && schema) {
|
|
1629
|
-
lines.push("// Input object types");
|
|
1630
|
-
const inputTypeLines = generateInputObjectTypeDefinitions(schema, schemaName, inputObjects);
|
|
1631
|
-
lines.push(...inputTypeLines);
|
|
1632
|
-
lines.push("");
|
|
1633
|
-
}
|
|
1634
|
-
const fragmentEntries = fragments.sort((a, b) => a.key.localeCompare(b.key)).map((f) => ` readonly "${f.key}": { readonly input: ${f.inputType}; readonly output: ${f.outputType} };`);
|
|
1635
|
-
const operationEntries = operations.sort((a, b) => a.key.localeCompare(b.key)).map((o) => ` readonly "${o.key}": { readonly input: ${o.inputType}; readonly output: ${o.outputType} };`);
|
|
1636
|
-
lines.push(`export type PrebuiltTypes_${schemaName} = {`);
|
|
1637
|
-
lines.push(" readonly fragments: {");
|
|
1638
|
-
if (fragmentEntries.length > 0) {
|
|
1639
|
-
lines.push(...fragmentEntries);
|
|
1640
|
-
}
|
|
1641
|
-
lines.push(" };");
|
|
1642
|
-
lines.push(" readonly operations: {");
|
|
1643
|
-
if (operationEntries.length > 0) {
|
|
1644
|
-
lines.push(...operationEntries);
|
|
1645
|
-
}
|
|
1646
|
-
lines.push(" };");
|
|
1647
|
-
lines.push("} satisfies PrebuiltTypeRegistry;");
|
|
1648
|
-
lines.push("");
|
|
1649
|
-
}
|
|
1650
|
-
return lines.join("\n");
|
|
1651
|
-
};
|
|
1652
|
-
/**
|
|
1653
|
-
* Emit prebuilt types to the prebuilt/types.ts file.
|
|
1654
|
-
*
|
|
1655
|
-
* This function uses a partial failure strategy: if type calculation fails for
|
|
1656
|
-
* individual elements (e.g., due to invalid field selections or missing schema
|
|
1657
|
-
* types), those elements are skipped and warnings are collected rather than
|
|
1658
|
-
* failing the entire emission. This allows builds to succeed even when some
|
|
1659
|
-
* elements have issues, while still reporting problems via warnings.
|
|
1660
|
-
*
|
|
1661
|
-
* @param options - Emitter options including schemas, field selections, and output directory
|
|
1662
|
-
* @returns Result containing output path and warnings, or error if a hard failure occurs
|
|
1663
|
-
*
|
|
1664
|
-
* @example
|
|
1665
|
-
* ```typescript
|
|
1666
|
-
* const result = await emitPrebuiltTypes({
|
|
1667
|
-
* schemas: { mySchema: schema },
|
|
1668
|
-
* fieldSelections,
|
|
1669
|
-
* outdir: "./generated",
|
|
1670
|
-
* injects: { mySchema: { scalars: "./scalars.ts" } },
|
|
1671
|
-
* });
|
|
1672
|
-
*
|
|
1673
|
-
* if (result.isOk()) {
|
|
1674
|
-
* console.log(`Generated: ${result.value.path}`);
|
|
1675
|
-
* if (result.value.warnings.length > 0) {
|
|
1676
|
-
* console.warn("Warnings:", result.value.warnings);
|
|
1677
|
-
* }
|
|
1678
|
-
* }
|
|
1679
|
-
* ```
|
|
1680
|
-
*/
|
|
1681
|
-
const emitPrebuiltTypes = async (options) => {
|
|
1682
|
-
const { schemas, fieldSelections, outdir, injects } = options;
|
|
1683
|
-
const groupResult = groupBySchema(fieldSelections, schemas);
|
|
1684
|
-
if (groupResult.isErr()) {
|
|
1685
|
-
return (0, neverthrow.err)(groupResult.error);
|
|
1686
|
-
}
|
|
1687
|
-
const { grouped, warnings } = groupResult.value;
|
|
1688
|
-
const code = generateTypesCode(grouped, schemas, injects, outdir);
|
|
1689
|
-
const typesPath = (0, node_path.join)(outdir, "prebuilt", "types.ts");
|
|
1690
|
-
try {
|
|
1691
|
-
await (0, node_fs_promises.writeFile)(typesPath, code, "utf-8");
|
|
1692
|
-
return (0, neverthrow.ok)({
|
|
1693
|
-
path: typesPath,
|
|
1694
|
-
warnings
|
|
1695
|
-
});
|
|
1696
|
-
} catch (error) {
|
|
1697
|
-
return (0, neverthrow.err)(builderErrors.writeFailed(typesPath, `Failed to write prebuilt types: ${error instanceof Error ? error.message : String(error)}`, error));
|
|
1698
|
-
}
|
|
1699
|
-
};
|
|
1700
|
-
|
|
1701
1440
|
//#endregion
|
|
1702
1441
|
//#region packages/builder/src/prebuilt/extractor.ts
|
|
1703
1442
|
/**
|
|
@@ -1751,6 +1490,106 @@ const extractFieldSelections = (elements) => {
|
|
|
1751
1490
|
};
|
|
1752
1491
|
};
|
|
1753
1492
|
|
|
1493
|
+
//#endregion
|
|
1494
|
+
//#region packages/builder/src/schema-loader.ts
|
|
1495
|
+
/**
|
|
1496
|
+
* Schema loader for CJS bundle evaluation.
|
|
1497
|
+
*
|
|
1498
|
+
* Loads AnyGraphqlSchema from the generated CJS bundle by accessing
|
|
1499
|
+
* the `$schema` property on each gql composer.
|
|
1500
|
+
*
|
|
1501
|
+
* @module
|
|
1502
|
+
*/
|
|
1503
|
+
/**
|
|
1504
|
+
* Load AnyGraphqlSchema from a generated CJS bundle.
|
|
1505
|
+
*
|
|
1506
|
+
* The generated CJS bundle exports a `gql` object where each property
|
|
1507
|
+
* is a GQL element composer with a `$schema` property containing the
|
|
1508
|
+
* schema definition. This function executes the bundle in a VM context
|
|
1509
|
+
* and extracts those schemas.
|
|
1510
|
+
*
|
|
1511
|
+
* @param cjsPath - Absolute path to the CJS bundle file
|
|
1512
|
+
* @param schemaNames - Names of schemas to load (e.g., ["default", "admin"])
|
|
1513
|
+
* @returns Record mapping schema names to AnyGraphqlSchema objects
|
|
1514
|
+
*
|
|
1515
|
+
* @example
|
|
1516
|
+
* ```typescript
|
|
1517
|
+
* const result = loadSchemasFromBundle(
|
|
1518
|
+
* "/path/to/generated/index.cjs",
|
|
1519
|
+
* ["default"]
|
|
1520
|
+
* );
|
|
1521
|
+
*
|
|
1522
|
+
* if (result.isOk()) {
|
|
1523
|
+
* const schemas = result.value;
|
|
1524
|
+
* console.log(schemas.default); // AnyGraphqlSchema
|
|
1525
|
+
* }
|
|
1526
|
+
* ```
|
|
1527
|
+
*/
|
|
1528
|
+
const loadSchemasFromBundle = (cjsPath, schemaNames) => {
|
|
1529
|
+
const resolvedPath = (0, node_path.resolve)(cjsPath);
|
|
1530
|
+
if (!(0, node_fs.existsSync)(resolvedPath)) {
|
|
1531
|
+
return (0, neverthrow.err)({
|
|
1532
|
+
code: "CONFIG_NOT_FOUND",
|
|
1533
|
+
message: `CJS bundle not found: ${resolvedPath}. Run 'soda-gql codegen' first.`,
|
|
1534
|
+
path: resolvedPath
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
let bundledCode;
|
|
1538
|
+
try {
|
|
1539
|
+
bundledCode = (0, node_fs.readFileSync)(resolvedPath, "utf-8");
|
|
1540
|
+
} catch (error) {
|
|
1541
|
+
return (0, neverthrow.err)({
|
|
1542
|
+
code: "DISCOVERY_IO_ERROR",
|
|
1543
|
+
message: `Failed to read CJS bundle: ${error instanceof Error ? error.message : String(error)}`,
|
|
1544
|
+
path: resolvedPath,
|
|
1545
|
+
cause: error
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
let finalExports;
|
|
1549
|
+
try {
|
|
1550
|
+
finalExports = executeSandbox(bundledCode, resolvedPath);
|
|
1551
|
+
} catch (error) {
|
|
1552
|
+
return (0, neverthrow.err)({
|
|
1553
|
+
code: "RUNTIME_MODULE_LOAD_FAILED",
|
|
1554
|
+
message: `Failed to execute CJS bundle: ${error instanceof Error ? error.message : String(error)}`,
|
|
1555
|
+
filePath: resolvedPath,
|
|
1556
|
+
astPath: "",
|
|
1557
|
+
cause: error
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
const gql = finalExports.gql;
|
|
1561
|
+
if (!gql || typeof gql !== "object") {
|
|
1562
|
+
return (0, neverthrow.err)({
|
|
1563
|
+
code: "CONFIG_INVALID",
|
|
1564
|
+
message: "CJS bundle does not export 'gql' object. Ensure codegen was run successfully.",
|
|
1565
|
+
path: resolvedPath
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
const schemas = {};
|
|
1569
|
+
for (const name of schemaNames) {
|
|
1570
|
+
const composer = gql[name];
|
|
1571
|
+
if (!composer) {
|
|
1572
|
+
const availableSchemas = Object.keys(gql).join(", ");
|
|
1573
|
+
return (0, neverthrow.err)({
|
|
1574
|
+
code: "SCHEMA_NOT_FOUND",
|
|
1575
|
+
message: `Schema '${name}' not found in gql exports. Available: ${availableSchemas || "(none)"}`,
|
|
1576
|
+
schemaLabel: name,
|
|
1577
|
+
canonicalId: `gql.${name}`
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
const schema = composer.$schema;
|
|
1581
|
+
if (!schema || typeof schema !== "object") {
|
|
1582
|
+
return (0, neverthrow.err)({
|
|
1583
|
+
code: "CONFIG_INVALID",
|
|
1584
|
+
message: `gql.${name}.$schema is not a valid schema object. Ensure codegen version is up to date.`,
|
|
1585
|
+
path: resolvedPath
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
schemas[name] = schema;
|
|
1589
|
+
}
|
|
1590
|
+
return (0, neverthrow.ok)(schemas);
|
|
1591
|
+
};
|
|
1592
|
+
|
|
1754
1593
|
//#endregion
|
|
1755
1594
|
//#region packages/builder/src/artifact/aggregate.ts
|
|
1756
1595
|
const canonicalToFilePath$1 = (canonicalId) => canonicalId.split("::")[0] ?? canonicalId;
|
|
@@ -3741,7 +3580,8 @@ const createBuilderSession = (options) => {
|
|
|
3741
3580
|
snapshots: new Map(),
|
|
3742
3581
|
moduleAdjacency: new Map(),
|
|
3743
3582
|
intermediateModules: new Map(),
|
|
3744
|
-
lastArtifact: null
|
|
3583
|
+
lastArtifact: null,
|
|
3584
|
+
lastIntermediateElements: null
|
|
3745
3585
|
};
|
|
3746
3586
|
const cacheFactory = createMemoryCache({
|
|
3747
3587
|
prefix: ["builder"],
|
|
@@ -3830,6 +3670,7 @@ const createBuilderSession = (options) => {
|
|
|
3830
3670
|
state.moduleAdjacency = currentModuleAdjacency;
|
|
3831
3671
|
state.lastArtifact = artifactResult.value;
|
|
3832
3672
|
state.intermediateModules = intermediateModules;
|
|
3673
|
+
state.lastIntermediateElements = elements;
|
|
3833
3674
|
ensureFileTracker().update(currentScan);
|
|
3834
3675
|
return (0, neverthrow.ok)(artifactResult.value);
|
|
3835
3676
|
};
|
|
@@ -3892,6 +3733,7 @@ const createBuilderSession = (options) => {
|
|
|
3892
3733
|
buildAsync,
|
|
3893
3734
|
getGeneration: () => state.gen,
|
|
3894
3735
|
getCurrentArtifact: () => state.lastArtifact,
|
|
3736
|
+
getIntermediateElements: () => state.lastIntermediateElements,
|
|
3895
3737
|
dispose: () => {
|
|
3896
3738
|
cacheFactory.save();
|
|
3897
3739
|
unregisterExitHandler(cacheFactory);
|
|
@@ -4026,6 +3868,7 @@ const createBuilderService = ({ config, entrypointsOverride }) => {
|
|
|
4026
3868
|
buildAsync: (options) => session.buildAsync(options),
|
|
4027
3869
|
getGeneration: () => session.getGeneration(),
|
|
4028
3870
|
getCurrentArtifact: () => session.getCurrentArtifact(),
|
|
3871
|
+
getIntermediateElements: () => session.getIntermediateElements(),
|
|
4029
3872
|
dispose: () => session.dispose()
|
|
4030
3873
|
};
|
|
4031
3874
|
};
|
|
@@ -4036,15 +3879,16 @@ exports.BuilderEffects = BuilderEffects;
|
|
|
4036
3879
|
exports.FileReadEffect = FileReadEffect;
|
|
4037
3880
|
exports.FileStatEffect = FileStatEffect;
|
|
4038
3881
|
exports.__clearGqlCache = __clearGqlCache;
|
|
3882
|
+
exports.builderErrors = builderErrors;
|
|
4039
3883
|
exports.collectAffectedFiles = collectAffectedFiles;
|
|
4040
3884
|
exports.createBuilderService = createBuilderService;
|
|
4041
3885
|
exports.createBuilderSession = createBuilderSession;
|
|
4042
3886
|
exports.createGraphqlSystemIdentifyHelper = createGraphqlSystemIdentifyHelper;
|
|
4043
|
-
exports.emitPrebuiltTypes = emitPrebuiltTypes;
|
|
4044
3887
|
exports.extractFieldSelections = extractFieldSelections;
|
|
4045
3888
|
exports.extractModuleAdjacency = extractModuleAdjacency;
|
|
4046
3889
|
exports.formatBuilderErrorForCLI = formatBuilderErrorForCLI;
|
|
4047
3890
|
exports.formatBuilderErrorStructured = formatBuilderErrorStructured;
|
|
4048
3891
|
exports.loadArtifact = loadArtifact;
|
|
4049
3892
|
exports.loadArtifactSync = loadArtifactSync;
|
|
3893
|
+
exports.loadSchemasFromBundle = loadSchemasFromBundle;
|
|
4050
3894
|
//# sourceMappingURL=index.cjs.map
|