@soda-gql/builder 0.7.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +662 -324
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +109 -4
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +110 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +661 -327
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -25,8 +25,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
}) : target, mod));
|
|
26
26
|
|
|
27
27
|
//#endregion
|
|
28
|
-
let node_crypto = require("node:crypto");
|
|
29
28
|
let node_fs = require("node:fs");
|
|
29
|
+
let node_fs_promises = require("node:fs/promises");
|
|
30
|
+
let neverthrow = require("neverthrow");
|
|
31
|
+
let zod = require("zod");
|
|
32
|
+
let node_crypto = require("node:crypto");
|
|
30
33
|
let node_path = require("node:path");
|
|
31
34
|
let node_vm = require("node:vm");
|
|
32
35
|
let __soda_gql_common = require("@soda-gql/common");
|
|
@@ -39,14 +42,452 @@ __soda_gql_core_runtime = __toESM(__soda_gql_core_runtime);
|
|
|
39
42
|
let __soda_gql_runtime = require("@soda-gql/runtime");
|
|
40
43
|
__soda_gql_runtime = __toESM(__soda_gql_runtime);
|
|
41
44
|
let __swc_core = require("@swc/core");
|
|
42
|
-
let neverthrow = require("neverthrow");
|
|
43
|
-
let node_fs_promises = require("node:fs/promises");
|
|
44
|
-
let zod = require("zod");
|
|
45
45
|
let typescript = require("typescript");
|
|
46
46
|
typescript = __toESM(typescript);
|
|
47
47
|
let fast_glob = require("fast-glob");
|
|
48
48
|
fast_glob = __toESM(fast_glob);
|
|
49
49
|
|
|
50
|
+
//#region packages/builder/src/schemas/artifact.ts
|
|
51
|
+
const BuilderArtifactElementMetadataSchema = zod.z.object({
|
|
52
|
+
sourcePath: 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
|
+
})
|
|
69
|
+
});
|
|
70
|
+
const BuilderArtifactFragmentSchema = zod.z.object({
|
|
71
|
+
id: zod.z.string(),
|
|
72
|
+
type: zod.z.literal("fragment"),
|
|
73
|
+
metadata: BuilderArtifactElementMetadataSchema,
|
|
74
|
+
prebuild: zod.z.object({ typename: zod.z.string() })
|
|
75
|
+
});
|
|
76
|
+
const BuilderArtifactElementSchema = zod.z.discriminatedUnion("type", [BuilderArtifactOperationSchema, BuilderArtifactFragmentSchema]);
|
|
77
|
+
const BuilderArtifactMetaSchema = zod.z.object({
|
|
78
|
+
version: zod.z.string(),
|
|
79
|
+
createdAt: zod.z.string()
|
|
80
|
+
});
|
|
81
|
+
const BuilderArtifactSchema = zod.z.object({
|
|
82
|
+
meta: BuilderArtifactMetaSchema.optional(),
|
|
83
|
+
elements: zod.z.record(zod.z.string(), BuilderArtifactElementSchema),
|
|
84
|
+
report: zod.z.object({
|
|
85
|
+
durationMs: zod.z.number(),
|
|
86
|
+
warnings: zod.z.array(zod.z.string()),
|
|
87
|
+
stats: zod.z.object({
|
|
88
|
+
hits: zod.z.number(),
|
|
89
|
+
misses: zod.z.number(),
|
|
90
|
+
skips: zod.z.number()
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region packages/builder/src/artifact/loader.ts
|
|
97
|
+
/**
|
|
98
|
+
* Load a pre-built artifact from a JSON file asynchronously.
|
|
99
|
+
*
|
|
100
|
+
* @param path - Absolute path to the artifact JSON file
|
|
101
|
+
* @returns Result with the parsed artifact or an error
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* const result = await loadArtifact("/path/to/artifact.json");
|
|
106
|
+
* if (result.isOk()) {
|
|
107
|
+
* const artifact = result.value;
|
|
108
|
+
* // Use artifact...
|
|
109
|
+
* }
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
const loadArtifact = async (path) => {
|
|
113
|
+
if (!(0, node_fs.existsSync)(path)) {
|
|
114
|
+
return (0, neverthrow.err)({
|
|
115
|
+
code: "ARTIFACT_NOT_FOUND",
|
|
116
|
+
message: `Artifact file not found: ${path}`,
|
|
117
|
+
filePath: path
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
let content;
|
|
121
|
+
try {
|
|
122
|
+
content = await (0, node_fs_promises.readFile)(path, "utf-8");
|
|
123
|
+
} catch (error) {
|
|
124
|
+
return (0, neverthrow.err)({
|
|
125
|
+
code: "ARTIFACT_NOT_FOUND",
|
|
126
|
+
message: `Failed to read artifact file: ${error instanceof Error ? error.message : String(error)}`,
|
|
127
|
+
filePath: path
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return parseAndValidateArtifact(content, path);
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Load a pre-built artifact from a JSON file synchronously.
|
|
134
|
+
*
|
|
135
|
+
* @param path - Absolute path to the artifact JSON file
|
|
136
|
+
* @returns Result with the parsed artifact or an error
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* const result = loadArtifactSync("/path/to/artifact.json");
|
|
141
|
+
* if (result.isOk()) {
|
|
142
|
+
* const artifact = result.value;
|
|
143
|
+
* // Use artifact...
|
|
144
|
+
* }
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
const loadArtifactSync = (path) => {
|
|
148
|
+
if (!(0, node_fs.existsSync)(path)) {
|
|
149
|
+
return (0, neverthrow.err)({
|
|
150
|
+
code: "ARTIFACT_NOT_FOUND",
|
|
151
|
+
message: `Artifact file not found: ${path}`,
|
|
152
|
+
filePath: path
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
let content;
|
|
156
|
+
try {
|
|
157
|
+
content = (0, node_fs.readFileSync)(path, "utf-8");
|
|
158
|
+
} catch (error) {
|
|
159
|
+
return (0, neverthrow.err)({
|
|
160
|
+
code: "ARTIFACT_NOT_FOUND",
|
|
161
|
+
message: `Failed to read artifact file: ${error instanceof Error ? error.message : String(error)}`,
|
|
162
|
+
filePath: path
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return parseAndValidateArtifact(content, path);
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* Parse JSON content and validate against BuilderArtifactSchema.
|
|
169
|
+
*/
|
|
170
|
+
function parseAndValidateArtifact(content, filePath) {
|
|
171
|
+
let parsed;
|
|
172
|
+
try {
|
|
173
|
+
parsed = JSON.parse(content);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
return (0, neverthrow.err)({
|
|
176
|
+
code: "ARTIFACT_PARSE_ERROR",
|
|
177
|
+
message: `Invalid JSON in artifact file: ${error instanceof Error ? error.message : String(error)}`,
|
|
178
|
+
filePath
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const validated = BuilderArtifactSchema.safeParse(parsed);
|
|
182
|
+
if (!validated.success) {
|
|
183
|
+
return (0, neverthrow.err)({
|
|
184
|
+
code: "ARTIFACT_VALIDATION_ERROR",
|
|
185
|
+
message: `Invalid artifact structure: ${validated.error.message}`,
|
|
186
|
+
filePath
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return (0, neverthrow.ok)(validated.data);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region packages/builder/src/errors/formatter.ts
|
|
194
|
+
/**
|
|
195
|
+
* Hints for each error code to help users understand and fix issues.
|
|
196
|
+
*/
|
|
197
|
+
const errorHints = {
|
|
198
|
+
ELEMENT_EVALUATION_FAILED: "Check if all imported fragments are properly exported and included in entry patterns.",
|
|
199
|
+
GRAPH_CIRCULAR_DEPENDENCY: "Break the circular import by extracting shared types to a common module.",
|
|
200
|
+
GRAPH_MISSING_IMPORT: "Verify the import path exists and the module is included in entry patterns.",
|
|
201
|
+
RUNTIME_MODULE_LOAD_FAILED: "Ensure the module can be imported and all dependencies are installed.",
|
|
202
|
+
CONFIG_NOT_FOUND: "Create a soda-gql.config.ts file in your project root.",
|
|
203
|
+
CONFIG_INVALID: "Check your configuration file for syntax errors or invalid options.",
|
|
204
|
+
ENTRY_NOT_FOUND: "Verify the entry pattern matches your file structure.",
|
|
205
|
+
INTERNAL_INVARIANT: "This is an internal error. Please report it at https://github.com/soda-gql/soda-gql/issues"
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Format a BuilderError into a structured FormattedError object.
|
|
209
|
+
*/
|
|
210
|
+
const formatBuilderErrorStructured = (error) => {
|
|
211
|
+
const base = {
|
|
212
|
+
code: error.code,
|
|
213
|
+
message: error.message,
|
|
214
|
+
hint: errorHints[error.code],
|
|
215
|
+
cause: "cause" in error ? error.cause : undefined
|
|
216
|
+
};
|
|
217
|
+
switch (error.code) {
|
|
218
|
+
case "ELEMENT_EVALUATION_FAILED": return {
|
|
219
|
+
...base,
|
|
220
|
+
location: {
|
|
221
|
+
modulePath: error.modulePath,
|
|
222
|
+
astPath: error.astPath || undefined
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
case "RUNTIME_MODULE_LOAD_FAILED": return {
|
|
226
|
+
...base,
|
|
227
|
+
location: {
|
|
228
|
+
modulePath: error.filePath,
|
|
229
|
+
astPath: error.astPath
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
case "GRAPH_MISSING_IMPORT": return {
|
|
233
|
+
...base,
|
|
234
|
+
relatedFiles: [error.importer, error.importee]
|
|
235
|
+
};
|
|
236
|
+
case "GRAPH_CIRCULAR_DEPENDENCY": return {
|
|
237
|
+
...base,
|
|
238
|
+
relatedFiles: error.chain
|
|
239
|
+
};
|
|
240
|
+
case "CONFIG_NOT_FOUND":
|
|
241
|
+
case "CONFIG_INVALID": return {
|
|
242
|
+
...base,
|
|
243
|
+
location: { modulePath: error.path }
|
|
244
|
+
};
|
|
245
|
+
case "FINGERPRINT_FAILED": return {
|
|
246
|
+
...base,
|
|
247
|
+
location: { modulePath: error.filePath }
|
|
248
|
+
};
|
|
249
|
+
case "DISCOVERY_IO_ERROR": return {
|
|
250
|
+
...base,
|
|
251
|
+
location: { modulePath: error.path }
|
|
252
|
+
};
|
|
253
|
+
default: return base;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
/**
|
|
257
|
+
* Format a BuilderError for CLI/stderr output with human-readable formatting.
|
|
258
|
+
* Includes location, hint, and related files when available.
|
|
259
|
+
*/
|
|
260
|
+
const formatBuilderErrorForCLI = (error) => {
|
|
261
|
+
const formatted = formatBuilderErrorStructured(error);
|
|
262
|
+
const lines = [];
|
|
263
|
+
lines.push(`Error [${formatted.code}]: ${formatted.message}`);
|
|
264
|
+
if (formatted.location) {
|
|
265
|
+
lines.push(` at ${formatted.location.modulePath}`);
|
|
266
|
+
if (formatted.location.astPath) {
|
|
267
|
+
lines.push(` in ${formatted.location.astPath}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (formatted.hint) {
|
|
271
|
+
lines.push("");
|
|
272
|
+
lines.push(` Hint: ${formatted.hint}`);
|
|
273
|
+
}
|
|
274
|
+
if (formatted.relatedFiles && formatted.relatedFiles.length > 0) {
|
|
275
|
+
lines.push("");
|
|
276
|
+
lines.push(" Related files:");
|
|
277
|
+
for (const file of formatted.relatedFiles) {
|
|
278
|
+
lines.push(` - ${file}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return lines.join("\n");
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region packages/builder/src/errors.ts
|
|
286
|
+
/**
|
|
287
|
+
* Error constructor helpers for concise error creation.
|
|
288
|
+
*/
|
|
289
|
+
const builderErrors = {
|
|
290
|
+
entryNotFound: (entry, message) => ({
|
|
291
|
+
code: "ENTRY_NOT_FOUND",
|
|
292
|
+
message: message ?? `Entry not found: ${entry}`,
|
|
293
|
+
entry
|
|
294
|
+
}),
|
|
295
|
+
configNotFound: (path, message) => ({
|
|
296
|
+
code: "CONFIG_NOT_FOUND",
|
|
297
|
+
message: message ?? `Config file not found: ${path}`,
|
|
298
|
+
path
|
|
299
|
+
}),
|
|
300
|
+
configInvalid: (path, message, cause) => ({
|
|
301
|
+
code: "CONFIG_INVALID",
|
|
302
|
+
message,
|
|
303
|
+
path,
|
|
304
|
+
cause
|
|
305
|
+
}),
|
|
306
|
+
discoveryIOError: (path, message, errno, cause) => ({
|
|
307
|
+
code: "DISCOVERY_IO_ERROR",
|
|
308
|
+
message,
|
|
309
|
+
path,
|
|
310
|
+
errno,
|
|
311
|
+
cause
|
|
312
|
+
}),
|
|
313
|
+
fingerprintFailed: (filePath, message, cause) => ({
|
|
314
|
+
code: "FINGERPRINT_FAILED",
|
|
315
|
+
message,
|
|
316
|
+
filePath,
|
|
317
|
+
cause
|
|
318
|
+
}),
|
|
319
|
+
unsupportedAnalyzer: (analyzer, message) => ({
|
|
320
|
+
code: "UNSUPPORTED_ANALYZER",
|
|
321
|
+
message: message ?? `Unsupported analyzer: ${analyzer}`,
|
|
322
|
+
analyzer
|
|
323
|
+
}),
|
|
324
|
+
canonicalPathInvalid: (path, reason) => ({
|
|
325
|
+
code: "CANONICAL_PATH_INVALID",
|
|
326
|
+
message: `Invalid canonical path: ${path}${reason ? ` (${reason})` : ""}`,
|
|
327
|
+
path,
|
|
328
|
+
reason
|
|
329
|
+
}),
|
|
330
|
+
canonicalScopeMismatch: (expected, actual) => ({
|
|
331
|
+
code: "CANONICAL_SCOPE_MISMATCH",
|
|
332
|
+
message: `Scope mismatch: expected ${expected}, got ${actual}`,
|
|
333
|
+
expected,
|
|
334
|
+
actual
|
|
335
|
+
}),
|
|
336
|
+
graphCircularDependency: (chain) => ({
|
|
337
|
+
code: "GRAPH_CIRCULAR_DEPENDENCY",
|
|
338
|
+
message: `Circular dependency detected: ${chain.join(" → ")}`,
|
|
339
|
+
chain
|
|
340
|
+
}),
|
|
341
|
+
graphMissingImport: (importer, importee) => ({
|
|
342
|
+
code: "GRAPH_MISSING_IMPORT",
|
|
343
|
+
message: `Missing import: "${importer}" imports "${importee}" but it's not in the graph`,
|
|
344
|
+
importer,
|
|
345
|
+
importee
|
|
346
|
+
}),
|
|
347
|
+
docDuplicate: (name, sources) => ({
|
|
348
|
+
code: "DOC_DUPLICATE",
|
|
349
|
+
message: `Duplicate document name: ${name} found in ${sources.length} files`,
|
|
350
|
+
name,
|
|
351
|
+
sources
|
|
352
|
+
}),
|
|
353
|
+
writeFailed: (outPath, message, cause) => ({
|
|
354
|
+
code: "WRITE_FAILED",
|
|
355
|
+
message,
|
|
356
|
+
outPath,
|
|
357
|
+
cause
|
|
358
|
+
}),
|
|
359
|
+
cacheCorrupted: (message, cachePath, cause) => ({
|
|
360
|
+
code: "CACHE_CORRUPTED",
|
|
361
|
+
message,
|
|
362
|
+
cachePath,
|
|
363
|
+
cause
|
|
364
|
+
}),
|
|
365
|
+
runtimeModuleLoadFailed: (filePath, astPath, message, cause) => ({
|
|
366
|
+
code: "RUNTIME_MODULE_LOAD_FAILED",
|
|
367
|
+
message,
|
|
368
|
+
filePath,
|
|
369
|
+
astPath,
|
|
370
|
+
cause
|
|
371
|
+
}),
|
|
372
|
+
artifactRegistrationFailed: (elementId, reason) => ({
|
|
373
|
+
code: "ARTIFACT_REGISTRATION_FAILED",
|
|
374
|
+
message: `Failed to register artifact element ${elementId}: ${reason}`,
|
|
375
|
+
elementId,
|
|
376
|
+
reason
|
|
377
|
+
}),
|
|
378
|
+
elementEvaluationFailed: (modulePath, astPath, message, cause) => ({
|
|
379
|
+
code: "ELEMENT_EVALUATION_FAILED",
|
|
380
|
+
message,
|
|
381
|
+
modulePath,
|
|
382
|
+
astPath,
|
|
383
|
+
cause
|
|
384
|
+
}),
|
|
385
|
+
internalInvariant: (message, context, cause) => ({
|
|
386
|
+
code: "INTERNAL_INVARIANT",
|
|
387
|
+
message: `Internal invariant violated: ${message}`,
|
|
388
|
+
context,
|
|
389
|
+
cause
|
|
390
|
+
})
|
|
391
|
+
};
|
|
392
|
+
/**
|
|
393
|
+
* Convenience helper to create an err Result from BuilderError.
|
|
394
|
+
*/
|
|
395
|
+
const builderErr = (error) => (0, neverthrow.err)(error);
|
|
396
|
+
/**
|
|
397
|
+
* Type guard for BuilderError.
|
|
398
|
+
*/
|
|
399
|
+
const isBuilderError = (error) => {
|
|
400
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" && "message" in error && typeof error.message === "string";
|
|
401
|
+
};
|
|
402
|
+
/**
|
|
403
|
+
* Format BuilderError for console output (human-readable).
|
|
404
|
+
*/
|
|
405
|
+
const formatBuilderError = (error) => {
|
|
406
|
+
const lines = [];
|
|
407
|
+
lines.push(`Error [${error.code}]: ${error.message}`);
|
|
408
|
+
switch (error.code) {
|
|
409
|
+
case "ENTRY_NOT_FOUND":
|
|
410
|
+
lines.push(` Entry: ${error.entry}`);
|
|
411
|
+
break;
|
|
412
|
+
case "CONFIG_NOT_FOUND":
|
|
413
|
+
case "CONFIG_INVALID":
|
|
414
|
+
lines.push(` Path: ${error.path}`);
|
|
415
|
+
if (error.code === "CONFIG_INVALID" && error.cause) {
|
|
416
|
+
lines.push(` Cause: ${error.cause}`);
|
|
417
|
+
}
|
|
418
|
+
break;
|
|
419
|
+
case "DISCOVERY_IO_ERROR":
|
|
420
|
+
lines.push(` Path: ${error.path}`);
|
|
421
|
+
if (error.errno !== undefined) {
|
|
422
|
+
lines.push(` Errno: ${error.errno}`);
|
|
423
|
+
}
|
|
424
|
+
break;
|
|
425
|
+
case "FINGERPRINT_FAILED":
|
|
426
|
+
lines.push(` File: ${error.filePath}`);
|
|
427
|
+
break;
|
|
428
|
+
case "CANONICAL_PATH_INVALID":
|
|
429
|
+
lines.push(` Path: ${error.path}`);
|
|
430
|
+
if (error.reason) {
|
|
431
|
+
lines.push(` Reason: ${error.reason}`);
|
|
432
|
+
}
|
|
433
|
+
break;
|
|
434
|
+
case "CANONICAL_SCOPE_MISMATCH":
|
|
435
|
+
lines.push(` Expected: ${error.expected}`);
|
|
436
|
+
lines.push(` Actual: ${error.actual}`);
|
|
437
|
+
break;
|
|
438
|
+
case "GRAPH_CIRCULAR_DEPENDENCY":
|
|
439
|
+
lines.push(` Chain: ${error.chain.join(" → ")}`);
|
|
440
|
+
break;
|
|
441
|
+
case "GRAPH_MISSING_IMPORT":
|
|
442
|
+
lines.push(` Importer: ${error.importer}`);
|
|
443
|
+
lines.push(` Importee: ${error.importee}`);
|
|
444
|
+
break;
|
|
445
|
+
case "DOC_DUPLICATE":
|
|
446
|
+
lines.push(` Name: ${error.name}`);
|
|
447
|
+
lines.push(` Sources:\n ${error.sources.join("\n ")}`);
|
|
448
|
+
break;
|
|
449
|
+
case "WRITE_FAILED":
|
|
450
|
+
lines.push(` Output path: ${error.outPath}`);
|
|
451
|
+
break;
|
|
452
|
+
case "CACHE_CORRUPTED":
|
|
453
|
+
if (error.cachePath) {
|
|
454
|
+
lines.push(` Cache path: ${error.cachePath}`);
|
|
455
|
+
}
|
|
456
|
+
break;
|
|
457
|
+
case "RUNTIME_MODULE_LOAD_FAILED":
|
|
458
|
+
lines.push(` File: ${error.filePath}`);
|
|
459
|
+
lines.push(` AST path: ${error.astPath}`);
|
|
460
|
+
break;
|
|
461
|
+
case "ARTIFACT_REGISTRATION_FAILED":
|
|
462
|
+
lines.push(` Element ID: ${error.elementId}`);
|
|
463
|
+
lines.push(` Reason: ${error.reason}`);
|
|
464
|
+
break;
|
|
465
|
+
case "ELEMENT_EVALUATION_FAILED":
|
|
466
|
+
lines.push(` at ${error.modulePath}`);
|
|
467
|
+
if (error.astPath) {
|
|
468
|
+
lines.push(` in ${error.astPath}`);
|
|
469
|
+
}
|
|
470
|
+
break;
|
|
471
|
+
case "INTERNAL_INVARIANT":
|
|
472
|
+
if (error.context) {
|
|
473
|
+
lines.push(` Context: ${error.context}`);
|
|
474
|
+
}
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
if ("cause" in error && error.cause && !["CONFIG_INVALID"].includes(error.code)) {
|
|
478
|
+
lines.push(` Caused by: ${error.cause}`);
|
|
479
|
+
}
|
|
480
|
+
return lines.join("\n");
|
|
481
|
+
};
|
|
482
|
+
/**
|
|
483
|
+
* Assert unreachable code path (for exhaustiveness checks).
|
|
484
|
+
* This is the ONLY acceptable throw in builder code.
|
|
485
|
+
*/
|
|
486
|
+
const assertUnreachable = (value, context) => {
|
|
487
|
+
throw new Error(`Unreachable code path${context ? ` in ${context}` : ""}: received ${JSON.stringify(value)}`);
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
//#endregion
|
|
50
491
|
//#region packages/builder/src/scheduler/effects.ts
|
|
51
492
|
/**
|
|
52
493
|
* File read effect - reads a file from the filesystem.
|
|
@@ -171,6 +612,8 @@ var OptionalFileStatEffect = class extends __soda_gql_common.Effect {
|
|
|
171
612
|
* Supports both sync and async schedulers, enabling parallel element evaluation
|
|
172
613
|
* when using async scheduler.
|
|
173
614
|
*
|
|
615
|
+
* Wraps errors with module context for better debugging.
|
|
616
|
+
*
|
|
174
617
|
* @example
|
|
175
618
|
* yield* new ElementEvaluationEffect(element).run();
|
|
176
619
|
*/
|
|
@@ -179,19 +622,39 @@ var ElementEvaluationEffect = class extends __soda_gql_common.Effect {
|
|
|
179
622
|
super();
|
|
180
623
|
this.element = element;
|
|
181
624
|
}
|
|
625
|
+
/**
|
|
626
|
+
* Wrap an error with element context for better debugging.
|
|
627
|
+
*/
|
|
628
|
+
wrapError(error) {
|
|
629
|
+
const context = __soda_gql_core.GqlElement.getContext(this.element);
|
|
630
|
+
if (context) {
|
|
631
|
+
const { filePath, astPath } = (0, __soda_gql_common.parseCanonicalId)(context.canonicalId);
|
|
632
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
633
|
+
throw builderErrors.elementEvaluationFailed(filePath, astPath, message, error);
|
|
634
|
+
}
|
|
635
|
+
throw error;
|
|
636
|
+
}
|
|
182
637
|
_executeSync() {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
638
|
+
try {
|
|
639
|
+
const generator = __soda_gql_core.GqlElement.createEvaluationGenerator(this.element);
|
|
640
|
+
const result = generator.next();
|
|
641
|
+
while (!result.done) {
|
|
642
|
+
throw new Error("Async operation required during sync element evaluation");
|
|
643
|
+
}
|
|
644
|
+
} catch (error) {
|
|
645
|
+
this.wrapError(error);
|
|
187
646
|
}
|
|
188
647
|
}
|
|
189
648
|
async _executeAsync() {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
649
|
+
try {
|
|
650
|
+
const generator = __soda_gql_core.GqlElement.createEvaluationGenerator(this.element);
|
|
651
|
+
let result = generator.next();
|
|
652
|
+
while (!result.done) {
|
|
653
|
+
await result.value;
|
|
654
|
+
result = generator.next();
|
|
655
|
+
}
|
|
656
|
+
} catch (error) {
|
|
657
|
+
this.wrapError(error);
|
|
195
658
|
}
|
|
196
659
|
}
|
|
197
660
|
};
|
|
@@ -887,74 +1350,33 @@ const createGraphqlSystemIdentifyHelper = (config) => {
|
|
|
887
1350
|
return getCanonicalFileName((0, node_fs.realpathSync)(resolved));
|
|
888
1351
|
} catch {
|
|
889
1352
|
return getCanonicalFileName(resolved);
|
|
890
|
-
}
|
|
891
|
-
};
|
|
892
|
-
const graphqlSystemPath = (0, node_path.resolve)(config.outdir, "index.ts");
|
|
893
|
-
const canonicalGraphqlSystemPath = toCanonical(graphqlSystemPath);
|
|
894
|
-
const canonicalAliases = new Set(config.graphqlSystemAliases.map((alias) => alias));
|
|
895
|
-
return {
|
|
896
|
-
isGraphqlSystemFile: ({ filePath }) => {
|
|
897
|
-
return toCanonical(filePath) === canonicalGraphqlSystemPath;
|
|
898
|
-
},
|
|
899
|
-
isGraphqlSystemImportSpecifier: ({ filePath, specifier }) => {
|
|
900
|
-
if (canonicalAliases.has(specifier)) {
|
|
901
|
-
return true;
|
|
902
|
-
}
|
|
903
|
-
if (!specifier.startsWith(".")) {
|
|
904
|
-
return false;
|
|
905
|
-
}
|
|
906
|
-
const resolved = (0, __soda_gql_common.resolveRelativeImportWithExistenceCheck)({
|
|
907
|
-
filePath,
|
|
908
|
-
specifier
|
|
909
|
-
});
|
|
910
|
-
if (!resolved) {
|
|
911
|
-
return false;
|
|
912
|
-
}
|
|
913
|
-
return toCanonical(resolved) === canonicalGraphqlSystemPath;
|
|
914
|
-
}
|
|
915
|
-
};
|
|
916
|
-
};
|
|
917
|
-
|
|
918
|
-
//#endregion
|
|
919
|
-
//#region packages/builder/src/schemas/artifact.ts
|
|
920
|
-
const BuilderArtifactElementMetadataSchema = zod.z.object({
|
|
921
|
-
sourcePath: zod.z.string(),
|
|
922
|
-
contentHash: zod.z.string()
|
|
923
|
-
});
|
|
924
|
-
const BuilderArtifactOperationSchema = zod.z.object({
|
|
925
|
-
id: zod.z.string(),
|
|
926
|
-
type: zod.z.literal("operation"),
|
|
927
|
-
metadata: BuilderArtifactElementMetadataSchema,
|
|
928
|
-
prebuild: zod.z.object({
|
|
929
|
-
operationType: zod.z.enum([
|
|
930
|
-
"query",
|
|
931
|
-
"mutation",
|
|
932
|
-
"subscription"
|
|
933
|
-
]),
|
|
934
|
-
operationName: zod.z.string(),
|
|
935
|
-
document: zod.z.unknown(),
|
|
936
|
-
variableNames: zod.z.array(zod.z.string())
|
|
937
|
-
})
|
|
938
|
-
});
|
|
939
|
-
const BuilderArtifactFragmentSchema = zod.z.object({
|
|
940
|
-
id: zod.z.string(),
|
|
941
|
-
type: zod.z.literal("fragment"),
|
|
942
|
-
metadata: BuilderArtifactElementMetadataSchema,
|
|
943
|
-
prebuild: zod.z.object({ typename: zod.z.string() })
|
|
944
|
-
});
|
|
945
|
-
const BuilderArtifactElementSchema = zod.z.discriminatedUnion("type", [BuilderArtifactOperationSchema, BuilderArtifactFragmentSchema]);
|
|
946
|
-
const BuilderArtifactSchema = zod.z.object({
|
|
947
|
-
elements: zod.z.record(zod.z.string(), BuilderArtifactElementSchema),
|
|
948
|
-
report: zod.z.object({
|
|
949
|
-
durationMs: zod.z.number(),
|
|
950
|
-
warnings: zod.z.array(zod.z.string()),
|
|
951
|
-
stats: zod.z.object({
|
|
952
|
-
hits: zod.z.number(),
|
|
953
|
-
misses: zod.z.number(),
|
|
954
|
-
skips: zod.z.number()
|
|
955
|
-
})
|
|
956
|
-
})
|
|
957
|
-
});
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
const graphqlSystemPath = (0, node_path.resolve)(config.outdir, "index.ts");
|
|
1356
|
+
const canonicalGraphqlSystemPath = toCanonical(graphqlSystemPath);
|
|
1357
|
+
const canonicalAliases = new Set(config.graphqlSystemAliases.map((alias) => alias));
|
|
1358
|
+
return {
|
|
1359
|
+
isGraphqlSystemFile: ({ filePath }) => {
|
|
1360
|
+
return toCanonical(filePath) === canonicalGraphqlSystemPath;
|
|
1361
|
+
},
|
|
1362
|
+
isGraphqlSystemImportSpecifier: ({ filePath, specifier }) => {
|
|
1363
|
+
if (canonicalAliases.has(specifier)) {
|
|
1364
|
+
return true;
|
|
1365
|
+
}
|
|
1366
|
+
if (!specifier.startsWith(".")) {
|
|
1367
|
+
return false;
|
|
1368
|
+
}
|
|
1369
|
+
const resolved = (0, __soda_gql_common.resolveRelativeImportWithExistenceCheck)({
|
|
1370
|
+
filePath,
|
|
1371
|
+
specifier
|
|
1372
|
+
});
|
|
1373
|
+
if (!resolved) {
|
|
1374
|
+
return false;
|
|
1375
|
+
}
|
|
1376
|
+
return toCanonical(resolved) === canonicalGraphqlSystemPath;
|
|
1377
|
+
}
|
|
1378
|
+
};
|
|
1379
|
+
};
|
|
958
1380
|
|
|
959
1381
|
//#endregion
|
|
960
1382
|
//#region packages/builder/src/artifact/aggregate.ts
|
|
@@ -1073,199 +1495,6 @@ const buildArtifact = ({ elements, analyses, stats: cache }) => {
|
|
|
1073
1495
|
});
|
|
1074
1496
|
};
|
|
1075
1497
|
|
|
1076
|
-
//#endregion
|
|
1077
|
-
//#region packages/builder/src/errors.ts
|
|
1078
|
-
/**
|
|
1079
|
-
* Error constructor helpers for concise error creation.
|
|
1080
|
-
*/
|
|
1081
|
-
const builderErrors = {
|
|
1082
|
-
entryNotFound: (entry, message) => ({
|
|
1083
|
-
code: "ENTRY_NOT_FOUND",
|
|
1084
|
-
message: message ?? `Entry not found: ${entry}`,
|
|
1085
|
-
entry
|
|
1086
|
-
}),
|
|
1087
|
-
configNotFound: (path, message) => ({
|
|
1088
|
-
code: "CONFIG_NOT_FOUND",
|
|
1089
|
-
message: message ?? `Config file not found: ${path}`,
|
|
1090
|
-
path
|
|
1091
|
-
}),
|
|
1092
|
-
configInvalid: (path, message, cause) => ({
|
|
1093
|
-
code: "CONFIG_INVALID",
|
|
1094
|
-
message,
|
|
1095
|
-
path,
|
|
1096
|
-
cause
|
|
1097
|
-
}),
|
|
1098
|
-
discoveryIOError: (path, message, errno, cause) => ({
|
|
1099
|
-
code: "DISCOVERY_IO_ERROR",
|
|
1100
|
-
message,
|
|
1101
|
-
path,
|
|
1102
|
-
errno,
|
|
1103
|
-
cause
|
|
1104
|
-
}),
|
|
1105
|
-
fingerprintFailed: (filePath, message, cause) => ({
|
|
1106
|
-
code: "FINGERPRINT_FAILED",
|
|
1107
|
-
message,
|
|
1108
|
-
filePath,
|
|
1109
|
-
cause
|
|
1110
|
-
}),
|
|
1111
|
-
unsupportedAnalyzer: (analyzer, message) => ({
|
|
1112
|
-
code: "UNSUPPORTED_ANALYZER",
|
|
1113
|
-
message: message ?? `Unsupported analyzer: ${analyzer}`,
|
|
1114
|
-
analyzer
|
|
1115
|
-
}),
|
|
1116
|
-
canonicalPathInvalid: (path, reason) => ({
|
|
1117
|
-
code: "CANONICAL_PATH_INVALID",
|
|
1118
|
-
message: `Invalid canonical path: ${path}${reason ? ` (${reason})` : ""}`,
|
|
1119
|
-
path,
|
|
1120
|
-
reason
|
|
1121
|
-
}),
|
|
1122
|
-
canonicalScopeMismatch: (expected, actual) => ({
|
|
1123
|
-
code: "CANONICAL_SCOPE_MISMATCH",
|
|
1124
|
-
message: `Scope mismatch: expected ${expected}, got ${actual}`,
|
|
1125
|
-
expected,
|
|
1126
|
-
actual
|
|
1127
|
-
}),
|
|
1128
|
-
graphCircularDependency: (chain) => ({
|
|
1129
|
-
code: "GRAPH_CIRCULAR_DEPENDENCY",
|
|
1130
|
-
message: `Circular dependency detected: ${chain.join(" → ")}`,
|
|
1131
|
-
chain
|
|
1132
|
-
}),
|
|
1133
|
-
graphMissingImport: (importer, importee) => ({
|
|
1134
|
-
code: "GRAPH_MISSING_IMPORT",
|
|
1135
|
-
message: `Missing import: "${importer}" imports "${importee}" but it's not in the graph`,
|
|
1136
|
-
importer,
|
|
1137
|
-
importee
|
|
1138
|
-
}),
|
|
1139
|
-
docDuplicate: (name, sources) => ({
|
|
1140
|
-
code: "DOC_DUPLICATE",
|
|
1141
|
-
message: `Duplicate document name: ${name} found in ${sources.length} files`,
|
|
1142
|
-
name,
|
|
1143
|
-
sources
|
|
1144
|
-
}),
|
|
1145
|
-
writeFailed: (outPath, message, cause) => ({
|
|
1146
|
-
code: "WRITE_FAILED",
|
|
1147
|
-
message,
|
|
1148
|
-
outPath,
|
|
1149
|
-
cause
|
|
1150
|
-
}),
|
|
1151
|
-
cacheCorrupted: (message, cachePath, cause) => ({
|
|
1152
|
-
code: "CACHE_CORRUPTED",
|
|
1153
|
-
message,
|
|
1154
|
-
cachePath,
|
|
1155
|
-
cause
|
|
1156
|
-
}),
|
|
1157
|
-
runtimeModuleLoadFailed: (filePath, astPath, message, cause) => ({
|
|
1158
|
-
code: "RUNTIME_MODULE_LOAD_FAILED",
|
|
1159
|
-
message,
|
|
1160
|
-
filePath,
|
|
1161
|
-
astPath,
|
|
1162
|
-
cause
|
|
1163
|
-
}),
|
|
1164
|
-
artifactRegistrationFailed: (elementId, reason) => ({
|
|
1165
|
-
code: "ARTIFACT_REGISTRATION_FAILED",
|
|
1166
|
-
message: `Failed to register artifact element ${elementId}: ${reason}`,
|
|
1167
|
-
elementId,
|
|
1168
|
-
reason
|
|
1169
|
-
}),
|
|
1170
|
-
internalInvariant: (message, context, cause) => ({
|
|
1171
|
-
code: "INTERNAL_INVARIANT",
|
|
1172
|
-
message: `Internal invariant violated: ${message}`,
|
|
1173
|
-
context,
|
|
1174
|
-
cause
|
|
1175
|
-
})
|
|
1176
|
-
};
|
|
1177
|
-
/**
|
|
1178
|
-
* Convenience helper to create an err Result from BuilderError.
|
|
1179
|
-
*/
|
|
1180
|
-
const builderErr = (error) => (0, neverthrow.err)(error);
|
|
1181
|
-
/**
|
|
1182
|
-
* Type guard for BuilderError.
|
|
1183
|
-
*/
|
|
1184
|
-
const isBuilderError = (error) => {
|
|
1185
|
-
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" && "message" in error && typeof error.message === "string";
|
|
1186
|
-
};
|
|
1187
|
-
/**
|
|
1188
|
-
* Format BuilderError for console output (human-readable).
|
|
1189
|
-
*/
|
|
1190
|
-
const formatBuilderError = (error) => {
|
|
1191
|
-
const lines = [];
|
|
1192
|
-
lines.push(`Error [${error.code}]: ${error.message}`);
|
|
1193
|
-
switch (error.code) {
|
|
1194
|
-
case "ENTRY_NOT_FOUND":
|
|
1195
|
-
lines.push(` Entry: ${error.entry}`);
|
|
1196
|
-
break;
|
|
1197
|
-
case "CONFIG_NOT_FOUND":
|
|
1198
|
-
case "CONFIG_INVALID":
|
|
1199
|
-
lines.push(` Path: ${error.path}`);
|
|
1200
|
-
if (error.code === "CONFIG_INVALID" && error.cause) {
|
|
1201
|
-
lines.push(` Cause: ${error.cause}`);
|
|
1202
|
-
}
|
|
1203
|
-
break;
|
|
1204
|
-
case "DISCOVERY_IO_ERROR":
|
|
1205
|
-
lines.push(` Path: ${error.path}`);
|
|
1206
|
-
if (error.errno !== undefined) {
|
|
1207
|
-
lines.push(` Errno: ${error.errno}`);
|
|
1208
|
-
}
|
|
1209
|
-
break;
|
|
1210
|
-
case "FINGERPRINT_FAILED":
|
|
1211
|
-
lines.push(` File: ${error.filePath}`);
|
|
1212
|
-
break;
|
|
1213
|
-
case "CANONICAL_PATH_INVALID":
|
|
1214
|
-
lines.push(` Path: ${error.path}`);
|
|
1215
|
-
if (error.reason) {
|
|
1216
|
-
lines.push(` Reason: ${error.reason}`);
|
|
1217
|
-
}
|
|
1218
|
-
break;
|
|
1219
|
-
case "CANONICAL_SCOPE_MISMATCH":
|
|
1220
|
-
lines.push(` Expected: ${error.expected}`);
|
|
1221
|
-
lines.push(` Actual: ${error.actual}`);
|
|
1222
|
-
break;
|
|
1223
|
-
case "GRAPH_CIRCULAR_DEPENDENCY":
|
|
1224
|
-
lines.push(` Chain: ${error.chain.join(" → ")}`);
|
|
1225
|
-
break;
|
|
1226
|
-
case "GRAPH_MISSING_IMPORT":
|
|
1227
|
-
lines.push(` Importer: ${error.importer}`);
|
|
1228
|
-
lines.push(` Importee: ${error.importee}`);
|
|
1229
|
-
break;
|
|
1230
|
-
case "DOC_DUPLICATE":
|
|
1231
|
-
lines.push(` Name: ${error.name}`);
|
|
1232
|
-
lines.push(` Sources:\n ${error.sources.join("\n ")}`);
|
|
1233
|
-
break;
|
|
1234
|
-
case "WRITE_FAILED":
|
|
1235
|
-
lines.push(` Output path: ${error.outPath}`);
|
|
1236
|
-
break;
|
|
1237
|
-
case "CACHE_CORRUPTED":
|
|
1238
|
-
if (error.cachePath) {
|
|
1239
|
-
lines.push(` Cache path: ${error.cachePath}`);
|
|
1240
|
-
}
|
|
1241
|
-
break;
|
|
1242
|
-
case "RUNTIME_MODULE_LOAD_FAILED":
|
|
1243
|
-
lines.push(` File: ${error.filePath}`);
|
|
1244
|
-
lines.push(` AST path: ${error.astPath}`);
|
|
1245
|
-
break;
|
|
1246
|
-
case "ARTIFACT_REGISTRATION_FAILED":
|
|
1247
|
-
lines.push(` Element ID: ${error.elementId}`);
|
|
1248
|
-
lines.push(` Reason: ${error.reason}`);
|
|
1249
|
-
break;
|
|
1250
|
-
case "INTERNAL_INVARIANT":
|
|
1251
|
-
if (error.context) {
|
|
1252
|
-
lines.push(` Context: ${error.context}`);
|
|
1253
|
-
}
|
|
1254
|
-
break;
|
|
1255
|
-
}
|
|
1256
|
-
if ("cause" in error && error.cause && !["CONFIG_INVALID"].includes(error.code)) {
|
|
1257
|
-
lines.push(` Caused by: ${error.cause}`);
|
|
1258
|
-
}
|
|
1259
|
-
return lines.join("\n");
|
|
1260
|
-
};
|
|
1261
|
-
/**
|
|
1262
|
-
* Assert unreachable code path (for exhaustiveness checks).
|
|
1263
|
-
* This is the ONLY acceptable throw in builder code.
|
|
1264
|
-
*/
|
|
1265
|
-
const assertUnreachable = (value, context) => {
|
|
1266
|
-
throw new Error(`Unreachable code path${context ? ` in ${context}` : ""}: received ${JSON.stringify(value)}`);
|
|
1267
|
-
};
|
|
1268
|
-
|
|
1269
1498
|
//#endregion
|
|
1270
1499
|
//#region packages/builder/src/ast/common/scope.ts
|
|
1271
1500
|
/**
|
|
@@ -1575,25 +1804,41 @@ const collectAllDefinitions$1 = ({ module: module$1, gqlIdentifiers, imports: _i
|
|
|
1575
1804
|
if (node.type === "CallExpression") {
|
|
1576
1805
|
const gqlCall = unwrapMethodChains$1(gqlIdentifiers, node);
|
|
1577
1806
|
if (gqlCall) {
|
|
1578
|
-
const
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1807
|
+
const needsAnonymousScope = tracker.currentDepth() === 0;
|
|
1808
|
+
let anonymousScopeHandle;
|
|
1809
|
+
if (needsAnonymousScope) {
|
|
1810
|
+
const anonymousName = getAnonymousName("anonymous");
|
|
1811
|
+
anonymousScopeHandle = tracker.enterScope({
|
|
1812
|
+
segment: anonymousName,
|
|
1813
|
+
kind: "expression",
|
|
1814
|
+
stableKey: "anonymous"
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
try {
|
|
1818
|
+
const { astPath } = tracker.registerDefinition();
|
|
1819
|
+
const isTopLevel = stack.length === 1;
|
|
1820
|
+
let isExported = false;
|
|
1821
|
+
let exportBinding;
|
|
1822
|
+
if (isTopLevel && stack[0]) {
|
|
1823
|
+
const topLevelName = stack[0].nameSegment;
|
|
1824
|
+
if (exportBindings.has(topLevelName)) {
|
|
1825
|
+
isExported = true;
|
|
1826
|
+
exportBinding = exportBindings.get(topLevelName);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
handledCalls.push(node);
|
|
1830
|
+
pending.push({
|
|
1831
|
+
astPath,
|
|
1832
|
+
isTopLevel,
|
|
1833
|
+
isExported,
|
|
1834
|
+
exportBinding,
|
|
1835
|
+
expression: expressionFromCall(gqlCall)
|
|
1836
|
+
});
|
|
1837
|
+
} finally {
|
|
1838
|
+
if (anonymousScopeHandle) {
|
|
1839
|
+
tracker.exitScope(anonymousScopeHandle);
|
|
1587
1840
|
}
|
|
1588
1841
|
}
|
|
1589
|
-
handledCalls.push(node);
|
|
1590
|
-
pending.push({
|
|
1591
|
-
astPath,
|
|
1592
|
-
isTopLevel,
|
|
1593
|
-
isExported,
|
|
1594
|
-
exportBinding,
|
|
1595
|
-
expression: expressionFromCall(gqlCall)
|
|
1596
|
-
});
|
|
1597
1842
|
return;
|
|
1598
1843
|
}
|
|
1599
1844
|
}
|
|
@@ -1606,6 +1851,8 @@ const collectAllDefinitions$1 = ({ module: module$1, gqlIdentifiers, imports: _i
|
|
|
1606
1851
|
visit(decl.init, newStack);
|
|
1607
1852
|
});
|
|
1608
1853
|
}
|
|
1854
|
+
} else if (decl.init) {
|
|
1855
|
+
visit(decl.init, stack);
|
|
1609
1856
|
}
|
|
1610
1857
|
});
|
|
1611
1858
|
return;
|
|
@@ -1957,25 +2204,41 @@ const collectAllDefinitions = ({ sourceFile, identifiers, exports: exports$1 })
|
|
|
1957
2204
|
if (typescript.default.isCallExpression(node)) {
|
|
1958
2205
|
const gqlCall = unwrapMethodChains(identifiers, node);
|
|
1959
2206
|
if (gqlCall) {
|
|
1960
|
-
const
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
2207
|
+
const needsAnonymousScope = tracker.currentDepth() === 0;
|
|
2208
|
+
let anonymousScopeHandle;
|
|
2209
|
+
if (needsAnonymousScope) {
|
|
2210
|
+
const anonymousName = getAnonymousName("anonymous");
|
|
2211
|
+
anonymousScopeHandle = tracker.enterScope({
|
|
2212
|
+
segment: anonymousName,
|
|
2213
|
+
kind: "expression",
|
|
2214
|
+
stableKey: "anonymous"
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
try {
|
|
2218
|
+
const { astPath } = tracker.registerDefinition();
|
|
2219
|
+
const isTopLevel = stack.length === 1;
|
|
2220
|
+
let isExported = false;
|
|
2221
|
+
let exportBinding;
|
|
2222
|
+
if (isTopLevel && stack[0]) {
|
|
2223
|
+
const topLevelName = stack[0].nameSegment;
|
|
2224
|
+
if (exportBindings.has(topLevelName)) {
|
|
2225
|
+
isExported = true;
|
|
2226
|
+
exportBinding = exportBindings.get(topLevelName);
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
handledCalls.push(node);
|
|
2230
|
+
pending.push({
|
|
2231
|
+
astPath,
|
|
2232
|
+
isTopLevel,
|
|
2233
|
+
isExported,
|
|
2234
|
+
exportBinding,
|
|
2235
|
+
expression: gqlCall.getText(sourceFile)
|
|
2236
|
+
});
|
|
2237
|
+
} finally {
|
|
2238
|
+
if (anonymousScopeHandle) {
|
|
2239
|
+
tracker.exitScope(anonymousScopeHandle);
|
|
1969
2240
|
}
|
|
1970
2241
|
}
|
|
1971
|
-
handledCalls.push(node);
|
|
1972
|
-
pending.push({
|
|
1973
|
-
astPath,
|
|
1974
|
-
isTopLevel,
|
|
1975
|
-
isExported,
|
|
1976
|
-
exportBinding,
|
|
1977
|
-
expression: gqlCall.getText(sourceFile)
|
|
1978
|
-
});
|
|
1979
2242
|
return;
|
|
1980
2243
|
}
|
|
1981
2244
|
}
|
|
@@ -2148,20 +2411,58 @@ const toEntryKey = (key) => {
|
|
|
2148
2411
|
return hasher.hash(key, "xxhash");
|
|
2149
2412
|
};
|
|
2150
2413
|
const PERSISTENCE_VERSION = "v1";
|
|
2414
|
+
/**
|
|
2415
|
+
* Validate persisted data structure.
|
|
2416
|
+
* Uses simple validation to detect corruption without strict schema.
|
|
2417
|
+
*/
|
|
2418
|
+
const isValidPersistedData = (data) => {
|
|
2419
|
+
if (typeof data !== "object" || data === null) return false;
|
|
2420
|
+
const record = data;
|
|
2421
|
+
if (typeof record.version !== "string") return false;
|
|
2422
|
+
if (typeof record.storage !== "object" || record.storage === null) return false;
|
|
2423
|
+
for (const value of Object.values(record.storage)) {
|
|
2424
|
+
if (!Array.isArray(value)) return false;
|
|
2425
|
+
for (const entry of value) {
|
|
2426
|
+
if (!Array.isArray(entry) || entry.length !== 2) return false;
|
|
2427
|
+
if (typeof entry[0] !== "string") return false;
|
|
2428
|
+
const envelope = entry[1];
|
|
2429
|
+
if (typeof envelope !== "object" || envelope === null) return false;
|
|
2430
|
+
const env = envelope;
|
|
2431
|
+
if (typeof env.key !== "string" || typeof env.version !== "string") return false;
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
return true;
|
|
2435
|
+
};
|
|
2151
2436
|
const createMemoryCache = ({ prefix = [], persistence } = {}) => {
|
|
2152
2437
|
const storage = new Map();
|
|
2153
2438
|
if (persistence?.enabled) {
|
|
2154
2439
|
try {
|
|
2155
2440
|
if ((0, node_fs.existsSync)(persistence.filePath)) {
|
|
2156
2441
|
const content = (0, node_fs.readFileSync)(persistence.filePath, "utf-8");
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2442
|
+
let parsed;
|
|
2443
|
+
try {
|
|
2444
|
+
parsed = JSON.parse(content);
|
|
2445
|
+
} catch {
|
|
2446
|
+
console.warn(`[cache] Corrupt cache file (invalid JSON), starting fresh: ${persistence.filePath}`);
|
|
2447
|
+
try {
|
|
2448
|
+
(0, node_fs.unlinkSync)(persistence.filePath);
|
|
2449
|
+
} catch {}
|
|
2450
|
+
parsed = null;
|
|
2451
|
+
}
|
|
2452
|
+
if (parsed) {
|
|
2453
|
+
if (!isValidPersistedData(parsed)) {
|
|
2454
|
+
console.warn(`[cache] Corrupt cache file (invalid structure), starting fresh: ${persistence.filePath}`);
|
|
2455
|
+
try {
|
|
2456
|
+
(0, node_fs.unlinkSync)(persistence.filePath);
|
|
2457
|
+
} catch {}
|
|
2458
|
+
} else if (parsed.version === PERSISTENCE_VERSION) {
|
|
2459
|
+
for (const [namespaceKey, entries] of Object.entries(parsed.storage)) {
|
|
2460
|
+
const namespaceMap = new Map();
|
|
2461
|
+
for (const [hashedKey, envelope] of entries) {
|
|
2462
|
+
namespaceMap.set(hashedKey, envelope);
|
|
2463
|
+
}
|
|
2464
|
+
storage.set(namespaceKey, namespaceMap);
|
|
2163
2465
|
}
|
|
2164
|
-
storage.set(namespaceKey, namespaceMap);
|
|
2165
2466
|
}
|
|
2166
2467
|
}
|
|
2167
2468
|
}
|
|
@@ -2282,11 +2583,8 @@ const createMemoryCache = ({ prefix = [], persistence } = {}) => {
|
|
|
2282
2583
|
version: PERSISTENCE_VERSION,
|
|
2283
2584
|
storage: serialized
|
|
2284
2585
|
};
|
|
2285
|
-
const
|
|
2286
|
-
|
|
2287
|
-
(0, node_fs.mkdirSync)(dir, { recursive: true });
|
|
2288
|
-
}
|
|
2289
|
-
(0, node_fs.writeFileSync)(persistence.filePath, JSON.stringify(data), "utf-8");
|
|
2586
|
+
const fs = (0, __soda_gql_common.getPortableFS)();
|
|
2587
|
+
fs.writeFileSyncAtomic(persistence.filePath, JSON.stringify(data));
|
|
2290
2588
|
} catch (error) {
|
|
2291
2589
|
console.warn(`[cache] Failed to save cache to ${persistence.filePath}:`, error);
|
|
2292
2590
|
}
|
|
@@ -3015,6 +3313,43 @@ const collectAffectedFiles = (input) => {
|
|
|
3015
3313
|
//#endregion
|
|
3016
3314
|
//#region packages/builder/src/session/builder-session.ts
|
|
3017
3315
|
/**
|
|
3316
|
+
* Singleton state for beforeExit handler registration.
|
|
3317
|
+
* Ensures only one handler is registered regardless of how many sessions are created.
|
|
3318
|
+
*/
|
|
3319
|
+
const exitHandlerState = {
|
|
3320
|
+
registered: false,
|
|
3321
|
+
factories: new Set()
|
|
3322
|
+
};
|
|
3323
|
+
/**
|
|
3324
|
+
* Register a cache factory for save on process exit.
|
|
3325
|
+
* Uses singleton pattern to prevent multiple handler registrations.
|
|
3326
|
+
*/
|
|
3327
|
+
const registerExitHandler = (cacheFactory) => {
|
|
3328
|
+
exitHandlerState.factories.add(cacheFactory);
|
|
3329
|
+
if (!exitHandlerState.registered) {
|
|
3330
|
+
exitHandlerState.registered = true;
|
|
3331
|
+
process.on("beforeExit", () => {
|
|
3332
|
+
for (const factory of exitHandlerState.factories) {
|
|
3333
|
+
factory.save();
|
|
3334
|
+
}
|
|
3335
|
+
});
|
|
3336
|
+
}
|
|
3337
|
+
};
|
|
3338
|
+
/**
|
|
3339
|
+
* Unregister a cache factory from the exit handler.
|
|
3340
|
+
*/
|
|
3341
|
+
const unregisterExitHandler = (cacheFactory) => {
|
|
3342
|
+
exitHandlerState.factories.delete(cacheFactory);
|
|
3343
|
+
};
|
|
3344
|
+
/**
|
|
3345
|
+
* Reset exit handler state for testing.
|
|
3346
|
+
* @internal
|
|
3347
|
+
*/
|
|
3348
|
+
const __resetExitHandlerForTests = () => {
|
|
3349
|
+
exitHandlerState.registered = false;
|
|
3350
|
+
exitHandlerState.factories.clear();
|
|
3351
|
+
};
|
|
3352
|
+
/**
|
|
3018
3353
|
* Create a new builder session.
|
|
3019
3354
|
*
|
|
3020
3355
|
* The session maintains in-memory state across builds to enable incremental processing.
|
|
@@ -3034,12 +3369,10 @@ const createBuilderSession = (options) => {
|
|
|
3034
3369
|
prefix: ["builder"],
|
|
3035
3370
|
persistence: {
|
|
3036
3371
|
enabled: true,
|
|
3037
|
-
filePath: (0, node_path.join)(process.cwd(), ".cache", "soda-gql", "builder", "cache.json")
|
|
3372
|
+
filePath: (0, node_path.join)(process.cwd(), "node_modules", ".cache", "soda-gql", "builder", "cache.json")
|
|
3038
3373
|
}
|
|
3039
3374
|
});
|
|
3040
|
-
|
|
3041
|
-
cacheFactory.save();
|
|
3042
|
-
});
|
|
3375
|
+
registerExitHandler(cacheFactory);
|
|
3043
3376
|
const graphqlHelper = createGraphqlSystemIdentifyHelper(config);
|
|
3044
3377
|
const ensureAstAnalyzer = (0, __soda_gql_common.cachedFn)(() => createAstAnalyzer({
|
|
3045
3378
|
analyzer: config.analyzer,
|
|
@@ -3183,6 +3516,7 @@ const createBuilderSession = (options) => {
|
|
|
3183
3516
|
getCurrentArtifact: () => state.lastArtifact,
|
|
3184
3517
|
dispose: () => {
|
|
3185
3518
|
cacheFactory.save();
|
|
3519
|
+
unregisterExitHandler(cacheFactory);
|
|
3186
3520
|
}
|
|
3187
3521
|
};
|
|
3188
3522
|
};
|
|
@@ -3329,4 +3663,8 @@ exports.createBuilderService = createBuilderService;
|
|
|
3329
3663
|
exports.createBuilderSession = createBuilderSession;
|
|
3330
3664
|
exports.createGraphqlSystemIdentifyHelper = createGraphqlSystemIdentifyHelper;
|
|
3331
3665
|
exports.extractModuleAdjacency = extractModuleAdjacency;
|
|
3666
|
+
exports.formatBuilderErrorForCLI = formatBuilderErrorForCLI;
|
|
3667
|
+
exports.formatBuilderErrorStructured = formatBuilderErrorStructured;
|
|
3668
|
+
exports.loadArtifact = loadArtifact;
|
|
3669
|
+
exports.loadArtifactSync = loadArtifactSync;
|
|
3332
3670
|
//# sourceMappingURL=index.cjs.map
|