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