@soda-gql/builder 0.0.9 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -26,19 +26,20 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
 
27
27
  //#endregion
28
28
  let __soda_gql_common = require("@soda-gql/common");
29
+ let node_fs = require("node:fs");
29
30
  let node_path = require("node:path");
31
+ let node_fs_promises = require("node:fs/promises");
32
+ let __soda_gql_core = require("@soda-gql/core");
33
+ __soda_gql_core = __toESM(__soda_gql_core);
30
34
  let zod = require("zod");
31
35
  let neverthrow = require("neverthrow");
32
36
  let node_crypto = require("node:crypto");
33
37
  let __swc_core = require("@swc/core");
34
38
  let typescript = require("typescript");
35
39
  typescript = __toESM(typescript);
36
- let node_fs = require("node:fs");
37
40
  let fast_glob = require("fast-glob");
38
41
  fast_glob = __toESM(fast_glob);
39
42
  let node_vm = require("node:vm");
40
- let __soda_gql_core = require("@soda-gql/core");
41
- __soda_gql_core = __toESM(__soda_gql_core);
42
43
  let __soda_gql_runtime = require("@soda-gql/runtime");
43
44
  __soda_gql_runtime = __toESM(__soda_gql_runtime);
44
45
 
@@ -70,7 +71,11 @@ const createGraphqlSystemIdentifyHelper = (config) => {
70
71
  const getCanonicalFileName = createGetCanonicalFileName(getUseCaseSensitiveFileNames());
71
72
  const toCanonical = (file) => {
72
73
  const resolved = (0, node_path.resolve)(file);
73
- return getCanonicalFileName(resolved);
74
+ try {
75
+ return getCanonicalFileName((0, node_fs.realpathSync)(resolved));
76
+ } catch {
77
+ return getCanonicalFileName(resolved);
78
+ }
74
79
  };
75
80
  const graphqlSystemPath = (0, node_path.resolve)(config.outdir, "index.ts");
76
81
  const canonicalGraphqlSystemPath = toCanonical(graphqlSystemPath);
@@ -98,6 +103,168 @@ const createGraphqlSystemIdentifyHelper = (config) => {
98
103
  };
99
104
  };
100
105
 
106
+ //#endregion
107
+ //#region packages/builder/src/scheduler/effects.ts
108
+ /**
109
+ * File read effect - reads a file from the filesystem.
110
+ * Works in both sync and async schedulers.
111
+ *
112
+ * @example
113
+ * const content = yield* new FileReadEffect("/path/to/file").run();
114
+ */
115
+ var FileReadEffect = class extends __soda_gql_common.Effect {
116
+ constructor(path) {
117
+ super();
118
+ this.path = path;
119
+ }
120
+ _executeSync() {
121
+ return (0, node_fs.readFileSync)(this.path, "utf-8");
122
+ }
123
+ _executeAsync() {
124
+ return (0, node_fs_promises.readFile)(this.path, "utf-8");
125
+ }
126
+ };
127
+ /**
128
+ * File stat effect - gets file stats from the filesystem.
129
+ * Works in both sync and async schedulers.
130
+ *
131
+ * @example
132
+ * const stats = yield* new FileStatEffect("/path/to/file").run();
133
+ */
134
+ var FileStatEffect = class extends __soda_gql_common.Effect {
135
+ constructor(path) {
136
+ super();
137
+ this.path = path;
138
+ }
139
+ _executeSync() {
140
+ const stats = (0, node_fs.statSync)(this.path);
141
+ return {
142
+ mtimeMs: stats.mtimeMs,
143
+ size: stats.size,
144
+ isFile: stats.isFile()
145
+ };
146
+ }
147
+ async _executeAsync() {
148
+ const stats = await (0, node_fs_promises.stat)(this.path);
149
+ return {
150
+ mtimeMs: stats.mtimeMs,
151
+ size: stats.size,
152
+ isFile: stats.isFile()
153
+ };
154
+ }
155
+ };
156
+ /**
157
+ * File read effect that returns null if file doesn't exist.
158
+ * Useful for discovery where missing files are expected.
159
+ */
160
+ var OptionalFileReadEffect = class extends __soda_gql_common.Effect {
161
+ constructor(path) {
162
+ super();
163
+ this.path = path;
164
+ }
165
+ _executeSync() {
166
+ try {
167
+ return (0, node_fs.readFileSync)(this.path, "utf-8");
168
+ } catch (error) {
169
+ if (error.code === "ENOENT") {
170
+ return null;
171
+ }
172
+ throw error;
173
+ }
174
+ }
175
+ async _executeAsync() {
176
+ try {
177
+ return await (0, node_fs_promises.readFile)(this.path, "utf-8");
178
+ } catch (error) {
179
+ if (error.code === "ENOENT") {
180
+ return null;
181
+ }
182
+ throw error;
183
+ }
184
+ }
185
+ };
186
+ /**
187
+ * File stat effect that returns null if file doesn't exist.
188
+ * Useful for discovery where missing files are expected.
189
+ */
190
+ var OptionalFileStatEffect = class extends __soda_gql_common.Effect {
191
+ constructor(path) {
192
+ super();
193
+ this.path = path;
194
+ }
195
+ _executeSync() {
196
+ try {
197
+ const stats = (0, node_fs.statSync)(this.path);
198
+ return {
199
+ mtimeMs: stats.mtimeMs,
200
+ size: stats.size,
201
+ isFile: stats.isFile()
202
+ };
203
+ } catch (error) {
204
+ if (error.code === "ENOENT") {
205
+ return null;
206
+ }
207
+ throw error;
208
+ }
209
+ }
210
+ async _executeAsync() {
211
+ try {
212
+ const stats = await (0, node_fs_promises.stat)(this.path);
213
+ return {
214
+ mtimeMs: stats.mtimeMs,
215
+ size: stats.size,
216
+ isFile: stats.isFile()
217
+ };
218
+ } catch (error) {
219
+ if (error.code === "ENOENT") {
220
+ return null;
221
+ }
222
+ throw error;
223
+ }
224
+ }
225
+ };
226
+ /**
227
+ * Element evaluation effect - evaluates a GqlElement using its generator.
228
+ * Supports both sync and async schedulers, enabling parallel element evaluation
229
+ * when using async scheduler.
230
+ *
231
+ * @example
232
+ * yield* new ElementEvaluationEffect(element).run();
233
+ */
234
+ var ElementEvaluationEffect = class extends __soda_gql_common.Effect {
235
+ constructor(element) {
236
+ super();
237
+ this.element = element;
238
+ }
239
+ _executeSync() {
240
+ const generator = __soda_gql_core.GqlElement.createEvaluationGenerator(this.element);
241
+ const result = generator.next();
242
+ while (!result.done) {
243
+ throw new Error("Async operation required during sync element evaluation");
244
+ }
245
+ }
246
+ async _executeAsync() {
247
+ const generator = __soda_gql_core.GqlElement.createEvaluationGenerator(this.element);
248
+ let result = generator.next();
249
+ while (!result.done) {
250
+ await result.value;
251
+ result = generator.next();
252
+ }
253
+ }
254
+ };
255
+ /**
256
+ * Builder effect constructors.
257
+ * Extends the base Effects with file I/O operations and element evaluation.
258
+ */
259
+ const BuilderEffects = {
260
+ ...__soda_gql_common.Effects,
261
+ readFile: (path) => new FileReadEffect(path),
262
+ stat: (path) => new FileStatEffect(path),
263
+ readFileOptional: (path) => new OptionalFileReadEffect(path),
264
+ statOptional: (path) => new OptionalFileStatEffect(path),
265
+ evaluateElement: (element) => new ElementEvaluationEffect(element)
266
+ };
267
+
101
268
  //#endregion
