@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.mjs
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { CanonicalIdSchema, buildAstPath, cachedFn, createCanonicalId, createCanonicalId as createCanonicalId$1, createCanonicalTracker, createCanonicalTracker as createCanonicalTracker$1, createOccurrenceTracker, createPathTracker, getPortableHasher, isExternalSpecifier, isRelativeSpecifier, normalizePath, resolveRelativeImportWithExistenceCheck, resolveRelativeImportWithReferences } from "@soda-gql/common";
|
|
1
|
+
import { CanonicalIdSchema, Effect, Effects, ParallelEffect, buildAstPath, cachedFn, createAsyncScheduler, createCanonicalId, createCanonicalId as createCanonicalId$1, createCanonicalTracker, createCanonicalTracker as createCanonicalTracker$1, createOccurrenceTracker, createPathTracker, createSyncScheduler, getPortableHasher, isExternalSpecifier, isRelativeSpecifier, normalizePath, resolveRelativeImportWithExistenceCheck, resolveRelativeImportWithReferences } from "@soda-gql/common";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { dirname, extname, join, normalize, resolve } from "node:path";
|
|
4
|
+
import { readFile, stat } from "node:fs/promises";
|
|
5
|
+
import * as sandboxCore from "@soda-gql/core";
|
|
6
|
+
import { ComposedOperation, GqlElement, InlineOperation, Model, Slice } from "@soda-gql/core";
|
|
3
7
|
import { z } from "zod";
|
|
4
8
|
import { err, ok } from "neverthrow";
|
|
5
9
|
import { createHash } from "node:crypto";
|
|
6
10
|
import { parseSync, transformSync } from "@swc/core";
|
|
7
11
|
import ts from "typescript";
|
|
8
|
-
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
9
12
|
import fg from "fast-glob";
|
|
10
13
|
import { Script, createContext } from "node:vm";
|
|
11
|
-
import * as sandboxCore from "@soda-gql/core";
|
|
12
|
-
import { ComposedOperation, GqlElement, InlineOperation, Model, Slice } from "@soda-gql/core";
|
|
13
14
|
import * as sandboxRuntime from "@soda-gql/runtime";
|
|
14
15
|
|
|
15
16
|
//#region packages/builder/src/internal/graphql-system.ts
|
|
@@ -40,7 +41,11 @@ const createGraphqlSystemIdentifyHelper = (config) => {
|
|
|
40
41
|
const getCanonicalFileName = createGetCanonicalFileName(getUseCaseSensitiveFileNames());
|
|
41
42
|
const toCanonical = (file) => {
|
|
42
43
|
const resolved = resolve(file);
|
|
43
|
-
|
|
44
|
+
try {
|
|
45
|
+
return getCanonicalFileName(realpathSync(resolved));
|
|
46
|
+
} catch {
|
|
47
|
+
return getCanonicalFileName(resolved);
|
|
48
|
+
}
|
|
44
49
|
};
|
|
45
50
|
const graphqlSystemPath = resolve(config.outdir, "index.ts");
|
|
46
51
|
const canonicalGraphqlSystemPath = toCanonical(graphqlSystemPath);
|
|
@@ -68,6 +73,168 @@ const createGraphqlSystemIdentifyHelper = (config) => {
|
|
|
68
73
|
};
|
|
69
74
|
};
|
|
70
75
|
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region packages/builder/src/scheduler/effects.ts
|
|
78
|
+
/**
|
|
79
|
+
* File read effect - reads a file from the filesystem.
|
|
80
|
+
* Works in both sync and async schedulers.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* const content = yield* new FileReadEffect("/path/to/file").run();
|
|
84
|
+
*/
|
|
85
|
+
var FileReadEffect = class extends Effect {
|
|
86
|
+
constructor(path) {
|
|
87
|
+
super();
|
|
88
|
+
this.path = path;
|
|
89
|
+
}
|
|
90
|
+
_executeSync() {
|
|
91
|
+
return readFileSync(this.path, "utf-8");
|
|
92
|
+
}
|
|
93
|
+
_executeAsync() {
|
|
94
|
+
return readFile(this.path, "utf-8");
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* File stat effect - gets file stats from the filesystem.
|
|
99
|
+
* Works in both sync and async schedulers.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* const stats = yield* new FileStatEffect("/path/to/file").run();
|
|
103
|
+
*/
|
|
104
|
+
var FileStatEffect = class extends Effect {
|
|
105
|
+
constructor(path) {
|
|
106
|
+
super();
|
|
107
|
+
this.path = path;
|
|
108
|
+
}
|
|
109
|
+
_executeSync() {
|
|
110
|
+
const stats = statSync(this.path);
|
|
111
|
+
return {
|
|
112
|
+
mtimeMs: stats.mtimeMs,
|
|
113
|
+
size: stats.size,
|
|
114
|
+
isFile: stats.isFile()
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
async _executeAsync() {
|
|
118
|
+
const stats = await stat(this.path);
|
|
119
|
+
return {
|
|
120
|
+
mtimeMs: stats.mtimeMs,
|
|
121
|
+
size: stats.size,
|
|
122
|
+
isFile: stats.isFile()
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* File read effect that returns null if file doesn't exist.
|
|
128
|
+
* Useful for discovery where missing files are expected.
|
|
129
|
+
*/
|
|
130
|
+
var OptionalFileReadEffect = class extends Effect {
|
|
131
|
+
constructor(path) {
|
|
132
|
+
super();
|
|
133
|
+
this.path = path;
|
|
134
|
+
}
|
|
135
|
+
_executeSync() {
|
|
136
|
+
try {
|
|
137
|
+
return readFileSync(this.path, "utf-8");
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (error.code === "ENOENT") {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async _executeAsync() {
|
|
146
|
+
try {
|
|
147
|
+
return await readFile(this.path, "utf-8");
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if (error.code === "ENOENT") {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* File stat effect that returns null if file doesn't exist.
|
|
158
|
+
* Useful for discovery where missing files are expected.
|
|
159
|
+
*/
|
|
160
|
+
var OptionalFileStatEffect = class extends Effect {
|
|
161
|
+
constructor(path) {
|
|
162
|
+
super();
|
|
163
|
+
this.path = path;
|
|
164
|
+
}
|
|
165
|
+
_executeSync() {
|
|
166
|
+
try {
|
|
167
|
+
const stats = statSync(this.path);
|
|
168
|
+
return {
|
|
169
|
+
mtimeMs: stats.mtimeMs,
|
|
170
|
+
size: stats.size,
|
|
171
|
+
isFile: stats.isFile()
|
|
172
|
+
};
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (error.code === "ENOENT") {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async _executeAsync() {
|
|
181
|
+
try {
|
|
182
|
+
const stats = await stat(this.path);
|
|
183
|
+
return {
|
|
184
|
+
mtimeMs: stats.mtimeMs,
|
|
185
|
+
size: stats.size,
|
|
186
|
+
isFile: stats.isFile()
|
|
187
|
+
};
|
|
188
|
+
} catch (error) {
|
|
189
|
+
if (error.code === "ENOENT") {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Element evaluation effect - evaluates a GqlElement using its generator.
|
|
198
|
+
* Supports both sync and async schedulers, enabling parallel element evaluation
|
|
199
|
+
* when using async scheduler.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* yield* new ElementEvaluationEffect(element).run();
|
|
203
|
+
*/
|
|
204
|
+
var ElementEvaluationEffect = class extends Effect {
|
|
205
|
+
constructor(element) {
|
|
206
|
+
super();
|
|
207
|
+
this.element = element;
|
|
208
|
+
}
|
|
209
|
+
_executeSync() {
|
|
210
|
+
const generator = GqlElement.createEvaluationGenerator(this.element);
|
|
211
|
+
const result = generator.next();
|
|
212
|
+
while (!result.done) {
|
|
213
|
+
throw new Error("Async operation required during sync element evaluation");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async _executeAsync() {
|
|
217
|
+
const generator = GqlElement.createEvaluationGenerator(this.element);
|
|
218
|
+
let result = generator.next();
|
|
219
|
+
while (!result.done) {
|
|
220
|
+
await result.value;
|
|
221
|
+
result = generator.next();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
/**
|
|
226
|
+
* Builder effect constructors.
|
|
227
|
+
* Extends the base Effects with file I/O operations and element evaluation.
|
|
228
|
+
*/
|
|
229
|
+
const BuilderEffects = {
|
|
230
|
+
...Effects,
|
|
231
|
+
readFile: (path) => new FileReadEffect(path),
|
|
232
|
+
stat: (path) => new FileStatEffect(path),
|
|
233
|
+
readFileOptional: (path) => new OptionalFileReadEffect(path),
|
|
234
|
+
statOptional: (path) => new OptionalFileStatEffect(path),
|
|
235
|
+
evaluateElement: (element) => new ElementEvaluationEffect(element)
|
|
236
|
+
};
|
|
237
|
+
|
|
71
238
|
//#endregion
|
|
72
239
|
//#region packages/builder/src/schemas/artifact.ts
|
|
73
240
|
const BuilderArtifactElementMetadataSchema = z.object({
|
|
@@ -189,7 +356,8 @@ const aggregate = ({ analyses, elements }) => {
|
|
|
189
356
|
operationName: element.element.operationName,
|
|
190
357
|
document: element.element.document,
|
|
191
358
|
variableNames: element.element.variableNames,
|
|
192
|
-
projectionPathGraph: element.element.projectionPathGraph
|
|
359
|
+
projectionPathGraph: element.element.projectionPathGraph,
|
|
360
|
+
metadata: element.element.metadata
|
|
193
361
|
};
|
|
194
362
|
registry.set(definition.canonicalId, {
|
|
195
363
|
id: definition.canonicalId,
|
|
@@ -207,7 +375,8 @@ const aggregate = ({ analyses, elements }) => {
|
|
|
207
375
|
operationType: element.element.operationType,
|
|
208
376
|
operationName: element.element.operationName,
|
|
209
377
|
document: element.element.document,
|
|
210
|
-
variableNames: element.element.variableNames
|
|
378
|
+
variableNames: element.element.variableNames,
|
|
379
|
+
metadata: element.element.metadata
|
|
211
380
|
};
|
|
212
381
|
registry.set(definition.canonicalId, {
|
|
213
382
|
id: definition.canonicalId,
|
|
@@ -929,58 +1098,41 @@ const collectAllDefinitions$1 = ({ module, gqlIdentifiers, imports: _imports, ex
|
|
|
929
1098
|
};
|
|
930
1099
|
};
|
|
931
1100
|
/**
|
|
932
|
-
*
|
|
1101
|
+
* SWC adapter implementation.
|
|
1102
|
+
* The analyze method parses and collects all data in one pass,
|
|
1103
|
+
* ensuring the AST (Module) is released after analysis.
|
|
933
1104
|
*/
|
|
934
|
-
const
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
tsx: input.filePath.endsWith(".tsx"),
|
|
945
|
-
target: "es2022",
|
|
946
|
-
decorators: false,
|
|
947
|
-
dynamicImport: true
|
|
948
|
-
});
|
|
949
|
-
if (program.type !== "Module") {
|
|
950
|
-
return null;
|
|
951
|
-
}
|
|
952
|
-
const swcModule = program;
|
|
953
|
-
swcModule.__filePath = input.filePath;
|
|
954
|
-
return swcModule;
|
|
955
|
-
},
|
|
956
|
-
collectGqlIdentifiers(file, helper) {
|
|
957
|
-
return collectGqlIdentifiers(file, helper);
|
|
958
|
-
},
|
|
959
|
-
collectImports(file) {
|
|
960
|
-
return collectImports$1(file);
|
|
961
|
-
},
|
|
962
|
-
collectExports(file) {
|
|
963
|
-
return collectExports$1(file);
|
|
964
|
-
},
|
|
965
|
-
collectDefinitions(file, context) {
|
|
966
|
-
const resolvePosition = toPositionResolver(context.source);
|
|
967
|
-
const { definitions, handledCalls } = collectAllDefinitions$1({
|
|
968
|
-
module: file,
|
|
969
|
-
gqlIdentifiers: context.gqlIdentifiers,
|
|
970
|
-
imports: context.imports,
|
|
971
|
-
exports: context.exports,
|
|
972
|
-
resolvePosition,
|
|
973
|
-
source: context.source
|
|
974
|
-
});
|
|
975
|
-
return {
|
|
976
|
-
definitions,
|
|
977
|
-
handles: handledCalls
|
|
978
|
-
};
|
|
979
|
-
},
|
|
980
|
-
collectDiagnostics(_file, _context) {
|
|
981
|
-
return collectDiagnostics$1();
|
|
1105
|
+
const swcAdapter = { analyze(input, helper) {
|
|
1106
|
+
const program = parseSync(input.source, {
|
|
1107
|
+
syntax: "typescript",
|
|
1108
|
+
tsx: input.filePath.endsWith(".tsx"),
|
|
1109
|
+
target: "es2022",
|
|
1110
|
+
decorators: false,
|
|
1111
|
+
dynamicImport: true
|
|
1112
|
+
});
|
|
1113
|
+
if (program.type !== "Module") {
|
|
1114
|
+
return null;
|
|
982
1115
|
}
|
|
983
|
-
|
|
1116
|
+
const swcModule = program;
|
|
1117
|
+
swcModule.__filePath = input.filePath;
|
|
1118
|
+
const gqlIdentifiers = collectGqlIdentifiers(swcModule, helper);
|
|
1119
|
+
const imports = collectImports$1(swcModule);
|
|
1120
|
+
const exports = collectExports$1(swcModule);
|
|
1121
|
+
const resolvePosition = toPositionResolver(input.source);
|
|
1122
|
+
const { definitions } = collectAllDefinitions$1({
|
|
1123
|
+
module: swcModule,
|
|
1124
|
+
gqlIdentifiers,
|
|
1125
|
+
imports,
|
|
1126
|
+
exports,
|
|
1127
|
+
resolvePosition,
|
|
1128
|
+
source: input.source
|
|
1129
|
+
});
|
|
1130
|
+
return {
|
|
1131
|
+
imports,
|
|
1132
|
+
exports,
|
|
1133
|
+
definitions
|
|
1134
|
+
};
|
|
1135
|
+
} };
|
|
984
1136
|
|
|
985
1137
|
//#endregion
|
|
986
1138
|
//#region packages/builder/src/ast/adapters/typescript.ts
|
|
@@ -1313,42 +1465,26 @@ const collectAllDefinitions = ({ sourceFile, identifiers, exports }) => {
|
|
|
1313
1465
|
};
|
|
1314
1466
|
};
|
|
1315
1467
|
/**
|
|
1316
|
-
*
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
return [];
|
|
1320
|
-
};
|
|
1321
|
-
/**
|
|
1322
|
-
* TypeScript adapter implementation
|
|
1468
|
+
* TypeScript adapter implementation.
|
|
1469
|
+
* The analyze method parses and collects all data in one pass,
|
|
1470
|
+
* ensuring the AST (ts.SourceFile) is released after analysis.
|
|
1323
1471
|
*/
|
|
1324
|
-
const typescriptAdapter = {
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
identifiers: context.gqlIdentifiers,
|
|
1341
|
-
exports: context.exports
|
|
1342
|
-
});
|
|
1343
|
-
return {
|
|
1344
|
-
definitions,
|
|
1345
|
-
handles: handledCalls
|
|
1346
|
-
};
|
|
1347
|
-
},
|
|
1348
|
-
collectDiagnostics(file, context) {
|
|
1349
|
-
return collectDiagnostics(file, context.gqlIdentifiers, context.handledCalls);
|
|
1350
|
-
}
|
|
1351
|
-
};
|
|
1472
|
+
const typescriptAdapter = { analyze(input, helper) {
|
|
1473
|
+
const sourceFile = createSourceFile(input.filePath, input.source);
|
|
1474
|
+
const gqlIdentifiers = collectGqlImports(sourceFile, helper);
|
|
1475
|
+
const imports = collectImports(sourceFile);
|
|
1476
|
+
const exports = collectExports(sourceFile);
|
|
1477
|
+
const { definitions } = collectAllDefinitions({
|
|
1478
|
+
sourceFile,
|
|
1479
|
+
identifiers: gqlIdentifiers,
|
|
1480
|
+
exports
|
|
1481
|
+
});
|
|
1482
|
+
return {
|
|
1483
|
+
imports,
|
|
1484
|
+
exports,
|
|
1485
|
+
definitions
|
|
1486
|
+
};
|
|
1487
|
+
} };
|
|
1352
1488
|
|
|
1353
1489
|
//#endregion
|
|
1354
1490
|
//#region packages/builder/src/ast/core.ts
|
|
@@ -1363,38 +1499,22 @@ const typescriptAdapter = {
|
|
|
1363
1499
|
const analyzeModuleCore = (input, adapter, graphqlHelper) => {
|
|
1364
1500
|
const hasher = getPortableHasher();
|
|
1365
1501
|
const signature = hasher.hash(input.source, "xxhash");
|
|
1366
|
-
const
|
|
1367
|
-
if (!
|
|
1502
|
+
const result = adapter.analyze(input, graphqlHelper);
|
|
1503
|
+
if (!result) {
|
|
1368
1504
|
return {
|
|
1369
1505
|
filePath: input.filePath,
|
|
1370
1506
|
signature,
|
|
1371
1507
|
definitions: [],
|
|
1372
|
-
diagnostics: [],
|
|
1373
1508
|
imports: [],
|
|
1374
1509
|
exports: []
|
|
1375
1510
|
};
|
|
1376
1511
|
}
|
|
1377
|
-
const gqlIdentifiers = adapter.collectGqlIdentifiers(file, graphqlHelper);
|
|
1378
|
-
const imports = adapter.collectImports(file);
|
|
1379
|
-
const exports = adapter.collectExports(file);
|
|
1380
|
-
const { definitions, handles } = adapter.collectDefinitions(file, {
|
|
1381
|
-
gqlIdentifiers,
|
|
1382
|
-
imports,
|
|
1383
|
-
exports,
|
|
1384
|
-
source: input.source
|
|
1385
|
-
});
|
|
1386
|
-
const diagnostics = adapter.collectDiagnostics(file, {
|
|
1387
|
-
gqlIdentifiers,
|
|
1388
|
-
handledCalls: handles,
|
|
1389
|
-
source: input.source
|
|
1390
|
-
});
|
|
1391
1512
|
return {
|
|
1392
1513
|
filePath: input.filePath,
|
|
1393
1514
|
signature,
|
|
1394
|
-
definitions,
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
exports
|
|
1515
|
+
definitions: result.definitions,
|
|
1516
|
+
imports: result.imports,
|
|
1517
|
+
exports: result.exports
|
|
1398
1518
|
};
|
|
1399
1519
|
};
|
|
1400
1520
|
|
|
@@ -1655,11 +1775,6 @@ const ModuleDefinitionSchema = z.object({
|
|
|
1655
1775
|
loc: SourceLocationSchema,
|
|
1656
1776
|
expression: z.string()
|
|
1657
1777
|
});
|
|
1658
|
-
const ModuleDiagnosticSchema = z.object({
|
|
1659
|
-
code: z.literal("NON_TOP_LEVEL_DEFINITION"),
|
|
1660
|
-
message: z.string(),
|
|
1661
|
-
loc: SourceLocationSchema
|
|
1662
|
-
});
|
|
1663
1778
|
const ModuleImportSchema = z.object({
|
|
1664
1779
|
source: z.string(),
|
|
1665
1780
|
imported: z.string(),
|
|
@@ -1688,7 +1803,6 @@ const ModuleAnalysisSchema = z.object({
|
|
|
1688
1803
|
filePath: z.string(),
|
|
1689
1804
|
signature: z.string(),
|
|
1690
1805
|
definitions: z.array(ModuleDefinitionSchema).readonly(),
|
|
1691
|
-
diagnostics: z.array(ModuleDiagnosticSchema).readonly(),
|
|
1692
1806
|
imports: z.array(ModuleImportSchema).readonly(),
|
|
1693
1807
|
exports: z.array(ModuleExportSchema).readonly()
|
|
1694
1808
|
});
|
|
@@ -1892,6 +2006,30 @@ function simpleHash(buffer) {
|
|
|
1892
2006
|
return hash.toString(16);
|
|
1893
2007
|
}
|
|
1894
2008
|
/**
|
|
2009
|
+
* Compute fingerprint from pre-read file content and stats.
|
|
2010
|
+
* This is used by the generator-based discoverer which already has the content.
|
|
2011
|
+
*
|
|
2012
|
+
* @param path - Absolute path to file (for caching)
|
|
2013
|
+
* @param stats - File stats (mtimeMs, size)
|
|
2014
|
+
* @param content - File content as string
|
|
2015
|
+
* @returns FileFingerprint
|
|
2016
|
+
*/
|
|
2017
|
+
function computeFingerprintFromContent(path, stats, content) {
|
|
2018
|
+
const cached = fingerprintCache.get(path);
|
|
2019
|
+
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
2020
|
+
return cached;
|
|
2021
|
+
}
|
|
2022
|
+
const buffer = Buffer.from(content, "utf-8");
|
|
2023
|
+
const hash = computeHashSync(buffer);
|
|
2024
|
+
const fingerprint = {
|
|
2025
|
+
hash,
|
|
2026
|
+
sizeBytes: stats.size,
|
|
2027
|
+
mtimeMs: stats.mtimeMs
|
|
2028
|
+
};
|
|
2029
|
+
fingerprintCache.set(path, fingerprint);
|
|
2030
|
+
return fingerprint;
|
|
2031
|
+
}
|
|
2032
|
+
/**
|
|
1895
2033
|
* Invalidate cached fingerprint for a specific path
|
|
1896
2034
|
*
|
|
1897
2035
|
* @param path - Absolute path to invalidate
|
|
@@ -1909,11 +2047,10 @@ function clearFingerprintCache() {
|
|
|
1909
2047
|
//#endregion
|
|
1910
2048
|
//#region packages/builder/src/discovery/discoverer.ts
|
|
1911
2049
|
/**
|
|
1912
|
-
*
|
|
1913
|
-
*
|
|
1914
|
-
* Supports caching with fingerprint-based invalidation to skip re-parsing unchanged files.
|
|
2050
|
+
* Generator-based module discovery that yields effects for file I/O.
|
|
2051
|
+
* This allows the discovery process to be executed with either sync or async schedulers.
|
|
1915
2052
|
*/
|
|
1916
|
-
|
|
2053
|
+
function* discoverModulesGen({ entryPaths, astAnalyzer, incremental }) {
|
|
1917
2054
|
const snapshots = new Map();
|
|
1918
2055
|
const stack = [...entryPaths];
|
|
1919
2056
|
const changedFiles = incremental?.changedFiles ?? new Set();
|
|
@@ -1942,16 +2079,17 @@ const discoverModules = ({ entryPaths, astAnalyzer, incremental }) => {
|
|
|
1942
2079
|
if (snapshots.has(filePath)) {
|
|
1943
2080
|
continue;
|
|
1944
2081
|
}
|
|
2082
|
+
let shouldReadFile = true;
|
|
1945
2083
|
if (invalidatedSet.has(filePath)) {
|
|
1946
2084
|
invalidateFingerprint(filePath);
|
|
1947
2085
|
cacheSkips++;
|
|
1948
2086
|
} else if (incremental) {
|
|
1949
2087
|
const cached = incremental.cache.peek(filePath);
|
|
1950
2088
|
if (cached) {
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
const mtimeMs = stats.mtimeMs;
|
|
1954
|
-
const sizeBytes = stats.size;
|
|
2089
|
+
const stats$1 = yield* new OptionalFileStatEffect(filePath).run();
|
|
2090
|
+
if (stats$1) {
|
|
2091
|
+
const mtimeMs = stats$1.mtimeMs;
|
|
2092
|
+
const sizeBytes = stats$1.size;
|
|
1955
2093
|
if (cached.fingerprint.mtimeMs === mtimeMs && cached.fingerprint.sizeBytes === sizeBytes) {
|
|
1956
2094
|
snapshots.set(filePath, cached);
|
|
1957
2095
|
cacheHits++;
|
|
@@ -1960,21 +2098,19 @@ const discoverModules = ({ entryPaths, astAnalyzer, incremental }) => {
|
|
|
1960
2098
|
stack.push(dep.resolvedPath);
|
|
1961
2099
|
}
|
|
1962
2100
|
}
|
|
1963
|
-
|
|
2101
|
+
shouldReadFile = false;
|
|
1964
2102
|
}
|
|
1965
|
-
}
|
|
2103
|
+
}
|
|
1966
2104
|
}
|
|
1967
2105
|
}
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
}
|
|
1977
|
-
return err(builderErrors.discoveryIOError(filePath, error instanceof Error ? error.message : String(error)));
|
|
2106
|
+
if (!shouldReadFile) {
|
|
2107
|
+
continue;
|
|
2108
|
+
}
|
|
2109
|
+
const source = yield* new OptionalFileReadEffect(filePath).run();
|
|
2110
|
+
if (source === null) {
|
|
2111
|
+
incremental?.cache.delete(filePath);
|
|
2112
|
+
invalidateFingerprint(filePath);
|
|
2113
|
+
continue;
|
|
1978
2114
|
}
|
|
1979
2115
|
const signature = createSourceHash(source);
|
|
1980
2116
|
const analysis = astAnalyzer.analyze({
|
|
@@ -1988,11 +2124,8 @@ const discoverModules = ({ entryPaths, astAnalyzer, incremental }) => {
|
|
|
1988
2124
|
stack.push(dep.resolvedPath);
|
|
1989
2125
|
}
|
|
1990
2126
|
}
|
|
1991
|
-
const
|
|
1992
|
-
|
|
1993
|
-
return err(builderErrors.discoveryIOError(filePath, `Failed to compute fingerprint: ${fingerprintResult.error.message}`));
|
|
1994
|
-
}
|
|
1995
|
-
const fingerprint = fingerprintResult.value;
|
|
2127
|
+
const stats = yield* new OptionalFileStatEffect(filePath).run();
|
|
2128
|
+
const fingerprint = computeFingerprintFromContent(filePath, stats, source);
|
|
1996
2129
|
const snapshot = {
|
|
1997
2130
|
filePath,
|
|
1998
2131
|
normalizedFilePath: normalizePath(filePath),
|
|
@@ -2008,12 +2141,44 @@ const discoverModules = ({ entryPaths, astAnalyzer, incremental }) => {
|
|
|
2008
2141
|
incremental.cache.store(snapshot);
|
|
2009
2142
|
}
|
|
2010
2143
|
}
|
|
2011
|
-
return
|
|
2144
|
+
return {
|
|
2012
2145
|
snapshots: Array.from(snapshots.values()),
|
|
2013
2146
|
cacheHits,
|
|
2014
2147
|
cacheMisses,
|
|
2015
2148
|
cacheSkips
|
|
2016
|
-
}
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
/**
|
|
2152
|
+
* Discover and analyze all modules starting from entry points.
|
|
2153
|
+
* Uses AST parsing instead of RegExp for reliable dependency extraction.
|
|
2154
|
+
* Supports caching with fingerprint-based invalidation to skip re-parsing unchanged files.
|
|
2155
|
+
*
|
|
2156
|
+
* This function uses the synchronous scheduler internally for backward compatibility.
|
|
2157
|
+
* For async execution with parallel file I/O, use discoverModulesGen with an async scheduler.
|
|
2158
|
+
*/
|
|
2159
|
+
const discoverModules = (options) => {
|
|
2160
|
+
const scheduler = createSyncScheduler();
|
|
2161
|
+
const result = scheduler.run(() => discoverModulesGen(options));
|
|
2162
|
+
if (result.isErr()) {
|
|
2163
|
+
const error = result.error;
|
|
2164
|
+
return err(builderErrors.discoveryIOError("unknown", error.message));
|
|
2165
|
+
}
|
|
2166
|
+
return ok(result.value);
|
|
2167
|
+
};
|
|
2168
|
+
/**
|
|
2169
|
+
* Asynchronous version of discoverModules.
|
|
2170
|
+
* Uses async scheduler for parallel file I/O operations.
|
|
2171
|
+
*
|
|
2172
|
+
* This is useful for large codebases where parallel file operations can improve performance.
|
|
2173
|
+
*/
|
|
2174
|
+
const discoverModulesAsync = async (options) => {
|
|
2175
|
+
const scheduler = createAsyncScheduler();
|
|
2176
|
+
const result = await scheduler.run(() => discoverModulesGen(options));
|
|
2177
|
+
if (result.isErr()) {
|
|
2178
|
+
const error = result.error;
|
|
2179
|
+
return err(builderErrors.discoveryIOError("unknown", error.message));
|
|
2180
|
+
}
|
|
2181
|
+
return ok(result.value);
|
|
2017
2182
|
};
|
|
2018
2183
|
|
|
2019
2184
|
//#endregion
|
|
@@ -2395,17 +2560,10 @@ const createIntermediateRegistry = ({ analyses } = {}) => {
|
|
|
2395
2560
|
}
|
|
2396
2561
|
return result;
|
|
2397
2562
|
};
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
if (!evaluated.has(filePath)) {
|
|
2403
|
-
evaluateModule(filePath, evaluated, inProgress);
|
|
2404
|
-
}
|
|
2405
|
-
}
|
|
2406
|
-
for (const element of elements.values()) {
|
|
2407
|
-
GqlElement.evaluate(element);
|
|
2408
|
-
}
|
|
2563
|
+
/**
|
|
2564
|
+
* Build artifacts record from evaluated elements.
|
|
2565
|
+
*/
|
|
2566
|
+
const buildArtifacts = () => {
|
|
2409
2567
|
const artifacts = {};
|
|
2410
2568
|
for (const [canonicalId, element] of elements.entries()) {
|
|
2411
2569
|
if (element instanceof Model) {
|
|
@@ -2432,6 +2590,76 @@ const createIntermediateRegistry = ({ analyses } = {}) => {
|
|
|
2432
2590
|
}
|
|
2433
2591
|
return artifacts;
|
|
2434
2592
|
};
|
|
2593
|
+
/**
|
|
2594
|
+
* Generator that evaluates all elements using the effect system.
|
|
2595
|
+
* Uses ParallelEffect to enable parallel evaluation in async mode.
|
|
2596
|
+
* In sync mode, ParallelEffect executes effects sequentially.
|
|
2597
|
+
*/
|
|
2598
|
+
function* evaluateElementsGen() {
|
|
2599
|
+
const effects = Array.from(elements.values(), (element) => new ElementEvaluationEffect(element));
|
|
2600
|
+
if (effects.length > 0) {
|
|
2601
|
+
yield* new ParallelEffect(effects).run();
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
/**
|
|
2605
|
+
* Synchronous evaluation - evaluates all modules and elements synchronously.
|
|
2606
|
+
* Throws if any element requires async operations (e.g., async metadata factory).
|
|
2607
|
+
*/
|
|
2608
|
+
const evaluate = () => {
|
|
2609
|
+
const evaluated = new Map();
|
|
2610
|
+
const inProgress = new Set();
|
|
2611
|
+
for (const filePath of modules.keys()) {
|
|
2612
|
+
if (!evaluated.has(filePath)) {
|
|
2613
|
+
evaluateModule(filePath, evaluated, inProgress);
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
const scheduler = createSyncScheduler();
|
|
2617
|
+
const result = scheduler.run(() => evaluateElementsGen());
|
|
2618
|
+
if (result.isErr()) {
|
|
2619
|
+
throw new Error(`Element evaluation failed: ${result.error.message}`);
|
|
2620
|
+
}
|
|
2621
|
+
return buildArtifacts();
|
|
2622
|
+
};
|
|
2623
|
+
/**
|
|
2624
|
+
* Asynchronous evaluation - evaluates all modules and elements with async support.
|
|
2625
|
+
* Supports async metadata factories and other async operations.
|
|
2626
|
+
*/
|
|
2627
|
+
const evaluateAsync = async () => {
|
|
2628
|
+
const evaluated = new Map();
|
|
2629
|
+
const inProgress = new Set();
|
|
2630
|
+
for (const filePath of modules.keys()) {
|
|
2631
|
+
if (!evaluated.has(filePath)) {
|
|
2632
|
+
evaluateModule(filePath, evaluated, inProgress);
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
const scheduler = createAsyncScheduler();
|
|
2636
|
+
const result = await scheduler.run(() => evaluateElementsGen());
|
|
2637
|
+
if (result.isErr()) {
|
|
2638
|
+
throw new Error(`Element evaluation failed: ${result.error.message}`);
|
|
2639
|
+
}
|
|
2640
|
+
return buildArtifacts();
|
|
2641
|
+
};
|
|
2642
|
+
/**
|
|
2643
|
+
* Evaluate all modules synchronously using trampoline.
|
|
2644
|
+
* This runs the module dependency resolution without element evaluation.
|
|
2645
|
+
* Call this before getElements() when using external scheduler control.
|
|
2646
|
+
*/
|
|
2647
|
+
const evaluateModules = () => {
|
|
2648
|
+
const evaluated = new Map();
|
|
2649
|
+
const inProgress = new Set();
|
|
2650
|
+
for (const filePath of modules.keys()) {
|
|
2651
|
+
if (!evaluated.has(filePath)) {
|
|
2652
|
+
evaluateModule(filePath, evaluated, inProgress);
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
};
|
|
2656
|
+
/**
|
|
2657
|
+
* Get all registered elements for external effect creation.
|
|
2658
|
+
* Call evaluateModules() first to ensure all modules have been evaluated.
|
|
2659
|
+
*/
|
|
2660
|
+
const getElements = () => {
|
|
2661
|
+
return Array.from(elements.values());
|
|
2662
|
+
};
|
|
2435
2663
|
const clear = () => {
|
|
2436
2664
|
modules.clear();
|
|
2437
2665
|
elements.clear();
|
|
@@ -2441,6 +2669,10 @@ const createIntermediateRegistry = ({ analyses } = {}) => {
|
|
|
2441
2669
|
requestImport,
|
|
2442
2670
|
addElement,
|
|
2443
2671
|
evaluate,
|
|
2672
|
+
evaluateAsync,
|
|
2673
|
+
evaluateModules,
|
|
2674
|
+
getElements,
|
|
2675
|
+
buildArtifacts,
|
|
2444
2676
|
clear
|
|
2445
2677
|
};
|
|
2446
2678
|
};
|
|
@@ -2574,7 +2806,11 @@ const generateIntermediateModules = function* ({ analyses, targetFiles, graphqlS
|
|
|
2574
2806
|
};
|
|
2575
2807
|
}
|
|
2576
2808
|
};
|
|
2577
|
-
|
|
2809
|
+
/**
|
|
2810
|
+
* Set up VM context and run intermediate module scripts.
|
|
2811
|
+
* Returns the registry for evaluation.
|
|
2812
|
+
*/
|
|
2813
|
+
const setupIntermediateModulesContext = ({ intermediateModules, graphqlSystemPath, analyses }) => {
|
|
2578
2814
|
const registry = createIntermediateRegistry({ analyses });
|
|
2579
2815
|
const gqlImportPath = resolveGraphqlSystemPath(graphqlSystemPath);
|
|
2580
2816
|
const { gql } = executeGraphqlSystemModule(gqlImportPath);
|
|
@@ -2590,10 +2826,50 @@ const evaluateIntermediateModules = ({ intermediateModules, graphqlSystemPath, a
|
|
|
2590
2826
|
throw error;
|
|
2591
2827
|
}
|
|
2592
2828
|
}
|
|
2829
|
+
return registry;
|
|
2830
|
+
};
|
|
2831
|
+
/**
|
|
2832
|
+
* Synchronous evaluation of intermediate modules.
|
|
2833
|
+
* Throws if any element requires async operations (e.g., async metadata factory).
|
|
2834
|
+
*/
|
|
2835
|
+
const evaluateIntermediateModules = (input) => {
|
|
2836
|
+
const registry = setupIntermediateModulesContext(input);
|
|
2593
2837
|
const elements = registry.evaluate();
|
|
2594
2838
|
registry.clear();
|
|
2595
2839
|
return elements;
|
|
2596
2840
|
};
|
|
2841
|
+
/**
|
|
2842
|
+
* Asynchronous evaluation of intermediate modules.
|
|
2843
|
+
* Supports async metadata factories and other async operations.
|
|
2844
|
+
*/
|
|
2845
|
+
const evaluateIntermediateModulesAsync = async (input) => {
|
|
2846
|
+
const registry = setupIntermediateModulesContext(input);
|
|
2847
|
+
const elements = await registry.evaluateAsync();
|
|
2848
|
+
registry.clear();
|
|
2849
|
+
return elements;
|
|
2850
|
+
};
|
|
2851
|
+
/**
|
|
2852
|
+
* Generator version of evaluateIntermediateModules for external scheduler control.
|
|
2853
|
+
* Yields effects for element evaluation, enabling unified scheduler at the root level.
|
|
2854
|
+
*
|
|
2855
|
+
* This function:
|
|
2856
|
+
* 1. Sets up the VM context and runs intermediate module scripts
|
|
2857
|
+
* 2. Runs synchronous module evaluation (trampoline - no I/O)
|
|
2858
|
+
* 3. Yields element evaluation effects via ParallelEffect
|
|
2859
|
+
* 4. Returns the artifacts record
|
|
2860
|
+
*/
|
|
2861
|
+
function* evaluateIntermediateModulesGen(input) {
|
|
2862
|
+
const registry = setupIntermediateModulesContext(input);
|
|
2863
|
+
registry.evaluateModules();
|
|
2864
|
+
const elements = registry.getElements();
|
|
2865
|
+
const effects = elements.map((element) => new ElementEvaluationEffect(element));
|
|
2866
|
+
if (effects.length > 0) {
|
|
2867
|
+
yield* new ParallelEffect(effects).run();
|
|
2868
|
+
}
|
|
2869
|
+
const artifacts = registry.buildArtifacts();
|
|
2870
|
+
registry.clear();
|
|
2871
|
+
return artifacts;
|
|
2872
|
+
}
|
|
2597
2873
|
|
|
2598
2874
|
//#endregion
|
|
2599
2875
|
//#region packages/builder/src/tracker/file-tracker.ts
|
|
@@ -2667,13 +2943,19 @@ const isEmptyDiff = (diff) => {
|
|
|
2667
2943
|
|
|
2668
2944
|
//#endregion
|
|
2669
2945
|
//#region packages/builder/src/session/dependency-validation.ts
|
|
2670
|
-
const validateModuleDependencies = ({ analyses }) => {
|
|
2946
|
+
const validateModuleDependencies = ({ analyses, graphqlSystemHelper }) => {
|
|
2671
2947
|
for (const analysis of analyses.values()) {
|
|
2672
2948
|
for (const { source, isTypeOnly } of analysis.imports) {
|
|
2673
2949
|
if (isTypeOnly) {
|
|
2674
2950
|
continue;
|
|
2675
2951
|
}
|
|
2676
2952
|
if (isRelativeSpecifier(source)) {
|
|
2953
|
+
if (graphqlSystemHelper.isGraphqlSystemImportSpecifier({
|
|
2954
|
+
filePath: analysis.filePath,
|
|
2955
|
+
specifier: source
|
|
2956
|
+
})) {
|
|
2957
|
+
continue;
|
|
2958
|
+
}
|
|
2677
2959
|
const resolvedModule = resolveRelativeImportWithReferences({
|
|
2678
2960
|
filePath: analysis.filePath,
|
|
2679
2961
|
specifier: source,
|
|
@@ -2809,8 +3091,11 @@ const createBuilderSession = (options) => {
|
|
|
2809
3091
|
evaluatorId
|
|
2810
3092
|
}));
|
|
2811
3093
|
const ensureFileTracker = cachedFn(() => createFileTracker());
|
|
2812
|
-
|
|
2813
|
-
|
|
3094
|
+
/**
|
|
3095
|
+
* Prepare build input. Shared between sync and async builds.
|
|
3096
|
+
* Returns either a skip result or the input for buildGen.
|
|
3097
|
+
*/
|
|
3098
|
+
const prepareBuildInput = (force) => {
|
|
2814
3099
|
const entryPathsResult = resolveEntryPaths(Array.from(entrypoints));
|
|
2815
3100
|
if (entryPathsResult.isErr()) {
|
|
2816
3101
|
return err(entryPathsResult.error);
|
|
@@ -2834,44 +3119,106 @@ const createBuilderSession = (options) => {
|
|
|
2834
3119
|
return err(prepareResult.error);
|
|
2835
3120
|
}
|
|
2836
3121
|
if (prepareResult.value.type === "should-skip") {
|
|
2837
|
-
return ok(
|
|
3122
|
+
return ok({
|
|
3123
|
+
type: "skip",
|
|
3124
|
+
artifact: prepareResult.value.data.artifact
|
|
3125
|
+
});
|
|
2838
3126
|
}
|
|
2839
3127
|
const { changedFiles, removedFiles } = prepareResult.value.data;
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
3128
|
+
return ok({
|
|
3129
|
+
type: "build",
|
|
3130
|
+
input: {
|
|
3131
|
+
entryPaths,
|
|
3132
|
+
astAnalyzer: ensureAstAnalyzer(),
|
|
3133
|
+
discoveryCache: ensureDiscoveryCache(),
|
|
3134
|
+
changedFiles,
|
|
3135
|
+
removedFiles,
|
|
3136
|
+
previousModuleAdjacency: state.moduleAdjacency,
|
|
3137
|
+
previousIntermediateModules: state.intermediateModules,
|
|
3138
|
+
graphqlSystemPath: resolve(config.outdir, "index.ts"),
|
|
3139
|
+
graphqlHelper
|
|
3140
|
+
},
|
|
3141
|
+
currentScan
|
|
2849
3142
|
});
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
3143
|
+
};
|
|
3144
|
+
/**
|
|
3145
|
+
* Finalize build and update session state.
|
|
3146
|
+
*/
|
|
3147
|
+
const finalizeBuild = (genResult, currentScan) => {
|
|
3148
|
+
const { snapshots, analyses, currentModuleAdjacency, intermediateModules, elements, stats } = genResult;
|
|
3149
|
+
const artifactResult = buildArtifact({
|
|
2855
3150
|
analyses,
|
|
2856
|
-
|
|
2857
|
-
stats
|
|
2858
|
-
previousIntermediateModules: state.intermediateModules,
|
|
2859
|
-
graphqlSystemPath: resolve(config.outdir, "index.ts")
|
|
3151
|
+
elements,
|
|
3152
|
+
stats
|
|
2860
3153
|
});
|
|
2861
|
-
if (
|
|
2862
|
-
return err(
|
|
3154
|
+
if (artifactResult.isErr()) {
|
|
3155
|
+
return err(artifactResult.error);
|
|
2863
3156
|
}
|
|
2864
|
-
const { intermediateModules, artifact } = buildResult.value;
|
|
2865
3157
|
state.gen++;
|
|
2866
3158
|
state.snapshots = snapshots;
|
|
2867
3159
|
state.moduleAdjacency = currentModuleAdjacency;
|
|
2868
|
-
state.lastArtifact =
|
|
3160
|
+
state.lastArtifact = artifactResult.value;
|
|
2869
3161
|
state.intermediateModules = intermediateModules;
|
|
2870
|
-
|
|
2871
|
-
return ok(
|
|
3162
|
+
ensureFileTracker().update(currentScan);
|
|
3163
|
+
return ok(artifactResult.value);
|
|
3164
|
+
};
|
|
3165
|
+
/**
|
|
3166
|
+
* Synchronous build using SyncScheduler.
|
|
3167
|
+
* Throws if any element requires async operations.
|
|
3168
|
+
*/
|
|
3169
|
+
const build = (options$1) => {
|
|
3170
|
+
const prepResult = prepareBuildInput(options$1?.force ?? false);
|
|
3171
|
+
if (prepResult.isErr()) {
|
|
3172
|
+
return err(prepResult.error);
|
|
3173
|
+
}
|
|
3174
|
+
if (prepResult.value.type === "skip") {
|
|
3175
|
+
return ok(prepResult.value.artifact);
|
|
3176
|
+
}
|
|
3177
|
+
const { input, currentScan } = prepResult.value;
|
|
3178
|
+
const scheduler = createSyncScheduler();
|
|
3179
|
+
try {
|
|
3180
|
+
const result = scheduler.run(() => buildGen(input));
|
|
3181
|
+
if (result.isErr()) {
|
|
3182
|
+
return err(convertSchedulerError(result.error));
|
|
3183
|
+
}
|
|
3184
|
+
return finalizeBuild(result.value, currentScan);
|
|
3185
|
+
} catch (error) {
|
|
3186
|
+
if (error && typeof error === "object" && "code" in error) {
|
|
3187
|
+
return err(error);
|
|
3188
|
+
}
|
|
3189
|
+
throw error;
|
|
3190
|
+
}
|
|
3191
|
+
};
|
|
3192
|
+
/**
|
|
3193
|
+
* Asynchronous build using AsyncScheduler.
|
|
3194
|
+
* Supports async metadata factories and parallel element evaluation.
|
|
3195
|
+
*/
|
|
3196
|
+
const buildAsync = async (options$1) => {
|
|
3197
|
+
const prepResult = prepareBuildInput(options$1?.force ?? false);
|
|
3198
|
+
if (prepResult.isErr()) {
|
|
3199
|
+
return err(prepResult.error);
|
|
3200
|
+
}
|
|
3201
|
+
if (prepResult.value.type === "skip") {
|
|
3202
|
+
return ok(prepResult.value.artifact);
|
|
3203
|
+
}
|
|
3204
|
+
const { input, currentScan } = prepResult.value;
|
|
3205
|
+
const scheduler = createAsyncScheduler();
|
|
3206
|
+
try {
|
|
3207
|
+
const result = await scheduler.run(() => buildGen(input));
|
|
3208
|
+
if (result.isErr()) {
|
|
3209
|
+
return err(convertSchedulerError(result.error));
|
|
3210
|
+
}
|
|
3211
|
+
return finalizeBuild(result.value, currentScan);
|
|
3212
|
+
} catch (error) {
|
|
3213
|
+
if (error && typeof error === "object" && "code" in error) {
|
|
3214
|
+
return err(error);
|
|
3215
|
+
}
|
|
3216
|
+
throw error;
|
|
3217
|
+
}
|
|
2872
3218
|
};
|
|
2873
3219
|
return {
|
|
2874
3220
|
build,
|
|
3221
|
+
buildAsync,
|
|
2875
3222
|
getGeneration: () => state.gen,
|
|
2876
3223
|
getCurrentArtifact: () => state.lastArtifact,
|
|
2877
3224
|
dispose: () => {
|
|
@@ -2897,13 +3244,18 @@ const prepare = (input) => {
|
|
|
2897
3244
|
}
|
|
2898
3245
|
});
|
|
2899
3246
|
};
|
|
2900
|
-
|
|
3247
|
+
/**
|
|
3248
|
+
* Unified build generator that yields effects for file I/O and element evaluation.
|
|
3249
|
+
* This enables single scheduler control at the root level for both sync and async execution.
|
|
3250
|
+
*/
|
|
3251
|
+
function* buildGen(input) {
|
|
3252
|
+
const { entryPaths, astAnalyzer, discoveryCache, changedFiles, removedFiles, previousModuleAdjacency, previousIntermediateModules, graphqlSystemPath, graphqlHelper } = input;
|
|
2901
3253
|
const affectedFiles = collectAffectedFiles({
|
|
2902
3254
|
changedFiles,
|
|
2903
3255
|
removedFiles,
|
|
2904
3256
|
previousModuleAdjacency
|
|
2905
3257
|
});
|
|
2906
|
-
const discoveryResult =
|
|
3258
|
+
const discoveryResult = yield* discoverModulesGen({
|
|
2907
3259
|
entryPaths,
|
|
2908
3260
|
astAnalyzer,
|
|
2909
3261
|
incremental: {
|
|
@@ -2913,16 +3265,16 @@ const discover = ({ discoveryCache, astAnalyzer, removedFiles, changedFiles, ent
|
|
|
2913
3265
|
affectedFiles
|
|
2914
3266
|
}
|
|
2915
3267
|
});
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
const
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
3268
|
+
const { cacheHits, cacheMisses, cacheSkips } = discoveryResult;
|
|
3269
|
+
const snapshots = new Map(discoveryResult.snapshots.map((snapshot) => [snapshot.normalizedFilePath, snapshot]));
|
|
3270
|
+
const analyses = new Map(discoveryResult.snapshots.map((snapshot) => [snapshot.normalizedFilePath, snapshot.analysis]));
|
|
3271
|
+
const dependenciesValidationResult = validateModuleDependencies({
|
|
3272
|
+
analyses,
|
|
3273
|
+
graphqlSystemHelper: graphqlHelper
|
|
3274
|
+
});
|
|
2923
3275
|
if (dependenciesValidationResult.isErr()) {
|
|
2924
3276
|
const error = dependenciesValidationResult.error;
|
|
2925
|
-
|
|
3277
|
+
throw builderErrors.graphMissingImport(error.chain[0], error.chain[1]);
|
|
2926
3278
|
}
|
|
2927
3279
|
const currentModuleAdjacency = extractModuleAdjacency({ snapshots });
|
|
2928
3280
|
const stats = {
|
|
@@ -2930,15 +3282,6 @@ const discover = ({ discoveryCache, astAnalyzer, removedFiles, changedFiles, ent
|
|
|
2930
3282
|
misses: cacheMisses,
|
|
2931
3283
|
skips: cacheSkips
|
|
2932
3284
|
};
|
|
2933
|
-
return ok({
|
|
2934
|
-
snapshots,
|
|
2935
|
-
analyses,
|
|
2936
|
-
currentModuleAdjacency,
|
|
2937
|
-
affectedFiles,
|
|
2938
|
-
stats
|
|
2939
|
-
});
|
|
2940
|
-
};
|
|
2941
|
-
const buildDiscovered = ({ analyses, affectedFiles, stats, previousIntermediateModules, graphqlSystemPath }) => {
|
|
2942
3285
|
const intermediateModules = new Map(previousIntermediateModules);
|
|
2943
3286
|
const targetFiles = new Set(affectedFiles);
|
|
2944
3287
|
for (const filePath of analyses.keys()) {
|
|
@@ -2961,23 +3304,29 @@ const buildDiscovered = ({ analyses, affectedFiles, stats, previousIntermediateM
|
|
|
2961
3304
|
})) {
|
|
2962
3305
|
intermediateModules.set(intermediateModule.filePath, intermediateModule);
|
|
2963
3306
|
}
|
|
2964
|
-
const elements =
|
|
3307
|
+
const elements = yield* evaluateIntermediateModulesGen({
|
|
2965
3308
|
intermediateModules,
|
|
2966
3309
|
graphqlSystemPath,
|
|
2967
3310
|
analyses
|
|
2968
3311
|
});
|
|
2969
|
-
|
|
3312
|
+
return {
|
|
3313
|
+
snapshots,
|
|
2970
3314
|
analyses,
|
|
3315
|
+
currentModuleAdjacency,
|
|
3316
|
+
intermediateModules,
|
|
2971
3317
|
elements,
|
|
2972
3318
|
stats
|
|
2973
|
-
}
|
|
2974
|
-
|
|
2975
|
-
|
|
3319
|
+
};
|
|
3320
|
+
}
|
|
3321
|
+
/**
|
|
3322
|
+
* Convert scheduler error to builder error.
|
|
3323
|
+
* If the cause is already a BuilderError, return it directly to preserve error codes.
|
|
3324
|
+
*/
|
|
3325
|
+
const convertSchedulerError = (error) => {
|
|
3326
|
+
if (error.cause && typeof error.cause === "object" && "code" in error.cause) {
|
|
3327
|
+
return error.cause;
|
|
2976
3328
|
}
|
|
2977
|
-
return
|
|
2978
|
-
intermediateModules,
|
|
2979
|
-
artifact: artifactResult.value
|
|
2980
|
-
});
|
|
3329
|
+
return builderErrors.internalInvariant(error.message, "scheduler", error.cause);
|
|
2981
3330
|
};
|
|
2982
3331
|
|
|
2983
3332
|
//#endregion
|
|
@@ -3002,6 +3351,7 @@ const createBuilderService = ({ config, entrypointsOverride }) => {
|
|
|
3002
3351
|
});
|
|
3003
3352
|
return {
|
|
3004
3353
|
build: (options) => session.build(options),
|
|
3354
|
+
buildAsync: (options) => session.buildAsync(options),
|
|
3005
3355
|
getGeneration: () => session.getGeneration(),
|
|
3006
3356
|
getCurrentArtifact: () => session.getCurrentArtifact(),
|
|
3007
3357
|
dispose: () => session.dispose()
|
|
@@ -3009,5 +3359,5 @@ const createBuilderService = ({ config, entrypointsOverride }) => {
|
|
|
3009
3359
|
};
|
|
3010
3360
|
|
|
3011
3361
|
//#endregion
|
|
3012
|
-
export { BuilderArtifactSchema, buildAstPath, createBuilderService, createBuilderSession, createCanonicalId, createCanonicalTracker, createGraphqlSystemIdentifyHelper, createOccurrenceTracker, createPathTracker };
|
|
3362
|
+
export { BuilderArtifactSchema, BuilderEffects, FileReadEffect, FileStatEffect, buildAstPath, collectAffectedFiles, createBuilderService, createBuilderSession, createCanonicalId, createCanonicalTracker, createGraphqlSystemIdentifyHelper, createOccurrenceTracker, createPathTracker, extractModuleAdjacency };
|
|
3013
3363
|
//# sourceMappingURL=index.mjs.map
|