@soda-gql/builder 0.0.1
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/LICENSE +21 -0
- package/dist/index.cjs +2935 -0
- package/dist/index.d.cts +820 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +820 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2870 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2935 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
let __soda_gql_common = require("@soda-gql/common");
|
|
25
|
+
__soda_gql_common = __toESM(__soda_gql_common);
|
|
26
|
+
let zod = require("zod");
|
|
27
|
+
zod = __toESM(zod);
|
|
28
|
+
let node_path = require("node:path");
|
|
29
|
+
node_path = __toESM(node_path);
|
|
30
|
+
let neverthrow = require("neverthrow");
|
|
31
|
+
neverthrow = __toESM(neverthrow);
|
|
32
|
+
let node_crypto = require("node:crypto");
|
|
33
|
+
node_crypto = __toESM(node_crypto);
|
|
34
|
+
let __swc_core = require("@swc/core");
|
|
35
|
+
__swc_core = __toESM(__swc_core);
|
|
36
|
+
let typescript = require("typescript");
|
|
37
|
+
typescript = __toESM(typescript);
|
|
38
|
+
let node_fs = require("node:fs");
|
|
39
|
+
node_fs = __toESM(node_fs);
|
|
40
|
+
let fast_glob = require("fast-glob");
|
|
41
|
+
fast_glob = __toESM(fast_glob);
|
|
42
|
+
let node_vm = require("node:vm");
|
|
43
|
+
node_vm = __toESM(node_vm);
|
|
44
|
+
let __soda_gql_core = require("@soda-gql/core");
|
|
45
|
+
__soda_gql_core = __toESM(__soda_gql_core);
|
|
46
|
+
let __soda_gql_runtime = require("@soda-gql/runtime");
|
|
47
|
+
__soda_gql_runtime = __toESM(__soda_gql_runtime);
|
|
48
|
+
|
|
49
|
+
//#region packages/builder/src/schemas/artifact.ts
|
|
50
|
+
const BuilderArtifactElementMetadataSchema = zod.z.object({
|
|
51
|
+
sourcePath: zod.z.string(),
|
|
52
|
+
sourceHash: zod.z.string(),
|
|
53
|
+
contentHash: zod.z.string()
|
|
54
|
+
});
|
|
55
|
+
const BuilderArtifactOperationSchema = zod.z.object({
|
|
56
|
+
id: zod.z.string(),
|
|
57
|
+
type: zod.z.literal("operation"),
|
|
58
|
+
metadata: BuilderArtifactElementMetadataSchema,
|
|
59
|
+
prebuild: zod.z.object({
|
|
60
|
+
operationType: zod.z.enum([
|
|
61
|
+
"query",
|
|
62
|
+
"mutation",
|
|
63
|
+
"subscription"
|
|
64
|
+
]),
|
|
65
|
+
operationName: zod.z.string(),
|
|
66
|
+
document: zod.z.unknown(),
|
|
67
|
+
variableNames: zod.z.array(zod.z.string()),
|
|
68
|
+
projectionPathGraph: zod.z.unknown()
|
|
69
|
+
})
|
|
70
|
+
});
|
|
71
|
+
const BuilderArtifactSliceSchema = zod.z.object({
|
|
72
|
+
id: zod.z.string(),
|
|
73
|
+
type: zod.z.literal("slice"),
|
|
74
|
+
metadata: BuilderArtifactElementMetadataSchema,
|
|
75
|
+
prebuild: zod.z.object({ operationType: zod.z.enum([
|
|
76
|
+
"query",
|
|
77
|
+
"mutation",
|
|
78
|
+
"subscription"
|
|
79
|
+
]) })
|
|
80
|
+
});
|
|
81
|
+
const BuilderArtifactModelSchema = zod.z.object({
|
|
82
|
+
id: zod.z.string(),
|
|
83
|
+
type: zod.z.literal("model"),
|
|
84
|
+
metadata: BuilderArtifactElementMetadataSchema,
|
|
85
|
+
prebuild: zod.z.object({ typename: zod.z.string() })
|
|
86
|
+
});
|
|
87
|
+
const BuilderArtifactElementSchema = zod.z.discriminatedUnion("type", [
|
|
88
|
+
BuilderArtifactOperationSchema,
|
|
89
|
+
BuilderArtifactSliceSchema,
|
|
90
|
+
BuilderArtifactModelSchema
|
|
91
|
+
]);
|
|
92
|
+
const BuilderArtifactSchema = zod.z.object({
|
|
93
|
+
elements: zod.z.record(zod.z.string(), BuilderArtifactElementSchema),
|
|
94
|
+
report: zod.z.object({
|
|
95
|
+
durationMs: zod.z.number(),
|
|
96
|
+
warnings: zod.z.array(zod.z.string()),
|
|
97
|
+
stats: zod.z.object({
|
|
98
|
+
hits: zod.z.number(),
|
|
99
|
+
misses: zod.z.number(),
|
|
100
|
+
skips: zod.z.number()
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region packages/builder/src/artifact/aggregate.ts
|
|
107
|
+
const canonicalToFilePath$1 = (canonicalId) => canonicalId.split("::")[0] ?? canonicalId;
|
|
108
|
+
const computeContentHash = (prebuild) => {
|
|
109
|
+
const hash = (0, node_crypto.createHash)("sha1");
|
|
110
|
+
hash.update(JSON.stringify(prebuild));
|
|
111
|
+
return hash.digest("hex");
|
|
112
|
+
};
|
|
113
|
+
const emitRegistrationError = (definition, message) => ({
|
|
114
|
+
code: "RUNTIME_MODULE_LOAD_FAILED",
|
|
115
|
+
filePath: canonicalToFilePath$1(definition.canonicalId),
|
|
116
|
+
astPath: definition.astPath,
|
|
117
|
+
message
|
|
118
|
+
});
|
|
119
|
+
const aggregate = ({ analyses, elements }) => {
|
|
120
|
+
const registry = new Map();
|
|
121
|
+
for (const analysis of analyses.values()) {
|
|
122
|
+
for (const definition of analysis.definitions) {
|
|
123
|
+
const element = elements[definition.canonicalId];
|
|
124
|
+
if (!element) {
|
|
125
|
+
const availableIds = Object.keys(elements).join(", ");
|
|
126
|
+
const message = `ARTIFACT_NOT_FOUND_IN_RUNTIME_MODULE: ${definition.canonicalId}\nAvailable: ${availableIds}`;
|
|
127
|
+
return (0, neverthrow.err)(emitRegistrationError(definition, message));
|
|
128
|
+
}
|
|
129
|
+
if (registry.has(definition.canonicalId)) {
|
|
130
|
+
return (0, neverthrow.err)(emitRegistrationError(definition, `ARTIFACT_ALREADY_REGISTERED`));
|
|
131
|
+
}
|
|
132
|
+
const metadata = {
|
|
133
|
+
sourcePath: analysis.filePath ?? canonicalToFilePath$1(definition.canonicalId),
|
|
134
|
+
sourceHash: analysis.signature,
|
|
135
|
+
contentHash: ""
|
|
136
|
+
};
|
|
137
|
+
if (element.type === "model") {
|
|
138
|
+
const prebuild = { typename: element.element.typename };
|
|
139
|
+
registry.set(definition.canonicalId, {
|
|
140
|
+
id: definition.canonicalId,
|
|
141
|
+
type: "model",
|
|
142
|
+
prebuild,
|
|
143
|
+
metadata: {
|
|
144
|
+
...metadata,
|
|
145
|
+
contentHash: computeContentHash(prebuild)
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (element.type === "slice") {
|
|
151
|
+
const prebuild = { operationType: element.element.operationType };
|
|
152
|
+
registry.set(definition.canonicalId, {
|
|
153
|
+
id: definition.canonicalId,
|
|
154
|
+
type: "slice",
|
|
155
|
+
prebuild,
|
|
156
|
+
metadata: {
|
|
157
|
+
...metadata,
|
|
158
|
+
contentHash: computeContentHash(prebuild)
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (element.type === "operation") {
|
|
164
|
+
const prebuild = {
|
|
165
|
+
operationType: element.element.operationType,
|
|
166
|
+
operationName: element.element.operationName,
|
|
167
|
+
document: element.element.document,
|
|
168
|
+
variableNames: element.element.variableNames,
|
|
169
|
+
projectionPathGraph: element.element.projectionPathGraph
|
|
170
|
+
};
|
|
171
|
+
registry.set(definition.canonicalId, {
|
|
172
|
+
id: definition.canonicalId,
|
|
173
|
+
type: "operation",
|
|
174
|
+
prebuild,
|
|
175
|
+
metadata: {
|
|
176
|
+
...metadata,
|
|
177
|
+
contentHash: computeContentHash(prebuild)
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (element.type === "inlineOperation") {
|
|
183
|
+
const prebuild = {
|
|
184
|
+
operationType: element.element.operationType,
|
|
185
|
+
operationName: element.element.operationName,
|
|
186
|
+
document: element.element.document,
|
|
187
|
+
variableNames: element.element.variableNames
|
|
188
|
+
};
|
|
189
|
+
registry.set(definition.canonicalId, {
|
|
190
|
+
id: definition.canonicalId,
|
|
191
|
+
type: "inlineOperation",
|
|
192
|
+
prebuild,
|
|
193
|
+
metadata: {
|
|
194
|
+
...metadata,
|
|
195
|
+
contentHash: computeContentHash(prebuild)
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
return (0, neverthrow.err)(emitRegistrationError(definition, "UNKNOWN_ARTIFACT_KIND"));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return (0, neverthrow.ok)(registry);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
//#endregion
|
|
207
|
+
//#region packages/builder/src/artifact/issue-handler.ts
|
|
208
|
+
const canonicalToFilePath = (canonicalId) => canonicalId.split("::")[0] ?? canonicalId;
|
|
209
|
+
const checkIssues = ({ elements }) => {
|
|
210
|
+
const operationNames = new Set();
|
|
211
|
+
for (const [canonicalId, { type, element }] of Object.entries(elements)) {
|
|
212
|
+
if (type !== "operation") {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (operationNames.has(element.operationName)) {
|
|
216
|
+
const sources = [canonicalToFilePath(canonicalId)];
|
|
217
|
+
return (0, neverthrow.err)({
|
|
218
|
+
code: "DOC_DUPLICATE",
|
|
219
|
+
message: `Duplicate document name: ${element.operationName}`,
|
|
220
|
+
name: element.operationName,
|
|
221
|
+
sources
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
operationNames.add(element.operationName);
|
|
225
|
+
}
|
|
226
|
+
return (0, neverthrow.ok)([]);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
//#endregion
|
|
230
|
+
//#region packages/builder/src/artifact/builder.ts
|
|
231
|
+
const buildArtifact = ({ elements, analyses, stats: cache }) => {
|
|
232
|
+
const issuesResult = checkIssues({ elements });
|
|
233
|
+
if (issuesResult.isErr()) {
|
|
234
|
+
return (0, neverthrow.err)(issuesResult.error);
|
|
235
|
+
}
|
|
236
|
+
const warnings = issuesResult.value;
|
|
237
|
+
const aggregationResult = aggregate({
|
|
238
|
+
analyses,
|
|
239
|
+
elements
|
|
240
|
+
});
|
|
241
|
+
if (aggregationResult.isErr()) {
|
|
242
|
+
return (0, neverthrow.err)(aggregationResult.error);
|
|
243
|
+
}
|
|
244
|
+
return (0, neverthrow.ok)({
|
|
245
|
+
elements: Object.fromEntries(aggregationResult.value.entries()),
|
|
246
|
+
report: {
|
|
247
|
+
durationMs: 0,
|
|
248
|
+
warnings,
|
|
249
|
+
stats: cache
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region packages/builder/src/errors.ts
|
|
256
|
+
/**
|
|
257
|
+
* Error constructor helpers for concise error creation.
|
|
258
|
+
*/
|
|
259
|
+
const builderErrors = {
|
|
260
|
+
entryNotFound: (entry, message) => ({
|
|
261
|
+
code: "ENTRY_NOT_FOUND",
|
|
262
|
+
message: message ?? `Entry not found: ${entry}`,
|
|
263
|
+
entry
|
|
264
|
+
}),
|
|
265
|
+
configNotFound: (path, message) => ({
|
|
266
|
+
code: "CONFIG_NOT_FOUND",
|
|
267
|
+
message: message ?? `Config file not found: ${path}`,
|
|
268
|
+
path
|
|
269
|
+
}),
|
|
270
|
+
configInvalid: (path, message, cause) => ({
|
|
271
|
+
code: "CONFIG_INVALID",
|
|
272
|
+
message,
|
|
273
|
+
path,
|
|
274
|
+
cause
|
|
275
|
+
}),
|
|
276
|
+
discoveryIOError: (path, message, errno, cause) => ({
|
|
277
|
+
code: "DISCOVERY_IO_ERROR",
|
|
278
|
+
message,
|
|
279
|
+
path,
|
|
280
|
+
errno,
|
|
281
|
+
cause
|
|
282
|
+
}),
|
|
283
|
+
fingerprintFailed: (filePath, message, cause) => ({
|
|
284
|
+
code: "FINGERPRINT_FAILED",
|
|
285
|
+
message,
|
|
286
|
+
filePath,
|
|
287
|
+
cause
|
|
288
|
+
}),
|
|
289
|
+
unsupportedAnalyzer: (analyzer, message) => ({
|
|
290
|
+
code: "UNSUPPORTED_ANALYZER",
|
|
291
|
+
message: message ?? `Unsupported analyzer: ${analyzer}`,
|
|
292
|
+
analyzer
|
|
293
|
+
}),
|
|
294
|
+
canonicalPathInvalid: (path, reason) => ({
|
|
295
|
+
code: "CANONICAL_PATH_INVALID",
|
|
296
|
+
message: `Invalid canonical path: ${path}${reason ? ` (${reason})` : ""}`,
|
|
297
|
+
path,
|
|
298
|
+
reason
|
|
299
|
+
}),
|
|
300
|
+
canonicalScopeMismatch: (expected, actual) => ({
|
|
301
|
+
code: "CANONICAL_SCOPE_MISMATCH",
|
|
302
|
+
message: `Scope mismatch: expected ${expected}, got ${actual}`,
|
|
303
|
+
expected,
|
|
304
|
+
actual
|
|
305
|
+
}),
|
|
306
|
+
graphCircularDependency: (chain) => ({
|
|
307
|
+
code: "GRAPH_CIRCULAR_DEPENDENCY",
|
|
308
|
+
message: `Circular dependency detected: ${chain.join(" → ")}`,
|
|
309
|
+
chain
|
|
310
|
+
}),
|
|
311
|
+
graphMissingImport: (importer, importee) => ({
|
|
312
|
+
code: "GRAPH_MISSING_IMPORT",
|
|
313
|
+
message: `Missing import: "${importer}" imports "${importee}" but it's not in the graph`,
|
|
314
|
+
importer,
|
|
315
|
+
importee
|
|
316
|
+
}),
|
|
317
|
+
docDuplicate: (name, sources) => ({
|
|
318
|
+
code: "DOC_DUPLICATE",
|
|
319
|
+
message: `Duplicate document name: ${name} found in ${sources.length} files`,
|
|
320
|
+
name,
|
|
321
|
+
sources
|
|
322
|
+
}),
|
|
323
|
+
writeFailed: (outPath, message, cause) => ({
|
|
324
|
+
code: "WRITE_FAILED",
|
|
325
|
+
message,
|
|
326
|
+
outPath,
|
|
327
|
+
cause
|
|
328
|
+
}),
|
|
329
|
+
cacheCorrupted: (message, cachePath, cause) => ({
|
|
330
|
+
code: "CACHE_CORRUPTED",
|
|
331
|
+
message,
|
|
332
|
+
cachePath,
|
|
333
|
+
cause
|
|
334
|
+
}),
|
|
335
|
+
runtimeModuleLoadFailed: (filePath, astPath, message, cause) => ({
|
|
336
|
+
code: "RUNTIME_MODULE_LOAD_FAILED",
|
|
337
|
+
message,
|
|
338
|
+
filePath,
|
|
339
|
+
astPath,
|
|
340
|
+
cause
|
|
341
|
+
}),
|
|
342
|
+
artifactRegistrationFailed: (elementId, reason) => ({
|
|
343
|
+
code: "ARTIFACT_REGISTRATION_FAILED",
|
|
344
|
+
message: `Failed to register artifact element ${elementId}: ${reason}`,
|
|
345
|
+
elementId,
|
|
346
|
+
reason
|
|
347
|
+
}),
|
|
348
|
+
internalInvariant: (message, context, cause) => ({
|
|
349
|
+
code: "INTERNAL_INVARIANT",
|
|
350
|
+
message: `Internal invariant violated: ${message}`,
|
|
351
|
+
context,
|
|
352
|
+
cause
|
|
353
|
+
})
|
|
354
|
+
};
|
|
355
|
+
/**
|
|
356
|
+
* Convenience helper to create an err Result from BuilderError.
|
|
357
|
+
*/
|
|
358
|
+
const builderErr = (error) => (0, neverthrow.err)(error);
|
|
359
|
+
/**
|
|
360
|
+
* Type guard for BuilderError.
|
|
361
|
+
*/
|
|
362
|
+
const isBuilderError = (error) => {
|
|
363
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" && "message" in error && typeof error.message === "string";
|
|
364
|
+
};
|
|
365
|
+
/**
|
|
366
|
+
* Format BuilderError for console output (human-readable).
|
|
367
|
+
*/
|
|
368
|
+
const formatBuilderError = (error) => {
|
|
369
|
+
const lines = [];
|
|
370
|
+
lines.push(`Error [${error.code}]: ${error.message}`);
|
|
371
|
+
switch (error.code) {
|
|
372
|
+
case "ENTRY_NOT_FOUND":
|
|
373
|
+
lines.push(` Entry: ${error.entry}`);
|
|
374
|
+
break;
|
|
375
|
+
case "CONFIG_NOT_FOUND":
|
|
376
|
+
case "CONFIG_INVALID":
|
|
377
|
+
lines.push(` Path: ${error.path}`);
|
|
378
|
+
if (error.code === "CONFIG_INVALID" && error.cause) {
|
|
379
|
+
lines.push(` Cause: ${error.cause}`);
|
|
380
|
+
}
|
|
381
|
+
break;
|
|
382
|
+
case "DISCOVERY_IO_ERROR":
|
|
383
|
+
lines.push(` Path: ${error.path}`);
|
|
384
|
+
if (error.errno !== undefined) {
|
|
385
|
+
lines.push(` Errno: ${error.errno}`);
|
|
386
|
+
}
|
|
387
|
+
break;
|
|
388
|
+
case "FINGERPRINT_FAILED":
|
|
389
|
+
lines.push(` File: ${error.filePath}`);
|
|
390
|
+
break;
|
|
391
|
+
case "CANONICAL_PATH_INVALID":
|
|
392
|
+
lines.push(` Path: ${error.path}`);
|
|
393
|
+
if (error.reason) {
|
|
394
|
+
lines.push(` Reason: ${error.reason}`);
|
|
395
|
+
}
|
|
396
|
+
break;
|
|
397
|
+
case "CANONICAL_SCOPE_MISMATCH":
|
|
398
|
+
lines.push(` Expected: ${error.expected}`);
|
|
399
|
+
lines.push(` Actual: ${error.actual}`);
|
|
400
|
+
break;
|
|
401
|
+
case "GRAPH_CIRCULAR_DEPENDENCY":
|
|
402
|
+
lines.push(` Chain: ${error.chain.join(" → ")}`);
|
|
403
|
+
break;
|
|
404
|
+
case "GRAPH_MISSING_IMPORT":
|
|
405
|
+
lines.push(` Importer: ${error.importer}`);
|
|
406
|
+
lines.push(` Importee: ${error.importee}`);
|
|
407
|
+
break;
|
|
408
|
+
case "DOC_DUPLICATE":
|
|
409
|
+
lines.push(` Name: ${error.name}`);
|
|
410
|
+
lines.push(` Sources:\n ${error.sources.join("\n ")}`);
|
|
411
|
+
break;
|
|
412
|
+
case "WRITE_FAILED":
|
|
413
|
+
lines.push(` Output path: ${error.outPath}`);
|
|
414
|
+
break;
|
|
415
|
+
case "CACHE_CORRUPTED":
|
|
416
|
+
if (error.cachePath) {
|
|
417
|
+
lines.push(` Cache path: ${error.cachePath}`);
|
|
418
|
+
}
|
|
419
|
+
break;
|
|
420
|
+
case "RUNTIME_MODULE_LOAD_FAILED":
|
|
421
|
+
lines.push(` File: ${error.filePath}`);
|
|
422
|
+
lines.push(` AST path: ${error.astPath}`);
|
|
423
|
+
break;
|
|
424
|
+
case "ARTIFACT_REGISTRATION_FAILED":
|
|
425
|
+
lines.push(` Element ID: ${error.elementId}`);
|
|
426
|
+
lines.push(` Reason: ${error.reason}`);
|
|
427
|
+
break;
|
|
428
|
+
case "INTERNAL_INVARIANT":
|
|
429
|
+
if (error.context) {
|
|
430
|
+
lines.push(` Context: ${error.context}`);
|
|
431
|
+
}
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
if ("cause" in error && error.cause && !["CONFIG_INVALID"].includes(error.code)) {
|
|
435
|
+
lines.push(` Caused by: ${error.cause}`);
|
|
436
|
+
}
|
|
437
|
+
return lines.join("\n");
|
|
438
|
+
};
|
|
439
|
+
/**
|
|
440
|
+
* Assert unreachable code path (for exhaustiveness checks).
|
|
441
|
+
* This is the ONLY acceptable throw in builder code.
|
|
442
|
+
*/
|
|
443
|
+
const assertUnreachable = (value, context) => {
|
|
444
|
+
throw new Error(`Unreachable code path${context ? ` in ${context}` : ""}: received ${JSON.stringify(value)}`);
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
//#endregion
|
|
448
|
+
//#region packages/builder/src/ast/common/scope.ts
|
|
449
|
+
/**
|
|
450
|
+
* Build AST path from scope stack
|
|
451
|
+
*/
|
|
452
|
+
const buildAstPath$1 = (stack) => {
|
|
453
|
+
return stack.map((frame) => frame.nameSegment).join(".");
|
|
454
|
+
};
|
|
455
|
+
/**
|
|
456
|
+
* Create an occurrence tracker for disambiguating anonymous/duplicate scopes.
|
|
457
|
+
*/
|
|
458
|
+
const createOccurrenceTracker$1 = () => {
|
|
459
|
+
const occurrenceCounters = new Map();
|
|
460
|
+
return { getNextOccurrence(key) {
|
|
461
|
+
const current = occurrenceCounters.get(key) ?? 0;
|
|
462
|
+
occurrenceCounters.set(key, current + 1);
|
|
463
|
+
return current;
|
|
464
|
+
} };
|
|
465
|
+
};
|
|
466
|
+
/**
|
|
467
|
+
* Create a path uniqueness tracker to ensure AST paths are unique.
|
|
468
|
+
*/
|
|
469
|
+
const createPathTracker$1 = () => {
|
|
470
|
+
const usedPaths = new Set();
|
|
471
|
+
return { ensureUniquePath(basePath) {
|
|
472
|
+
let path = basePath;
|
|
473
|
+
let suffix = 0;
|
|
474
|
+
while (usedPaths.has(path)) {
|
|
475
|
+
suffix++;
|
|
476
|
+
path = `${basePath}$${suffix}`;
|
|
477
|
+
}
|
|
478
|
+
usedPaths.add(path);
|
|
479
|
+
return path;
|
|
480
|
+
} };
|
|
481
|
+
};
|
|
482
|
+
/**
|
|
483
|
+
* Create an export bindings map from module exports.
|
|
484
|
+
* Maps local variable names to their exported names.
|
|
485
|
+
*/
|
|
486
|
+
const createExportBindingsMap = (exports$1) => {
|
|
487
|
+
const exportBindings = new Map();
|
|
488
|
+
exports$1.forEach((exp) => {
|
|
489
|
+
if (exp.kind === "named" && exp.local && !exp.isTypeOnly) {
|
|
490
|
+
exportBindings.set(exp.local, exp.exported);
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
return exportBindings;
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
//#endregion
|
|
497
|
+
//#region packages/builder/src/ast/adapters/swc.ts
|
|
498
|
+
const getLineStarts = (source) => {
|
|
499
|
+
const starts = [0];
|
|
500
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
501
|
+
if (source[index] === "\n") {
|
|
502
|
+
starts.push(index + 1);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return starts;
|
|
506
|
+
};
|
|
507
|
+
const toPositionResolver = (source) => {
|
|
508
|
+
const lineStarts = getLineStarts(source);
|
|
509
|
+
return (offset) => {
|
|
510
|
+
let low = 0;
|
|
511
|
+
let high = lineStarts.length - 1;
|
|
512
|
+
while (low <= high) {
|
|
513
|
+
const mid = Math.floor((low + high) / 2);
|
|
514
|
+
const start = lineStarts[mid];
|
|
515
|
+
const next = mid + 1 < lineStarts.length ? lineStarts[mid + 1] : source.length + 1;
|
|
516
|
+
if (start == null || next == null) {
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
if (offset < start) {
|
|
520
|
+
high = mid - 1;
|
|
521
|
+
} else if (offset >= next) {
|
|
522
|
+
low = mid + 1;
|
|
523
|
+
} else {
|
|
524
|
+
return {
|
|
525
|
+
line: mid + 1,
|
|
526
|
+
column: offset - start + 1
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return {
|
|
531
|
+
line: lineStarts.length,
|
|
532
|
+
column: offset - lineStarts[lineStarts.length - 1] + 1
|
|
533
|
+
};
|
|
534
|
+
};
|
|
535
|
+
};
|
|
536
|
+
const toLocation$1 = (resolvePosition, span) => ({
|
|
537
|
+
start: resolvePosition(span.start),
|
|
538
|
+
end: resolvePosition(span.end)
|
|
539
|
+
});
|
|
540
|
+
const collectImports$1 = (module$1) => {
|
|
541
|
+
const imports = [];
|
|
542
|
+
const handle = (declaration) => {
|
|
543
|
+
const source = declaration.source.value;
|
|
544
|
+
declaration.specifiers?.forEach((specifier) => {
|
|
545
|
+
if (specifier.type === "ImportSpecifier") {
|
|
546
|
+
const imported = specifier.imported ? specifier.imported.value : specifier.local.value;
|
|
547
|
+
imports.push({
|
|
548
|
+
source,
|
|
549
|
+
imported,
|
|
550
|
+
local: specifier.local.value,
|
|
551
|
+
kind: "named",
|
|
552
|
+
isTypeOnly: Boolean(specifier.isTypeOnly)
|
|
553
|
+
});
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
if (specifier.type === "ImportNamespaceSpecifier") {
|
|
557
|
+
imports.push({
|
|
558
|
+
source,
|
|
559
|
+
imported: "*",
|
|
560
|
+
local: specifier.local.value,
|
|
561
|
+
kind: "namespace",
|
|
562
|
+
isTypeOnly: false
|
|
563
|
+
});
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (specifier.type === "ImportDefaultSpecifier") {
|
|
567
|
+
imports.push({
|
|
568
|
+
source,
|
|
569
|
+
imported: "default",
|
|
570
|
+
local: specifier.local.value,
|
|
571
|
+
kind: "default",
|
|
572
|
+
isTypeOnly: false
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
};
|
|
577
|
+
module$1.body.forEach((item) => {
|
|
578
|
+
if (item.type === "ImportDeclaration") {
|
|
579
|
+
handle(item);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
if ("declaration" in item && item.declaration && "type" in item.declaration && item.declaration.type === "ImportDeclaration") {
|
|
583
|
+
handle(item.declaration);
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
return imports;
|
|
587
|
+
};
|
|
588
|
+
const collectExports$1 = (module$1) => {
|
|
589
|
+
const exports$1 = [];
|
|
590
|
+
const handle = (declaration) => {
|
|
591
|
+
if (declaration.type === "ExportDeclaration") {
|
|
592
|
+
if (declaration.declaration.type === "VariableDeclaration") {
|
|
593
|
+
declaration.declaration.declarations.forEach((decl) => {
|
|
594
|
+
if (decl.id.type === "Identifier") {
|
|
595
|
+
exports$1.push({
|
|
596
|
+
kind: "named",
|
|
597
|
+
exported: decl.id.value,
|
|
598
|
+
local: decl.id.value,
|
|
599
|
+
isTypeOnly: false
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
if (declaration.declaration.type === "FunctionDeclaration") {
|
|
605
|
+
const ident = declaration.declaration.identifier;
|
|
606
|
+
if (ident) {
|
|
607
|
+
exports$1.push({
|
|
608
|
+
kind: "named",
|
|
609
|
+
exported: ident.value,
|
|
610
|
+
local: ident.value,
|
|
611
|
+
isTypeOnly: false
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (declaration.type === "ExportNamedDeclaration") {
|
|
618
|
+
const source = declaration.source?.value;
|
|
619
|
+
declaration.specifiers?.forEach((specifier) => {
|
|
620
|
+
if (specifier.type !== "ExportSpecifier") {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
const exported = specifier.exported ? specifier.exported.value : specifier.orig.value;
|
|
624
|
+
const local = specifier.orig.value;
|
|
625
|
+
if (source) {
|
|
626
|
+
exports$1.push({
|
|
627
|
+
kind: "reexport",
|
|
628
|
+
exported,
|
|
629
|
+
local,
|
|
630
|
+
source,
|
|
631
|
+
isTypeOnly: Boolean(specifier.isTypeOnly)
|
|
632
|
+
});
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
exports$1.push({
|
|
636
|
+
kind: "named",
|
|
637
|
+
exported,
|
|
638
|
+
local,
|
|
639
|
+
isTypeOnly: Boolean(specifier.isTypeOnly)
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (declaration.type === "ExportAllDeclaration") {
|
|
645
|
+
exports$1.push({
|
|
646
|
+
kind: "reexport",
|
|
647
|
+
exported: "*",
|
|
648
|
+
source: declaration.source.value,
|
|
649
|
+
isTypeOnly: false
|
|
650
|
+
});
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
if (declaration.type === "ExportDefaultDeclaration" || declaration.type === "ExportDefaultExpression") {
|
|
654
|
+
exports$1.push({
|
|
655
|
+
kind: "named",
|
|
656
|
+
exported: "default",
|
|
657
|
+
local: "default",
|
|
658
|
+
isTypeOnly: false
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
module$1.body.forEach((item) => {
|
|
663
|
+
if (item.type === "ExportDeclaration" || item.type === "ExportNamedDeclaration" || item.type === "ExportAllDeclaration" || item.type === "ExportDefaultDeclaration" || item.type === "ExportDefaultExpression") {
|
|
664
|
+
handle(item);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
if ("declaration" in item && item.declaration) {
|
|
668
|
+
const declaration = item.declaration;
|
|
669
|
+
if (declaration.type === "ExportDeclaration" || declaration.type === "ExportNamedDeclaration" || declaration.type === "ExportAllDeclaration" || declaration.type === "ExportDefaultDeclaration" || declaration.type === "ExportDefaultExpression") {
|
|
670
|
+
handle(declaration);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
return exports$1;
|
|
675
|
+
};
|
|
676
|
+
const collectGqlIdentifiers = (module$1, helper) => {
|
|
677
|
+
const identifiers = new Set();
|
|
678
|
+
module$1.body.forEach((item) => {
|
|
679
|
+
const declaration = item.type === "ImportDeclaration" ? item : "declaration" in item && item.declaration && item.declaration.type === "ImportDeclaration" ? item.declaration : null;
|
|
680
|
+
if (!declaration) {
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (!helper.isGraphqlSystemImportSpecifier({
|
|
684
|
+
filePath: module$1.__filePath,
|
|
685
|
+
specifier: declaration.source.value
|
|
686
|
+
})) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
declaration.specifiers?.forEach((specifier) => {
|
|
690
|
+
if (specifier.type === "ImportSpecifier") {
|
|
691
|
+
const imported = specifier.imported ? specifier.imported.value : specifier.local.value;
|
|
692
|
+
if (imported === "gql") {
|
|
693
|
+
identifiers.add(specifier.local.value);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
return identifiers;
|
|
699
|
+
};
|
|
700
|
+
const isGqlCall = (identifiers, call) => {
|
|
701
|
+
const callee = call.callee;
|
|
702
|
+
if (callee.type !== "MemberExpression") {
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
705
|
+
if (callee.object.type !== "Identifier") {
|
|
706
|
+
return false;
|
|
707
|
+
}
|
|
708
|
+
if (!identifiers.has(callee.object.value)) {
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
if (callee.property.type !== "Identifier") {
|
|
712
|
+
return false;
|
|
713
|
+
}
|
|
714
|
+
const firstArg = call.arguments[0];
|
|
715
|
+
if (!firstArg?.expression || firstArg.expression.type !== "ArrowFunctionExpression") {
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
return true;
|
|
719
|
+
};
|
|
720
|
+
const collectAllDefinitions$1 = ({ module: module$1, gqlIdentifiers, imports: _imports, exports: exports$1, resolvePosition, source }) => {
|
|
721
|
+
const getPropertyName$1 = (property) => {
|
|
722
|
+
if (!property) {
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
if (property.type === "Identifier") {
|
|
726
|
+
return property.value;
|
|
727
|
+
}
|
|
728
|
+
if (property.type === "StringLiteral" || property.type === "NumericLiteral") {
|
|
729
|
+
return property.value;
|
|
730
|
+
}
|
|
731
|
+
return null;
|
|
732
|
+
};
|
|
733
|
+
const pending = [];
|
|
734
|
+
const handledCalls = [];
|
|
735
|
+
const exportBindings = createExportBindingsMap(exports$1);
|
|
736
|
+
const tracker = (0, __soda_gql_common.createCanonicalTracker)({
|
|
737
|
+
filePath: module$1.__filePath,
|
|
738
|
+
getExportName: (localName) => exportBindings.get(localName)
|
|
739
|
+
});
|
|
740
|
+
const anonymousCounters = new Map();
|
|
741
|
+
const getAnonymousName = (kind) => {
|
|
742
|
+
const count = anonymousCounters.get(kind) ?? 0;
|
|
743
|
+
anonymousCounters.set(kind, count + 1);
|
|
744
|
+
return `${kind}#${count}`;
|
|
745
|
+
};
|
|
746
|
+
const withScope = (stack, segment, kind, stableKey, callback) => {
|
|
747
|
+
const handle = tracker.enterScope({
|
|
748
|
+
segment,
|
|
749
|
+
kind,
|
|
750
|
+
stableKey
|
|
751
|
+
});
|
|
752
|
+
try {
|
|
753
|
+
const frame = {
|
|
754
|
+
nameSegment: segment,
|
|
755
|
+
kind
|
|
756
|
+
};
|
|
757
|
+
return callback([...stack, frame]);
|
|
758
|
+
} finally {
|
|
759
|
+
tracker.exitScope(handle);
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
const expressionFromCall = (call) => {
|
|
763
|
+
let start = call.span.start;
|
|
764
|
+
if (start > 0 && source[start] === "q" && source[start - 1] === "g" && source.slice(start, start + 3) === "ql.") {
|
|
765
|
+
start -= 1;
|
|
766
|
+
}
|
|
767
|
+
const raw = source.slice(start, call.span.end);
|
|
768
|
+
const marker = raw.indexOf("gql");
|
|
769
|
+
if (marker >= 0) {
|
|
770
|
+
return raw.slice(marker);
|
|
771
|
+
}
|
|
772
|
+
return raw;
|
|
773
|
+
};
|
|
774
|
+
const visit = (node, stack) => {
|
|
775
|
+
if (!node || typeof node !== "object") {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
if (node.type === "CallExpression" && isGqlCall(gqlIdentifiers, node)) {
|
|
779
|
+
const { astPath } = tracker.registerDefinition();
|
|
780
|
+
const isTopLevel = stack.length === 1;
|
|
781
|
+
let isExported = false;
|
|
782
|
+
let exportBinding;
|
|
783
|
+
if (isTopLevel && stack[0]) {
|
|
784
|
+
const topLevelName = stack[0].nameSegment;
|
|
785
|
+
if (exportBindings.has(topLevelName)) {
|
|
786
|
+
isExported = true;
|
|
787
|
+
exportBinding = exportBindings.get(topLevelName);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
handledCalls.push(node);
|
|
791
|
+
pending.push({
|
|
792
|
+
astPath,
|
|
793
|
+
isTopLevel,
|
|
794
|
+
isExported,
|
|
795
|
+
exportBinding,
|
|
796
|
+
loc: toLocation$1(resolvePosition, node.span),
|
|
797
|
+
expression: expressionFromCall(node)
|
|
798
|
+
});
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
if (node.type === "VariableDeclaration") {
|
|
802
|
+
node.declarations?.forEach((decl) => {
|
|
803
|
+
if (decl.id?.type === "Identifier") {
|
|
804
|
+
const varName = decl.id.value;
|
|
805
|
+
if (decl.init) {
|
|
806
|
+
withScope(stack, varName, "variable", `var:${varName}`, (newStack) => {
|
|
807
|
+
visit(decl.init, newStack);
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
if (node.type === "FunctionDeclaration") {
|
|
815
|
+
const funcName = node.identifier?.value ?? getAnonymousName("function");
|
|
816
|
+
if (node.body) {
|
|
817
|
+
withScope(stack, funcName, "function", `func:${funcName}`, (newStack) => {
|
|
818
|
+
visit(node.body, newStack);
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
if (node.type === "ArrowFunctionExpression") {
|
|
824
|
+
const arrowName = getAnonymousName("arrow");
|
|
825
|
+
if (node.body) {
|
|
826
|
+
withScope(stack, arrowName, "function", "arrow", (newStack) => {
|
|
827
|
+
visit(node.body, newStack);
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
if (node.type === "FunctionExpression") {
|
|
833
|
+
const funcName = node.identifier?.value ?? getAnonymousName("function");
|
|
834
|
+
if (node.body) {
|
|
835
|
+
withScope(stack, funcName, "function", `func:${funcName}`, (newStack) => {
|
|
836
|
+
visit(node.body, newStack);
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
if (node.type === "ClassDeclaration") {
|
|
842
|
+
const className = node.identifier?.value ?? getAnonymousName("class");
|
|
843
|
+
withScope(stack, className, "class", `class:${className}`, (classStack) => {
|
|
844
|
+
node.body?.forEach((member) => {
|
|
845
|
+
if (member.type === "MethodProperty" || member.type === "ClassProperty") {
|
|
846
|
+
const memberName = member.key?.value ?? null;
|
|
847
|
+
if (memberName) {
|
|
848
|
+
const memberKind = member.type === "MethodProperty" ? "method" : "property";
|
|
849
|
+
withScope(classStack, memberName, memberKind, `member:${className}.${memberName}`, (memberStack) => {
|
|
850
|
+
if (member.type === "MethodProperty" && member.body) {
|
|
851
|
+
visit(member.body, memberStack);
|
|
852
|
+
} else if (member.type === "ClassProperty" && member.value) {
|
|
853
|
+
visit(member.value, memberStack);
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
if (node.type === "KeyValueProperty") {
|
|
863
|
+
const propName = getPropertyName$1(node.key);
|
|
864
|
+
if (propName) {
|
|
865
|
+
withScope(stack, propName, "property", `prop:${propName}`, (newStack) => {
|
|
866
|
+
visit(node.value, newStack);
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
if (Array.isArray(node)) {
|
|
872
|
+
for (const child of node) {
|
|
873
|
+
visit(child, stack);
|
|
874
|
+
}
|
|
875
|
+
} else {
|
|
876
|
+
for (const value of Object.values(node)) {
|
|
877
|
+
if (Array.isArray(value)) {
|
|
878
|
+
for (const child of value) {
|
|
879
|
+
visit(child, stack);
|
|
880
|
+
}
|
|
881
|
+
} else if (value && typeof value === "object") {
|
|
882
|
+
visit(value, stack);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
module$1.body.forEach((statement) => {
|
|
888
|
+
visit(statement, []);
|
|
889
|
+
});
|
|
890
|
+
const definitions = pending.map((item) => ({
|
|
891
|
+
canonicalId: (0, __soda_gql_common.createCanonicalId)(module$1.__filePath, item.astPath),
|
|
892
|
+
astPath: item.astPath,
|
|
893
|
+
isTopLevel: item.isTopLevel,
|
|
894
|
+
isExported: item.isExported,
|
|
895
|
+
exportBinding: item.exportBinding,
|
|
896
|
+
loc: item.loc,
|
|
897
|
+
expression: item.expression
|
|
898
|
+
}));
|
|
899
|
+
return {
|
|
900
|
+
definitions,
|
|
901
|
+
handledCalls
|
|
902
|
+
};
|
|
903
|
+
};
|
|
904
|
+
/**
|
|
905
|
+
* Collect diagnostics (now empty since we support all definition types)
|
|
906
|
+
*/
|
|
907
|
+
const collectDiagnostics$1 = () => {
|
|
908
|
+
return [];
|
|
909
|
+
};
|
|
910
|
+
/**
|
|
911
|
+
* SWC adapter implementation
|
|
912
|
+
*/
|
|
913
|
+
const swcAdapter = {
|
|
914
|
+
parse(input) {
|
|
915
|
+
const program = (0, __swc_core.parseSync)(input.source, {
|
|
916
|
+
syntax: "typescript",
|
|
917
|
+
tsx: input.filePath.endsWith(".tsx"),
|
|
918
|
+
target: "es2022",
|
|
919
|
+
decorators: false,
|
|
920
|
+
dynamicImport: true
|
|
921
|
+
});
|
|
922
|
+
if (program.type !== "Module") {
|
|
923
|
+
return null;
|
|
924
|
+
}
|
|
925
|
+
const swcModule = program;
|
|
926
|
+
swcModule.__filePath = input.filePath;
|
|
927
|
+
return swcModule;
|
|
928
|
+
},
|
|
929
|
+
collectGqlIdentifiers(file, helper) {
|
|
930
|
+
return collectGqlIdentifiers(file, helper);
|
|
931
|
+
},
|
|
932
|
+
collectImports(file) {
|
|
933
|
+
return collectImports$1(file);
|
|
934
|
+
},
|
|
935
|
+
collectExports(file) {
|
|
936
|
+
return collectExports$1(file);
|
|
937
|
+
},
|
|
938
|
+
collectDefinitions(file, context) {
|
|
939
|
+
const resolvePosition = toPositionResolver(context.source);
|
|
940
|
+
const { definitions, handledCalls } = collectAllDefinitions$1({
|
|
941
|
+
module: file,
|
|
942
|
+
gqlIdentifiers: context.gqlIdentifiers,
|
|
943
|
+
imports: context.imports,
|
|
944
|
+
exports: context.exports,
|
|
945
|
+
resolvePosition,
|
|
946
|
+
source: context.source
|
|
947
|
+
});
|
|
948
|
+
return {
|
|
949
|
+
definitions,
|
|
950
|
+
handles: handledCalls
|
|
951
|
+
};
|
|
952
|
+
},
|
|
953
|
+
collectDiagnostics(_file, _context) {
|
|
954
|
+
return collectDiagnostics$1();
|
|
955
|
+
}
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
//#endregion
|
|
959
|
+
//#region packages/builder/src/ast/adapters/typescript.ts
|
|
960
|
+
const createSourceFile = (filePath, source) => {
|
|
961
|
+
const scriptKind = (0, node_path.extname)(filePath) === ".tsx" ? typescript.default.ScriptKind.TSX : typescript.default.ScriptKind.TS;
|
|
962
|
+
return typescript.default.createSourceFile(filePath, source, typescript.default.ScriptTarget.ES2022, true, scriptKind);
|
|
963
|
+
};
|
|
964
|
+
const toLocation = (sourceFile, node) => {
|
|
965
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
966
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
967
|
+
return {
|
|
968
|
+
start: {
|
|
969
|
+
line: start.line + 1,
|
|
970
|
+
column: start.character + 1
|
|
971
|
+
},
|
|
972
|
+
end: {
|
|
973
|
+
line: end.line + 1,
|
|
974
|
+
column: end.character + 1
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
};
|
|
978
|
+
const collectGqlImports = (sourceFile, helper) => {
|
|
979
|
+
const identifiers = new Set();
|
|
980
|
+
sourceFile.statements.forEach((statement) => {
|
|
981
|
+
if (!typescript.default.isImportDeclaration(statement) || !statement.importClause) {
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
const moduleText = statement.moduleSpecifier.text;
|
|
985
|
+
if (!helper.isGraphqlSystemImportSpecifier({
|
|
986
|
+
filePath: sourceFile.fileName,
|
|
987
|
+
specifier: moduleText
|
|
988
|
+
})) {
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
if (statement.importClause.namedBindings && typescript.default.isNamedImports(statement.importClause.namedBindings)) {
|
|
992
|
+
statement.importClause.namedBindings.elements.forEach((element) => {
|
|
993
|
+
const imported = element.propertyName ? element.propertyName.text : element.name.text;
|
|
994
|
+
if (imported === "gql") {
|
|
995
|
+
identifiers.add(element.name.text);
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
return identifiers;
|
|
1001
|
+
};
|
|
1002
|
+
const collectImports = (sourceFile) => {
|
|
1003
|
+
const imports = [];
|
|
1004
|
+
sourceFile.statements.forEach((statement) => {
|
|
1005
|
+
if (!typescript.default.isImportDeclaration(statement) || !statement.importClause) {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
const moduleText = statement.moduleSpecifier.text;
|
|
1009
|
+
const { importClause } = statement;
|
|
1010
|
+
if (importClause.name) {
|
|
1011
|
+
imports.push({
|
|
1012
|
+
source: moduleText,
|
|
1013
|
+
imported: "default",
|
|
1014
|
+
local: importClause.name.text,
|
|
1015
|
+
kind: "default",
|
|
1016
|
+
isTypeOnly: Boolean(importClause.isTypeOnly)
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
const { namedBindings } = importClause;
|
|
1020
|
+
if (!namedBindings) {
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
if (typescript.default.isNamespaceImport(namedBindings)) {
|
|
1024
|
+
imports.push({
|
|
1025
|
+
source: moduleText,
|
|
1026
|
+
imported: "*",
|
|
1027
|
+
local: namedBindings.name.text,
|
|
1028
|
+
kind: "namespace",
|
|
1029
|
+
isTypeOnly: Boolean(importClause.isTypeOnly)
|
|
1030
|
+
});
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
namedBindings.elements.forEach((element) => {
|
|
1034
|
+
imports.push({
|
|
1035
|
+
source: moduleText,
|
|
1036
|
+
imported: element.propertyName ? element.propertyName.text : element.name.text,
|
|
1037
|
+
local: element.name.text,
|
|
1038
|
+
kind: "named",
|
|
1039
|
+
isTypeOnly: Boolean(importClause.isTypeOnly || element.isTypeOnly)
|
|
1040
|
+
});
|
|
1041
|
+
});
|
|
1042
|
+
});
|
|
1043
|
+
return imports;
|
|
1044
|
+
};
|
|
1045
|
+
const collectExports = (sourceFile) => {
|
|
1046
|
+
const exports$1 = [];
|
|
1047
|
+
sourceFile.statements.forEach((statement) => {
|
|
1048
|
+
if (typescript.default.isExportDeclaration(statement)) {
|
|
1049
|
+
const moduleSpecifier = statement.moduleSpecifier ? statement.moduleSpecifier.text : undefined;
|
|
1050
|
+
if (statement.exportClause && typescript.default.isNamedExports(statement.exportClause)) {
|
|
1051
|
+
statement.exportClause.elements.forEach((element) => {
|
|
1052
|
+
if (moduleSpecifier) {
|
|
1053
|
+
exports$1.push({
|
|
1054
|
+
kind: "reexport",
|
|
1055
|
+
exported: element.name.text,
|
|
1056
|
+
local: element.propertyName ? element.propertyName.text : undefined,
|
|
1057
|
+
source: moduleSpecifier,
|
|
1058
|
+
isTypeOnly: Boolean(statement.isTypeOnly || element.isTypeOnly)
|
|
1059
|
+
});
|
|
1060
|
+
} else {
|
|
1061
|
+
exports$1.push({
|
|
1062
|
+
kind: "named",
|
|
1063
|
+
exported: element.name.text,
|
|
1064
|
+
local: element.propertyName ? element.propertyName.text : element.name.text,
|
|
1065
|
+
isTypeOnly: Boolean(statement.isTypeOnly || element.isTypeOnly)
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
if (moduleSpecifier) {
|
|
1072
|
+
exports$1.push({
|
|
1073
|
+
kind: "reexport",
|
|
1074
|
+
exported: "*",
|
|
1075
|
+
source: moduleSpecifier,
|
|
1076
|
+
isTypeOnly: Boolean(statement.isTypeOnly)
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
if (typescript.default.isExportAssignment(statement)) {
|
|
1082
|
+
exports$1.push({
|
|
1083
|
+
kind: "named",
|
|
1084
|
+
exported: "default",
|
|
1085
|
+
local: "default",
|
|
1086
|
+
isTypeOnly: false
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
if (typescript.default.isVariableStatement(statement) && statement.modifiers?.some((modifier) => modifier.kind === typescript.default.SyntaxKind.ExportKeyword)) {
|
|
1090
|
+
statement.declarationList.declarations.forEach((declaration) => {
|
|
1091
|
+
if (typescript.default.isIdentifier(declaration.name)) {
|
|
1092
|
+
exports$1.push({
|
|
1093
|
+
kind: "named",
|
|
1094
|
+
exported: declaration.name.text,
|
|
1095
|
+
local: declaration.name.text,
|
|
1096
|
+
isTypeOnly: false
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
if (typescript.default.isFunctionDeclaration(statement) && statement.modifiers?.some((modifier) => modifier.kind === typescript.default.SyntaxKind.ExportKeyword) && statement.name) {
|
|
1102
|
+
exports$1.push({
|
|
1103
|
+
kind: "named",
|
|
1104
|
+
exported: statement.name.text,
|
|
1105
|
+
local: statement.name.text,
|
|
1106
|
+
isTypeOnly: false
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
return exports$1;
|
|
1111
|
+
};
|
|
1112
|
+
const isGqlDefinitionCall = (identifiers, callExpression) => {
|
|
1113
|
+
const expression = callExpression.expression;
|
|
1114
|
+
if (!typescript.default.isPropertyAccessExpression(expression)) {
|
|
1115
|
+
return false;
|
|
1116
|
+
}
|
|
1117
|
+
if (!typescript.default.isIdentifier(expression.expression) || !identifiers.has(expression.expression.text)) {
|
|
1118
|
+
return false;
|
|
1119
|
+
}
|
|
1120
|
+
const [factory] = callExpression.arguments;
|
|
1121
|
+
if (!factory || !typescript.default.isArrowFunction(factory)) {
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
return true;
|
|
1125
|
+
};
|
|
1126
|
+
/**
|
|
1127
|
+
* Get property name from AST node
|
|
1128
|
+
*/
|
|
1129
|
+
const getPropertyName = (name) => {
|
|
1130
|
+
if (typescript.default.isIdentifier(name)) {
|
|
1131
|
+
return name.text;
|
|
1132
|
+
}
|
|
1133
|
+
if (typescript.default.isStringLiteral(name) || typescript.default.isNumericLiteral(name)) {
|
|
1134
|
+
return name.text;
|
|
1135
|
+
}
|
|
1136
|
+
return null;
|
|
1137
|
+
};
|
|
1138
|
+
/**
|
|
1139
|
+
* Collect all gql definitions (exported, non-exported, top-level, nested)
|
|
1140
|
+
*/
|
|
1141
|
+
const collectAllDefinitions = ({ sourceFile, identifiers, exports: exports$1 }) => {
|
|
1142
|
+
const pending = [];
|
|
1143
|
+
const handledCalls = [];
|
|
1144
|
+
const exportBindings = createExportBindingsMap(exports$1);
|
|
1145
|
+
const tracker = (0, __soda_gql_common.createCanonicalTracker)({
|
|
1146
|
+
filePath: sourceFile.fileName,
|
|
1147
|
+
getExportName: (localName) => exportBindings.get(localName)
|
|
1148
|
+
});
|
|
1149
|
+
const anonymousCounters = new Map();
|
|
1150
|
+
const getAnonymousName = (kind) => {
|
|
1151
|
+
const count = anonymousCounters.get(kind) ?? 0;
|
|
1152
|
+
anonymousCounters.set(kind, count + 1);
|
|
1153
|
+
return `${kind}#${count}`;
|
|
1154
|
+
};
|
|
1155
|
+
const withScope = (stack, segment, kind, stableKey, callback) => {
|
|
1156
|
+
const handle = tracker.enterScope({
|
|
1157
|
+
segment,
|
|
1158
|
+
kind,
|
|
1159
|
+
stableKey
|
|
1160
|
+
});
|
|
1161
|
+
try {
|
|
1162
|
+
const frame = {
|
|
1163
|
+
nameSegment: segment,
|
|
1164
|
+
kind
|
|
1165
|
+
};
|
|
1166
|
+
return callback([...stack, frame]);
|
|
1167
|
+
} finally {
|
|
1168
|
+
tracker.exitScope(handle);
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
const visit = (node, stack) => {
|
|
1172
|
+
if (typescript.default.isCallExpression(node) && isGqlDefinitionCall(identifiers, node)) {
|
|
1173
|
+
const { astPath } = tracker.registerDefinition();
|
|
1174
|
+
const isTopLevel = stack.length === 1;
|
|
1175
|
+
let isExported = false;
|
|
1176
|
+
let exportBinding;
|
|
1177
|
+
if (isTopLevel && stack[0]) {
|
|
1178
|
+
const topLevelName = stack[0].nameSegment;
|
|
1179
|
+
if (exportBindings.has(topLevelName)) {
|
|
1180
|
+
isExported = true;
|
|
1181
|
+
exportBinding = exportBindings.get(topLevelName);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
handledCalls.push(node);
|
|
1185
|
+
pending.push({
|
|
1186
|
+
astPath,
|
|
1187
|
+
isTopLevel,
|
|
1188
|
+
isExported,
|
|
1189
|
+
exportBinding,
|
|
1190
|
+
loc: toLocation(sourceFile, node),
|
|
1191
|
+
expression: node.getText(sourceFile)
|
|
1192
|
+
});
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
if (typescript.default.isVariableDeclaration(node) && node.name && typescript.default.isIdentifier(node.name)) {
|
|
1196
|
+
const varName = node.name.text;
|
|
1197
|
+
if (node.initializer) {
|
|
1198
|
+
const next = node.initializer;
|
|
1199
|
+
withScope(stack, varName, "variable", `var:${varName}`, (newStack) => {
|
|
1200
|
+
visit(next, newStack);
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
if (typescript.default.isFunctionDeclaration(node)) {
|
|
1206
|
+
const funcName = node.name?.text ?? getAnonymousName("function");
|
|
1207
|
+
if (node.body) {
|
|
1208
|
+
const next = node.body;
|
|
1209
|
+
withScope(stack, funcName, "function", `func:${funcName}`, (newStack) => {
|
|
1210
|
+
typescript.default.forEachChild(next, (child) => visit(child, newStack));
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
if (typescript.default.isArrowFunction(node)) {
|
|
1216
|
+
const arrowName = getAnonymousName("arrow");
|
|
1217
|
+
withScope(stack, arrowName, "function", "arrow", (newStack) => {
|
|
1218
|
+
if (typescript.default.isBlock(node.body)) {
|
|
1219
|
+
typescript.default.forEachChild(node.body, (child) => visit(child, newStack));
|
|
1220
|
+
} else {
|
|
1221
|
+
visit(node.body, newStack);
|
|
1222
|
+
}
|
|
1223
|
+
});
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
if (typescript.default.isFunctionExpression(node)) {
|
|
1227
|
+
const funcName = node.name?.text ?? getAnonymousName("function");
|
|
1228
|
+
if (node.body) {
|
|
1229
|
+
withScope(stack, funcName, "function", `func:${funcName}`, (newStack) => {
|
|
1230
|
+
typescript.default.forEachChild(node.body, (child) => visit(child, newStack));
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
if (typescript.default.isClassDeclaration(node)) {
|
|
1236
|
+
const className = node.name?.text ?? getAnonymousName("class");
|
|
1237
|
+
withScope(stack, className, "class", `class:${className}`, (classStack) => {
|
|
1238
|
+
node.members.forEach((member) => {
|
|
1239
|
+
if (typescript.default.isMethodDeclaration(member) || typescript.default.isPropertyDeclaration(member)) {
|
|
1240
|
+
const memberName = member.name && typescript.default.isIdentifier(member.name) ? member.name.text : null;
|
|
1241
|
+
if (memberName) {
|
|
1242
|
+
const memberKind = typescript.default.isMethodDeclaration(member) ? "method" : "property";
|
|
1243
|
+
withScope(classStack, memberName, memberKind, `member:${className}.${memberName}`, (memberStack) => {
|
|
1244
|
+
if (typescript.default.isMethodDeclaration(member) && member.body) {
|
|
1245
|
+
typescript.default.forEachChild(member.body, (child) => visit(child, memberStack));
|
|
1246
|
+
} else if (typescript.default.isPropertyDeclaration(member) && member.initializer) {
|
|
1247
|
+
visit(member.initializer, memberStack);
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
});
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
if (typescript.default.isPropertyAssignment(node)) {
|
|
1257
|
+
const propName = getPropertyName(node.name);
|
|
1258
|
+
if (propName) {
|
|
1259
|
+
withScope(stack, propName, "property", `prop:${propName}`, (newStack) => {
|
|
1260
|
+
visit(node.initializer, newStack);
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
typescript.default.forEachChild(node, (child) => visit(child, stack));
|
|
1266
|
+
};
|
|
1267
|
+
sourceFile.statements.forEach((statement) => {
|
|
1268
|
+
visit(statement, []);
|
|
1269
|
+
});
|
|
1270
|
+
const definitions = pending.map((item) => ({
|
|
1271
|
+
canonicalId: (0, __soda_gql_common.createCanonicalId)(sourceFile.fileName, item.astPath),
|
|
1272
|
+
astPath: item.astPath,
|
|
1273
|
+
isTopLevel: item.isTopLevel,
|
|
1274
|
+
isExported: item.isExported,
|
|
1275
|
+
exportBinding: item.exportBinding,
|
|
1276
|
+
loc: item.loc,
|
|
1277
|
+
expression: item.expression
|
|
1278
|
+
}));
|
|
1279
|
+
return {
|
|
1280
|
+
definitions,
|
|
1281
|
+
handledCalls
|
|
1282
|
+
};
|
|
1283
|
+
};
|
|
1284
|
+
/**
|
|
1285
|
+
* Collect diagnostics (now empty since we support all definition types)
|
|
1286
|
+
*/
|
|
1287
|
+
const collectDiagnostics = (_sourceFile, _identifiers, _handledCalls) => {
|
|
1288
|
+
return [];
|
|
1289
|
+
};
|
|
1290
|
+
/**
|
|
1291
|
+
* TypeScript adapter implementation
|
|
1292
|
+
*/
|
|
1293
|
+
const typescriptAdapter = {
|
|
1294
|
+
parse(input) {
|
|
1295
|
+
return createSourceFile(input.filePath, input.source);
|
|
1296
|
+
},
|
|
1297
|
+
collectGqlIdentifiers(file, helper) {
|
|
1298
|
+
return collectGqlImports(file, helper);
|
|
1299
|
+
},
|
|
1300
|
+
collectImports(file) {
|
|
1301
|
+
return collectImports(file);
|
|
1302
|
+
},
|
|
1303
|
+
collectExports(file) {
|
|
1304
|
+
return collectExports(file);
|
|
1305
|
+
},
|
|
1306
|
+
collectDefinitions(file, context) {
|
|
1307
|
+
const { definitions, handledCalls } = collectAllDefinitions({
|
|
1308
|
+
sourceFile: file,
|
|
1309
|
+
identifiers: context.gqlIdentifiers,
|
|
1310
|
+
exports: context.exports
|
|
1311
|
+
});
|
|
1312
|
+
return {
|
|
1313
|
+
definitions,
|
|
1314
|
+
handles: handledCalls
|
|
1315
|
+
};
|
|
1316
|
+
},
|
|
1317
|
+
collectDiagnostics(file, context) {
|
|
1318
|
+
return collectDiagnostics(file, context.gqlIdentifiers, context.handledCalls);
|
|
1319
|
+
}
|
|
1320
|
+
};
|
|
1321
|
+
|
|
1322
|
+
//#endregion
|
|
1323
|
+
//#region packages/builder/src/ast/core.ts
|
|
1324
|
+
/**
|
|
1325
|
+
* Core analyzer function that orchestrates the analysis pipeline.
|
|
1326
|
+
* Adapters implement the AnalyzerAdapter interface to provide parser-specific logic.
|
|
1327
|
+
*/
|
|
1328
|
+
const analyzeModuleCore = (input, adapter, graphqlHelper) => {
|
|
1329
|
+
const hasher = (0, __soda_gql_common.getPortableHasher)();
|
|
1330
|
+
const signature = hasher.hash(input.source, "xxhash");
|
|
1331
|
+
const file = adapter.parse(input);
|
|
1332
|
+
if (!file) {
|
|
1333
|
+
return {
|
|
1334
|
+
filePath: input.filePath,
|
|
1335
|
+
signature,
|
|
1336
|
+
definitions: [],
|
|
1337
|
+
diagnostics: [],
|
|
1338
|
+
imports: [],
|
|
1339
|
+
exports: []
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
const gqlIdentifiers = adapter.collectGqlIdentifiers(file, graphqlHelper);
|
|
1343
|
+
const imports = adapter.collectImports(file);
|
|
1344
|
+
const exports$1 = adapter.collectExports(file);
|
|
1345
|
+
const { definitions, handles } = adapter.collectDefinitions(file, {
|
|
1346
|
+
gqlIdentifiers,
|
|
1347
|
+
imports,
|
|
1348
|
+
exports: exports$1,
|
|
1349
|
+
source: input.source
|
|
1350
|
+
});
|
|
1351
|
+
const diagnostics = adapter.collectDiagnostics(file, {
|
|
1352
|
+
gqlIdentifiers,
|
|
1353
|
+
handledCalls: handles,
|
|
1354
|
+
source: input.source
|
|
1355
|
+
});
|
|
1356
|
+
return {
|
|
1357
|
+
filePath: input.filePath,
|
|
1358
|
+
signature,
|
|
1359
|
+
definitions,
|
|
1360
|
+
diagnostics,
|
|
1361
|
+
imports,
|
|
1362
|
+
exports: exports$1
|
|
1363
|
+
};
|
|
1364
|
+
};
|
|
1365
|
+
|
|
1366
|
+
//#endregion
|
|
1367
|
+
//#region packages/builder/src/ast/index.ts
|
|
1368
|
+
const createAstAnalyzer = ({ analyzer, graphqlHelper }) => {
|
|
1369
|
+
const analyze = (input) => {
|
|
1370
|
+
if (analyzer === "ts") {
|
|
1371
|
+
return analyzeModuleCore(input, typescriptAdapter, graphqlHelper);
|
|
1372
|
+
}
|
|
1373
|
+
if (analyzer === "swc") {
|
|
1374
|
+
return analyzeModuleCore(input, swcAdapter, graphqlHelper);
|
|
1375
|
+
}
|
|
1376
|
+
return assertUnreachable(analyzer, "createAstAnalyzer");
|
|
1377
|
+
};
|
|
1378
|
+
return {
|
|
1379
|
+
type: analyzer,
|
|
1380
|
+
analyze
|
|
1381
|
+
};
|
|
1382
|
+
};
|
|
1383
|
+
const getAstAnalyzer = (analyzer) => {
|
|
1384
|
+
throw new Error("getAstAnalyzer is deprecated. Use createAstAnalyzer with graphqlHelper parameter instead.");
|
|
1385
|
+
};
|
|
1386
|
+
|
|
1387
|
+
//#endregion
|
|
1388
|
+
//#region packages/builder/src/cache/json-cache.ts
|
|
1389
|
+
const JSON_EXT = ".json";
|
|
1390
|
+
const sanitizeSegment = (segment) => segment.replace(/[\\/]/g, "_");
|
|
1391
|
+
const ensureDirectory = (directory) => {
|
|
1392
|
+
if (!(0, node_fs.existsSync)(directory)) {
|
|
1393
|
+
(0, node_fs.mkdirSync)(directory, { recursive: true });
|
|
1394
|
+
}
|
|
1395
|
+
};
|
|
1396
|
+
const toNamespacePath = (root, segments) => (0, node_path.join)(root, ...segments.map(sanitizeSegment));
|
|
1397
|
+
const toEntryFilename = (key) => {
|
|
1398
|
+
const hasher = (0, __soda_gql_common.getPortableHasher)();
|
|
1399
|
+
return `${hasher.hash(key, "xxhash")}${JSON_EXT}`;
|
|
1400
|
+
};
|
|
1401
|
+
const createJsonCache = ({ rootDir, prefix = [] }) => {
|
|
1402
|
+
const basePath = toNamespacePath(rootDir, prefix);
|
|
1403
|
+
ensureDirectory(basePath);
|
|
1404
|
+
const enumerateFiles = (directory) => {
|
|
1405
|
+
if (!(0, node_fs.existsSync)(directory)) {
|
|
1406
|
+
return [];
|
|
1407
|
+
}
|
|
1408
|
+
return (0, node_fs.readdirSync)(directory, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(JSON_EXT)).map((entry) => (0, node_path.join)(directory, entry.name));
|
|
1409
|
+
};
|
|
1410
|
+
return {
|
|
1411
|
+
createStore: ({ namespace, schema, version = "v1" }) => {
|
|
1412
|
+
const storeRoot = toNamespacePath(basePath, namespace);
|
|
1413
|
+
ensureDirectory(storeRoot);
|
|
1414
|
+
const envelopeSchema = zod.z.object({
|
|
1415
|
+
key: zod.z.string(),
|
|
1416
|
+
version: zod.z.string(),
|
|
1417
|
+
value: schema
|
|
1418
|
+
});
|
|
1419
|
+
const resolveEntryPath = (key) => (0, node_path.join)(storeRoot, toEntryFilename(key));
|
|
1420
|
+
const readEnvelope = (filePath) => {
|
|
1421
|
+
try {
|
|
1422
|
+
const raw = (0, node_fs.readFileSync)(filePath, "utf8");
|
|
1423
|
+
const parsed = envelopeSchema.safeParse(JSON.parse(raw));
|
|
1424
|
+
if (!parsed.success) {
|
|
1425
|
+
(0, node_fs.unlinkSync)(filePath);
|
|
1426
|
+
return null;
|
|
1427
|
+
}
|
|
1428
|
+
if (parsed.data.version !== version) {
|
|
1429
|
+
(0, node_fs.unlinkSync)(filePath);
|
|
1430
|
+
return null;
|
|
1431
|
+
}
|
|
1432
|
+
return parsed.data;
|
|
1433
|
+
} catch {
|
|
1434
|
+
(0, node_fs.unlinkSync)(filePath);
|
|
1435
|
+
return null;
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
const load = (key) => {
|
|
1439
|
+
const filePath = resolveEntryPath(key);
|
|
1440
|
+
if (!(0, node_fs.existsSync)(filePath)) {
|
|
1441
|
+
return null;
|
|
1442
|
+
}
|
|
1443
|
+
const envelope = readEnvelope(filePath);
|
|
1444
|
+
if (!envelope || envelope.key !== key) {
|
|
1445
|
+
return null;
|
|
1446
|
+
}
|
|
1447
|
+
return envelope.value;
|
|
1448
|
+
};
|
|
1449
|
+
const store = (key, value) => {
|
|
1450
|
+
const filePath = resolveEntryPath(key);
|
|
1451
|
+
ensureDirectory(storeRoot);
|
|
1452
|
+
const envelope = {
|
|
1453
|
+
key,
|
|
1454
|
+
version,
|
|
1455
|
+
value
|
|
1456
|
+
};
|
|
1457
|
+
(0, node_fs.writeFileSync)(filePath, JSON.stringify(envelope));
|
|
1458
|
+
};
|
|
1459
|
+
const deleteEntry = (key) => {
|
|
1460
|
+
const filePath = resolveEntryPath(key);
|
|
1461
|
+
if ((0, node_fs.existsSync)(filePath)) {
|
|
1462
|
+
(0, node_fs.unlinkSync)(filePath);
|
|
1463
|
+
}
|
|
1464
|
+
};
|
|
1465
|
+
function* iterateEntries() {
|
|
1466
|
+
for (const filePath of enumerateFiles(storeRoot)) {
|
|
1467
|
+
const envelope = readEnvelope(filePath);
|
|
1468
|
+
if (!envelope) {
|
|
1469
|
+
continue;
|
|
1470
|
+
}
|
|
1471
|
+
yield [envelope.key, envelope.value];
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
const clear = () => {
|
|
1475
|
+
for (const filePath of enumerateFiles(storeRoot)) {
|
|
1476
|
+
(0, node_fs.unlinkSync)(filePath);
|
|
1477
|
+
}
|
|
1478
|
+
};
|
|
1479
|
+
const size = () => {
|
|
1480
|
+
let count = 0;
|
|
1481
|
+
for (const _ of iterateEntries()) {
|
|
1482
|
+
count += 1;
|
|
1483
|
+
}
|
|
1484
|
+
return count;
|
|
1485
|
+
};
|
|
1486
|
+
return {
|
|
1487
|
+
load,
|
|
1488
|
+
store,
|
|
1489
|
+
delete: deleteEntry,
|
|
1490
|
+
entries: iterateEntries,
|
|
1491
|
+
clear,
|
|
1492
|
+
size
|
|
1493
|
+
};
|
|
1494
|
+
},
|
|
1495
|
+
clearAll: () => {
|
|
1496
|
+
if ((0, node_fs.existsSync)(basePath)) {
|
|
1497
|
+
(0, node_fs.rmSync)(basePath, {
|
|
1498
|
+
recursive: true,
|
|
1499
|
+
force: true
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
ensureDirectory(basePath);
|
|
1503
|
+
}
|
|
1504
|
+
};
|
|
1505
|
+
};
|
|
1506
|
+
|
|
1507
|
+
//#endregion
|
|
1508
|
+
//#region packages/builder/src/cache/json-entity-cache.ts
|
|
1509
|
+
/**
|
|
1510
|
+
* Abstract base class for JSON-based entity caches.
|
|
1511
|
+
* Provides common caching functionality with signature-based eviction.
|
|
1512
|
+
*/
|
|
1513
|
+
var JsonEntityCache = class {
|
|
1514
|
+
cacheStore;
|
|
1515
|
+
keyNormalizer;
|
|
1516
|
+
constructor(options) {
|
|
1517
|
+
this.cacheStore = options.factory.createStore({
|
|
1518
|
+
namespace: [...options.namespace],
|
|
1519
|
+
schema: options.schema,
|
|
1520
|
+
version: options.version
|
|
1521
|
+
});
|
|
1522
|
+
this.keyNormalizer = options.keyNormalizer ?? __soda_gql_common.normalizePath;
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Normalize a key for consistent cache lookups.
|
|
1526
|
+
*/
|
|
1527
|
+
normalizeKey(key) {
|
|
1528
|
+
return this.keyNormalizer(key);
|
|
1529
|
+
}
|
|
1530
|
+
/**
|
|
1531
|
+
* Load raw value from cache without signature validation.
|
|
1532
|
+
*/
|
|
1533
|
+
loadRaw(key) {
|
|
1534
|
+
return this.cacheStore.load(key);
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Store raw value to cache.
|
|
1538
|
+
*/
|
|
1539
|
+
storeRaw(key, value) {
|
|
1540
|
+
this.cacheStore.store(key, value);
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Delete an entry from the cache.
|
|
1544
|
+
*/
|
|
1545
|
+
delete(key) {
|
|
1546
|
+
const normalizedKey = this.normalizeKey(key);
|
|
1547
|
+
this.cacheStore.delete(normalizedKey);
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Get all cached entries.
|
|
1551
|
+
* Subclasses should override this to provide custom iteration.
|
|
1552
|
+
*/
|
|
1553
|
+
*baseEntries() {
|
|
1554
|
+
for (const [, value] of this.cacheStore.entries()) {
|
|
1555
|
+
yield value;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Clear all entries from the cache.
|
|
1560
|
+
*/
|
|
1561
|
+
clear() {
|
|
1562
|
+
this.cacheStore.clear();
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Get the number of entries in the cache.
|
|
1566
|
+
*/
|
|
1567
|
+
size() {
|
|
1568
|
+
return this.cacheStore.size();
|
|
1569
|
+
}
|
|
1570
|
+
};
|
|
1571
|
+
|
|
1572
|
+
//#endregion
|
|
1573
|
+
//#region packages/builder/src/schemas/cache.ts
|
|
1574
|
+
const SourcePositionSchema = zod.z.object({
|
|
1575
|
+
line: zod.z.number(),
|
|
1576
|
+
column: zod.z.number()
|
|
1577
|
+
});
|
|
1578
|
+
const SourceLocationSchema = zod.z.object({
|
|
1579
|
+
start: SourcePositionSchema,
|
|
1580
|
+
end: SourcePositionSchema
|
|
1581
|
+
});
|
|
1582
|
+
const ModuleDefinitionSchema = zod.z.object({
|
|
1583
|
+
canonicalId: __soda_gql_common.CanonicalIdSchema,
|
|
1584
|
+
astPath: zod.z.string(),
|
|
1585
|
+
isTopLevel: zod.z.boolean(),
|
|
1586
|
+
isExported: zod.z.boolean(),
|
|
1587
|
+
exportBinding: zod.z.string().optional(),
|
|
1588
|
+
loc: SourceLocationSchema,
|
|
1589
|
+
expression: zod.z.string()
|
|
1590
|
+
});
|
|
1591
|
+
const ModuleDiagnosticSchema = zod.z.object({
|
|
1592
|
+
code: zod.z.literal("NON_TOP_LEVEL_DEFINITION"),
|
|
1593
|
+
message: zod.z.string(),
|
|
1594
|
+
loc: SourceLocationSchema
|
|
1595
|
+
});
|
|
1596
|
+
const ModuleImportSchema = zod.z.object({
|
|
1597
|
+
source: zod.z.string(),
|
|
1598
|
+
imported: zod.z.string(),
|
|
1599
|
+
local: zod.z.string(),
|
|
1600
|
+
kind: zod.z.enum([
|
|
1601
|
+
"named",
|
|
1602
|
+
"namespace",
|
|
1603
|
+
"default"
|
|
1604
|
+
]),
|
|
1605
|
+
isTypeOnly: zod.z.boolean()
|
|
1606
|
+
});
|
|
1607
|
+
const ModuleExportSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
|
|
1608
|
+
kind: zod.z.literal("named"),
|
|
1609
|
+
exported: zod.z.string(),
|
|
1610
|
+
local: zod.z.string(),
|
|
1611
|
+
source: zod.z.undefined().optional(),
|
|
1612
|
+
isTypeOnly: zod.z.boolean()
|
|
1613
|
+
}), zod.z.object({
|
|
1614
|
+
kind: zod.z.literal("reexport"),
|
|
1615
|
+
exported: zod.z.string(),
|
|
1616
|
+
source: zod.z.string(),
|
|
1617
|
+
local: zod.z.string().optional(),
|
|
1618
|
+
isTypeOnly: zod.z.boolean()
|
|
1619
|
+
})]);
|
|
1620
|
+
const ModuleAnalysisSchema = zod.z.object({
|
|
1621
|
+
filePath: zod.z.string(),
|
|
1622
|
+
signature: zod.z.string(),
|
|
1623
|
+
definitions: zod.z.array(ModuleDefinitionSchema).readonly(),
|
|
1624
|
+
diagnostics: zod.z.array(ModuleDiagnosticSchema).readonly(),
|
|
1625
|
+
imports: zod.z.array(ModuleImportSchema).readonly(),
|
|
1626
|
+
exports: zod.z.array(ModuleExportSchema).readonly()
|
|
1627
|
+
});
|
|
1628
|
+
|
|
1629
|
+
//#endregion
|
|
1630
|
+
//#region packages/builder/src/schemas/discovery.ts
|
|
1631
|
+
const FileFingerprintSchema = zod.z.object({
|
|
1632
|
+
hash: zod.z.string(),
|
|
1633
|
+
sizeBytes: zod.z.number(),
|
|
1634
|
+
mtimeMs: zod.z.number()
|
|
1635
|
+
});
|
|
1636
|
+
const DiscoveredDependencySchema = zod.z.object({
|
|
1637
|
+
specifier: zod.z.string(),
|
|
1638
|
+
resolvedPath: zod.z.string().nullable(),
|
|
1639
|
+
isExternal: zod.z.boolean()
|
|
1640
|
+
});
|
|
1641
|
+
const DiscoverySnapshotSchema = zod.z.object({
|
|
1642
|
+
filePath: zod.z.string(),
|
|
1643
|
+
normalizedFilePath: zod.z.string(),
|
|
1644
|
+
signature: zod.z.string(),
|
|
1645
|
+
fingerprint: FileFingerprintSchema,
|
|
1646
|
+
analyzer: zod.z.string(),
|
|
1647
|
+
createdAtMs: zod.z.number(),
|
|
1648
|
+
analysis: ModuleAnalysisSchema,
|
|
1649
|
+
dependencies: zod.z.array(DiscoveredDependencySchema).readonly()
|
|
1650
|
+
});
|
|
1651
|
+
|
|
1652
|
+
//#endregion
|
|
1653
|
+
//#region packages/builder/src/discovery/cache.ts
|
|
1654
|
+
const DISCOVERY_CACHE_VERSION = "discovery-cache/v3";
|
|
1655
|
+
var JsonDiscoveryCache = class extends JsonEntityCache {
|
|
1656
|
+
constructor(options) {
|
|
1657
|
+
const namespace = [
|
|
1658
|
+
...options.namespacePrefix ?? ["discovery"],
|
|
1659
|
+
options.analyzer,
|
|
1660
|
+
options.evaluatorId
|
|
1661
|
+
];
|
|
1662
|
+
super({
|
|
1663
|
+
factory: options.factory,
|
|
1664
|
+
namespace,
|
|
1665
|
+
schema: DiscoverySnapshotSchema,
|
|
1666
|
+
version: options.version ?? DISCOVERY_CACHE_VERSION
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
load(filePath, expectedSignature) {
|
|
1670
|
+
const key = this.normalizeKey(filePath);
|
|
1671
|
+
const snapshot = this.loadRaw(key);
|
|
1672
|
+
if (!snapshot) {
|
|
1673
|
+
return null;
|
|
1674
|
+
}
|
|
1675
|
+
if (snapshot.signature !== expectedSignature) {
|
|
1676
|
+
this.delete(filePath);
|
|
1677
|
+
return null;
|
|
1678
|
+
}
|
|
1679
|
+
return snapshot;
|
|
1680
|
+
}
|
|
1681
|
+
peek(filePath) {
|
|
1682
|
+
const key = this.normalizeKey(filePath);
|
|
1683
|
+
return this.loadRaw(key);
|
|
1684
|
+
}
|
|
1685
|
+
store(snapshot) {
|
|
1686
|
+
const key = this.normalizeKey(snapshot.filePath);
|
|
1687
|
+
this.storeRaw(key, snapshot);
|
|
1688
|
+
}
|
|
1689
|
+
entries() {
|
|
1690
|
+
return this.baseEntries();
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
const createDiscoveryCache = (options) => new JsonDiscoveryCache(options);
|
|
1694
|
+
|
|
1695
|
+
//#endregion
|
|
1696
|
+
//#region packages/builder/src/discovery/common.ts
|
|
1697
|
+
/**
|
|
1698
|
+
* Extract all unique dependencies (relative + external) from the analysis.
|
|
1699
|
+
* Resolves local specifiers immediately so discovery only traverses once.
|
|
1700
|
+
*/
|
|
1701
|
+
const extractModuleDependencies = (analysis) => {
|
|
1702
|
+
const dependencies = new Map();
|
|
1703
|
+
const addDependency = (specifier) => {
|
|
1704
|
+
if (dependencies.has(specifier)) {
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
const isExternal = (0, __soda_gql_common.isExternalSpecifier)(specifier);
|
|
1708
|
+
const resolvedPath = isExternal ? null : (0, __soda_gql_common.resolveRelativeImportWithExistenceCheck)({
|
|
1709
|
+
filePath: analysis.filePath,
|
|
1710
|
+
specifier
|
|
1711
|
+
});
|
|
1712
|
+
dependencies.set(specifier, {
|
|
1713
|
+
specifier,
|
|
1714
|
+
resolvedPath,
|
|
1715
|
+
isExternal
|
|
1716
|
+
});
|
|
1717
|
+
};
|
|
1718
|
+
for (const imp of analysis.imports) {
|
|
1719
|
+
addDependency(imp.source);
|
|
1720
|
+
}
|
|
1721
|
+
for (const exp of analysis.exports) {
|
|
1722
|
+
if (exp.kind === "reexport") {
|
|
1723
|
+
addDependency(exp.source);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
return Array.from(dependencies.values());
|
|
1727
|
+
};
|
|
1728
|
+
const createSourceHash = (source) => {
|
|
1729
|
+
const hasher = (0, __soda_gql_common.getPortableHasher)();
|
|
1730
|
+
return hasher.hash(source, "xxhash");
|
|
1731
|
+
};
|
|
1732
|
+
|
|
1733
|
+
//#endregion
|
|
1734
|
+
//#region packages/builder/src/discovery/fingerprint.ts
|
|
1735
|
+
/**
|
|
1736
|
+
* In-memory fingerprint cache keyed by absolute path
|
|
1737
|
+
*/
|
|
1738
|
+
const fingerprintCache = new Map();
|
|
1739
|
+
/**
|
|
1740
|
+
* Lazy-loaded xxhash instance
|
|
1741
|
+
*/
|
|
1742
|
+
let xxhashInstance = null;
|
|
1743
|
+
/**
|
|
1744
|
+
* Lazily load xxhash-wasm instance
|
|
1745
|
+
*/
|
|
1746
|
+
async function getXXHash() {
|
|
1747
|
+
if (xxhashInstance === null) {
|
|
1748
|
+
const { default: xxhash } = await import("xxhash-wasm");
|
|
1749
|
+
xxhashInstance = await xxhash();
|
|
1750
|
+
}
|
|
1751
|
+
return xxhashInstance;
|
|
1752
|
+
}
|
|
1753
|
+
/**
|
|
1754
|
+
* Compute file fingerprint with memoization.
|
|
1755
|
+
* Uses mtime to short-circuit re-hashing unchanged files.
|
|
1756
|
+
*
|
|
1757
|
+
* @param path - Absolute path to file
|
|
1758
|
+
* @returns Result containing FileFingerprint or FingerprintError
|
|
1759
|
+
*/
|
|
1760
|
+
function computeFingerprint(path) {
|
|
1761
|
+
try {
|
|
1762
|
+
const stats = (0, node_fs.statSync)(path);
|
|
1763
|
+
if (!stats.isFile()) {
|
|
1764
|
+
return (0, neverthrow.err)({
|
|
1765
|
+
code: "NOT_A_FILE",
|
|
1766
|
+
path,
|
|
1767
|
+
message: `Path is not a file: ${path}`
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
const mtimeMs = stats.mtimeMs;
|
|
1771
|
+
const cached = fingerprintCache.get(path);
|
|
1772
|
+
if (cached && cached.mtimeMs === mtimeMs) {
|
|
1773
|
+
return (0, neverthrow.ok)(cached);
|
|
1774
|
+
}
|
|
1775
|
+
const contents = (0, node_fs.readFileSync)(path);
|
|
1776
|
+
const sizeBytes = stats.size;
|
|
1777
|
+
const hash = computeHashSync(contents);
|
|
1778
|
+
const fingerprint = {
|
|
1779
|
+
hash,
|
|
1780
|
+
sizeBytes,
|
|
1781
|
+
mtimeMs
|
|
1782
|
+
};
|
|
1783
|
+
fingerprintCache.set(path, fingerprint);
|
|
1784
|
+
return (0, neverthrow.ok)(fingerprint);
|
|
1785
|
+
} catch (error) {
|
|
1786
|
+
if (error.code === "ENOENT") {
|
|
1787
|
+
return (0, neverthrow.err)({
|
|
1788
|
+
code: "FILE_NOT_FOUND",
|
|
1789
|
+
path,
|
|
1790
|
+
message: `File not found: ${path}`
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
return (0, neverthrow.err)({
|
|
1794
|
+
code: "READ_ERROR",
|
|
1795
|
+
path,
|
|
1796
|
+
message: `Failed to read file: ${error}`
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Compute hash synchronously.
|
|
1802
|
+
* For first call, uses simple string hash as fallback.
|
|
1803
|
+
* Subsequent calls will use xxhash after async initialization.
|
|
1804
|
+
*/
|
|
1805
|
+
function computeHashSync(contents) {
|
|
1806
|
+
if (xxhashInstance !== null) {
|
|
1807
|
+
const hash = xxhashInstance.h64Raw(new Uint8Array(contents));
|
|
1808
|
+
return hash.toString(16);
|
|
1809
|
+
}
|
|
1810
|
+
void getXXHash();
|
|
1811
|
+
return simpleHash(contents);
|
|
1812
|
+
}
|
|
1813
|
+
/**
|
|
1814
|
+
* Simple hash function for initial calls before xxhash loads
|
|
1815
|
+
*/
|
|
1816
|
+
function simpleHash(buffer) {
|
|
1817
|
+
let hash = 0;
|
|
1818
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
1819
|
+
const byte = buffer[i];
|
|
1820
|
+
if (byte !== undefined) {
|
|
1821
|
+
hash = (hash << 5) - hash + byte;
|
|
1822
|
+
hash = hash & hash;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
return hash.toString(16);
|
|
1826
|
+
}
|
|
1827
|
+
/**
|
|
1828
|
+
* Invalidate cached fingerprint for a specific path
|
|
1829
|
+
*
|
|
1830
|
+
* @param path - Absolute path to invalidate
|
|
1831
|
+
*/
|
|
1832
|
+
function invalidateFingerprint(path) {
|
|
1833
|
+
fingerprintCache.delete(path);
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Clear all cached fingerprints
|
|
1837
|
+
*/
|
|
1838
|
+
function clearFingerprintCache() {
|
|
1839
|
+
fingerprintCache.clear();
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
//#endregion
|
|
1843
|
+
//#region packages/builder/src/discovery/discoverer.ts
|
|
1844
|
+
/**
|
|
1845
|
+
* Discover and analyze all modules starting from entry points.
|
|
1846
|
+
* Uses AST parsing instead of RegExp for reliable dependency extraction.
|
|
1847
|
+
* Supports caching with fingerprint-based invalidation to skip re-parsing unchanged files.
|
|
1848
|
+
*/
|
|
1849
|
+
const discoverModules = ({ entryPaths, astAnalyzer, incremental }) => {
|
|
1850
|
+
const snapshots = new Map();
|
|
1851
|
+
const stack = [...entryPaths];
|
|
1852
|
+
const changedFiles = incremental?.changedFiles ?? new Set();
|
|
1853
|
+
const removedFiles = incremental?.removedFiles ?? new Set();
|
|
1854
|
+
const affectedFiles = incremental?.affectedFiles ?? new Set();
|
|
1855
|
+
const invalidatedSet = new Set([
|
|
1856
|
+
...changedFiles,
|
|
1857
|
+
...removedFiles,
|
|
1858
|
+
...affectedFiles
|
|
1859
|
+
]);
|
|
1860
|
+
let cacheHits = 0;
|
|
1861
|
+
let cacheMisses = 0;
|
|
1862
|
+
let cacheSkips = 0;
|
|
1863
|
+
if (incremental) {
|
|
1864
|
+
for (const filePath of removedFiles) {
|
|
1865
|
+
incremental.cache.delete(filePath);
|
|
1866
|
+
invalidateFingerprint(filePath);
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
while (stack.length > 0) {
|
|
1870
|
+
const rawFilePath = stack.pop();
|
|
1871
|
+
if (!rawFilePath) {
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1874
|
+
const filePath = (0, __soda_gql_common.normalizePath)(rawFilePath);
|
|
1875
|
+
if (snapshots.has(filePath)) {
|
|
1876
|
+
continue;
|
|
1877
|
+
}
|
|
1878
|
+
if (invalidatedSet.has(filePath)) {
|
|
1879
|
+
invalidateFingerprint(filePath);
|
|
1880
|
+
cacheSkips++;
|
|
1881
|
+
} else if (incremental) {
|
|
1882
|
+
const cached = incremental.cache.peek(filePath);
|
|
1883
|
+
if (cached) {
|
|
1884
|
+
try {
|
|
1885
|
+
const stats = (0, node_fs.statSync)(filePath);
|
|
1886
|
+
const mtimeMs = stats.mtimeMs;
|
|
1887
|
+
const sizeBytes = stats.size;
|
|
1888
|
+
if (cached.fingerprint.mtimeMs === mtimeMs && cached.fingerprint.sizeBytes === sizeBytes) {
|
|
1889
|
+
snapshots.set(filePath, cached);
|
|
1890
|
+
cacheHits++;
|
|
1891
|
+
for (const dep of cached.dependencies) {
|
|
1892
|
+
if (dep.resolvedPath && !snapshots.has(dep.resolvedPath)) {
|
|
1893
|
+
stack.push(dep.resolvedPath);
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
continue;
|
|
1897
|
+
}
|
|
1898
|
+
} catch {}
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
let source;
|
|
1902
|
+
try {
|
|
1903
|
+
source = (0, node_fs.readFileSync)(filePath, "utf8");
|
|
1904
|
+
} catch (error) {
|
|
1905
|
+
if (error.code === "ENOENT") {
|
|
1906
|
+
incremental?.cache.delete(filePath);
|
|
1907
|
+
invalidateFingerprint(filePath);
|
|
1908
|
+
continue;
|
|
1909
|
+
}
|
|
1910
|
+
return (0, neverthrow.err)(builderErrors.discoveryIOError(filePath, error instanceof Error ? error.message : String(error)));
|
|
1911
|
+
}
|
|
1912
|
+
const signature = createSourceHash(source);
|
|
1913
|
+
const analysis = astAnalyzer.analyze({
|
|
1914
|
+
filePath,
|
|
1915
|
+
source
|
|
1916
|
+
});
|
|
1917
|
+
cacheMisses++;
|
|
1918
|
+
const dependencies = extractModuleDependencies(analysis);
|
|
1919
|
+
for (const dep of dependencies) {
|
|
1920
|
+
if (!dep.isExternal && dep.resolvedPath && !snapshots.has(dep.resolvedPath)) {
|
|
1921
|
+
stack.push(dep.resolvedPath);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
const fingerprintResult = computeFingerprint(filePath);
|
|
1925
|
+
if (fingerprintResult.isErr()) {
|
|
1926
|
+
return (0, neverthrow.err)(builderErrors.discoveryIOError(filePath, `Failed to compute fingerprint: ${fingerprintResult.error.message}`));
|
|
1927
|
+
}
|
|
1928
|
+
const fingerprint = fingerprintResult.value;
|
|
1929
|
+
const snapshot = {
|
|
1930
|
+
filePath,
|
|
1931
|
+
normalizedFilePath: (0, __soda_gql_common.normalizePath)(filePath),
|
|
1932
|
+
signature,
|
|
1933
|
+
fingerprint,
|
|
1934
|
+
analyzer: astAnalyzer.type,
|
|
1935
|
+
createdAtMs: Date.now(),
|
|
1936
|
+
analysis,
|
|
1937
|
+
dependencies
|
|
1938
|
+
};
|
|
1939
|
+
snapshots.set(filePath, snapshot);
|
|
1940
|
+
if (incremental) {
|
|
1941
|
+
incremental.cache.store(snapshot);
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
return (0, neverthrow.ok)({
|
|
1945
|
+
snapshots: Array.from(snapshots.values()),
|
|
1946
|
+
cacheHits,
|
|
1947
|
+
cacheMisses,
|
|
1948
|
+
cacheSkips
|
|
1949
|
+
});
|
|
1950
|
+
};
|
|
1951
|
+
|
|
1952
|
+
//#endregion
|
|
1953
|
+
//#region packages/builder/src/utils/glob.ts
|
|
1954
|
+
/**
|
|
1955
|
+
* Scan files matching a glob pattern from the given directory
|
|
1956
|
+
* @param pattern - Glob pattern (e.g., "src/**\/*.ts")
|
|
1957
|
+
* @param cwd - Working directory (defaults to process.cwd())
|
|
1958
|
+
* @returns Array of matched file paths (relative to cwd)
|
|
1959
|
+
*/
|
|
1960
|
+
const scanGlob = (pattern, cwd = process.cwd()) => {
|
|
1961
|
+
if (typeof Bun !== "undefined" && Bun.Glob) {
|
|
1962
|
+
const { Glob } = Bun;
|
|
1963
|
+
const glob = new Glob(pattern);
|
|
1964
|
+
return Array.from(glob.scanSync(cwd));
|
|
1965
|
+
}
|
|
1966
|
+
return fast_glob.default.sync(pattern, {
|
|
1967
|
+
cwd,
|
|
1968
|
+
dot: true,
|
|
1969
|
+
onlyFiles: true
|
|
1970
|
+
});
|
|
1971
|
+
};
|
|
1972
|
+
|
|
1973
|
+
//#endregion
|
|
1974
|
+
//#region packages/builder/src/discovery/entry-paths.ts
|
|
1975
|
+
const scanEntries = (pattern) => {
|
|
1976
|
+
return scanGlob(pattern, process.cwd());
|
|
1977
|
+
};
|
|
1978
|
+
/**
|
|
1979
|
+
* Resolve entry file paths from glob patterns or direct paths.
|
|
1980
|
+
* Used by the discovery system to find entry points for module traversal.
|
|
1981
|
+
* All paths are normalized to POSIX format for consistent cache key matching.
|
|
1982
|
+
* Uses Node.js normalize() + backslash replacement to match normalizePath from @soda-gql/common.
|
|
1983
|
+
*/
|
|
1984
|
+
const resolveEntryPaths = (entries) => {
|
|
1985
|
+
const resolvedPaths = entries.flatMap((entry) => {
|
|
1986
|
+
const absolute = (0, node_path.resolve)(entry);
|
|
1987
|
+
if ((0, node_fs.existsSync)(absolute)) {
|
|
1988
|
+
return [(0, node_path.normalize)(absolute).replace(/\\/g, "/")];
|
|
1989
|
+
}
|
|
1990
|
+
const matches = scanEntries(entry).map((match) => {
|
|
1991
|
+
return (0, node_path.normalize)((0, node_path.resolve)(match)).replace(/\\/g, "/");
|
|
1992
|
+
});
|
|
1993
|
+
return matches;
|
|
1994
|
+
});
|
|
1995
|
+
if (resolvedPaths.length === 0) {
|
|
1996
|
+
return (0, neverthrow.err)({
|
|
1997
|
+
code: "ENTRY_NOT_FOUND",
|
|
1998
|
+
message: `No entry files matched ${entries.join(", ")}`,
|
|
1999
|
+
entry: entries.join(", ")
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
return (0, neverthrow.ok)(resolvedPaths);
|
|
2003
|
+
};
|
|
2004
|
+
|
|
2005
|
+
//#endregion
|
|
2006
|
+
//#region packages/builder/src/intermediate-module/codegen.ts
|
|
2007
|
+
const formatFactory = (expression) => {
|
|
2008
|
+
const trimmed = expression.trim();
|
|
2009
|
+
if (!trimmed.includes("\n")) {
|
|
2010
|
+
return trimmed;
|
|
2011
|
+
}
|
|
2012
|
+
const lines = trimmed.split("\n").map((line) => line.trimEnd());
|
|
2013
|
+
const indented = lines.map((line, index) => index === 0 ? line : ` ${line}`).join("\n");
|
|
2014
|
+
return `(\n ${indented}\n )`;
|
|
2015
|
+
};
|
|
2016
|
+
const buildTree = (definitions) => {
|
|
2017
|
+
const roots = new Map();
|
|
2018
|
+
definitions.forEach((definition) => {
|
|
2019
|
+
const parts = definition.astPath.split(".");
|
|
2020
|
+
const expressionText = definition.expression.trim();
|
|
2021
|
+
if (parts.length === 1) {
|
|
2022
|
+
const rootName = parts[0];
|
|
2023
|
+
if (rootName) {
|
|
2024
|
+
roots.set(rootName, {
|
|
2025
|
+
expression: expressionText,
|
|
2026
|
+
canonicalId: definition.canonicalId,
|
|
2027
|
+
children: new Map()
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
} else {
|
|
2031
|
+
const rootName = parts[0];
|
|
2032
|
+
if (!rootName) return;
|
|
2033
|
+
let root = roots.get(rootName);
|
|
2034
|
+
if (!root) {
|
|
2035
|
+
root = { children: new Map() };
|
|
2036
|
+
roots.set(rootName, root);
|
|
2037
|
+
}
|
|
2038
|
+
let current = root;
|
|
2039
|
+
for (let i = 1; i < parts.length - 1; i++) {
|
|
2040
|
+
const part = parts[i];
|
|
2041
|
+
if (!part) continue;
|
|
2042
|
+
let child = current.children.get(part);
|
|
2043
|
+
if (!child) {
|
|
2044
|
+
child = { children: new Map() };
|
|
2045
|
+
current.children.set(part, child);
|
|
2046
|
+
}
|
|
2047
|
+
current = child;
|
|
2048
|
+
}
|
|
2049
|
+
const leafName = parts[parts.length - 1];
|
|
2050
|
+
if (leafName) {
|
|
2051
|
+
current.children.set(leafName, {
|
|
2052
|
+
expression: expressionText,
|
|
2053
|
+
canonicalId: definition.canonicalId,
|
|
2054
|
+
children: new Map()
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
});
|
|
2059
|
+
return roots;
|
|
2060
|
+
};
|
|
2061
|
+
/**
|
|
2062
|
+
* Check if a string is a valid JavaScript identifier
|
|
2063
|
+
*/
|
|
2064
|
+
const isValidIdentifier = (name) => {
|
|
2065
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) && !isReservedWord(name);
|
|
2066
|
+
};
|
|
2067
|
+
/**
|
|
2068
|
+
* Check if a string is a JavaScript reserved word
|
|
2069
|
+
*/
|
|
2070
|
+
const isReservedWord = (name) => {
|
|
2071
|
+
const reserved = new Set([
|
|
2072
|
+
"break",
|
|
2073
|
+
"case",
|
|
2074
|
+
"catch",
|
|
2075
|
+
"class",
|
|
2076
|
+
"const",
|
|
2077
|
+
"continue",
|
|
2078
|
+
"debugger",
|
|
2079
|
+
"default",
|
|
2080
|
+
"delete",
|
|
2081
|
+
"do",
|
|
2082
|
+
"else",
|
|
2083
|
+
"export",
|
|
2084
|
+
"extends",
|
|
2085
|
+
"finally",
|
|
2086
|
+
"for",
|
|
2087
|
+
"function",
|
|
2088
|
+
"if",
|
|
2089
|
+
"import",
|
|
2090
|
+
"in",
|
|
2091
|
+
"instanceof",
|
|
2092
|
+
"new",
|
|
2093
|
+
"return",
|
|
2094
|
+
"super",
|
|
2095
|
+
"switch",
|
|
2096
|
+
"this",
|
|
2097
|
+
"throw",
|
|
2098
|
+
"try",
|
|
2099
|
+
"typeof",
|
|
2100
|
+
"var",
|
|
2101
|
+
"void",
|
|
2102
|
+
"while",
|
|
2103
|
+
"with",
|
|
2104
|
+
"yield",
|
|
2105
|
+
"let",
|
|
2106
|
+
"static",
|
|
2107
|
+
"enum",
|
|
2108
|
+
"await",
|
|
2109
|
+
"implements",
|
|
2110
|
+
"interface",
|
|
2111
|
+
"package",
|
|
2112
|
+
"private",
|
|
2113
|
+
"protected",
|
|
2114
|
+
"public"
|
|
2115
|
+
]);
|
|
2116
|
+
return reserved.has(name);
|
|
2117
|
+
};
|
|
2118
|
+
/**
|
|
2119
|
+
* Format a key for use in an object literal
|
|
2120
|
+
* Invalid identifiers are quoted, valid ones are not
|
|
2121
|
+
*/
|
|
2122
|
+
const formatObjectKey = (key) => {
|
|
2123
|
+
return isValidIdentifier(key) ? key : `"${key}"`;
|
|
2124
|
+
};
|
|
2125
|
+
const renderTreeNode = (node, indent) => {
|
|
2126
|
+
if (node.expression && node.children.size === 0 && node.canonicalId) {
|
|
2127
|
+
const expr = formatFactory(node.expression);
|
|
2128
|
+
return `registry.addElement("${node.canonicalId}", () => ${expr})`;
|
|
2129
|
+
}
|
|
2130
|
+
const indentStr = " ".repeat(indent);
|
|
2131
|
+
const entries = Array.from(node.children.entries()).map(([key, child]) => {
|
|
2132
|
+
const value = renderTreeNode(child, indent + 1);
|
|
2133
|
+
const formattedKey = formatObjectKey(key);
|
|
2134
|
+
return `${indentStr} ${formattedKey}: ${value},`;
|
|
2135
|
+
});
|
|
2136
|
+
if (entries.length === 0) {
|
|
2137
|
+
return "{}";
|
|
2138
|
+
}
|
|
2139
|
+
return `{\n${entries.join("\n")}\n${indentStr}}`;
|
|
2140
|
+
};
|
|
2141
|
+
const buildNestedObject = (definition) => {
|
|
2142
|
+
const tree = buildTree(definition);
|
|
2143
|
+
const declarations = [];
|
|
2144
|
+
const returnEntries = [];
|
|
2145
|
+
tree.forEach((node, rootName) => {
|
|
2146
|
+
if (node.children.size > 0) {
|
|
2147
|
+
const objectLiteral = renderTreeNode(node, 2);
|
|
2148
|
+
declarations.push(` const ${rootName} = ${objectLiteral};`);
|
|
2149
|
+
returnEntries.push(rootName);
|
|
2150
|
+
} else if (node.expression && node.canonicalId) {
|
|
2151
|
+
const expr = formatFactory(node.expression);
|
|
2152
|
+
declarations.push(` const ${rootName} = registry.addElement("${node.canonicalId}", () => ${expr});`);
|
|
2153
|
+
returnEntries.push(rootName);
|
|
2154
|
+
}
|
|
2155
|
+
});
|
|
2156
|
+
const returnStatement = returnEntries.length > 0 ? ` return {\n${returnEntries.map((name) => ` ${name},`).join("\n")}\n };` : " return {};";
|
|
2157
|
+
if (declarations.length === 0) {
|
|
2158
|
+
return returnStatement;
|
|
2159
|
+
}
|
|
2160
|
+
return `${declarations.join("\n")}\n${returnStatement}`;
|
|
2161
|
+
};
|
|
2162
|
+
/**
|
|
2163
|
+
* Render import statements for the intermediate module using ModuleSummary.
|
|
2164
|
+
* Only includes imports from modules that have gql exports.
|
|
2165
|
+
*/
|
|
2166
|
+
const renderImportStatements = ({ filePath, analysis, analyses }) => {
|
|
2167
|
+
const importLines = [];
|
|
2168
|
+
const importedRootNames = new Set();
|
|
2169
|
+
const namespaceImports = new Set();
|
|
2170
|
+
const importsByFile = new Map();
|
|
2171
|
+
analysis.imports.forEach((imp) => {
|
|
2172
|
+
if (imp.isTypeOnly) {
|
|
2173
|
+
return;
|
|
2174
|
+
}
|
|
2175
|
+
if (!imp.source.startsWith(".")) {
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
const resolvedPath = (0, __soda_gql_common.resolveRelativeImportWithReferences)({
|
|
2179
|
+
filePath,
|
|
2180
|
+
specifier: imp.source,
|
|
2181
|
+
references: analyses
|
|
2182
|
+
});
|
|
2183
|
+
if (!resolvedPath) {
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
const imports = importsByFile.get(resolvedPath) ?? [];
|
|
2187
|
+
imports.push(imp);
|
|
2188
|
+
importsByFile.set(resolvedPath, imports);
|
|
2189
|
+
});
|
|
2190
|
+
importsByFile.forEach((imports, filePath$1) => {
|
|
2191
|
+
const namespaceImport = imports.find((imp) => imp.kind === "namespace");
|
|
2192
|
+
if (namespaceImport) {
|
|
2193
|
+
importLines.push(` const ${namespaceImport.local} = registry.importModule("${filePath$1}");`);
|
|
2194
|
+
namespaceImports.add(namespaceImport.local);
|
|
2195
|
+
importedRootNames.add(namespaceImport.local);
|
|
2196
|
+
} else {
|
|
2197
|
+
const rootNames = new Set();
|
|
2198
|
+
imports.forEach((imp) => {
|
|
2199
|
+
if (imp.kind === "named" || imp.kind === "default") {
|
|
2200
|
+
rootNames.add(imp.local);
|
|
2201
|
+
importedRootNames.add(imp.local);
|
|
2202
|
+
}
|
|
2203
|
+
});
|
|
2204
|
+
if (rootNames.size > 0) {
|
|
2205
|
+
const destructured = Array.from(rootNames).sort().join(", ");
|
|
2206
|
+
importLines.push(` const { ${destructured} } = registry.importModule("${filePath$1}");`);
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
});
|
|
2210
|
+
return {
|
|
2211
|
+
imports: importLines.length > 0 ? `${importLines.join("\n")}` : "",
|
|
2212
|
+
importedRootNames,
|
|
2213
|
+
namespaceImports
|
|
2214
|
+
};
|
|
2215
|
+
};
|
|
2216
|
+
const renderRegistryBlock = ({ filePath, analysis, analyses }) => {
|
|
2217
|
+
const { imports } = renderImportStatements({
|
|
2218
|
+
filePath,
|
|
2219
|
+
analysis,
|
|
2220
|
+
analyses
|
|
2221
|
+
});
|
|
2222
|
+
return [
|
|
2223
|
+
`registry.setModule("${filePath}", () => {`,
|
|
2224
|
+
imports,
|
|
2225
|
+
"",
|
|
2226
|
+
buildNestedObject(analysis.definitions),
|
|
2227
|
+
"});"
|
|
2228
|
+
].join("\n");
|
|
2229
|
+
};
|
|
2230
|
+
|
|
2231
|
+
//#endregion
|
|
2232
|
+
//#region packages/builder/src/intermediate-module/registry.ts
|
|
2233
|
+
const createIntermediateRegistry = () => {
|
|
2234
|
+
const modules = new Map();
|
|
2235
|
+
const elements = new Map();
|
|
2236
|
+
const setModule = (filePath, factory) => {
|
|
2237
|
+
let cached;
|
|
2238
|
+
modules.set(filePath, () => cached ??= factory());
|
|
2239
|
+
};
|
|
2240
|
+
const importModule = (filePath) => {
|
|
2241
|
+
const factory = modules.get(filePath);
|
|
2242
|
+
if (!factory) {
|
|
2243
|
+
throw new Error(`Module not found or yet to be registered: ${filePath}`);
|
|
2244
|
+
}
|
|
2245
|
+
return factory();
|
|
2246
|
+
};
|
|
2247
|
+
const addElement = (canonicalId, factory) => {
|
|
2248
|
+
const builder = factory();
|
|
2249
|
+
__soda_gql_core.GqlElement.setContext(builder, { canonicalId });
|
|
2250
|
+
elements.set(canonicalId, builder);
|
|
2251
|
+
return builder;
|
|
2252
|
+
};
|
|
2253
|
+
const evaluate = () => {
|
|
2254
|
+
for (const mod of modules.values()) {
|
|
2255
|
+
mod();
|
|
2256
|
+
}
|
|
2257
|
+
for (const element of elements.values()) {
|
|
2258
|
+
__soda_gql_core.GqlElement.evaluate(element);
|
|
2259
|
+
}
|
|
2260
|
+
const artifacts = {};
|
|
2261
|
+
for (const [canonicalId, element] of elements.entries()) {
|
|
2262
|
+
if (element instanceof __soda_gql_core.Model) {
|
|
2263
|
+
artifacts[canonicalId] = {
|
|
2264
|
+
type: "model",
|
|
2265
|
+
element
|
|
2266
|
+
};
|
|
2267
|
+
} else if (element instanceof __soda_gql_core.Slice) {
|
|
2268
|
+
artifacts[canonicalId] = {
|
|
2269
|
+
type: "slice",
|
|
2270
|
+
element
|
|
2271
|
+
};
|
|
2272
|
+
} else if (element instanceof __soda_gql_core.ComposedOperation) {
|
|
2273
|
+
artifacts[canonicalId] = {
|
|
2274
|
+
type: "operation",
|
|
2275
|
+
element
|
|
2276
|
+
};
|
|
2277
|
+
} else if (element instanceof __soda_gql_core.InlineOperation) {
|
|
2278
|
+
artifacts[canonicalId] = {
|
|
2279
|
+
type: "inlineOperation",
|
|
2280
|
+
element
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
return artifacts;
|
|
2285
|
+
};
|
|
2286
|
+
const clear = () => {
|
|
2287
|
+
modules.clear();
|
|
2288
|
+
elements.clear();
|
|
2289
|
+
};
|
|
2290
|
+
return {
|
|
2291
|
+
setModule,
|
|
2292
|
+
importModule,
|
|
2293
|
+
addElement,
|
|
2294
|
+
evaluate,
|
|
2295
|
+
clear
|
|
2296
|
+
};
|
|
2297
|
+
};
|
|
2298
|
+
|
|
2299
|
+
//#endregion
|
|
2300
|
+
//#region packages/builder/src/intermediate-module/evaluation.ts
|
|
2301
|
+
const transpile = ({ filePath, sourceCode }) => {
|
|
2302
|
+
try {
|
|
2303
|
+
const result = (0, __swc_core.transformSync)(sourceCode, {
|
|
2304
|
+
filename: `${filePath}.ts`,
|
|
2305
|
+
jsc: {
|
|
2306
|
+
parser: {
|
|
2307
|
+
syntax: "typescript",
|
|
2308
|
+
tsx: false
|
|
2309
|
+
},
|
|
2310
|
+
target: "es2022"
|
|
2311
|
+
},
|
|
2312
|
+
module: { type: "es6" },
|
|
2313
|
+
sourceMaps: false,
|
|
2314
|
+
minify: false
|
|
2315
|
+
});
|
|
2316
|
+
return (0, neverthrow.ok)(result.code);
|
|
2317
|
+
} catch (error) {
|
|
2318
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2319
|
+
return (0, neverthrow.err)({
|
|
2320
|
+
code: "RUNTIME_MODULE_LOAD_FAILED",
|
|
2321
|
+
filePath,
|
|
2322
|
+
astPath: "",
|
|
2323
|
+
message: `SWC transpilation failed: ${message}`
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
};
|
|
2327
|
+
/**
|
|
2328
|
+
* Resolve graphql system path to the bundled CJS file.
|
|
2329
|
+
* Accepts both .ts (for backward compatibility) and .cjs paths.
|
|
2330
|
+
* Maps .ts to sibling .cjs file if it exists.
|
|
2331
|
+
*/
|
|
2332
|
+
function resolveGraphqlSystemPath(configPath) {
|
|
2333
|
+
const ext = (0, node_path.extname)(configPath);
|
|
2334
|
+
if (ext === ".cjs") {
|
|
2335
|
+
return (0, node_path.resolve)(process.cwd(), configPath);
|
|
2336
|
+
}
|
|
2337
|
+
if (ext === ".ts") {
|
|
2338
|
+
const basePath = configPath.slice(0, -3);
|
|
2339
|
+
const cjsPath = `${basePath}.cjs`;
|
|
2340
|
+
const resolvedCjsPath = (0, node_path.resolve)(process.cwd(), cjsPath);
|
|
2341
|
+
if ((0, node_fs.existsSync)(resolvedCjsPath)) {
|
|
2342
|
+
return resolvedCjsPath;
|
|
2343
|
+
}
|
|
2344
|
+
return (0, node_path.resolve)(process.cwd(), configPath);
|
|
2345
|
+
}
|
|
2346
|
+
return (0, node_path.resolve)(process.cwd(), configPath);
|
|
2347
|
+
}
|
|
2348
|
+
/**
|
|
2349
|
+
* Bundle and execute GraphQL system module using rspack + memfs.
|
|
2350
|
+
* Creates a self-contained bundle that can run in VM context.
|
|
2351
|
+
* This is cached per session to avoid re-bundling.
|
|
2352
|
+
*/
|
|
2353
|
+
let cachedGql = null;
|
|
2354
|
+
let cachedModulePath = null;
|
|
2355
|
+
function executeGraphqlSystemModule(modulePath) {
|
|
2356
|
+
if (cachedModulePath === modulePath && cachedGql !== null) {
|
|
2357
|
+
return { gql: cachedGql };
|
|
2358
|
+
}
|
|
2359
|
+
const bundledCode = (0, node_fs.readFileSync)(modulePath, "utf-8");
|
|
2360
|
+
const moduleExports = {};
|
|
2361
|
+
const sandbox = {
|
|
2362
|
+
require: (path) => {
|
|
2363
|
+
if (path === "@soda-gql/core") {
|
|
2364
|
+
return __soda_gql_core;
|
|
2365
|
+
}
|
|
2366
|
+
if (path === "@soda-gql/runtime") {
|
|
2367
|
+
return __soda_gql_runtime;
|
|
2368
|
+
}
|
|
2369
|
+
throw new Error(`Unknown module: ${path}`);
|
|
2370
|
+
},
|
|
2371
|
+
module: { exports: moduleExports },
|
|
2372
|
+
exports: moduleExports,
|
|
2373
|
+
__dirname: (0, node_path.resolve)(modulePath, ".."),
|
|
2374
|
+
__filename: modulePath,
|
|
2375
|
+
global: undefined,
|
|
2376
|
+
globalThis: undefined
|
|
2377
|
+
};
|
|
2378
|
+
sandbox.global = sandbox;
|
|
2379
|
+
sandbox.globalThis = sandbox;
|
|
2380
|
+
new node_vm.Script(bundledCode, { filename: modulePath }).runInNewContext(sandbox);
|
|
2381
|
+
const exportedGql = moduleExports.gql ?? moduleExports.default;
|
|
2382
|
+
if (exportedGql === undefined) {
|
|
2383
|
+
throw new Error(`No 'gql' export found in GraphQL system module: ${modulePath}`);
|
|
2384
|
+
}
|
|
2385
|
+
cachedGql = exportedGql;
|
|
2386
|
+
cachedModulePath = modulePath;
|
|
2387
|
+
return { gql: cachedGql };
|
|
2388
|
+
}
|
|
2389
|
+
/**
|
|
2390
|
+
* Build intermediate modules from dependency graph.
|
|
2391
|
+
* Each intermediate module corresponds to one source file.
|
|
2392
|
+
*/
|
|
2393
|
+
const generateIntermediateModules = function* ({ analyses, targetFiles }) {
|
|
2394
|
+
for (const filePath of targetFiles) {
|
|
2395
|
+
const analysis = analyses.get(filePath);
|
|
2396
|
+
if (!analysis) {
|
|
2397
|
+
continue;
|
|
2398
|
+
}
|
|
2399
|
+
const sourceCode = renderRegistryBlock({
|
|
2400
|
+
filePath,
|
|
2401
|
+
analysis,
|
|
2402
|
+
analyses
|
|
2403
|
+
});
|
|
2404
|
+
const transpiledCodeResult = transpile({
|
|
2405
|
+
filePath,
|
|
2406
|
+
sourceCode
|
|
2407
|
+
});
|
|
2408
|
+
if (transpiledCodeResult.isErr()) {
|
|
2409
|
+
continue;
|
|
2410
|
+
}
|
|
2411
|
+
const transpiledCode = transpiledCodeResult.value;
|
|
2412
|
+
const script = new node_vm.Script(transpiledCode);
|
|
2413
|
+
const hash = (0, node_crypto.createHash)("sha1");
|
|
2414
|
+
hash.update(transpiledCode);
|
|
2415
|
+
const contentHash = hash.digest("hex");
|
|
2416
|
+
const canonicalIds = analysis.definitions.map((definition) => definition.canonicalId);
|
|
2417
|
+
yield {
|
|
2418
|
+
filePath,
|
|
2419
|
+
canonicalIds,
|
|
2420
|
+
sourceCode,
|
|
2421
|
+
transpiledCode,
|
|
2422
|
+
contentHash,
|
|
2423
|
+
script
|
|
2424
|
+
};
|
|
2425
|
+
}
|
|
2426
|
+
};
|
|
2427
|
+
const evaluateIntermediateModules = ({ intermediateModules, graphqlSystemPath }) => {
|
|
2428
|
+
const registry = createIntermediateRegistry();
|
|
2429
|
+
const gqlImportPath = resolveGraphqlSystemPath(graphqlSystemPath);
|
|
2430
|
+
const { gql } = executeGraphqlSystemModule(gqlImportPath);
|
|
2431
|
+
const vmContext = (0, node_vm.createContext)({
|
|
2432
|
+
gql,
|
|
2433
|
+
registry
|
|
2434
|
+
});
|
|
2435
|
+
for (const { script, filePath } of intermediateModules.values()) {
|
|
2436
|
+
try {
|
|
2437
|
+
script.runInContext(vmContext);
|
|
2438
|
+
} catch (error) {
|
|
2439
|
+
console.error(`Error evaluating intermediate module ${filePath}:`, error);
|
|
2440
|
+
throw error;
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
const elements = registry.evaluate();
|
|
2444
|
+
registry.clear();
|
|
2445
|
+
return elements;
|
|
2446
|
+
};
|
|
2447
|
+
|
|
2448
|
+
//#endregion
|
|
2449
|
+
//#region packages/builder/src/internal/graphql-system.ts
|
|
2450
|
+
/**
|
|
2451
|
+
* Create a canonical file name getter based on platform.
|
|
2452
|
+
* On case-sensitive filesystems, paths are returned as-is.
|
|
2453
|
+
* On case-insensitive filesystems, paths are lowercased for comparison.
|
|
2454
|
+
*/
|
|
2455
|
+
const createGetCanonicalFileName = (useCaseSensitiveFileNames) => {
|
|
2456
|
+
return useCaseSensitiveFileNames ? (path) => path : (path) => path.toLowerCase();
|
|
2457
|
+
};
|
|
2458
|
+
/**
|
|
2459
|
+
* Detect if the filesystem is case-sensitive.
|
|
2460
|
+
* We assume Unix-like systems are case-sensitive, and Windows is not.
|
|
2461
|
+
*/
|
|
2462
|
+
const getUseCaseSensitiveFileNames = () => {
|
|
2463
|
+
return process.platform !== "win32";
|
|
2464
|
+
};
|
|
2465
|
+
/**
|
|
2466
|
+
* Create a GraphqlSystemIdentifyHelper from the resolved config.
|
|
2467
|
+
* Uses canonical path comparison to handle casing, symlinks, and aliases.
|
|
2468
|
+
*/
|
|
2469
|
+
const createGraphqlSystemIdentifyHelper = (config) => {
|
|
2470
|
+
const getCanonicalFileName = createGetCanonicalFileName(getUseCaseSensitiveFileNames());
|
|
2471
|
+
const toCanonical = (file) => {
|
|
2472
|
+
const resolved = (0, node_path.resolve)(file);
|
|
2473
|
+
return getCanonicalFileName(resolved);
|
|
2474
|
+
};
|
|
2475
|
+
const graphqlSystemPath = (0, node_path.resolve)(config.outdir, "index.ts");
|
|
2476
|
+
const canonicalGraphqlSystemPath = toCanonical(graphqlSystemPath);
|
|
2477
|
+
const canonicalAliases = new Set(config.graphqlSystemAliases.map((alias) => alias));
|
|
2478
|
+
return {
|
|
2479
|
+
isGraphqlSystemFile: ({ filePath }) => {
|
|
2480
|
+
return toCanonical(filePath) === canonicalGraphqlSystemPath;
|
|
2481
|
+
},
|
|
2482
|
+
isGraphqlSystemImportSpecifier: ({ filePath, specifier }) => {
|
|
2483
|
+
if (canonicalAliases.has(specifier)) {
|
|
2484
|
+
return true;
|
|
2485
|
+
}
|
|
2486
|
+
if (!specifier.startsWith(".")) {
|
|
2487
|
+
return false;
|
|
2488
|
+
}
|
|
2489
|
+
const resolved = (0, __soda_gql_common.resolveRelativeImportWithExistenceCheck)({
|
|
2490
|
+
filePath,
|
|
2491
|
+
specifier
|
|
2492
|
+
});
|
|
2493
|
+
if (!resolved) {
|
|
2494
|
+
return false;
|
|
2495
|
+
}
|
|
2496
|
+
return toCanonical(resolved) === canonicalGraphqlSystemPath;
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
};
|
|
2500
|
+
|
|
2501
|
+
//#endregion
|
|
2502
|
+
//#region packages/builder/src/tracker/file-tracker.ts
|
|
2503
|
+
/**
|
|
2504
|
+
* Create a file tracker that maintains in-memory state for change detection.
|
|
2505
|
+
*
|
|
2506
|
+
* The tracker keeps file metadata (mtime, size) in memory during the process lifetime
|
|
2507
|
+
* and detects which files have been added, updated, or removed. State is scoped to
|
|
2508
|
+
* the process and does not persist across restarts.
|
|
2509
|
+
*/
|
|
2510
|
+
const createFileTracker = () => {
|
|
2511
|
+
let currentScan = { files: new Map() };
|
|
2512
|
+
let nextScan = null;
|
|
2513
|
+
const scan = (extraPaths) => {
|
|
2514
|
+
const allPathsToScan = new Set([...extraPaths, ...currentScan.files.keys()]);
|
|
2515
|
+
const files = new Map();
|
|
2516
|
+
for (const path of allPathsToScan) {
|
|
2517
|
+
try {
|
|
2518
|
+
const normalized = (0, __soda_gql_common.normalizePath)(path);
|
|
2519
|
+
const stats = (0, node_fs.statSync)(normalized);
|
|
2520
|
+
files.set(normalized, {
|
|
2521
|
+
mtimeMs: stats.mtimeMs,
|
|
2522
|
+
size: stats.size
|
|
2523
|
+
});
|
|
2524
|
+
} catch {}
|
|
2525
|
+
}
|
|
2526
|
+
nextScan = { files };
|
|
2527
|
+
return (0, neverthrow.ok)(nextScan);
|
|
2528
|
+
};
|
|
2529
|
+
const detectChanges = () => {
|
|
2530
|
+
const previous = currentScan;
|
|
2531
|
+
const current = nextScan ?? currentScan;
|
|
2532
|
+
const added = new Set();
|
|
2533
|
+
const updated = new Set();
|
|
2534
|
+
const removed = new Set();
|
|
2535
|
+
for (const [path, currentMetadata] of current.files) {
|
|
2536
|
+
const previousMetadata = previous.files.get(path);
|
|
2537
|
+
if (!previousMetadata) {
|
|
2538
|
+
added.add(path);
|
|
2539
|
+
} else if (previousMetadata.mtimeMs !== currentMetadata.mtimeMs || previousMetadata.size !== currentMetadata.size) {
|
|
2540
|
+
updated.add(path);
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
for (const path of previous.files.keys()) {
|
|
2544
|
+
if (!current.files.has(path)) {
|
|
2545
|
+
removed.add(path);
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
return {
|
|
2549
|
+
added,
|
|
2550
|
+
updated,
|
|
2551
|
+
removed
|
|
2552
|
+
};
|
|
2553
|
+
};
|
|
2554
|
+
const update = (scan$1) => {
|
|
2555
|
+
currentScan = scan$1;
|
|
2556
|
+
nextScan = null;
|
|
2557
|
+
};
|
|
2558
|
+
return {
|
|
2559
|
+
scan,
|
|
2560
|
+
detectChanges,
|
|
2561
|
+
update
|
|
2562
|
+
};
|
|
2563
|
+
};
|
|
2564
|
+
/**
|
|
2565
|
+
* Check if a file diff is empty (no changes detected).
|
|
2566
|
+
*/
|
|
2567
|
+
const isEmptyDiff = (diff) => {
|
|
2568
|
+
return diff.added.size === 0 && diff.updated.size === 0 && diff.removed.size === 0;
|
|
2569
|
+
};
|
|
2570
|
+
|
|
2571
|
+
//#endregion
|
|
2572
|
+
//#region packages/builder/src/session/dependency-validation.ts
|
|
2573
|
+
const validateModuleDependencies = ({ analyses }) => {
|
|
2574
|
+
for (const analysis of analyses.values()) {
|
|
2575
|
+
for (const { source, isTypeOnly } of analysis.imports) {
|
|
2576
|
+
if (isTypeOnly) {
|
|
2577
|
+
continue;
|
|
2578
|
+
}
|
|
2579
|
+
if ((0, __soda_gql_common.isRelativeSpecifier)(source)) {
|
|
2580
|
+
const resolvedModule = (0, __soda_gql_common.resolveRelativeImportWithReferences)({
|
|
2581
|
+
filePath: analysis.filePath,
|
|
2582
|
+
specifier: source,
|
|
2583
|
+
references: analyses
|
|
2584
|
+
});
|
|
2585
|
+
if (!resolvedModule) {
|
|
2586
|
+
return (0, neverthrow.err)({
|
|
2587
|
+
code: "MISSING_IMPORT",
|
|
2588
|
+
chain: [analysis.filePath, source]
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
return (0, neverthrow.ok)(null);
|
|
2595
|
+
};
|
|
2596
|
+
|
|
2597
|
+
//#endregion
|
|
2598
|
+
//#region packages/builder/src/session/module-adjacency.ts
|
|
2599
|
+
/**
|
|
2600
|
+
* Extract module-level adjacency from dependency graph.
|
|
2601
|
+
* Returns Map of file path -> set of files that import it.
|
|
2602
|
+
* All paths are normalized to POSIX format for consistent cache key matching.
|
|
2603
|
+
*/
|
|
2604
|
+
const extractModuleAdjacency = ({ snapshots }) => {
|
|
2605
|
+
const importsByModule = new Map();
|
|
2606
|
+
for (const snapshot of snapshots.values()) {
|
|
2607
|
+
const { normalizedFilePath, dependencies, analysis } = snapshot;
|
|
2608
|
+
const imports = new Set();
|
|
2609
|
+
for (const { resolvedPath } of dependencies) {
|
|
2610
|
+
if (resolvedPath && resolvedPath !== normalizedFilePath && snapshots.has(resolvedPath)) {
|
|
2611
|
+
imports.add(resolvedPath);
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
if (dependencies.length === 0 && analysis.imports.length > 0) {
|
|
2615
|
+
for (const imp of analysis.imports) {
|
|
2616
|
+
if (imp.isTypeOnly) {
|
|
2617
|
+
continue;
|
|
2618
|
+
}
|
|
2619
|
+
const resolved = (0, __soda_gql_common.resolveRelativeImportWithReferences)({
|
|
2620
|
+
filePath: normalizedFilePath,
|
|
2621
|
+
specifier: imp.source,
|
|
2622
|
+
references: snapshots
|
|
2623
|
+
});
|
|
2624
|
+
if (resolved) {
|
|
2625
|
+
imports.add(resolved);
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
if (imports.size > 0) {
|
|
2630
|
+
importsByModule.set(normalizedFilePath, imports);
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
const adjacency = new Map();
|
|
2634
|
+
for (const [importer, imports] of importsByModule) {
|
|
2635
|
+
for (const imported of imports) {
|
|
2636
|
+
if (!adjacency.has(imported)) {
|
|
2637
|
+
adjacency.set(imported, new Set());
|
|
2638
|
+
}
|
|
2639
|
+
adjacency.get(imported)?.add(importer);
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
for (const modulePath of snapshots.keys()) {
|
|
2643
|
+
if (!adjacency.has(modulePath)) {
|
|
2644
|
+
adjacency.set(modulePath, new Set());
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
return adjacency;
|
|
2648
|
+
};
|
|
2649
|
+
/**
|
|
2650
|
+
* Collect all modules affected by changes, including transitive dependents.
|
|
2651
|
+
* Uses BFS to traverse module adjacency graph.
|
|
2652
|
+
* All paths are already normalized from extractModuleAdjacency.
|
|
2653
|
+
*/
|
|
2654
|
+
const collectAffectedFiles = (input) => {
|
|
2655
|
+
const { changedFiles, removedFiles, previousModuleAdjacency } = input;
|
|
2656
|
+
const affected = new Set([...changedFiles, ...removedFiles]);
|
|
2657
|
+
const queue = [...changedFiles];
|
|
2658
|
+
const visited = new Set(changedFiles);
|
|
2659
|
+
while (queue.length > 0) {
|
|
2660
|
+
const current = queue.shift();
|
|
2661
|
+
if (!current) break;
|
|
2662
|
+
const dependents = previousModuleAdjacency.get(current);
|
|
2663
|
+
if (dependents) {
|
|
2664
|
+
for (const dependent of dependents) {
|
|
2665
|
+
if (!visited.has(dependent)) {
|
|
2666
|
+
visited.add(dependent);
|
|
2667
|
+
affected.add(dependent);
|
|
2668
|
+
queue.push(dependent);
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
return affected;
|
|
2674
|
+
};
|
|
2675
|
+
|
|
2676
|
+
//#endregion
|
|
2677
|
+
//#region packages/builder/src/session/builder-session.ts
|
|
2678
|
+
/**
|
|
2679
|
+
* Create a new builder session.
|
|
2680
|
+
*
|
|
2681
|
+
* The session maintains in-memory state across builds to enable incremental processing.
|
|
2682
|
+
*/
|
|
2683
|
+
const createBuilderSession = (options) => {
|
|
2684
|
+
const config = options.config;
|
|
2685
|
+
const evaluatorId = options.evaluatorId ?? "default";
|
|
2686
|
+
const entrypoints = new Set(options.entrypointsOverride ?? config.include);
|
|
2687
|
+
const state = {
|
|
2688
|
+
gen: 0,
|
|
2689
|
+
snapshots: new Map(),
|
|
2690
|
+
moduleAdjacency: new Map(),
|
|
2691
|
+
intermediateModules: new Map(),
|
|
2692
|
+
lastArtifact: null
|
|
2693
|
+
};
|
|
2694
|
+
const cacheFactory = createJsonCache({
|
|
2695
|
+
rootDir: (0, node_path.join)(process.cwd(), ".cache", "soda-gql", "builder"),
|
|
2696
|
+
prefix: ["builder"]
|
|
2697
|
+
});
|
|
2698
|
+
const graphqlHelper = createGraphqlSystemIdentifyHelper(config);
|
|
2699
|
+
const ensureAstAnalyzer = (0, __soda_gql_common.cachedFn)(() => createAstAnalyzer({
|
|
2700
|
+
analyzer: config.analyzer,
|
|
2701
|
+
graphqlHelper
|
|
2702
|
+
}));
|
|
2703
|
+
const ensureDiscoveryCache = (0, __soda_gql_common.cachedFn)(() => createDiscoveryCache({
|
|
2704
|
+
factory: cacheFactory,
|
|
2705
|
+
analyzer: config.analyzer,
|
|
2706
|
+
evaluatorId
|
|
2707
|
+
}));
|
|
2708
|
+
const ensureFileTracker = (0, __soda_gql_common.cachedFn)(() => createFileTracker());
|
|
2709
|
+
const build = (options$1) => {
|
|
2710
|
+
const force = options$1?.force ?? false;
|
|
2711
|
+
const entryPathsResult = resolveEntryPaths(Array.from(entrypoints));
|
|
2712
|
+
if (entryPathsResult.isErr()) {
|
|
2713
|
+
return (0, neverthrow.err)(entryPathsResult.error);
|
|
2714
|
+
}
|
|
2715
|
+
const entryPaths = entryPathsResult.value;
|
|
2716
|
+
const tracker = ensureFileTracker();
|
|
2717
|
+
const scanResult = tracker.scan(entryPaths);
|
|
2718
|
+
if (scanResult.isErr()) {
|
|
2719
|
+
const trackerError = scanResult.error;
|
|
2720
|
+
return (0, neverthrow.err)(builderErrors.discoveryIOError(trackerError.type === "scan-failed" ? trackerError.path : "unknown", `Failed to scan files: ${trackerError.message}`));
|
|
2721
|
+
}
|
|
2722
|
+
const currentScan = scanResult.value;
|
|
2723
|
+
const diff = tracker.detectChanges();
|
|
2724
|
+
const prepareResult = prepare({
|
|
2725
|
+
diff,
|
|
2726
|
+
entryPaths,
|
|
2727
|
+
lastArtifact: state.lastArtifact,
|
|
2728
|
+
force
|
|
2729
|
+
});
|
|
2730
|
+
if (prepareResult.isErr()) {
|
|
2731
|
+
return (0, neverthrow.err)(prepareResult.error);
|
|
2732
|
+
}
|
|
2733
|
+
if (prepareResult.value.type === "should-skip") {
|
|
2734
|
+
return (0, neverthrow.ok)(prepareResult.value.data.artifact);
|
|
2735
|
+
}
|
|
2736
|
+
const { changedFiles, removedFiles } = prepareResult.value.data;
|
|
2737
|
+
const discoveryCache = ensureDiscoveryCache();
|
|
2738
|
+
const astAnalyzer = ensureAstAnalyzer();
|
|
2739
|
+
const discoveryResult = discover({
|
|
2740
|
+
discoveryCache,
|
|
2741
|
+
astAnalyzer,
|
|
2742
|
+
removedFiles,
|
|
2743
|
+
changedFiles,
|
|
2744
|
+
entryPaths,
|
|
2745
|
+
previousModuleAdjacency: state.moduleAdjacency
|
|
2746
|
+
});
|
|
2747
|
+
if (discoveryResult.isErr()) {
|
|
2748
|
+
return (0, neverthrow.err)(discoveryResult.error);
|
|
2749
|
+
}
|
|
2750
|
+
const { snapshots, analyses, currentModuleAdjacency, affectedFiles, stats } = discoveryResult.value;
|
|
2751
|
+
const buildResult = buildDiscovered({
|
|
2752
|
+
analyses,
|
|
2753
|
+
affectedFiles,
|
|
2754
|
+
stats,
|
|
2755
|
+
previousIntermediateModules: state.intermediateModules,
|
|
2756
|
+
graphqlSystemPath: (0, node_path.resolve)(config.outdir, "index.ts")
|
|
2757
|
+
});
|
|
2758
|
+
if (buildResult.isErr()) {
|
|
2759
|
+
return (0, neverthrow.err)(buildResult.error);
|
|
2760
|
+
}
|
|
2761
|
+
const { intermediateModules, artifact } = buildResult.value;
|
|
2762
|
+
state.gen++;
|
|
2763
|
+
state.snapshots = snapshots;
|
|
2764
|
+
state.moduleAdjacency = currentModuleAdjacency;
|
|
2765
|
+
state.lastArtifact = artifact;
|
|
2766
|
+
state.intermediateModules = intermediateModules;
|
|
2767
|
+
tracker.update(currentScan);
|
|
2768
|
+
return (0, neverthrow.ok)(artifact);
|
|
2769
|
+
};
|
|
2770
|
+
return {
|
|
2771
|
+
build,
|
|
2772
|
+
getGeneration: () => state.gen,
|
|
2773
|
+
getCurrentArtifact: () => state.lastArtifact
|
|
2774
|
+
};
|
|
2775
|
+
};
|
|
2776
|
+
const prepare = (input) => {
|
|
2777
|
+
const { diff, lastArtifact, force } = input;
|
|
2778
|
+
const changedFiles = new Set([...diff.added, ...diff.updated]);
|
|
2779
|
+
const removedFiles = diff.removed;
|
|
2780
|
+
if (!force && isEmptyDiff(diff) && lastArtifact) {
|
|
2781
|
+
return (0, neverthrow.ok)({
|
|
2782
|
+
type: "should-skip",
|
|
2783
|
+
data: { artifact: lastArtifact }
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
return (0, neverthrow.ok)({
|
|
2787
|
+
type: "should-build",
|
|
2788
|
+
data: {
|
|
2789
|
+
changedFiles,
|
|
2790
|
+
removedFiles
|
|
2791
|
+
}
|
|
2792
|
+
});
|
|
2793
|
+
};
|
|
2794
|
+
const discover = ({ discoveryCache, astAnalyzer, removedFiles, changedFiles, entryPaths, previousModuleAdjacency }) => {
|
|
2795
|
+
const affectedFiles = collectAffectedFiles({
|
|
2796
|
+
changedFiles,
|
|
2797
|
+
removedFiles,
|
|
2798
|
+
previousModuleAdjacency
|
|
2799
|
+
});
|
|
2800
|
+
const discoveryResult = discoverModules({
|
|
2801
|
+
entryPaths,
|
|
2802
|
+
astAnalyzer,
|
|
2803
|
+
incremental: {
|
|
2804
|
+
cache: discoveryCache,
|
|
2805
|
+
changedFiles,
|
|
2806
|
+
removedFiles,
|
|
2807
|
+
affectedFiles
|
|
2808
|
+
}
|
|
2809
|
+
});
|
|
2810
|
+
if (discoveryResult.isErr()) {
|
|
2811
|
+
return (0, neverthrow.err)(discoveryResult.error);
|
|
2812
|
+
}
|
|
2813
|
+
const { cacheHits, cacheMisses, cacheSkips } = discoveryResult.value;
|
|
2814
|
+
const snapshots = new Map(discoveryResult.value.snapshots.map((snapshot) => [snapshot.normalizedFilePath, snapshot]));
|
|
2815
|
+
const analyses = new Map(discoveryResult.value.snapshots.map((snapshot) => [snapshot.normalizedFilePath, snapshot.analysis]));
|
|
2816
|
+
const dependenciesValidationResult = validateModuleDependencies({ analyses });
|
|
2817
|
+
if (dependenciesValidationResult.isErr()) {
|
|
2818
|
+
const error = dependenciesValidationResult.error;
|
|
2819
|
+
return (0, neverthrow.err)(builderErrors.graphMissingImport(error.chain[0], error.chain[1]));
|
|
2820
|
+
}
|
|
2821
|
+
const currentModuleAdjacency = extractModuleAdjacency({ snapshots });
|
|
2822
|
+
const stats = {
|
|
2823
|
+
hits: cacheHits,
|
|
2824
|
+
misses: cacheMisses,
|
|
2825
|
+
skips: cacheSkips
|
|
2826
|
+
};
|
|
2827
|
+
return (0, neverthrow.ok)({
|
|
2828
|
+
snapshots,
|
|
2829
|
+
analyses,
|
|
2830
|
+
currentModuleAdjacency,
|
|
2831
|
+
affectedFiles,
|
|
2832
|
+
stats
|
|
2833
|
+
});
|
|
2834
|
+
};
|
|
2835
|
+
const buildDiscovered = ({ analyses, affectedFiles, stats, previousIntermediateModules, graphqlSystemPath }) => {
|
|
2836
|
+
const intermediateModules = new Map(previousIntermediateModules);
|
|
2837
|
+
const targetFiles = new Set(affectedFiles);
|
|
2838
|
+
for (const filePath of analyses.keys()) {
|
|
2839
|
+
if (!previousIntermediateModules.has(filePath)) {
|
|
2840
|
+
targetFiles.add(filePath);
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
if (targetFiles.size === 0) {
|
|
2844
|
+
for (const filePath of analyses.keys()) {
|
|
2845
|
+
targetFiles.add(filePath);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
for (const targetFilePath of targetFiles) {
|
|
2849
|
+
intermediateModules.delete(targetFilePath);
|
|
2850
|
+
}
|
|
2851
|
+
for (const intermediateModule of generateIntermediateModules({
|
|
2852
|
+
analyses,
|
|
2853
|
+
targetFiles
|
|
2854
|
+
})) {
|
|
2855
|
+
intermediateModules.set(intermediateModule.filePath, intermediateModule);
|
|
2856
|
+
}
|
|
2857
|
+
const elements = evaluateIntermediateModules({
|
|
2858
|
+
intermediateModules,
|
|
2859
|
+
graphqlSystemPath
|
|
2860
|
+
});
|
|
2861
|
+
const artifactResult = buildArtifact({
|
|
2862
|
+
analyses,
|
|
2863
|
+
elements,
|
|
2864
|
+
stats
|
|
2865
|
+
});
|
|
2866
|
+
if (artifactResult.isErr()) {
|
|
2867
|
+
return (0, neverthrow.err)(artifactResult.error);
|
|
2868
|
+
}
|
|
2869
|
+
return (0, neverthrow.ok)({
|
|
2870
|
+
intermediateModules,
|
|
2871
|
+
artifact: artifactResult.value
|
|
2872
|
+
});
|
|
2873
|
+
};
|
|
2874
|
+
|
|
2875
|
+
//#endregion
|
|
2876
|
+
//#region packages/builder/src/service.ts
|
|
2877
|
+
/**
|
|
2878
|
+
* Create a builder service instance with session support.
|
|
2879
|
+
*
|
|
2880
|
+
* The service maintains a long-lived session for incremental builds.
|
|
2881
|
+
* File changes are automatically detected using an internal file tracker.
|
|
2882
|
+
* First build() call initializes the session, subsequent calls perform
|
|
2883
|
+
* incremental builds based on detected file changes.
|
|
2884
|
+
*
|
|
2885
|
+
* Note: Empty entry arrays will produce ENTRY_NOT_FOUND errors at build time.
|
|
2886
|
+
*
|
|
2887
|
+
* @param config - Builder configuration including entry patterns, analyzer, mode, and optional debugDir
|
|
2888
|
+
* @returns BuilderService instance
|
|
2889
|
+
*/
|
|
2890
|
+
const createBuilderService = ({ config, entrypointsOverride }) => {
|
|
2891
|
+
const session = createBuilderSession({
|
|
2892
|
+
config,
|
|
2893
|
+
entrypointsOverride
|
|
2894
|
+
});
|
|
2895
|
+
return {
|
|
2896
|
+
build: (options) => session.build(options),
|
|
2897
|
+
getGeneration: () => session.getGeneration(),
|
|
2898
|
+
getCurrentArtifact: () => session.getCurrentArtifact()
|
|
2899
|
+
};
|
|
2900
|
+
};
|
|
2901
|
+
|
|
2902
|
+
//#endregion
|
|
2903
|
+
exports.BuilderArtifactSchema = BuilderArtifactSchema;
|
|
2904
|
+
Object.defineProperty(exports, 'buildAstPath', {
|
|
2905
|
+
enumerable: true,
|
|
2906
|
+
get: function () {
|
|
2907
|
+
return __soda_gql_common.buildAstPath;
|
|
2908
|
+
}
|
|
2909
|
+
});
|
|
2910
|
+
exports.createBuilderService = createBuilderService;
|
|
2911
|
+
exports.createBuilderSession = createBuilderSession;
|
|
2912
|
+
Object.defineProperty(exports, 'createCanonicalId', {
|
|
2913
|
+
enumerable: true,
|
|
2914
|
+
get: function () {
|
|
2915
|
+
return __soda_gql_common.createCanonicalId;
|
|
2916
|
+
}
|
|
2917
|
+
});
|
|
2918
|
+
Object.defineProperty(exports, 'createCanonicalTracker', {
|
|
2919
|
+
enumerable: true,
|
|
2920
|
+
get: function () {
|
|
2921
|
+
return __soda_gql_common.createCanonicalTracker;
|
|
2922
|
+
}
|
|
2923
|
+
});
|
|
2924
|
+
Object.defineProperty(exports, 'createOccurrenceTracker', {
|
|
2925
|
+
enumerable: true,
|
|
2926
|
+
get: function () {
|
|
2927
|
+
return __soda_gql_common.createOccurrenceTracker;
|
|
2928
|
+
}
|
|
2929
|
+
});
|
|
2930
|
+
Object.defineProperty(exports, 'createPathTracker', {
|
|
2931
|
+
enumerable: true,
|
|
2932
|
+
get: function () {
|
|
2933
|
+
return __soda_gql_common.createPathTracker;
|
|
2934
|
+
}
|
|
2935
|
+
});
|