@soda-gql/builder 0.0.8 → 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/README.md +125 -0
- package/dist/index.cjs +569 -214
- package/dist/index.d.cts +229 -54
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +228 -53
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +565 -215
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -5
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
|
-
|
|
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
|
-
*
|
|
963
|
-
|
|
964
|
-
|
|
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
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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
|
-
*
|
|
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
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
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
|
|
1397
|
-
if (!
|
|
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
|
-
|
|
1426
|
-
|
|
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
|
-
*
|
|
1943
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
1982
|
-
|
|
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
|
-
|
|
2131
|
+
shouldReadFile = false;
|
|
1994
2132
|
}
|
|
1995
|
-
}
|
|
2133
|
+
}
|
|
1996
2134
|
}
|
|
1997
2135
|
}
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
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
|
|
2022
|
-
|
|
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
|
|
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
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2843
|
-
|
|
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)(
|
|
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
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
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
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
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
|
-
|
|
2887
|
-
stats
|
|
2888
|
-
previousIntermediateModules: state.intermediateModules,
|
|
2889
|
-
graphqlSystemPath: (0, node_path.resolve)(config.outdir, "index.ts")
|
|
3181
|
+
elements,
|
|
3182
|
+
stats
|
|
2890
3183
|
});
|
|
2891
|
-
if (
|
|
2892
|
-
return (0, neverthrow.err)(
|
|
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 =
|
|
3190
|
+
state.lastArtifact = artifactResult.value;
|
|
2899
3191
|
state.intermediateModules = intermediateModules;
|
|
2900
|
-
|
|
2901
|
-
return (0, neverthrow.ok)(
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
const
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
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
|
-
|
|
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 =
|
|
3337
|
+
const elements = yield* evaluateIntermediateModulesGen({
|
|
2995
3338
|
intermediateModules,
|
|
2996
3339
|
graphqlSystemPath,
|
|
2997
3340
|
analyses
|
|
2998
3341
|
});
|
|
2999
|
-
|
|
3342
|
+
return {
|
|
3343
|
+
snapshots,
|
|
3000
3344
|
analyses,
|
|
3345
|
+
currentModuleAdjacency,
|
|
3346
|
+
intermediateModules,
|
|
3001
3347
|
elements,
|
|
3002
3348
|
stats
|
|
3003
|
-
}
|
|
3004
|
-
|
|
3005
|
-
|
|
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 (
|
|
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;
|