102
269
  //#region packages/builder/src/schemas/artifact.ts
103
270
  const BuilderArtifactElementMetadataSchema = zod.z.object({
@@ -219,7 +386,8 @@ const aggregate = ({ analyses, elements }) => {
219
386
  operationName: element.element.operationName,
220
387
  document: element.element.document,
221
388
  variableNames: element.element.variableNames,
222
- projectionPathGraph: element.element.projectionPathGraph
389
+ projectionPathGraph: element.element.projectionPathGraph,
390
+ metadata: element.element.metadata
223
391
  };
224
392
  registry.set(definition.canonicalId, {
225
393
  id: definition.canonicalId,
@@ -237,7 +405,8 @@ const aggregate = ({ analyses, elements }) => {
237
405
  operationType: element.element.operationType,
238
406
  operationName: element.element.operationName,
239
407
  document: element.element.document,
240
- variableNames: element.element.variableNames
408
+ variableNames: element.element.variableNames,
409
+ metadata: element.element.metadata
241
410
  };
242
411
  registry.set(definition.canonicalId, {
243
412
  id: definition.canonicalId,
@@ -959,58 +1128,41 @@ const collectAllDefinitions$1 = ({ module: module$1, gqlIdentifiers, imports: _i
959
1128
  };
960
1129
  };
961
1130
  /**
962
- * Collect diagnostics (now empty since we support all definition types)
963
- */
964
- const collectDiagnostics$1 = () => {
965
- return [];
966
- };
967
- /**
968
- * SWC adapter implementation
1131
+ * SWC adapter implementation.
1132
+ * The analyze method parses and collects all data in one pass,
1133
+ * ensuring the AST (Module) is released after analysis.
969
1134
  */
970
- const swcAdapter = {
971
- parse(input) {
972
- const program = (0, __swc_core.parseSync)(input.source, {
973
- syntax: "typescript",
974
- tsx: input.filePath.endsWith(".tsx"),
975
- target: "es2022",
976
- decorators: false,
977
- dynamicImport: true
978
- });
979
- if (program.type !== "Module") {
980
- return null;
981
- }
982
- const swcModule = program;
983
- swcModule.__filePath = input.filePath;
984
- return swcModule;
985
- },
986
- collectGqlIdentifiers(file, helper) {
987
- return collectGqlIdentifiers(file, helper);
988
- },
989
- collectImports(file) {
990
- return collectImports$1(file);
991
- },
992
- collectExports(file) {
993
- return collectExports$1(file);
994
- },
995
- collectDefinitions(file, context) {
996
- const resolvePosition = toPositionResolver(context.source);
997
- const { definitions, handledCalls } = collectAllDefinitions$1({
998
- module: file,
999
- gqlIdentifiers: context.gqlIdentifiers,
1000
- imports: context.imports,
1001
- exports: context.exports,
1002
- resolvePosition,
1003
- source: context.source
1004
- });
1005
- return {
1006
- definitions,
1007
- handles: handledCalls
1008
- };
1009
- },
1010
- collectDiagnostics(_file, _context) {
1011
- return collectDiagnostics$1();
1135
+ const swcAdapter = { analyze(input, helper) {
1136
+ const program = (0, __swc_core.parseSync)(input.source, {
1137
+ syntax: "typescript",
1138
+ tsx: input.filePath.endsWith(".tsx"),
1139
+ target: "es2022",
1140
+ decorators: false,
1141
+ dynamicImport: true
1142
+ });
1143
+ if (program.type !== "Module") {
1144
+ return null;
1012
1145
  }
1013
- };
1146
+ const swcModule = program;
1147
+ swcModule.__filePath = input.filePath;
1148
+ const gqlIdentifiers = collectGqlIdentifiers(swcModule, helper);
1149
+ const imports = collectImports$1(swcModule);
1150
+ const exports$1 = collectExports$1(swcModule);
1151
+ const resolvePosition = toPositionResolver(input.source);
1152
+ const { definitions } = collectAllDefinitions$1({
1153
+ module: swcModule,
1154
+ gqlIdentifiers,
1155
+ imports,
1156
+ exports: exports$1,
1157
+ resolvePosition,
1158
+ source: input.source
1159
+ });
1160
+ return {
1161
+ imports,
1162
+ exports: exports$1,
1163
+ definitions
1164
+ };
1165
+ } };
1014
1166
 
1015
1167
  //#endregion
1016
1168
  //#region packages/builder/src/ast/adapters/typescript.ts
@@ -1343,42 +1495,26 @@ const collectAllDefinitions = ({ sourceFile, identifiers, exports: exports$1 })
1343
1495
  };
1344
1496
  };
1345
1497
  /**
1346
- * Collect diagnostics (now empty since we support all definition types)
1498
+ * TypeScript adapter implementation.
1499
+ * The analyze method parses and collects all data in one pass,
1500
+ * ensuring the AST (ts.SourceFile) is released after analysis.
1347
1501
  */
1348
- const collectDiagnostics = (_sourceFile, _identifiers, _handledCalls) => {
1349
- return [];
1350
- };
1351
- /**
1352
- * TypeScript adapter implementation
1353
- */
1354
- const typescriptAdapter = {
1355
- parse(input) {
1356
- return createSourceFile(input.filePath, input.source);
1357
- },
1358
- collectGqlIdentifiers(file, helper) {
1359
- return collectGqlImports(file, helper);
1360
- },
1361
- collectImports(file) {
1362
- return collectImports(file);
1363
- },
1364
- collectExports(file) {
1365
- return collectExports(file);
1366
- },
1367
- collectDefinitions(file, context) {
1368
- const { definitions, handledCalls } = collectAllDefinitions({
1369
- sourceFile: file,
1370
- identifiers: context.gqlIdentifiers,
1371
- exports: context.exports
1372
- });
1373
- return {
1374
- definitions,
1375
- handles: handledCalls
1376
- };
1377
- },
1378
- collectDiagnostics(file, context) {
1379
- return collectDiagnostics(file, context.gqlIdentifiers, context.handledCalls);
1380
- }
1381
- };
1502
+ const typescriptAdapter = { analyze(input, helper) {
1503
+ const sourceFile = createSourceFile(input.filePath, input.source);
1504
+ const gqlIdentifiers = collectGqlImports(sourceFile, helper);
1505
+ const imports = collectImports(sourceFile);
1506
+ const exports$1 = collectExports(sourceFile);
1507
+ const { definitions } = collectAllDefinitions({
1508
+ sourceFile,
1509
+ identifiers: gqlIdentifiers,
1510
+ exports: exports$1
1511
+ });
1512
+ return {
1513
+ imports,
1514
+ exports: exports$1,
1515
+ definitions
1516
+ };
1517
+ } };
1382
1518
 
1383
1519
  //#endregion
1384
1520
  //#region packages/builder/src/ast/core.ts
@@ -1393,38 +1529,22 @@ const typescriptAdapter = {
1393
1529
  const analyzeModuleCore = (input, adapter, graphqlHelper) => {
1394
1530
  const hasher = (0, __soda_gql_common.getPortableHasher)();
1395
1531
  const signature = hasher.hash(input.source, "xxhash");
1396
- const file = adapter.parse(input);
1397
- if (!file) {
1532
+ const result = adapter.analyze(input, graphqlHelper);
1533
+ if (!result) {
1398
1534
  return {
1399
1535
  filePath: input.filePath,
1400
1536
  signature,
1401
1537
  definitions: [],
1402
- diagnostics: [],
1403
1538
  imports: [],
1404
1539
  exports: []
1405
1540
  };
1406
1541
  }
1407
- const gqlIdentifiers = adapter.collectGqlIdentifiers(file, graphqlHelper);
1408
- const imports = adapter.collectImports(file);
1409
- const exports$1 = adapter.collectExports(file);
1410
- const { definitions, handles } = adapter.collectDefinitions(file, {
1411
- gqlIdentifiers,
1412
- imports,
1413
- exports: exports$1,
1414
- source: input.source
1415
- });
1416
- const diagnostics = adapter.collectDiagnostics(file, {
1417
- gqlIdentifiers,
1418
- handledCalls: handles,
1419
- source: input.source
1420
- });
1421
1542
  return {
1422
1543
  filePath: input.filePath,
1423
1544
  signature,
1424
- definitions,
1425
- diagnostics,
1426
- imports,
1427
- exports: exports$1
1545
+ definitions: result.definitions,
1546
+ imports: result.imports,
1547
+ exports: result.exports
1428
1548
  };
1429
1549
  };
1430
1550
 
@@ -1685,11 +1805,6 @@ const ModuleDefinitionSchema = zod.z.object({
1685
1805
  loc: SourceLocationSchema,
1686
1806
  expression: zod.z.string()
1687
1807
  });
1688
- const ModuleDiagnosticSchema = zod.z.object({
1689
- code: zod.z.literal("NON_TOP_LEVEL_DEFINITION"),
1690
- message: zod.z.string(),
1691
- loc: SourceLocationSchema
1692
- });
1693
1808
  const ModuleImportSchema = zod.z.object({
1694
1809
  source: zod.z.string(),
1695
1810
  imported: zod.z.string(),
@@ -1718,7 +1833,6 @@ const ModuleAnalysisSchema = zod.z.object({
1718
1833
  filePath: zod.z.string(),
1719
1834
  signature: zod.z.string(),
1720
1835
  definitions: zod.z.array(ModuleDefinitionSchema).readonly(),
1721
- diagnostics: zod.z.array(ModuleDiagnosticSchema).readonly(),
1722
1836
  imports: zod.z.array(ModuleImportSchema).readonly(),
1723
1837
  exports: zod.z.array(ModuleExportSchema).readonly()
1724
1838
  });
@@ -1922,6 +2036,30 @@ function simpleHash(buffer) {
1922
2036
  return hash.toString(16);
1923
2037
  }
1924
2038
  /**
2039
+ * Compute fingerprint from pre-read file content and stats.
2040
+ * This is used by the generator-based discoverer which already has the content.
2041
+ *
2042
+ * @param path - Absolute path to file (for caching)
2043
+ * @param stats - File stats (mtimeMs, size)
2044
+ * @param content - File content as string
2045
+ * @returns FileFingerprint
2046
+ */
2047
+ function computeFingerprintFromContent(path, stats, content) {
2048
+ const cached = fingerprintCache.get(path);
2049
+ if (cached && cached.mtimeMs === stats.mtimeMs) {
2050
+ return cached;
2051
+ }
2052
+ const buffer = Buffer.from(content, "utf-8");
2053
+ const hash = computeHashSync(buffer);
2054
+ const fingerprint = {
2055
+ hash,
2056
+ sizeBytes: stats.size,
2057
+ mtimeMs: stats.mtimeMs
2058
+ };
2059
+ fingerprintCache.set(path, fingerprint);
2060
+ return fingerprint;
2061
+ }
2062
+ /**
1925
2063
  * Invalidate cached fingerprint for a specific path
1926
2064
  *
1927
2065
  * @param path - Absolute path to invalidate
@@ -1939,11 +2077,10 @@ function clearFingerprintCache() {
1939
2077
  //#endregion
1940
2078
  //#region packages/builder/src/discovery/discoverer.ts
1941
2079
  /**
1942
- * Discover and analyze all modules starting from entry points.
1943
- * Uses AST parsing instead of RegExp for reliable dependency extraction.
1944
- * Supports caching with fingerprint-based invalidation to skip re-parsing unchanged files.
2080
+ * Generator-based module discovery that yields effects for file I/O.
2081
+ * This allows the discovery process to be executed with either sync or async schedulers.
1945
2082
  */
1946
- const discoverModules = ({ entryPaths, astAnalyzer, incremental }) => {
2083
+ function* discoverModulesGen({ entryPaths, astAnalyzer, incremental }) {
1947
2084
  const snapshots = new Map();
1948
2085
  const stack = [...entryPaths];
1949
2086
  const changedFiles = incremental?.changedFiles ?? new Set();
@@ -1972,16 +2109,17 @@ const discoverModules = ({ entryPaths, astAnalyzer, incremental }) => {
1972
2109
  if (snapshots.has(filePath)) {
1973
2110
  continue;
1974
2111
  }
2112
+ let shouldReadFile = true;
1975
2113
  if (invalidatedSet.has(filePath)) {
1976
2114
  invalidateFingerprint(filePath);
1977
2115
  cacheSkips++;
1978
2116
  } else if (incremental) {
1979
2117
  const cached = incremental.cache.peek(filePath);
1980
2118
  if (cached) {
1981
- try {
1982
- const stats = (0, node_fs.statSync)(filePath);
1983
- const mtimeMs = stats.mtimeMs;
1984
- const sizeBytes = stats.size;
2119
+ const stats$1 = yield* new OptionalFileStatEffect(filePath).run();
2120
+ if (stats$1) {
2121
+ const mtimeMs = stats$1.mtimeMs;
2122
+ const sizeBytes = stats$1.size;
1985
2123
  if (cached.fingerprint.mtimeMs === mtimeMs && cached.fingerprint.sizeBytes === sizeBytes) {
1986
2124
  snapshots.set(filePath, cached);
1987
2125
  cacheHits++;
@@ -1990,21 +2128,19 @@ const discoverModules = ({ entryPaths, astAnalyzer, incremental }) => {
1990
2128
  stack.push(dep.resolvedPath);
1991
2129
  }
1992
2130
  }
1993
- continue;
2131
+ shouldReadFile = false;
1994
2132
  }
1995
- } catch {}
2133
+ }
1996
2134
  }
1997
2135
  }
1998
- let source;
1999
- try {
2000
- source = (0, node_fs.readFileSync)(filePath, "utf8");
2001
- } catch (error) {
2002
- if (error.code === "ENOENT") {
2003
- incremental?.cache.delete(filePath);
2004
- invalidateFingerprint(filePath);
2005
- continue;
2006
- }
2007
- return (0, neverthrow.err)(builderErrors.discoveryIOError(filePath, error instanceof Error ? error.message : String(error)));
2136
+ if (!shouldReadFile) {
2137
+ continue;
2138
+ }
2139
+ const source = yield* new OptionalFileReadEffect(filePath).run();
2140
+ if (source === null) {
2141
+ incremental?.cache.delete(filePath);
2142
+ invalidateFingerprint(filePath);
2143
+ continue;
2008
2144
  }
2009
2145
  const signature = createSourceHash(source);
2010
2146
  const analysis = astAnalyzer.analyze({
@@ -2018,11 +2154,8 @@ const discoverModules = ({ entryPaths, astAnalyzer, incremental }) => {
2018
2154
  stack.push(dep.resolvedPath);
2019
2155
  }
2020
2156
  }
2021
- const fingerprintResult = computeFingerprint(filePath);
2022
- if (fingerprintResult.isErr()) {
2023
- return (0, neverthrow.err)(builderErrors.discoveryIOError(filePath, `Failed to compute fingerprint: ${fingerprintResult.error.message}`));
2024
- }
2025
- const fingerprint = fingerprintResult.value;
2157
+ const stats = yield* new OptionalFileStatEffect(filePath).run();
2158
+ const fingerprint = computeFingerprintFromContent(filePath, stats, source);
2026
2159
  const snapshot = {
2027
2160
  filePath,
2028
2161
  normalizedFilePath: (0, __soda_gql_common.normalizePath)(filePath),
@@ -2038,12 +2171,44 @@ const discoverModules = ({ entryPaths, astAnalyzer, incremental }) => {
2038
2171
  incremental.cache.store(snapshot);
2039
2172
  }
2040
2173
  }
2041
- return (0, neverthrow.ok)({
2174
+ return {
2042
2175
  snapshots: Array.from(snapshots.values()),
2043
2176
  cacheHits,
2044
2177
  cacheMisses,
2045
2178
  cacheSkips
2046
- });
2179
+ };
2180
+ }
2181
+ /**
2182
+ * Discover and analyze all modules starting from entry points.
2183
+ * Uses AST parsing instead of RegExp for reliable dependency extraction.
2184
+ * Supports caching with fingerprint-based invalidation to skip re-parsing unchanged files.
2185
+ *
2186
+ * This function uses the synchronous scheduler internally for backward compatibility.
2187
+ * For async execution with parallel file I/O, use discoverModulesGen with an async scheduler.
2188
+ */
2189
+ const discoverModules = (options) => {
2190
+ const scheduler = (0, __soda_gql_common.createSyncScheduler)();
2191
+ const result = scheduler.run(() => discoverModulesGen(options));
2192
+ if (result.isErr()) {
2193
+ const error = result.error;
2194
+ return (0, neverthrow.err)(builderErrors.discoveryIOError("unknown", error.message));
2195
+ }
2196
+ return (0, neverthrow.ok)(result.value);
2197
+ };
2198
+ /**
2199
+ * Asynchronous version of discoverModules.
2200
+ * Uses async scheduler for parallel file I/O operations.
2201
+ *
2202
+ * This is useful for large codebases where parallel file operations can improve performance.
2203
+ */
2204
+ const discoverModulesAsync = async (options) => {
2205
+ const scheduler = (0, __soda_gql_common.createAsyncScheduler)();
2206
+ const result = await scheduler.run(() => discoverModulesGen(options));
2207
+ if (result.isErr()) {
2208
+ const error = result.error;
2209
+ return (0, neverthrow.err)(builderErrors.discoveryIOError("unknown", error.message));
2210
+ }
2211
+ return (0, neverthrow.ok)(result.value);
2047
2212
  };
2048
2213
 
2049
2214
  //#endregion
@@ -2425,17 +2590,10 @@ const createIntermediateRegistry = ({ analyses } = {}) => {
2425
2590
  }
2426
2591
  return result;
2427
2592
  };
2428
- const evaluate = () => {
2429
- const evaluated = new Map();
2430
- const inProgress = new Set();
2431
- for (const filePath of modules.keys()) {
2432
- if (!evaluated.has(filePath)) {
2433
- evaluateModule(filePath, evaluated, inProgress);
2434
- }
2435
- }
2436
- for (const element of elements.values()) {
2437
- __soda_gql_core.GqlElement.evaluate(element);
2438
- }
2593
+ /**
2594
+ * Build artifacts record from evaluated elements.
2595
+ */
2596
+ const buildArtifacts = () => {
2439
2597
  const artifacts = {};
2440
2598
  for (const [canonicalId, element] of elements.entries()) {
2441
2599
  if (element instanceof __soda_gql_core.Model) {
@@ -2462,6 +2620,76 @@ const createIntermediateRegistry = ({ analyses } = {}) => {
2462
2620
  }
2463
2621
  return artifacts;
2464
2622
  };
2623
+ /**
2624
+ * Generator that evaluates all elements using the effect system.
2625
+ * Uses ParallelEffect to enable parallel evaluation in async mode.
2626
+ * In sync mode, ParallelEffect executes effects sequentially.
2627
+ */
2628
+ function* evaluateElementsGen() {
2629
+ const effects = Array.from(elements.values(), (element) => new ElementEvaluationEffect(element));
2630
+ if (effects.length > 0) {
2631
+ yield* new __soda_gql_common.ParallelEffect(effects).run();
2632
+ }
2633
+ }
2634
+ /**
2635
+ * Synchronous evaluation - evaluates all modules and elements synchronously.
2636
+ * Throws if any element requires async operations (e.g., async metadata factory).
2637
+ */
2638
+ const evaluate = () => {
2639
+ const evaluated = new Map();
2640
+ const inProgress = new Set();
2641
+ for (const filePath of modules.keys()) {
2642
+ if (!evaluated.has(filePath)) {
2643
+ evaluateModule(filePath, evaluated, inProgress);
2644
+ }
2645
+ }
2646
+ const scheduler = (0, __soda_gql_common.createSyncScheduler)();
2647
+ const result = scheduler.run(() => evaluateElementsGen());
2648
+ if (result.isErr()) {
2649
+ throw new Error(`Element evaluation failed: ${result.error.message}`);
2650
+ }
2651
+ return buildArtifacts();
2652
+ };
2653
+ /**
2654
+ * Asynchronous evaluation - evaluates all modules and elements with async support.
2655
+ * Supports async metadata factories and other async operations.
2656
+ */
2657
+ const evaluateAsync = async () => {
2658
+ const evaluated = new Map();
2659
+ const inProgress = new Set();
2660
+ for (const filePath of modules.keys()) {
2661
+ if (!evaluated.has(filePath)) {
2662
+ evaluateModule(filePath, evaluated, inProgress);
2663
+ }
2664
+ }
2665
+ const scheduler = (0, __soda_gql_common.createAsyncScheduler)();
2666
+ const result = await scheduler.run(() => evaluateElementsGen());
2667
+ if (result.isErr()) {
2668
+ throw new Error(`Element evaluation failed: ${result.error.message}`);
2669
+ }
2670
+ return buildArtifacts();
2671
+ };
2672
+ /**
2673
+ * Evaluate all modules synchronously using trampoline.
2674
+ * This runs the module dependency resolution without element evaluation.
2675
+ * Call this before getElements() when using external scheduler control.
2676
+ */
2677
+ const evaluateModules = () => {
2678
+ const evaluated = new Map();
2679
+ const inProgress = new Set();
2680
+ for (const filePath of modules.keys()) {
2681
+ if (!evaluated.has(filePath)) {
2682
+ evaluateModule(filePath, evaluated, inProgress);
2683
+ }
2684
+ }
2685
+ };
2686
+ /**
2687
+ * Get all registered elements for external effect creation.
2688
+ * Call evaluateModules() first to ensure all modules have been evaluated.
2689
+ */
2690
+ const getElements = () => {
2691
+ return Array.from(elements.values());
2692
+ };
2465
2693
  const clear = () => {
2466
2694
  modules.clear();
2467
2695
  elements.clear();
@@ -2471,6 +2699,10 @@ const createIntermediateRegistry = ({ analyses } = {}) => {
2471
2699
  requestImport,
2472
2700
  addElement,
2473
2701
  evaluate,
2702
+ evaluateAsync,
2703
+ evaluateModules,
2704
+ getElements,
2705
+ buildArtifacts,
2474
2706
  clear
2475
2707
  };
2476
2708
  };
@@ -2604,7 +2836,11 @@ const generateIntermediateModules = function* ({ analyses, targetFiles, graphqlS
2604
2836
  };
2605
2837
  }
2606
2838
  };
2607
- const evaluateIntermediateModules = ({ intermediateModules, graphqlSystemPath, analyses }) => {
2839
+ /**
2840
+ * Set up VM context and run intermediate module scripts.
2841
+ * Returns the registry for evaluation.
2842
+ */
2843
+ const setupIntermediateModulesContext = ({ intermediateModules, graphqlSystemPath, analyses }) => {
2608
2844
  const registry = createIntermediateRegistry({ analyses });
2609
2845
  const gqlImportPath = resolveGraphqlSystemPath(graphqlSystemPath);
2610
2846
  const { gql } = executeGraphqlSystemModule(gqlImportPath);
@@ -2620,10 +2856,50 @@ const evaluateIntermediateModules = ({ intermediateModules, graphqlSystemPath, a
2620
2856
  throw error;
2621
2857
  }
2622
2858
  }
2859
+ return registry;
2860
+ };
2861
+ /**
2862
+ * Synchronous evaluation of intermediate modules.
2863
+ * Throws if any element requires async operations (e.g., async metadata factory).
2864
+ */
2865
+ const evaluateIntermediateModules = (input) => {
2866
+ const registry = setupIntermediateModulesContext(input);
2623
2867
  const elements = registry.evaluate();
2624
2868
  registry.clear();
2625
2869
  return elements;
2626
2870
  };
2871
+ /**
2872
+ * Asynchronous evaluation of intermediate modules.
2873
+ * Supports async metadata factories and other async operations.
2874
+ */
2875
+ const evaluateIntermediateModulesAsync = async (input) => {
2876
+ const registry = setupIntermediateModulesContext(input);
2877
+ const elements = await registry.evaluateAsync();
2878
+ registry.clear();
2879
+ return elements;
2880
+ };
2881
+ /**
2882
+ * Generator version of evaluateIntermediateModules for external scheduler control.
2883
+ * Yields effects for element evaluation, enabling unified scheduler at the root level.
2884
+ *
2885
+ * This function:
2886
+ * 1. Sets up the VM context and runs intermediate module scripts
2887
+ * 2. Runs synchronous module evaluation (trampoline - no I/O)
2888
+ * 3. Yields element evaluation effects via ParallelEffect
2889
+ * 4. Returns the artifacts record
2890
+ */
2891
+ function* evaluateIntermediateModulesGen(input) {
2892
+ const registry = setupIntermediateModulesContext(input);
2893
+ registry.evaluateModules();
2894
+ const elements = registry.getElements();
2895
+ const effects = elements.map((element) => new ElementEvaluationEffect(element));
2896
+ if (effects.length > 0) {
2897
+ yield* new __soda_gql_common.ParallelEffect(effects).run();
2898
+ }
2899
+ const artifacts = registry.buildArtifacts();
2900
+ registry.clear();
2901
+ return artifacts;
2902
+ }
2627
2903
 
2628
2904
  //#endregion
2629
2905
  //#region packages/builder/src/tracker/file-tracker.ts
@@ -2697,13 +2973,19 @@ const isEmptyDiff = (diff) => {
2697
2973
 
2698
2974
  //#endregion
2699
2975
  //#region packages/builder/src/session/dependency-validation.ts
2700
- const validateModuleDependencies = ({ analyses }) => {
2976
+ const validateModuleDependencies = ({ analyses, graphqlSystemHelper }) => {
2701
2977
  for (const analysis of analyses.values()) {
2702
2978
  for (const { source, isTypeOnly } of analysis.imports) {
2703
2979
  if (isTypeOnly) {
2704
2980
  continue;
2705
2981
  }
2706
2982
  if ((0, __soda_gql_common.isRelativeSpecifier)(source)) {
2983
+ if (graphqlSystemHelper.isGraphqlSystemImportSpecifier({
2984
+ filePath: analysis.filePath,
2985
+ specifier: source
2986
+ })) {
2987
+ continue;
2988
+ }
2707
2989
  const resolvedModule = (0, __soda_gql_common.resolveRelativeImportWithReferences)({
2708
2990
  filePath: analysis.filePath,
2709
2991
  specifier: source,
@@ -2839,8 +3121,11 @@ const createBuilderSession = (options) => {
2839
3121
  evaluatorId
2840
3122
  }));
2841
3123
  const ensureFileTracker = (0, __soda_gql_common.cachedFn)(() => createFileTracker());
2842
- const build = (options$1) => {
2843
- const force = options$1?.force ?? false;
3124
+ /**
3125
+ * Prepare build input. Shared between sync and async builds.
3126
+ * Returns either a skip result or the input for buildGen.
3127
+ */
3128
+ const prepareBuildInput = (force) => {
2844
3129
  const entryPathsResult = resolveEntryPaths(Array.from(entrypoints));
2845
3130
  if (entryPathsResult.isErr()) {
2846
3131
  return (0, neverthrow.err)(entryPathsResult.error);
@@ -2864,44 +3149,106 @@ const createBuilderSession = (options) => {
2864
3149
  return (0, neverthrow.err)(prepareResult.error);
2865
3150
  }
2866
3151
  if (prepareResult.value.type === "should-skip") {
2867
- return (0, neverthrow.ok)(prepareResult.value.data.artifact);
3152
+ return (0, neverthrow.ok)({
3153
+ type: "skip",
3154
+ artifact: prepareResult.value.data.artifact
3155
+ });
2868
3156
  }
2869
3157
  const { changedFiles, removedFiles } = prepareResult.value.data;
2870
- const discoveryCache = ensureDiscoveryCache();
2871
- const astAnalyzer = ensureAstAnalyzer();
2872
- const discoveryResult = discover({
2873
- discoveryCache,
2874
- astAnalyzer,
2875
- removedFiles,
2876
- changedFiles,
2877
- entryPaths,
2878
- previousModuleAdjacency: state.moduleAdjacency
3158
+ return (0, neverthrow.ok)({
3159
+ type: "build",
3160
+ input: {
3161
+ entryPaths,
3162
+ astAnalyzer: ensureAstAnalyzer(),
3163
+ discoveryCache: ensureDiscoveryCache(),
3164
+ changedFiles,
3165
+ removedFiles,
3166
+ previousModuleAdjacency: state.moduleAdjacency,
3167
+ previousIntermediateModules: state.intermediateModules,
3168
+ graphqlSystemPath: (0, node_path.resolve)(config.outdir, "index.ts"),
3169
+ graphqlHelper
3170
+ },
3171
+ currentScan
2879
3172
  });
2880
- if (discoveryResult.isErr()) {
2881
- return (0, neverthrow.err)(discoveryResult.error);
2882
- }
2883
- const { snapshots, analyses, currentModuleAdjacency, affectedFiles, stats } = discoveryResult.value;
2884
- const buildResult = buildDiscovered({
3173
+ };
3174
+ /**
3175
+ * Finalize build and update session state.
3176
+ */
3177
+ const finalizeBuild = (genResult, currentScan) => {
3178
+ const { snapshots, analyses, currentModuleAdjacency, intermediateModules, elements, stats } = genResult;
3179
+ const artifactResult = buildArtifact({
2885
3180
  analyses,
2886
- affectedFiles,
2887
- stats,
2888
- previousIntermediateModules: state.intermediateModules,
2889
- graphqlSystemPath: (0, node_path.resolve)(config.outdir, "index.ts")
3181
+ elements,
3182
+ stats
2890
3183
  });
2891
- if (buildResult.isErr()) {
2892
- return (0, neverthrow.err)(buildResult.error);
3184
+ if (artifactResult.isErr()) {
3185
+ return (0, neverthrow.err)(artifactResult.error);
2893
3186
  }
2894
- const { intermediateModules, artifact } = buildResult.value;
2895
3187
  state.gen++;
2896
3188
  state.snapshots = snapshots;
2897
3189
  state.moduleAdjacency = currentModuleAdjacency;
2898
- state.lastArtifact = artifact;
3190
+ state.lastArtifact = artifactResult.value;
2899
3191
  state.intermediateModules = intermediateModules;
2900
- tracker.update(currentScan);
2901
- return (0, neverthrow.ok)(artifact);
3192
+ ensureFileTracker().update(currentScan);
3193
+ return (0, neverthrow.ok)(artifactResult.value);
3194
+ };
3195
+ /**
3196
+ * Synchronous build using SyncScheduler.
3197
+ * Throws if any element requires async operations.
3198
+ */
3199
+ const build = (options$1) => {
3200
+ const prepResult = prepareBuildInput(options$1?.force ?? false);
3201
+ if (prepResult.isErr()) {
3202
+ return (0, neverthrow.err)(prepResult.error);
3203
+ }
3204
+ if (prepResult.value.type === "skip") {
3205
+ return (0, neverthrow.ok)(prepResult.value.artifact);
3206
+ }
3207
+ const { input, currentScan } = prepResult.value;
3208
+ const scheduler = (0, __soda_gql_common.createSyncScheduler)();
3209
+ try {
3210
+ const result = scheduler.run(() => buildGen(input));
3211
+ if (result.isErr()) {
3212
+ return (0, neverthrow.err)(convertSchedulerError(result.error));
3213
+ }
3214
+ return finalizeBuild(result.value, currentScan);
3215
+ } catch (error) {
3216
+ if (error && typeof error === "object" && "code" in error) {
3217
+ return (0, neverthrow.err)(error);
3218
+ }
3219
+ throw error;
3220
+ }
3221
+ };
3222
+ /**
3223
+ * Asynchronous build using AsyncScheduler.
3224
+ * Supports async metadata factories and parallel element evaluation.
3225
+ */
3226
+ const buildAsync = async (options$1) => {
3227
+ const prepResult = prepareBuildInput(options$1?.force ?? false);
3228
+ if (prepResult.isErr()) {
3229
+ return (0, neverthrow.err)(prepResult.error);
3230
+ }
3231
+ if (prepResult.value.type === "skip") {
3232
+ return (0, neverthrow.ok)(prepResult.value.artifact);
3233
+ }
3234
+ const { input, currentScan } = prepResult.value;
3235
+ const scheduler = (0, __soda_gql_common.createAsyncScheduler)();
3236
+ try {
3237
+ const result = await scheduler.run(() => buildGen(input));
3238
+ if (result.isErr()) {
3239
+ return (0, neverthrow.err)(convertSchedulerError(result.error));
3240
+ }
3241
+ return finalizeBuild(result.value, currentScan);
3242
+ } catch (error) {
3243
+ if (error && typeof error === "object" && "code" in error) {
3244
+ return (0, neverthrow.err)(error);
3245
+ }
3246
+ throw error;
3247
+ }
2902
3248
  };
2903
3249
  return {
2904
3250
  build,
3251
+ buildAsync,
2905
3252
  getGeneration: () => state.gen,
2906
3253
  getCurrentArtifact: () => state.lastArtifact,
2907
3254
  dispose: () => {
@@ -2927,13 +3274,18 @@ const prepare = (input) => {
2927
3274
  }
2928
3275
  });
2929
3276
  };
2930
- const discover = ({ discoveryCache, astAnalyzer, removedFiles, changedFiles, entryPaths, previousModuleAdjacency }) => {
3277
+ /**
3278
+ * Unified build generator that yields effects for file I/O and element evaluation.
3279
+ * This enables single scheduler control at the root level for both sync and async execution.
3280
+ */
3281
+ function* buildGen(input) {
3282
+ const { entryPaths, astAnalyzer, discoveryCache, changedFiles, removedFiles, previousModuleAdjacency, previousIntermediateModules, graphqlSystemPath, graphqlHelper } = input;
2931
3283
  const affectedFiles = collectAffectedFiles({
2932
3284
  changedFiles,
2933
3285
  removedFiles,
2934
3286
  previousModuleAdjacency
2935
3287
  });
2936
- const discoveryResult = discoverModules({
3288
+ const discoveryResult = yield* discoverModulesGen({
2937
3289
  entryPaths,
2938
3290
  astAnalyzer,
2939
3291
  incremental: {
@@ -2943,16 +3295,16 @@ const discover = ({ discoveryCache, astAnalyzer, removedFiles, changedFiles, ent
2943
3295
  affectedFiles
2944
3296
  }
2945
3297
  });
2946
- if (discoveryResult.isErr()) {
2947
- return (0, neverthrow.err)(discoveryResult.error);
2948
- }
2949
- const { cacheHits, cacheMisses, cacheSkips } = discoveryResult.value;
2950
- const snapshots = new Map(discoveryResult.value.snapshots.map((snapshot) => [snapshot.normalizedFilePath, snapshot]));
2951
- const analyses = new Map(discoveryResult.value.snapshots.map((snapshot) => [snapshot.normalizedFilePath, snapshot.analysis]));
2952
- const dependenciesValidationResult = validateModuleDependencies({ analyses });
3298
+ const { cacheHits, cacheMisses, cacheSkips } = discoveryResult;
3299
+ const snapshots = new Map(discoveryResult.snapshots.map((snapshot) => [snapshot.normalizedFilePath, snapshot]));
3300
+ const analyses = new Map(discoveryResult.snapshots.map((snapshot) => [snapshot.normalizedFilePath, snapshot.analysis]));
3301
+ const dependenciesValidationResult = validateModuleDependencies({
3302
+ analyses,
3303
+ graphqlSystemHelper: graphqlHelper
3304
+ });
2953
3305
  if (dependenciesValidationResult.isErr()) {
2954
3306
  const error = dependenciesValidationResult.error;
2955
- return (0, neverthrow.err)(builderErrors.graphMissingImport(error.chain[0], error.chain[1]));
3307
+ throw builderErrors.graphMissingImport(error.chain[0], error.chain[1]);
2956
3308
  }
2957
3309
  const currentModuleAdjacency = extractModuleAdjacency({ snapshots });
2958
3310
  const stats = {
@@ -2960,15 +3312,6 @@ const discover = ({ discoveryCache, astAnalyzer, removedFiles, changedFiles, ent
2960
3312
  misses: cacheMisses,
2961
3313
  skips: cacheSkips
2962
3314
  };
2963
- return (0, neverthrow.ok)({
2964
- snapshots,
2965
- analyses,
2966
- currentModuleAdjacency,
2967
- affectedFiles,
2968
- stats
2969
- });
2970
- };
2971
- const buildDiscovered = ({ analyses, affectedFiles, stats, previousIntermediateModules, graphqlSystemPath }) => {
2972
3315
  const intermediateModules = new Map(previousIntermediateModules);
2973
3316
  const targetFiles = new Set(affectedFiles);
2974
3317
  for (const filePath of analyses.keys()) {
@@ -2991,23 +3334,29 @@ const buildDiscovered = ({ analyses, affectedFiles, stats, previousIntermediateM
2991
3334
  })) {
2992
3335
  intermediateModules.set(intermediateModule.filePath, intermediateModule);
2993
3336
  }
2994
- const elements = evaluateIntermediateModules({
3337
+ const elements = yield* evaluateIntermediateModulesGen({
2995
3338
  intermediateModules,
2996
3339
  graphqlSystemPath,
2997
3340
  analyses
2998
3341
  });
2999
- const artifactResult = buildArtifact({
3342
+ return {
3343
+ snapshots,
3000
3344
  analyses,
3345
+ currentModuleAdjacency,
3346
+ intermediateModules,
3001
3347
  elements,
3002
3348
  stats
3003
- });
3004
- if (artifactResult.isErr()) {
3005
- return (0, neverthrow.err)(artifactResult.error);
3349
+ };
3350
+ }
3351
+ /**
3352
+ * Convert scheduler error to builder error.
3353
+ * If the cause is already a BuilderError, return it directly to preserve error codes.
3354
+ */
3355
+ const convertSchedulerError = (error) => {
3356
+ if (error.cause && typeof error.cause === "object" && "code" in error.cause) {
3357
+ return error.cause;
3006
3358
  }
3007
- return (0, neverthrow.ok)({
3008
- intermediateModules,
3009
- artifact: artifactResult.value
3010
- });
3359
+ return builderErrors.internalInvariant(error.message, "scheduler", error.cause);
3011
3360
  };
3012
3361
 
3013
3362
  //#endregion
@@ -3032,6 +3381,7 @@ const createBuilderService = ({ config, entrypointsOverride }) => {
3032
3381
  });
3033
3382
  return {
3034
3383
  build: (options) => session.build(options),
3384
+ buildAsync: (options) => session.buildAsync(options),
3035
3385
  getGeneration: () => session.getGeneration(),
3036
3386
  getCurrentArtifact: () => session.getCurrentArtifact(),
3037
3387
  dispose: () => session.dispose()
@@ -3040,12 +3390,16 @@ const createBuilderService = ({ config, entrypointsOverride }) => {
3040
3390
 
3041
3391
  //#endregion
3042
3392
  exports.BuilderArtifactSchema = BuilderArtifactSchema;
3393
+ exports.BuilderEffects = BuilderEffects;
3394
+ exports.FileReadEffect = FileReadEffect;
3395
+ exports.FileStatEffect = FileStatEffect;
3043
3396
  Object.defineProperty(exports, 'buildAstPath', {
3044
3397
  enumerable: true,
3045
3398
  get: function () {
3046
3399
  return __soda_gql_common.buildAstPath;
3047
3400
  }
3048
3401
  });
3402
+ exports.collectAffectedFiles = collectAffectedFiles;
3049
3403
  exports.createBuilderService = createBuilderService;
3050
3404
  exports.createBuilderSession = createBuilderSession;
3051
3405
  Object.defineProperty(exports, 'createCanonicalId', {
@@ -3072,4 +3426,5 @@ Object.defineProperty(exports, 'createPathTracker', {
3072
3426
  get: function () {
3073
3427
  return __soda_gql_common.createPathTracker;
3074
3428
  }
3075
- });
3429
+ });
3430
+ exports.extractModuleAdjacency = extractModuleAdjacency;