@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 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 moduleExports = {};
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