@dudousxd/nestjs-codegen 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -168,11 +168,6 @@ function validateUserConfig(userConfig) {
168
168
  );
169
169
  }
170
170
  }
171
- if (userConfig.scopes && Object.keys(userConfig.scopes).length > 0) {
172
- console.warn(
173
- "[nestjs-codegen] The top-level `scopes` option has no effect and is deprecated. For several Inertia apps use `pages.scopes` (one entry per InertiaModule.forFeature scope); for several controller sets use one `contracts.glob` (e.g. a brace glob)."
174
- );
175
- }
176
171
  }
177
172
  function isGlobInput(value) {
178
173
  if (typeof value === "string") return true;
@@ -220,7 +215,6 @@ function applyDefaults(userConfig, cwd) {
220
215
  glob: userConfig.contracts?.glob ?? "src/**/*.controller.ts",
221
216
  debounceMs: userConfig.contracts?.debounceMs ?? 500
222
217
  },
223
- scopes: userConfig.scopes ?? {},
224
218
  codegen: {
225
219
  outDir,
226
220
  cwd: resolvedCwd
@@ -315,7 +309,7 @@ async function discoverPages(opts) {
315
309
  const rel = (0, import_node_path2.relative)(opts.cwd, file);
316
310
  const nameRel = (0, import_node_path2.relative)(pagesBase, file).split(import_node_path2.sep).join("/");
317
311
  const name = `${prefix}${computeName(nameRel, opts.componentNameStrategy)}`;
318
- const source = await (0, import_promises2.readFile)(file, "utf8");
312
+ const source = await (opts.read ? opts.read(file) : (0, import_promises2.readFile)(file, "utf8"));
319
313
  const propsSource = extractPropsSource(source, opts.propsExport);
320
314
  out.push({ name, absolutePath: file, relativePath: rel, propsSource, scope });
321
315
  }
@@ -323,10 +317,14 @@ async function discoverPages(opts) {
323
317
  assertUniqueNames(out);
324
318
  return out;
325
319
  }
326
- async function discoverPageScopes(config) {
320
+ async function discoverPageScopes(config, read) {
327
321
  const pages = config.pages;
328
322
  if (!pages) return [];
329
- const common = { cwd: config.codegen.cwd, propsExport: pages.propsExport };
323
+ const common = {
324
+ cwd: config.codegen.cwd,
325
+ propsExport: pages.propsExport,
326
+ ...read ? { read } : {}
327
+ };
330
328
  const out = await discoverPages({
331
329
  ...common,
332
330
  glob: pages.glob,
@@ -397,297 +395,60 @@ function extractPropsSource(source, exportName) {
397
395
  }
398
396
 
399
397
  // src/discovery/shared-props.ts
400
- var import_node_path3 = require("path");
401
- var import_ts_morph = require("ts-morph");
402
- function discoverSharedProps(project, moduleEntry) {
403
- try {
404
- let sourceFile = project.getSourceFile(moduleEntry);
405
- if (!sourceFile) {
398
+ var import_node_fs3 = require("fs");
399
+ var import_node_path5 = require("path");
400
+ var import_ts_morph13 = require("ts-morph");
401
+
402
+ // src/discovery/contracts-fast.ts
403
+ var import_node_fs2 = require("fs");
404
+ var import_node_path4 = require("path");
405
+ var import_fast_glob2 = __toESM(require("fast-glob"), 1);
406
+ var import_ts_morph12 = require("ts-morph");
407
+
408
+ // src/discovery/disk-fingerprints.ts
409
+ var import_node_fs = require("fs");
410
+ var DiskFingerprints = class {
411
+ stats = /* @__PURE__ */ new Map();
412
+ /** The paths whose disk content differs from `text`, or that no longer exist. */
413
+ changed(files) {
414
+ const changed = [];
415
+ for (const { path, text } of files) {
416
+ let key;
406
417
  try {
407
- sourceFile = project.addSourceFileAtPath(moduleEntry);
418
+ const stat2 = (0, import_node_fs.statSync)(path);
419
+ key = `${stat2.mtimeMs}:${stat2.size}`;
408
420
  } catch {
409
- return null;
410
- }
411
- }
412
- const forRootCall = findForRootCall(sourceFile);
413
- if (!forRootCall) return null;
414
- const initializer = findShareInitializer(forRootCall);
415
- if (!initializer) return null;
416
- return extractShareType(initializer, sourceFile, project);
417
- } catch {
418
- return null;
419
- }
420
- }
421
- function discoverSharedPropsFromConfig(config) {
422
- if (!config.app?.moduleEntry) return null;
423
- try {
424
- const tsconfigPath = config.app.tsconfig ?? (0, import_node_path3.join)(config.codegen.cwd, "tsconfig.json");
425
- let project;
426
- try {
427
- project = new import_ts_morph.Project({
428
- tsConfigFilePath: tsconfigPath,
429
- skipAddingFilesFromTsConfig: true,
430
- skipLoadingLibFiles: true,
431
- skipFileDependencyResolution: true
432
- });
433
- } catch {
434
- project = new import_ts_morph.Project({
435
- skipAddingFilesFromTsConfig: true,
436
- skipLoadingLibFiles: true,
437
- skipFileDependencyResolution: true,
438
- compilerOptions: { allowJs: true, strict: false }
439
- });
440
- }
441
- return discoverSharedProps(project, config.app.moduleEntry);
442
- } catch {
443
- return null;
444
- }
445
- }
446
- function findForRootCall(sourceFile) {
447
- const callExpressions = sourceFile.getDescendantsOfKind(import_ts_morph.SyntaxKind.CallExpression);
448
- for (const call of callExpressions) {
449
- const expr = call.getExpression();
450
- if (!import_ts_morph.Node.isPropertyAccessExpression(expr)) continue;
451
- const methodName = expr.getName();
452
- const objectExpr = expr.getExpression();
453
- if (methodName === "forRoot" && import_ts_morph.Node.isIdentifier(objectExpr)) {
454
- const name = objectExpr.getText();
455
- if (name === "InertiaModule") {
456
- return call;
457
- }
458
- }
459
- }
460
- return null;
461
- }
462
- function findShareInitializer(forRootCall) {
463
- if (!import_ts_morph.Node.isCallExpression(forRootCall)) return null;
464
- const args = forRootCall.getArguments();
465
- const firstArg2 = args[0];
466
- if (!firstArg2 || !import_ts_morph.Node.isObjectLiteralExpression(firstArg2)) return null;
467
- for (const prop of firstArg2.getProperties()) {
468
- if (import_ts_morph.Node.isPropertyAssignment(prop) && prop.getName() === "share") {
469
- return prop.getInitializer() ?? null;
470
- }
471
- }
472
- return null;
473
- }
474
- function extractShareType(node, sourceFile, project) {
475
- if (import_ts_morph.Node.isIdentifier(node)) {
476
- const ref = resolveIdentifierToImportRef(node, sourceFile, project);
477
- if (ref) {
478
- return {
479
- typeString: `Awaited<ReturnType<typeof import('${ref.importPath}').${ref.exportName}>>`,
480
- properties: null,
481
- isImportRef: true
482
- };
483
- }
484
- }
485
- if (import_ts_morph.Node.isArrowFunction(node)) {
486
- const result = extractFromFunctionLike(node, sourceFile);
487
- return result ? { ...result, isImportRef: false } : null;
488
- }
489
- if (import_ts_morph.Node.isFunctionExpression(node)) {
490
- const result = extractFromFunctionLike(node, sourceFile);
491
- return result ? { ...result, isImportRef: false } : null;
492
- }
493
- if (import_ts_morph.Node.isObjectLiteralExpression(node)) {
494
- const result = extractFromObjectLiteral(node);
495
- return result ? { ...result, isImportRef: false } : null;
496
- }
497
- return null;
498
- }
499
- function resolveIdentifierToImportRef(id, sourceFile, project) {
500
- if (!import_ts_morph.Node.isIdentifier(id)) return null;
501
- const name = id.getText();
502
- const localFunc = sourceFile.getFunction(name);
503
- if (localFunc?.isExported()) {
504
- const filePath = sourceFile.getFilePath().replace(/\.ts$/, "");
505
- return { importPath: filePath, exportName: name };
506
- }
507
- const localVar = sourceFile.getVariableDeclaration(name);
508
- if (localVar?.isExported()) {
509
- const filePath = sourceFile.getFilePath().replace(/\.ts$/, "");
510
- return { importPath: filePath, exportName: name };
511
- }
512
- for (const imp of sourceFile.getImportDeclarations()) {
513
- for (const named of imp.getNamedImports()) {
514
- const importedName = named.getAliasNode()?.getText() ?? named.getName();
515
- if (importedName !== name) continue;
516
- const resolvedSource = imp.getModuleSpecifierSourceFile();
517
- if (!resolvedSource) continue;
518
- const originalName = named.getName();
519
- const fn = resolvedSource.getFunction(originalName);
520
- if (fn?.isExported()) {
521
- const filePath = resolvedSource.getFilePath().replace(/\.ts$/, "");
522
- return { importPath: filePath, exportName: originalName };
523
- }
524
- const v = resolvedSource.getVariableDeclaration(originalName);
525
- if (v?.isExported()) {
526
- const filePath = resolvedSource.getFilePath().replace(/\.ts$/, "");
527
- return { importPath: filePath, exportName: originalName };
528
- }
529
- }
530
- }
531
- return null;
532
- }
533
- function extractFromFunctionLike(node, _sourceFile) {
534
- const returnTypeNode = import_ts_morph.Node.isArrowFunction(node) || import_ts_morph.Node.isFunctionExpression(node) ? node.getReturnTypeNode() : null;
535
- if (returnTypeNode) {
536
- return extractFromReturnTypeAnnotation(returnTypeNode);
537
- }
538
- if (import_ts_morph.Node.isArrowFunction(node)) {
539
- const body = node.getBody();
540
- if (import_ts_morph.Node.isParenthesizedExpression(body)) {
541
- const inner = body.getExpression();
542
- if (import_ts_morph.Node.isObjectLiteralExpression(inner)) {
543
- return extractFromObjectLiteral(inner);
544
- }
545
- }
546
- if (import_ts_morph.Node.isObjectLiteralExpression(body)) {
547
- return extractFromObjectLiteral(body);
548
- }
549
- if (import_ts_morph.Node.isBlock(body)) {
550
- return extractFromBlockReturn(body);
551
- }
552
- }
553
- if (import_ts_morph.Node.isFunctionExpression(node)) {
554
- const body = node.getBody();
555
- if (import_ts_morph.Node.isBlock(body)) {
556
- return extractFromBlockReturn(body);
557
- }
558
- }
559
- return null;
560
- }
561
- function extractFromReturnTypeAnnotation(typeNode) {
562
- if (import_ts_morph.Node.isTypeReference(typeNode)) {
563
- const typeName = typeNode.getTypeName();
564
- if (import_ts_morph.Node.isIdentifier(typeName) && typeName.getText() === "Promise") {
565
- const typeArgs = typeNode.getTypeArguments();
566
- const firstArg2 = typeArgs[0];
567
- if (firstArg2) {
568
- return extractFromReturnTypeAnnotation(firstArg2);
421
+ this.stats.delete(path);
422
+ changed.push(path);
423
+ continue;
569
424
  }
570
- return null;
571
- }
572
- }
573
- if (import_ts_morph.Node.isTypeLiteral(typeNode)) {
574
- const properties = [];
575
- for (const member of typeNode.getMembers()) {
576
- if (import_ts_morph.Node.isPropertySignature(member)) {
577
- const name = member.getName();
578
- const memberTypeNode = member.getTypeNode();
579
- const type = memberTypeNode ? memberTypeNode.getText() : "unknown";
580
- properties.push({ name, type });
425
+ if (this.stats.get(path) === key) continue;
426
+ let disk;
427
+ try {
428
+ disk = (0, import_node_fs.readFileSync)(path, "utf-8");
429
+ } catch {
430
+ disk = void 0;
581
431
  }
582
- }
583
- if (properties.length === 0) return null;
584
- const typeString = `{ ${properties.map((p) => `${p.name}: ${p.type}`).join("; ")} }`;
585
- return { typeString, properties, isImportRef: false };
586
- }
587
- return null;
588
- }
589
- function extractFromBlockReturn(block) {
590
- if (!import_ts_morph.Node.isBlock(block)) return null;
591
- const statements = block.getStatements();
592
- for (let i = statements.length - 1; i >= 0; i--) {
593
- const stmt = statements[i];
594
- if (!import_ts_morph.Node.isReturnStatement(stmt)) continue;
595
- const expr = stmt.getExpression();
596
- if (!expr) continue;
597
- if (import_ts_morph.Node.isObjectLiteralExpression(expr)) {
598
- return extractFromObjectLiteral(expr);
599
- }
600
- if (import_ts_morph.Node.isParenthesizedExpression(expr)) {
601
- const inner = expr.getExpression();
602
- if (import_ts_morph.Node.isObjectLiteralExpression(inner)) {
603
- return extractFromObjectLiteral(inner);
432
+ if (disk === text) {
433
+ this.stats.set(path, key);
434
+ } else {
435
+ this.stats.delete(path);
436
+ changed.push(path);
604
437
  }
605
438
  }
606
- break;
607
- }
608
- return null;
609
- }
610
- function extractFromObjectLiteral(objLiteral) {
611
- if (!import_ts_morph.Node.isObjectLiteralExpression(objLiteral)) return null;
612
- const properties = [];
613
- for (const prop of objLiteral.getProperties()) {
614
- if (!import_ts_morph.Node.isPropertyAssignment(prop)) continue;
615
- const name = prop.getName();
616
- const initializer = prop.getInitializer();
617
- if (!initializer) continue;
618
- const type = inferExpressionType(initializer);
619
- properties.push({ name, type });
620
- }
621
- if (properties.length === 0) return null;
622
- const typeString = `{ ${properties.map((p) => `${p.name}: ${p.type}`).join("; ")} }`;
623
- return { typeString, properties, isImportRef: false };
624
- }
625
- function inferExpressionType(node) {
626
- if (import_ts_morph.Node.isStringLiteral(node)) return "string";
627
- if (import_ts_morph.Node.isTemplateExpression(node) || import_ts_morph.Node.isNoSubstitutionTemplateLiteral(node))
628
- return "string";
629
- if (import_ts_morph.Node.isNumericLiteral(node)) return "number";
630
- if (node.getKind() === import_ts_morph.SyntaxKind.TrueKeyword || node.getKind() === import_ts_morph.SyntaxKind.FalseKeyword) {
631
- return "boolean";
632
- }
633
- if (node.getKind() === import_ts_morph.SyntaxKind.NullKeyword) return "null";
634
- if (import_ts_morph.Node.isIdentifier(node) && node.getText() === "undefined") return "undefined";
635
- if (import_ts_morph.Node.isObjectLiteralExpression(node)) {
636
- const props = node.getProperties();
637
- if (props.length === 0) return "Record<string, unknown>";
638
- const entries = [];
639
- for (const prop of props) {
640
- if (!import_ts_morph.Node.isPropertyAssignment(prop)) continue;
641
- const key = prop.getName();
642
- const init = prop.getInitializer();
643
- if (!init) continue;
644
- entries.push(`${key}: ${inferExpressionType(init)}`);
645
- }
646
- if (entries.length === 0) return "Record<string, unknown>";
647
- return `{ ${entries.join("; ")} }`;
439
+ return changed;
648
440
  }
649
- if (import_ts_morph.Node.isArrayLiteralExpression(node)) {
650
- const elements = node.getElements();
651
- if (elements.length === 0) return "Array<unknown>";
652
- const first = elements[0];
653
- if (first) return `Array<${inferExpressionType(first)}>`;
654
- return "Array<unknown>";
441
+ /** Forget `path`, so its next check compares content again. */
442
+ forget(path) {
443
+ this.stats.delete(path);
655
444
  }
656
- if (import_ts_morph.Node.isConditionalExpression(node)) {
657
- const whenTrue = inferExpressionType(node.getWhenTrue());
658
- const whenFalse = inferExpressionType(node.getWhenFalse());
659
- if (whenTrue === whenFalse) return whenTrue;
660
- return `${whenTrue} | ${whenFalse}`;
661
- }
662
- if (import_ts_morph.Node.isParenthesizedExpression(node)) {
663
- return inferExpressionType(node.getExpression());
664
- }
665
- if (import_ts_morph.Node.isAsExpression(node)) {
666
- const typeNode = node.getTypeNode();
667
- if (typeNode) return typeNode.getText();
668
- }
669
- return "unknown";
670
- }
671
-
672
- // src/emit/emit-api.ts
673
- var import_promises3 = require("fs/promises");
674
- var import_node_path7 = require("path");
675
-
676
- // src/extension/registry.ts
677
- var import_node_path6 = require("path");
678
- var import_ts_morph13 = require("ts-morph");
679
-
680
- // src/discovery/contracts-fast.ts
681
- var import_node_fs = require("fs");
682
- var import_node_path5 = require("path");
683
- var import_fast_glob2 = __toESM(require("fast-glob"), 1);
684
- var import_ts_morph12 = require("ts-morph");
445
+ };
685
446
 
686
447
  // src/discovery/dto-type-resolver.ts
687
448
  var import_ts_morph11 = require("ts-morph");
688
449
 
689
450
  // src/discovery/dto-to-ir.ts
690
- var import_ts_morph3 = require("ts-morph");
451
+ var import_ts_morph2 = require("ts-morph");
691
452
 
692
453
  // src/util/debug-log.ts
693
454
  var debugEnabled = false;
@@ -699,8 +460,8 @@ function debugWarn(message) {
699
460
  }
700
461
 
701
462
  // src/discovery/type-ref-resolution.ts
702
- var import_node_path4 = require("path");
703
- var import_ts_morph2 = require("ts-morph");
463
+ var import_node_path3 = require("path");
464
+ var import_ts_morph = require("ts-morph");
704
465
  var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
705
466
  var _ctxByProject = /* @__PURE__ */ new WeakMap();
706
467
  function setDiscoveryContext(project, ctx) {
@@ -741,17 +502,17 @@ function findTypeInFile(name, file) {
741
502
  }
742
503
  function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
743
504
  if (moduleSpecifier.startsWith(".")) {
744
- const dir = (0, import_node_path4.dirname)(sourceFile.getFilePath());
505
+ const dir = (0, import_node_path3.dirname)(sourceFile.getFilePath());
745
506
  const ext = /\.(c|m)?(j|t)sx?$/.exec(moduleSpecifier)?.[0];
746
507
  const base = ext ? moduleSpecifier.slice(0, -ext.length) : moduleSpecifier;
747
508
  const flavour = ext?.startsWith(".c") ? "c" : ext?.startsWith(".m") ? "m" : "";
748
509
  const candidates = [
749
- (0, import_node_path4.resolve)(dir, `${base}.${flavour}ts`),
750
- ...flavour === "" ? [(0, import_node_path4.resolve)(dir, `${base}.tsx`)] : [],
751
- (0, import_node_path4.resolve)(dir, `${moduleSpecifier}.ts`),
752
- (0, import_node_path4.resolve)(dir, moduleSpecifier, "index.ts"),
753
- (0, import_node_path4.resolve)(dir, `${base}.d.${flavour}ts`),
754
- ...ext === void 0 ? [(0, import_node_path4.resolve)(dir, moduleSpecifier, "index.d.ts")] : []
510
+ (0, import_node_path3.resolve)(dir, `${base}.${flavour}ts`),
511
+ ...flavour === "" ? [(0, import_node_path3.resolve)(dir, `${base}.tsx`)] : [],
512
+ (0, import_node_path3.resolve)(dir, `${moduleSpecifier}.ts`),
513
+ (0, import_node_path3.resolve)(dir, moduleSpecifier, "index.ts"),
514
+ (0, import_node_path3.resolve)(dir, `${base}.d.${flavour}ts`),
515
+ ...ext === void 0 ? [(0, import_node_path3.resolve)(dir, moduleSpecifier, "index.d.ts")] : []
755
516
  ];
756
517
  return [...new Set(candidates)];
757
518
  }
@@ -773,8 +534,8 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
773
534
  const rest = moduleSpecifier.slice(prefix.length);
774
535
  const candidates = [];
775
536
  for (const mapping of mappings) {
776
- const resolved = (0, import_node_path4.resolve)(baseUrl, mapping.replace("*", rest));
777
- candidates.push(`${resolved}.ts`, (0, import_node_path4.resolve)(resolved, "index.ts"));
537
+ const resolved = (0, import_node_path3.resolve)(baseUrl, mapping.replace("*", rest));
538
+ candidates.push(`${resolved}.ts`, (0, import_node_path3.resolve)(resolved, "index.ts"));
778
539
  }
779
540
  dbg(" resolved candidates:", candidates);
780
541
  return candidates;
@@ -965,13 +726,13 @@ function resolveTypeRef(nodeOrName, sourceFile, project, opts) {
965
726
  name = nodeOrName;
966
727
  } else {
967
728
  const typeNode = nodeOrName;
968
- if (opts.unwrapContainers && import_ts_morph2.Node.isArrayTypeNode(typeNode)) {
729
+ if (opts.unwrapContainers && import_ts_morph.Node.isArrayTypeNode(typeNode)) {
969
730
  const inner = resolveTypeRef(typeNode.getElementTypeNode(), sourceFile, project, opts);
970
731
  return inner ? { ...inner, isArray: true } : null;
971
732
  }
972
- if (!import_ts_morph2.Node.isTypeReference(typeNode)) return null;
733
+ if (!import_ts_morph.Node.isTypeReference(typeNode)) return null;
973
734
  const typeName = typeNode.getTypeName();
974
- const refName = import_ts_morph2.Node.isIdentifier(typeName) ? typeName.getText() : null;
735
+ const refName = import_ts_morph.Node.isIdentifier(typeName) ? typeName.getText() : null;
975
736
  if (!refName) return null;
976
737
  if (opts.unwrapContainers && refName === "Promise") {
977
738
  const first = typeNode.getTypeArguments()[0];
@@ -1105,7 +866,7 @@ function buildProperty(prop, classFile, ctx) {
1105
866
  const dec = (n) => decorators.get(n);
1106
867
  const typeNode = prop.getTypeNode();
1107
868
  const typeText = typeNode?.getText() ?? "unknown";
1108
- const isArrayType = !!typeNode && import_ts_morph3.Node.isArrayTypeNode(typeNode);
869
+ const isArrayType = !!typeNode && import_ts_morph2.Node.isArrayTypeNode(typeNode);
1109
870
  const asField = (child) => applyPresence(
1110
871
  has("IsArray") || isArrayType ? { kind: "array", element: child } : child,
1111
872
  decorators
@@ -1319,22 +1080,22 @@ function firstArgText(decorator) {
1319
1080
  }
1320
1081
  function numericArg(decorator) {
1321
1082
  const arg = firstArg(decorator);
1322
- if (arg && import_ts_morph3.Node.isNumericLiteral(arg)) return arg.getText();
1083
+ if (arg && import_ts_morph2.Node.isNumericLiteral(arg)) return arg.getText();
1323
1084
  return null;
1324
1085
  }
1325
1086
  function numericArgs(decorator) {
1326
1087
  const args = decorator?.getArguments() ?? [];
1327
- const num = (n) => n && import_ts_morph3.Node.isNumericLiteral(n) ? n.getText() : null;
1088
+ const num = (n) => n && import_ts_morph2.Node.isNumericLiteral(n) ? n.getText() : null;
1328
1089
  return [num(args[0]), num(args[1])];
1329
1090
  }
1330
1091
  function messageRaw(decorator) {
1331
1092
  const args = decorator?.getArguments() ?? [];
1332
1093
  for (const arg of args) {
1333
- if (import_ts_morph3.Node.isObjectLiteralExpression(arg)) {
1094
+ if (import_ts_morph2.Node.isObjectLiteralExpression(arg)) {
1334
1095
  for (const prop of arg.getProperties()) {
1335
- if (import_ts_morph3.Node.isPropertyAssignment(prop) && prop.getName() === "message") {
1096
+ if (import_ts_morph2.Node.isPropertyAssignment(prop) && prop.getName() === "message") {
1336
1097
  const init = prop.getInitializer();
1337
- if (init && import_ts_morph3.Node.isStringLiteral(init)) return init.getText();
1098
+ if (init && import_ts_morph2.Node.isStringLiteral(init)) return init.getText();
1338
1099
  }
1339
1100
  }
1340
1101
  }
@@ -1343,30 +1104,30 @@ function messageRaw(decorator) {
1343
1104
  }
1344
1105
  function resolveDiscriminator(decorator) {
1345
1106
  const optsArg = decorator?.getArguments()[1];
1346
- if (!optsArg || !import_ts_morph3.Node.isObjectLiteralExpression(optsArg)) return null;
1107
+ if (!optsArg || !import_ts_morph2.Node.isObjectLiteralExpression(optsArg)) return null;
1347
1108
  let discProp;
1348
1109
  for (const prop of optsArg.getProperties()) {
1349
- if (import_ts_morph3.Node.isPropertyAssignment(prop) && prop.getName() === "discriminator") {
1110
+ if (import_ts_morph2.Node.isPropertyAssignment(prop) && prop.getName() === "discriminator") {
1350
1111
  discProp = prop.getInitializer();
1351
1112
  }
1352
1113
  }
1353
- if (!discProp || !import_ts_morph3.Node.isObjectLiteralExpression(discProp)) return null;
1114
+ if (!discProp || !import_ts_morph2.Node.isObjectLiteralExpression(discProp)) return null;
1354
1115
  let property = null;
1355
1116
  const subTypes = [];
1356
1117
  for (const prop of discProp.getProperties()) {
1357
- if (!import_ts_morph3.Node.isPropertyAssignment(prop)) continue;
1118
+ if (!import_ts_morph2.Node.isPropertyAssignment(prop)) continue;
1358
1119
  const name = prop.getName();
1359
1120
  const init = prop.getInitializer();
1360
1121
  if (!init) continue;
1361
- if (name === "property" && import_ts_morph3.Node.isStringLiteral(init)) {
1122
+ if (name === "property" && import_ts_morph2.Node.isStringLiteral(init)) {
1362
1123
  property = init.getLiteralValue();
1363
- } else if (name === "subTypes" && import_ts_morph3.Node.isArrayLiteralExpression(init)) {
1124
+ } else if (name === "subTypes" && import_ts_morph2.Node.isArrayLiteralExpression(init)) {
1364
1125
  for (const el of init.getElements()) {
1365
- if (!import_ts_morph3.Node.isObjectLiteralExpression(el)) continue;
1126
+ if (!import_ts_morph2.Node.isObjectLiteralExpression(el)) continue;
1366
1127
  for (const p of el.getProperties()) {
1367
- if (!import_ts_morph3.Node.isPropertyAssignment(p) || p.getName() !== "name") continue;
1128
+ if (!import_ts_morph2.Node.isPropertyAssignment(p) || p.getName() !== "name") continue;
1368
1129
  const nameInit = p.getInitializer();
1369
- if (nameInit && import_ts_morph3.Node.isIdentifier(nameInit)) subTypes.push(nameInit.getText());
1130
+ if (nameInit && import_ts_morph2.Node.isIdentifier(nameInit)) subTypes.push(nameInit.getText());
1370
1131
  }
1371
1132
  }
1372
1133
  }
@@ -1377,9 +1138,9 @@ function resolveDiscriminator(decorator) {
1377
1138
  function resolveTypeFactoryName(decorator) {
1378
1139
  const arg = firstArg(decorator);
1379
1140
  if (!arg) return null;
1380
- if (import_ts_morph3.Node.isArrowFunction(arg)) {
1141
+ if (import_ts_morph2.Node.isArrowFunction(arg)) {
1381
1142
  const body = arg.getBody();
1382
- if (import_ts_morph3.Node.isIdentifier(body)) return body.getText();
1143
+ if (import_ts_morph2.Node.isIdentifier(body)) return body.getText();
1383
1144
  }
1384
1145
  return null;
1385
1146
  }
@@ -1388,12 +1149,12 @@ function singularClassName(typeText) {
1388
1149
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(inner) ? inner : null;
1389
1150
  }
1390
1151
  function genericTypeArgNames(typeNode) {
1391
- if (!typeNode || !import_ts_morph3.Node.isTypeReference(typeNode)) return [];
1152
+ if (!typeNode || !import_ts_morph2.Node.isTypeReference(typeNode)) return [];
1392
1153
  const names = [];
1393
1154
  for (const arg of typeNode.getTypeArguments()) {
1394
- if (!import_ts_morph3.Node.isTypeReference(arg)) return [];
1155
+ if (!import_ts_morph2.Node.isTypeReference(arg)) return [];
1395
1156
  const tn = arg.getTypeName();
1396
- if (!import_ts_morph3.Node.isIdentifier(tn)) return [];
1157
+ if (!import_ts_morph2.Node.isIdentifier(tn)) return [];
1397
1158
  names.push(tn.getText());
1398
1159
  }
1399
1160
  return names;
@@ -1401,7 +1162,7 @@ function genericTypeArgNames(typeNode) {
1401
1162
  function enumSchemaFromDecorator(decorator, classFile, ctx) {
1402
1163
  const arg = firstArg(decorator);
1403
1164
  if (!arg) return null;
1404
- if (import_ts_morph3.Node.isIdentifier(arg)) {
1165
+ if (import_ts_morph2.Node.isIdentifier(arg)) {
1405
1166
  const name = arg.getText();
1406
1167
  const resolved = findType(name, classFile, ctx.project);
1407
1168
  if (resolved && resolved.kind === "enum") {
@@ -1415,12 +1176,12 @@ function enumSchemaFromDecorator(decorator, classFile, ctx) {
1415
1176
  }
1416
1177
  return { kind: "unknown", note: `@IsEnum(${name}): enum not resolvable to literals` };
1417
1178
  }
1418
- if (import_ts_morph3.Node.isObjectLiteralExpression(arg)) {
1179
+ if (import_ts_morph2.Node.isObjectLiteralExpression(arg)) {
1419
1180
  const values = [];
1420
1181
  for (const p of arg.getProperties()) {
1421
- if (!import_ts_morph3.Node.isPropertyAssignment(p)) continue;
1182
+ if (!import_ts_morph2.Node.isPropertyAssignment(p)) continue;
1422
1183
  const init = p.getInitializer();
1423
- if (init && import_ts_morph3.Node.isStringLiteral(init)) values.push(init.getText());
1184
+ if (init && import_ts_morph2.Node.isStringLiteral(init)) values.push(init.getText());
1424
1185
  }
1425
1186
  if (values.length > 0) return { kind: "enum", literals: values };
1426
1187
  }
@@ -1428,9 +1189,9 @@ function enumSchemaFromDecorator(decorator, classFile, ctx) {
1428
1189
  }
1429
1190
  function inSchemaFromDecorator(decorator) {
1430
1191
  const arg = firstArg(decorator);
1431
- if (arg && import_ts_morph3.Node.isArrayLiteralExpression(arg)) {
1192
+ if (arg && import_ts_morph2.Node.isArrayLiteralExpression(arg)) {
1432
1193
  const elements = arg.getElements();
1433
- const allStrings = elements.every((e) => import_ts_morph3.Node.isStringLiteral(e));
1194
+ const allStrings = elements.every((e) => import_ts_morph2.Node.isStringLiteral(e));
1434
1195
  if (allStrings && elements.length > 0) {
1435
1196
  return { kind: "enum", literals: elements.map((e) => e.getText()) };
1436
1197
  }
@@ -1448,7 +1209,7 @@ function inSchemaFromDecorator(decorator) {
1448
1209
  var import_ts_morph6 = require("ts-morph");
1449
1210
 
1450
1211
  // src/discovery/filter-field-types.ts
1451
- var import_ts_morph4 = require("ts-morph");
1212
+ var import_ts_morph3 = require("ts-morph");
1452
1213
 
1453
1214
  // src/discovery/enum-resolution.ts
1454
1215
  var _enumCache = /* @__PURE__ */ new WeakMap();
@@ -1501,28 +1262,28 @@ function markNullable(r, nullable) {
1501
1262
  }
1502
1263
  var TRANSPARENT_TYPE_BRANDS = /* @__PURE__ */ new Set(["Opt", "Hidden"]);
1503
1264
  function classifyTypeNode(typeNode, sourceFile, project, opts) {
1504
- if (import_ts_morph4.Node.isUnionTypeNode(typeNode)) {
1265
+ if (import_ts_morph3.Node.isUnionTypeNode(typeNode)) {
1505
1266
  let nullable = false;
1506
1267
  const stringLits = [];
1507
1268
  const numberLits = [];
1508
1269
  const others = [];
1509
1270
  for (const member of typeNode.getTypeNodes()) {
1510
1271
  const kind = member.getKind();
1511
- if (kind === import_ts_morph4.SyntaxKind.NullKeyword || kind === import_ts_morph4.SyntaxKind.UndefinedKeyword) {
1272
+ if (kind === import_ts_morph3.SyntaxKind.NullKeyword || kind === import_ts_morph3.SyntaxKind.UndefinedKeyword) {
1512
1273
  nullable = true;
1513
1274
  continue;
1514
1275
  }
1515
- if (import_ts_morph4.Node.isLiteralTypeNode(member)) {
1276
+ if (import_ts_morph3.Node.isLiteralTypeNode(member)) {
1516
1277
  const lit = member.getLiteral();
1517
- if (import_ts_morph4.Node.isStringLiteral(lit)) {
1278
+ if (import_ts_morph3.Node.isStringLiteral(lit)) {
1518
1279
  stringLits.push(lit.getLiteralValue());
1519
1280
  continue;
1520
1281
  }
1521
- if (import_ts_morph4.Node.isNumericLiteral(lit)) {
1282
+ if (import_ts_morph3.Node.isNumericLiteral(lit)) {
1522
1283
  numberLits.push(lit.getText());
1523
1284
  continue;
1524
1285
  }
1525
- if (lit.getKind() === import_ts_morph4.SyntaxKind.NullKeyword) {
1286
+ if (lit.getKind() === import_ts_morph3.SyntaxKind.NullKeyword) {
1526
1287
  nullable = true;
1527
1288
  continue;
1528
1289
  }
@@ -1542,19 +1303,19 @@ function classifyTypeNode(typeNode, sourceFile, project, opts) {
1542
1303
  return markNullable({ kind: "unknown" }, nullable);
1543
1304
  }
1544
1305
  switch (typeNode.getKind()) {
1545
- case import_ts_morph4.SyntaxKind.StringKeyword:
1306
+ case import_ts_morph3.SyntaxKind.StringKeyword:
1546
1307
  return { kind: "string" };
1547
- case import_ts_morph4.SyntaxKind.NumberKeyword:
1308
+ case import_ts_morph3.SyntaxKind.NumberKeyword:
1548
1309
  return { kind: "number" };
1549
- case import_ts_morph4.SyntaxKind.BooleanKeyword:
1310
+ case import_ts_morph3.SyntaxKind.BooleanKeyword:
1550
1311
  return { kind: "boolean" };
1551
- case import_ts_morph4.SyntaxKind.AnyKeyword:
1552
- case import_ts_morph4.SyntaxKind.UnknownKeyword:
1312
+ case import_ts_morph3.SyntaxKind.AnyKeyword:
1313
+ case import_ts_morph3.SyntaxKind.UnknownKeyword:
1553
1314
  return { kind: "unknown" };
1554
1315
  default:
1555
1316
  break;
1556
1317
  }
1557
- if (import_ts_morph4.Node.isTypeReference(typeNode)) {
1318
+ if (import_ts_morph3.Node.isTypeReference(typeNode)) {
1558
1319
  const refName = typeNode.getTypeName().getText();
1559
1320
  if (refName === "Date") return { kind: "date" };
1560
1321
  if (refName === "Record" || refName === "Object") return { kind: "json" };
@@ -1572,25 +1333,25 @@ function classifyTypeNode(typeNode, sourceFile, project, opts) {
1572
1333
  if (typeRef) return { kind: "unknown", typeRef };
1573
1334
  return { kind: "unknown" };
1574
1335
  }
1575
- if (import_ts_morph4.Node.isTypeLiteral(typeNode)) return { kind: "json" };
1336
+ if (import_ts_morph3.Node.isTypeLiteral(typeNode)) return { kind: "json" };
1576
1337
  return { kind: "unknown" };
1577
1338
  }
1578
1339
  function enumFromDecoratorArgs(args, sourceFile, project) {
1579
1340
  for (const arg of args) {
1580
- if (import_ts_morph4.Node.isArrowFunction(arg)) {
1341
+ if (import_ts_morph3.Node.isArrowFunction(arg)) {
1581
1342
  const body = arg.getBody();
1582
- if (import_ts_morph4.Node.isIdentifier(body)) {
1343
+ if (import_ts_morph3.Node.isIdentifier(body)) {
1583
1344
  const en = resolveEnumValues(body.getText(), sourceFile, project);
1584
1345
  if (en) return en;
1585
1346
  }
1586
1347
  }
1587
- if (import_ts_morph4.Node.isObjectLiteralExpression(arg)) {
1348
+ if (import_ts_morph3.Node.isObjectLiteralExpression(arg)) {
1588
1349
  const itemsProp = arg.getProperty("items");
1589
- if (itemsProp && import_ts_morph4.Node.isPropertyAssignment(itemsProp)) {
1350
+ if (itemsProp && import_ts_morph3.Node.isPropertyAssignment(itemsProp)) {
1590
1351
  const init = itemsProp.getInitializer();
1591
- if (init && import_ts_morph4.Node.isArrowFunction(init)) {
1352
+ if (init && import_ts_morph3.Node.isArrowFunction(init)) {
1592
1353
  const body = init.getBody();
1593
- if (import_ts_morph4.Node.isIdentifier(body)) {
1354
+ if (import_ts_morph3.Node.isIdentifier(body)) {
1594
1355
  const en = resolveEnumValues(body.getText(), sourceFile, project);
1595
1356
  if (en) return en;
1596
1357
  }
@@ -1613,7 +1374,7 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
1613
1374
  return { kind: "string" };
1614
1375
  }
1615
1376
  for (const arg of args) {
1616
- if (import_ts_morph4.Node.isStringLiteral(arg)) {
1377
+ if (import_ts_morph3.Node.isStringLiteral(arg)) {
1617
1378
  const raw = arg.getLiteralValue();
1618
1379
  const kind = classifyTypeKeyword(raw);
1619
1380
  if (kind) {
@@ -1621,11 +1382,11 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
1621
1382
  return { kind };
1622
1383
  }
1623
1384
  }
1624
- if (import_ts_morph4.Node.isObjectLiteralExpression(arg)) {
1385
+ if (import_ts_morph3.Node.isObjectLiteralExpression(arg)) {
1625
1386
  const enumProp = arg.getProperty("enum");
1626
- if (enumProp && import_ts_morph4.Node.isPropertyAssignment(enumProp)) {
1387
+ if (enumProp && import_ts_morph3.Node.isPropertyAssignment(enumProp)) {
1627
1388
  const init = enumProp.getInitializer();
1628
- if (init && import_ts_morph4.Node.isIdentifier(init)) {
1389
+ if (init && import_ts_morph3.Node.isIdentifier(init)) {
1629
1390
  const en = resolveEnumValues(init.getText(), sourceFile, project);
1630
1391
  if (en) {
1631
1392
  return en.numeric ? { kind: "number", enumValues: en.values, numericEnum: true } : { kind: "string", enumValues: en.values };
@@ -1635,15 +1396,15 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
1635
1396
  }
1636
1397
  for (const key of ["columnType", "type"]) {
1637
1398
  const typeProp = arg.getProperty(key);
1638
- if (!typeProp || !import_ts_morph4.Node.isPropertyAssignment(typeProp)) continue;
1399
+ if (!typeProp || !import_ts_morph3.Node.isPropertyAssignment(typeProp)) continue;
1639
1400
  const init = typeProp.getInitializer();
1640
1401
  if (!init) continue;
1641
- if (import_ts_morph4.Node.isStringLiteral(init)) {
1402
+ if (import_ts_morph3.Node.isStringLiteral(init)) {
1642
1403
  const kind = classifyTypeKeyword(init.getLiteralValue());
1643
1404
  if (kind) return { kind };
1644
1405
  continue;
1645
1406
  }
1646
- if (import_ts_morph4.Node.isIdentifier(init)) {
1407
+ if (import_ts_morph3.Node.isIdentifier(init)) {
1647
1408
  const kind = classifyTypeKeyword(init.getText());
1648
1409
  if (kind) return { kind };
1649
1410
  }
@@ -1686,6 +1447,49 @@ function toFilterFieldType(name, r) {
1686
1447
 
1687
1448
  // src/discovery/heritage.ts
1688
1449
  var import_ts_morph5 = require("ts-morph");
1450
+
1451
+ // src/discovery/unresolved-imports.ts
1452
+ var import_ts_morph4 = require("ts-morph");
1453
+ var UnresolvedImports = class {
1454
+ read;
1455
+ /**
1456
+ * The files with an import that failed to resolve in `program` and now resolves
1457
+ * to a TypeScript file the program does not hold.
1458
+ */
1459
+ nowResolving(program) {
1460
+ if (this.read?.program !== program) {
1461
+ this.read = { program, imports: failedImports(program) };
1462
+ }
1463
+ const options = program.getCompilerOptions();
1464
+ const files = /* @__PURE__ */ new Set();
1465
+ for (const { specifier, from } of this.read.imports) {
1466
+ const file = import_ts_morph4.ts.resolveModuleName(specifier, from, options, import_ts_morph4.ts.sys).resolvedModule?.resolvedFileName;
1467
+ if (file !== void 0 && /\.(m|c)?tsx?$/.test(file) && !program.getSourceFile(file)) {
1468
+ files.add(from);
1469
+ }
1470
+ }
1471
+ return [...files];
1472
+ }
1473
+ };
1474
+ function failedImports(program) {
1475
+ const checker = program.getTypeChecker();
1476
+ const imports = [];
1477
+ for (const file of program.getSourceFiles()) {
1478
+ if (program.isSourceFileDefaultLibrary(file) || file.isDeclarationFile) continue;
1479
+ if (program.isSourceFileFromExternalLibrary(file)) continue;
1480
+ for (const statement of file.statements) {
1481
+ if (!import_ts_morph4.ts.isImportDeclaration(statement) && !import_ts_morph4.ts.isExportDeclaration(statement)) continue;
1482
+ const specifier = statement.moduleSpecifier;
1483
+ if (!specifier || !import_ts_morph4.ts.isStringLiteral(specifier)) continue;
1484
+ if (!checker.getSymbolAtLocation(specifier)) {
1485
+ imports.push({ specifier: specifier.text, from: file.fileName });
1486
+ }
1487
+ }
1488
+ }
1489
+ return imports;
1490
+ }
1491
+
1492
+ // src/discovery/heritage.ts
1689
1493
  function resolveHeritageCall(node) {
1690
1494
  if (!node) return void 0;
1691
1495
  const expr = unwrapExpression(node);
@@ -1885,15 +1689,42 @@ function resolvePropertyClassReference(prop) {
1885
1689
  return { key, ref };
1886
1690
  }
1887
1691
  var mixinTypeProject;
1692
+ var mixinFiles = new DiskFingerprints();
1693
+ var mixinStale = false;
1694
+ var mixinUnresolved = new UnresolvedImports();
1888
1695
  function getMixinTypeProject() {
1889
- mixinTypeProject ??= new import_ts_morph5.Project({
1890
- skipAddingFilesFromTsConfig: true,
1891
- compilerOptions: { strict: true }
1892
- });
1696
+ if (mixinTypeProject && mixinStale && !refreshMixinTypeProject(mixinTypeProject)) {
1697
+ mixinTypeProject = void 0;
1698
+ }
1699
+ mixinStale = false;
1700
+ if (!mixinTypeProject) {
1701
+ mixinTypeProject = new import_ts_morph5.Project({
1702
+ skipAddingFilesFromTsConfig: true,
1703
+ compilerOptions: { strict: true }
1704
+ });
1705
+ mixinFiles = new DiskFingerprints();
1706
+ mixinUnresolved = new UnresolvedImports();
1707
+ }
1893
1708
  return mixinTypeProject;
1894
1709
  }
1895
1710
  function clearMixinTypeProject() {
1896
1711
  mixinTypeProject = void 0;
1712
+ mixinStale = false;
1713
+ }
1714
+ function invalidateMixinTypeProject() {
1715
+ mixinStale = true;
1716
+ }
1717
+ function refreshMixinTypeProject(project) {
1718
+ const program = project.getProgram().compilerObject;
1719
+ const files = program.getSourceFiles().filter((file) => !program.isSourceFileDefaultLibrary(file)).map((file) => ({ path: file.fileName, text: file.text }));
1720
+ const changed = mixinFiles.changed(files);
1721
+ if (changed.length === 0) return mixinUnresolved.nowResolving(program).length === 0;
1722
+ for (const path of changed) {
1723
+ const sf = project.getSourceFile(path);
1724
+ if (sf?.refreshFromFileSystemSync() !== import_ts_morph5.FileSystemRefreshResult.Updated) return false;
1725
+ }
1726
+ project.resolveSourceFileDependencies();
1727
+ return true;
1897
1728
  }
1898
1729
  function resolveInstantiatedReturnType(cls, methodName) {
1899
1730
  const filePath = cls.getSourceFile().getFilePath();
@@ -2251,8 +2082,7 @@ var SchemaTypeProgram = class {
2251
2082
  skipAddingFilesFromTsConfig: true,
2252
2083
  compilerOptions: this.options
2253
2084
  });
2254
- for (const sf of discovery.getSourceFiles()) this.request(sf.getFilePath(), { schema: true });
2255
- this.flush();
2085
+ this.requestDiscoveryFiles();
2256
2086
  }
2257
2087
  project;
2258
2088
  options;
@@ -2265,6 +2095,53 @@ var SchemaTypeProgram = class {
2265
2095
  needs = /* @__PURE__ */ new Map();
2266
2096
  /** Pruned text per file, as last written into {@link project}. */
2267
2097
  written = /* @__PURE__ */ new Map();
2098
+ /** What {@link project} read from disk unpruned (packages, declaration files). */
2099
+ externalFiles = new DiskFingerprints();
2100
+ scratchFiles = new DiskFingerprints();
2101
+ /** The last pruning of each file, with the source text and needs it was pruned for. */
2102
+ pruned = /* @__PURE__ */ new Map();
2103
+ /**
2104
+ * Bring the program up to date with discovery's files and the disk, keeping the
2105
+ * lib and package declarations it has already parsed and checked. Leaves it
2106
+ * holding exactly the files a new program built now would hold, with the same
2107
+ * text. Returns false when a file the program read unpruned has changed on disk
2108
+ * (a package was reinstalled, a `.d.ts` was edited): the caller builds a new
2109
+ * program instead.
2110
+ */
2111
+ refresh() {
2112
+ const program = this.project.getProgram().compilerObject;
2113
+ const external = program.getSourceFiles().filter(
2114
+ (file) => !program.isSourceFileDefaultLibrary(file) && !this.written.has(file.fileName)
2115
+ ).map((file) => ({ path: file.fileName, text: file.text }));
2116
+ if (this.externalFiles.changed(external).length > 0) return false;
2117
+ const scratch = this.scratch.getSourceFiles().map((sf) => ({ path: sf.getFilePath(), text: sf.getFullText() }));
2118
+ for (const path of this.scratchFiles.changed(scratch)) {
2119
+ const sf = this.scratch.getSourceFile(path);
2120
+ if (sf?.refreshFromFileSystemSync() === import_ts_morph7.FileSystemRefreshResult.Deleted) {
2121
+ this.scratchFiles.forget(path);
2122
+ }
2123
+ }
2124
+ this.needs.clear();
2125
+ this.requestDiscoveryFiles();
2126
+ for (const path of [...this.written.keys()]) {
2127
+ if (this.needs.has(path)) continue;
2128
+ const sf = this.project.getSourceFile(path);
2129
+ if (sf) this.project.removeSourceFile(sf);
2130
+ this.written.delete(path);
2131
+ }
2132
+ return true;
2133
+ }
2134
+ /**
2135
+ * Everything discovery holds now (the controllers) goes in before the first
2136
+ * query: a file added afterwards discards the program and its checker, and
2137
+ * rebuilding them once per controller is exactly the cost this avoids.
2138
+ */
2139
+ requestDiscoveryFiles() {
2140
+ for (const sf of this.discovery.getSourceFiles()) {
2141
+ this.request(sf.getFilePath(), { schema: true });
2142
+ }
2143
+ this.flush();
2144
+ }
2268
2145
  /**
2269
2146
  * The typed copy of a discovery file, with its schema positions kept. Adding one
2270
2147
  * that was not there yet (a contract file discovery reached lazily) rebuilds the
@@ -2305,12 +2182,21 @@ var SchemaTypeProgram = class {
2305
2182
  while (this.queue.length > 0) {
2306
2183
  const path = this.queue.pop();
2307
2184
  const original = this.original(path);
2308
- if (!original) continue;
2309
- const needs = this.needs.get(path);
2310
- const text = this.prune(original, needs);
2185
+ if (!original) {
2186
+ if (this.written.has(path)) changed.set(path, void 0);
2187
+ continue;
2188
+ }
2189
+ const { text, requests } = this.pruneCached(path, original, this.needs.get(path));
2190
+ for (const { path: target, want } of requests) this.request(target, want);
2311
2191
  if (this.written.get(path) !== text) changed.set(path, text);
2312
2192
  }
2313
2193
  for (const [path, text] of changed) {
2194
+ if (text === void 0) {
2195
+ const sf = this.project.getSourceFile(path);
2196
+ if (sf) this.project.removeSourceFile(sf);
2197
+ this.written.delete(path);
2198
+ continue;
2199
+ }
2314
2200
  this.project.createSourceFile(path, text, { overwrite: true });
2315
2201
  this.written.set(path, text);
2316
2202
  }
@@ -2319,11 +2205,29 @@ var SchemaTypeProgram = class {
2319
2205
  original(path) {
2320
2206
  return this.discovery.getSourceFile(path) ?? this.scratch.getSourceFile(path) ?? this.scratch.addSourceFileAtPathIfExists(path);
2321
2207
  }
2208
+ /** {@link prune}, reusing the last result while the file's text and needs are the same. */
2209
+ pruneCached(path, sf, needs) {
2210
+ const source = sf.getFullText();
2211
+ const key = `${needs.schema}:${needs.all}:${[...needs.names].sort().join(",")}`;
2212
+ const last = this.pruned.get(path);
2213
+ if (last && last.source === source && last.needs === key) return last;
2214
+ const result = this.prune(sf, needs);
2215
+ if (result.resolved) this.pruned.set(path, { ...result, source, needs: key });
2216
+ else this.pruned.delete(path);
2217
+ return result;
2218
+ }
2322
2219
  /**
2323
2220
  * `sf`'s text with every import/re-export nothing in `needs` references blanked,
2324
- * requesting from each kept one's target the names it brings in.
2221
+ * and the names each kept one brings in from its target.
2325
2222
  */
2326
2223
  prune(sf, needs) {
2224
+ const requests = [];
2225
+ let resolved = true;
2226
+ const resolveFrom = (specifier) => {
2227
+ const target = this.resolve(specifier, sf);
2228
+ if (target === null) resolved = false;
2229
+ return target ?? void 0;
2230
+ };
2327
2231
  const locals = topLevelDeclarations(sf);
2328
2232
  const roots = [];
2329
2233
  if (needs.all) roots.push(...sf.getStatements());
@@ -2376,7 +2280,7 @@ var SchemaTypeProgram = class {
2376
2280
  }
2377
2281
  };
2378
2282
  for (const decl of sf.getImportDeclarations()) {
2379
- const target = this.resolve(decl.getModuleSpecifierValue(), sf);
2283
+ const target = resolveFrom(decl.getModuleSpecifierValue());
2380
2284
  const namespace = decl.getNamespaceImport()?.getText();
2381
2285
  const def = decl.getDefaultImport()?.getText();
2382
2286
  const named = decl.getNamedImports().map((n) => ({
@@ -2390,21 +2294,24 @@ var SchemaTypeProgram = class {
2390
2294
  continue;
2391
2295
  }
2392
2296
  if (target) {
2393
- this.request(target, {
2394
- all: wantsNamespace,
2395
- names: [...wantsDefault ? ["default"] : [], ...named.map((n) => n.imported)]
2297
+ requests.push({
2298
+ path: target,
2299
+ want: {
2300
+ all: wantsNamespace,
2301
+ names: [...wantsDefault ? ["default"] : [], ...named.map((n) => n.imported)]
2302
+ }
2396
2303
  });
2397
2304
  }
2398
2305
  }
2399
2306
  for (const exp of sf.getExportDeclarations()) {
2400
2307
  const spec = exp.getModuleSpecifierValue();
2401
2308
  if (spec === void 0) continue;
2402
- const target = this.resolve(spec, sf);
2309
+ const target = resolveFrom(spec);
2403
2310
  if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) {
2404
2311
  if (!needs.all && unresolvedNames.length === 0) {
2405
2312
  blank(exp);
2406
2313
  } else if (target) {
2407
- this.request(target, { all: needs.all, names: unresolvedNames });
2314
+ requests.push({ path: target, want: { all: needs.all, names: unresolvedNames } });
2408
2315
  }
2409
2316
  continue;
2410
2317
  }
@@ -2412,13 +2319,14 @@ var SchemaTypeProgram = class {
2412
2319
  (n) => needs.all || needs.names.has((n.getAliasNode() ?? n.getNameNode()).getText())
2413
2320
  ).map((n) => n.getNameNode().getText());
2414
2321
  if (wanted.length === 0) blank(exp);
2415
- else if (target) this.request(target, { names: wanted });
2322
+ else if (target) requests.push({ path: target, want: { names: wanted } });
2416
2323
  }
2417
- return chars.join("");
2324
+ return { text: chars.join(""), requests, resolved };
2418
2325
  }
2419
2326
  /**
2420
- * The project source file a module specifier resolves to, or undefined for a
2421
- * package (read unpruned by the program itself) or an unresolvable one.
2327
+ * The project source file a module specifier resolves to; undefined for a
2328
+ * package or a declaration file (read unpruned by the program itself), null for
2329
+ * one that does not resolve.
2422
2330
  */
2423
2331
  resolve(specifier, from) {
2424
2332
  const resolved = import_ts_morph7.ts.resolveModuleName(
@@ -2427,7 +2335,8 @@ var SchemaTypeProgram = class {
2427
2335
  this.options,
2428
2336
  import_ts_morph7.ts.sys
2429
2337
  ).resolvedModule;
2430
- if (!resolved || resolved.isExternalLibraryImport) return void 0;
2338
+ if (!resolved) return null;
2339
+ if (resolved.isExternalLibraryImport) return void 0;
2431
2340
  const file = resolved.resolvedFileName;
2432
2341
  if (file.endsWith(".d.ts") || !/\.(m|c)?tsx?$/.test(file)) return void 0;
2433
2342
  return file;
@@ -2482,8 +2391,13 @@ function defaultExport(sf) {
2482
2391
 
2483
2392
  // src/discovery/schema-type-inference.ts
2484
2393
  var programs = /* @__PURE__ */ new WeakMap();
2394
+ var stale = /* @__PURE__ */ new WeakSet();
2485
2395
  function programFor(discovery) {
2486
2396
  let program = programs.get(discovery);
2397
+ if (program && stale.has(discovery)) {
2398
+ stale.delete(discovery);
2399
+ if (!program.refresh()) program = void 0;
2400
+ }
2487
2401
  if (!program) {
2488
2402
  program = new SchemaTypeProgram(discovery);
2489
2403
  programs.set(discovery, program);
@@ -2492,6 +2406,10 @@ function programFor(discovery) {
2492
2406
  }
2493
2407
  function clearSchemaTypeProject(discovery) {
2494
2408
  programs.delete(discovery);
2409
+ stale.delete(discovery);
2410
+ }
2411
+ function invalidateSchemaTypeProject(discovery) {
2412
+ if (programs.has(discovery)) stale.add(discovery);
2495
2413
  }
2496
2414
  function counterpart(typedSf, node) {
2497
2415
  const start = node.getStart();
@@ -3439,7 +3357,7 @@ async function discoverContractsFast(opts) {
3439
3357
  return routes;
3440
3358
  }
3441
3359
  function resolveTsconfigPath(cwd, tsconfig) {
3442
- return tsconfig ? (0, import_node_path5.resolve)(tsconfig) : (0, import_node_path5.join)(cwd, "tsconfig.json");
3360
+ return tsconfig ? (0, import_node_path4.resolve)(tsconfig) : (0, import_node_path4.join)(cwd, "tsconfig.json");
3443
3361
  }
3444
3362
  var NO_TSCONFIG_OPTIONS = {
3445
3363
  allowJs: true,
@@ -3447,7 +3365,7 @@ var NO_TSCONFIG_OPTIONS = {
3447
3365
  strict: false
3448
3366
  };
3449
3367
  function loadDiscoveryTsconfig(tsconfigPath) {
3450
- if (!(0, import_node_fs.existsSync)(tsconfigPath)) return { options: null, files: [] };
3368
+ if (!(0, import_node_fs2.existsSync)(tsconfigPath)) return { options: null, files: [] };
3451
3369
  const read = import_ts_morph12.ts.readConfigFile(tsconfigPath, (path) => import_ts_morph12.ts.sys.readFile(path));
3452
3370
  if (read.error) {
3453
3371
  return {
@@ -3469,7 +3387,7 @@ function loadDiscoveryTsconfig(tsconfigPath) {
3469
3387
  const parsed = import_ts_morph12.ts.parseJsonSourceFileConfigFileContent(
3470
3388
  sourceFile,
3471
3389
  host,
3472
- (0, import_node_path5.dirname)(tsconfigPath),
3390
+ (0, import_node_path4.dirname)(tsconfigPath),
3473
3391
  void 0,
3474
3392
  tsconfigPath
3475
3393
  );
@@ -3531,6 +3449,9 @@ var PersistentDiscovery = class _PersistentDiscovery {
3531
3449
  glob;
3532
3450
  /** Absolute paths of the controllers currently loaded as extraction roots. */
3533
3451
  controllerPaths = /* @__PURE__ */ new Set();
3452
+ /** Every file in {@link project}, as last read from disk. */
3453
+ files = new DiskFingerprints();
3454
+ unresolved = new UnresolvedImports();
3534
3455
  constructor(project, cwd, glob) {
3535
3456
  this.project = project;
3536
3457
  this.cwd = cwd;
@@ -3556,6 +3477,8 @@ var PersistentDiscovery = class _PersistentDiscovery {
3556
3477
  }
3557
3478
  /** Run the initial extraction (equivalent to a first `discoverContractsFast`). */
3558
3479
  discover() {
3480
+ clearMixinTypeProject();
3481
+ clearSchemaTypeProject(this.project);
3559
3482
  return this.runExtraction();
3560
3483
  }
3561
3484
  /**
@@ -3563,26 +3486,39 @@ var PersistentDiscovery = class _PersistentDiscovery {
3563
3486
  * from disk (controllers AND any lazily-loaded DTO/imported files), re-globs
3564
3487
  * to pick up added/removed controllers, clears the per-Project caches, then
3565
3488
  * re-extracts. `changedPaths` is a hint; correctness does not depend on it
3566
- * being exhaustive because re-globbing + refresh-on-presence covers the set.
3489
+ * being exhaustive because re-globbing + comparing every loaded file with the
3490
+ * disk covers the set.
3567
3491
  */
3568
- async rediscover(changedPaths) {
3569
- if (changedPaths) {
3570
- for (const p of changedPaths) {
3571
- const abs = (0, import_node_path5.resolve)(p);
3572
- const sf = this.project.getSourceFile(abs);
3573
- if (sf) {
3574
- await sf.refreshFromFileSystem();
3575
- }
3492
+ async rediscover(changedPaths, options = {}) {
3493
+ const hinted = new Set([...changedPaths ?? []].map((p) => (0, import_node_path4.resolve)(p)));
3494
+ const program = this.project.getProgram().compilerObject;
3495
+ const sources = program.getSourceFiles().filter(
3496
+ (file) => !program.isSourceFileDefaultLibrary(file) && !program.isSourceFileFromExternalLibrary(file)
3497
+ ).map((file) => ({ path: file.fileName, text: file.text }));
3498
+ for (const path of /* @__PURE__ */ new Set([...hinted, ...this.files.changed(sources)])) {
3499
+ const sf = this.project.getSourceFile(path);
3500
+ if (sf && await sf.refreshFromFileSystem() === import_ts_morph12.FileSystemRefreshResult.Deleted) {
3501
+ this.files.forget(path);
3576
3502
  }
3577
3503
  }
3504
+ const inProject = new Set(this.project.getSourceFiles().map((sf) => sf.getFilePath()));
3505
+ const definitions = this.project.getLanguageService().compilerObject.getProgram();
3506
+ for (const path of definitions ? this.unresolved.nowResolving(definitions) : []) {
3507
+ const sf = this.project.getSourceFile(path);
3508
+ if (!sf) continue;
3509
+ this.project.removeSourceFile(sf);
3510
+ if (inProject.has(path)) this.project.addSourceFileAtPathIfExists(path);
3511
+ }
3578
3512
  const globbed = new Set(
3579
3513
  await (0, import_fast_glob2.default)(this.glob, { cwd: this.cwd, absolute: true, onlyFiles: true })
3580
3514
  );
3515
+ let controllersChanged = false;
3581
3516
  for (const f of globbed) {
3582
3517
  if (!this.controllerPaths.has(f)) {
3583
3518
  try {
3584
3519
  this.project.addSourceFileAtPath(f);
3585
3520
  this.controllerPaths.add(f);
3521
+ controllersChanged = true;
3586
3522
  } catch {
3587
3523
  }
3588
3524
  }
@@ -3592,16 +3528,22 @@ var PersistentDiscovery = class _PersistentDiscovery {
3592
3528
  const sf = this.project.getSourceFile(f);
3593
3529
  if (sf) this.project.removeSourceFile(sf);
3594
3530
  this.controllerPaths.delete(f);
3531
+ controllersChanged = true;
3595
3532
  }
3596
3533
  }
3534
+ if (controllersChanged || options.filesAddedOrRemoved) {
3535
+ clearMixinTypeProject();
3536
+ clearSchemaTypeProject(this.project);
3537
+ } else {
3538
+ invalidateMixinTypeProject();
3539
+ invalidateSchemaTypeProject(this.project);
3540
+ }
3597
3541
  return this.runExtraction();
3598
3542
  }
3599
3543
  /** Clear stale per-Project caches, then extract over the controller set. */
3600
3544
  runExtraction() {
3601
3545
  clearTypeResolutionCaches(this.project);
3602
3546
  clearEnumCache(this.project);
3603
- clearMixinTypeProject();
3604
- clearSchemaTypeProject(this.project);
3605
3547
  return extractRoutesFrom(this.project, this.controllerPaths);
3606
3548
  }
3607
3549
  };
@@ -3888,7 +3830,300 @@ function extractFromSourceFile(sourceFile, project) {
3888
3830
  return routes;
3889
3831
  }
3890
3832
 
3833
+ // src/discovery/shared-props.ts
3834
+ function discoverSharedProps(project, moduleEntry) {
3835
+ try {
3836
+ let sourceFile = project.getSourceFile(moduleEntry);
3837
+ if (!sourceFile) {
3838
+ try {
3839
+ sourceFile = project.addSourceFileAtPath(moduleEntry);
3840
+ } catch {
3841
+ return null;
3842
+ }
3843
+ }
3844
+ const forRootCall = findForRootCall(sourceFile);
3845
+ if (!forRootCall) return null;
3846
+ const initializer = findShareInitializer(forRootCall);
3847
+ if (!initializer) return null;
3848
+ return extractShareType(initializer, sourceFile, project);
3849
+ } catch {
3850
+ return null;
3851
+ }
3852
+ }
3853
+ function discoverSharedPropsFromConfig(config) {
3854
+ if (!config.app?.moduleEntry) return null;
3855
+ try {
3856
+ const tsconfigPath = config.app.tsconfig ?? (0, import_node_path5.join)(config.codegen.cwd, "tsconfig.json");
3857
+ let project;
3858
+ try {
3859
+ project = sharedPropsProject(tsconfigPath);
3860
+ } catch {
3861
+ project = new import_ts_morph13.Project({
3862
+ skipAddingFilesFromTsConfig: true,
3863
+ skipLoadingLibFiles: true,
3864
+ skipFileDependencyResolution: true,
3865
+ compilerOptions: { allowJs: true, strict: false }
3866
+ });
3867
+ }
3868
+ return discoverSharedProps(project, config.app.moduleEntry);
3869
+ } catch {
3870
+ return null;
3871
+ }
3872
+ }
3873
+ var parsedTsconfigs = /* @__PURE__ */ new Map();
3874
+ function readText(file) {
3875
+ try {
3876
+ return (0, import_node_fs3.readFileSync)(file, "utf8");
3877
+ } catch {
3878
+ return "\0missing";
3879
+ }
3880
+ }
3881
+ function sharedPropsProject(tsconfigPath) {
3882
+ const text = loadDiscoveryTsconfig(tsconfigPath).files.map((file) => `${file}\0${readText(file)}`).join("\0");
3883
+ const flags = {
3884
+ skipAddingFilesFromTsConfig: true,
3885
+ skipLoadingLibFiles: true,
3886
+ skipFileDependencyResolution: true
3887
+ };
3888
+ const parsed = parsedTsconfigs.get(tsconfigPath);
3889
+ if (parsed?.text === text) return new import_ts_morph13.Project({ ...flags, compilerOptions: parsed.options });
3890
+ const project = new import_ts_morph13.Project({ ...flags, tsConfigFilePath: tsconfigPath });
3891
+ parsedTsconfigs.set(tsconfigPath, { text, options: project.getCompilerOptions() });
3892
+ return project;
3893
+ }
3894
+ function findForRootCall(sourceFile) {
3895
+ const callExpressions = sourceFile.getDescendantsOfKind(import_ts_morph13.SyntaxKind.CallExpression);
3896
+ for (const call of callExpressions) {
3897
+ const expr = call.getExpression();
3898
+ if (!import_ts_morph13.Node.isPropertyAccessExpression(expr)) continue;
3899
+ const methodName = expr.getName();
3900
+ const objectExpr = expr.getExpression();
3901
+ if (methodName === "forRoot" && import_ts_morph13.Node.isIdentifier(objectExpr)) {
3902
+ const name = objectExpr.getText();
3903
+ if (name === "InertiaModule") {
3904
+ return call;
3905
+ }
3906
+ }
3907
+ }
3908
+ return null;
3909
+ }
3910
+ function findShareInitializer(forRootCall) {
3911
+ if (!import_ts_morph13.Node.isCallExpression(forRootCall)) return null;
3912
+ const args = forRootCall.getArguments();
3913
+ const firstArg2 = args[0];
3914
+ if (!firstArg2 || !import_ts_morph13.Node.isObjectLiteralExpression(firstArg2)) return null;
3915
+ for (const prop of firstArg2.getProperties()) {
3916
+ if (import_ts_morph13.Node.isPropertyAssignment(prop) && prop.getName() === "share") {
3917
+ return prop.getInitializer() ?? null;
3918
+ }
3919
+ }
3920
+ return null;
3921
+ }
3922
+ function extractShareType(node, sourceFile, project) {
3923
+ if (import_ts_morph13.Node.isIdentifier(node)) {
3924
+ const ref = resolveIdentifierToImportRef(node, sourceFile, project);
3925
+ if (ref) {
3926
+ return {
3927
+ typeString: `Awaited<ReturnType<typeof import('${ref.importPath}').${ref.exportName}>>`,
3928
+ properties: null,
3929
+ isImportRef: true
3930
+ };
3931
+ }
3932
+ }
3933
+ if (import_ts_morph13.Node.isArrowFunction(node)) {
3934
+ const result = extractFromFunctionLike(node, sourceFile);
3935
+ return result ? { ...result, isImportRef: false } : null;
3936
+ }
3937
+ if (import_ts_morph13.Node.isFunctionExpression(node)) {
3938
+ const result = extractFromFunctionLike(node, sourceFile);
3939
+ return result ? { ...result, isImportRef: false } : null;
3940
+ }
3941
+ if (import_ts_morph13.Node.isObjectLiteralExpression(node)) {
3942
+ const result = extractFromObjectLiteral(node);
3943
+ return result ? { ...result, isImportRef: false } : null;
3944
+ }
3945
+ return null;
3946
+ }
3947
+ function resolveIdentifierToImportRef(id, sourceFile, project) {
3948
+ if (!import_ts_morph13.Node.isIdentifier(id)) return null;
3949
+ const name = id.getText();
3950
+ const localFunc = sourceFile.getFunction(name);
3951
+ if (localFunc?.isExported()) {
3952
+ const filePath = sourceFile.getFilePath().replace(/\.ts$/, "");
3953
+ return { importPath: filePath, exportName: name };
3954
+ }
3955
+ const localVar = sourceFile.getVariableDeclaration(name);
3956
+ if (localVar?.isExported()) {
3957
+ const filePath = sourceFile.getFilePath().replace(/\.ts$/, "");
3958
+ return { importPath: filePath, exportName: name };
3959
+ }
3960
+ for (const imp of sourceFile.getImportDeclarations()) {
3961
+ for (const named of imp.getNamedImports()) {
3962
+ const importedName = named.getAliasNode()?.getText() ?? named.getName();
3963
+ if (importedName !== name) continue;
3964
+ const resolvedSource = imp.getModuleSpecifierSourceFile();
3965
+ if (!resolvedSource) continue;
3966
+ const originalName = named.getName();
3967
+ const fn = resolvedSource.getFunction(originalName);
3968
+ if (fn?.isExported()) {
3969
+ const filePath = resolvedSource.getFilePath().replace(/\.ts$/, "");
3970
+ return { importPath: filePath, exportName: originalName };
3971
+ }
3972
+ const v = resolvedSource.getVariableDeclaration(originalName);
3973
+ if (v?.isExported()) {
3974
+ const filePath = resolvedSource.getFilePath().replace(/\.ts$/, "");
3975
+ return { importPath: filePath, exportName: originalName };
3976
+ }
3977
+ }
3978
+ }
3979
+ return null;
3980
+ }
3981
+ function extractFromFunctionLike(node, _sourceFile) {
3982
+ const returnTypeNode = import_ts_morph13.Node.isArrowFunction(node) || import_ts_morph13.Node.isFunctionExpression(node) ? node.getReturnTypeNode() : null;
3983
+ if (returnTypeNode) {
3984
+ return extractFromReturnTypeAnnotation(returnTypeNode);
3985
+ }
3986
+ if (import_ts_morph13.Node.isArrowFunction(node)) {
3987
+ const body = node.getBody();
3988
+ if (import_ts_morph13.Node.isParenthesizedExpression(body)) {
3989
+ const inner = body.getExpression();
3990
+ if (import_ts_morph13.Node.isObjectLiteralExpression(inner)) {
3991
+ return extractFromObjectLiteral(inner);
3992
+ }
3993
+ }
3994
+ if (import_ts_morph13.Node.isObjectLiteralExpression(body)) {
3995
+ return extractFromObjectLiteral(body);
3996
+ }
3997
+ if (import_ts_morph13.Node.isBlock(body)) {
3998
+ return extractFromBlockReturn(body);
3999
+ }
4000
+ }
4001
+ if (import_ts_morph13.Node.isFunctionExpression(node)) {
4002
+ const body = node.getBody();
4003
+ if (import_ts_morph13.Node.isBlock(body)) {
4004
+ return extractFromBlockReturn(body);
4005
+ }
4006
+ }
4007
+ return null;
4008
+ }
4009
+ function extractFromReturnTypeAnnotation(typeNode) {
4010
+ if (import_ts_morph13.Node.isTypeReference(typeNode)) {
4011
+ const typeName = typeNode.getTypeName();
4012
+ if (import_ts_morph13.Node.isIdentifier(typeName) && typeName.getText() === "Promise") {
4013
+ const typeArgs = typeNode.getTypeArguments();
4014
+ const firstArg2 = typeArgs[0];
4015
+ if (firstArg2) {
4016
+ return extractFromReturnTypeAnnotation(firstArg2);
4017
+ }
4018
+ return null;
4019
+ }
4020
+ }
4021
+ if (import_ts_morph13.Node.isTypeLiteral(typeNode)) {
4022
+ const properties = [];
4023
+ for (const member of typeNode.getMembers()) {
4024
+ if (import_ts_morph13.Node.isPropertySignature(member)) {
4025
+ const name = member.getName();
4026
+ const memberTypeNode = member.getTypeNode();
4027
+ const type = memberTypeNode ? memberTypeNode.getText() : "unknown";
4028
+ properties.push({ name, type });
4029
+ }
4030
+ }
4031
+ if (properties.length === 0) return null;
4032
+ const typeString = `{ ${properties.map((p) => `${p.name}: ${p.type}`).join("; ")} }`;
4033
+ return { typeString, properties, isImportRef: false };
4034
+ }
4035
+ return null;
4036
+ }
4037
+ function extractFromBlockReturn(block) {
4038
+ if (!import_ts_morph13.Node.isBlock(block)) return null;
4039
+ const statements = block.getStatements();
4040
+ for (let i = statements.length - 1; i >= 0; i--) {
4041
+ const stmt = statements[i];
4042
+ if (!import_ts_morph13.Node.isReturnStatement(stmt)) continue;
4043
+ const expr = stmt.getExpression();
4044
+ if (!expr) continue;
4045
+ if (import_ts_morph13.Node.isObjectLiteralExpression(expr)) {
4046
+ return extractFromObjectLiteral(expr);
4047
+ }
4048
+ if (import_ts_morph13.Node.isParenthesizedExpression(expr)) {
4049
+ const inner = expr.getExpression();
4050
+ if (import_ts_morph13.Node.isObjectLiteralExpression(inner)) {
4051
+ return extractFromObjectLiteral(inner);
4052
+ }
4053
+ }
4054
+ break;
4055
+ }
4056
+ return null;
4057
+ }
4058
+ function extractFromObjectLiteral(objLiteral) {
4059
+ if (!import_ts_morph13.Node.isObjectLiteralExpression(objLiteral)) return null;
4060
+ const properties = [];
4061
+ for (const prop of objLiteral.getProperties()) {
4062
+ if (!import_ts_morph13.Node.isPropertyAssignment(prop)) continue;
4063
+ const name = prop.getName();
4064
+ const initializer = prop.getInitializer();
4065
+ if (!initializer) continue;
4066
+ const type = inferExpressionType(initializer);
4067
+ properties.push({ name, type });
4068
+ }
4069
+ if (properties.length === 0) return null;
4070
+ const typeString = `{ ${properties.map((p) => `${p.name}: ${p.type}`).join("; ")} }`;
4071
+ return { typeString, properties, isImportRef: false };
4072
+ }
4073
+ function inferExpressionType(node) {
4074
+ if (import_ts_morph13.Node.isStringLiteral(node)) return "string";
4075
+ if (import_ts_morph13.Node.isTemplateExpression(node) || import_ts_morph13.Node.isNoSubstitutionTemplateLiteral(node))
4076
+ return "string";
4077
+ if (import_ts_morph13.Node.isNumericLiteral(node)) return "number";
4078
+ if (node.getKind() === import_ts_morph13.SyntaxKind.TrueKeyword || node.getKind() === import_ts_morph13.SyntaxKind.FalseKeyword) {
4079
+ return "boolean";
4080
+ }
4081
+ if (node.getKind() === import_ts_morph13.SyntaxKind.NullKeyword) return "null";
4082
+ if (import_ts_morph13.Node.isIdentifier(node) && node.getText() === "undefined") return "undefined";
4083
+ if (import_ts_morph13.Node.isObjectLiteralExpression(node)) {
4084
+ const props = node.getProperties();
4085
+ if (props.length === 0) return "Record<string, unknown>";
4086
+ const entries = [];
4087
+ for (const prop of props) {
4088
+ if (!import_ts_morph13.Node.isPropertyAssignment(prop)) continue;
4089
+ const key = prop.getName();
4090
+ const init = prop.getInitializer();
4091
+ if (!init) continue;
4092
+ entries.push(`${key}: ${inferExpressionType(init)}`);
4093
+ }
4094
+ if (entries.length === 0) return "Record<string, unknown>";
4095
+ return `{ ${entries.join("; ")} }`;
4096
+ }
4097
+ if (import_ts_morph13.Node.isArrayLiteralExpression(node)) {
4098
+ const elements = node.getElements();
4099
+ if (elements.length === 0) return "Array<unknown>";
4100
+ const first = elements[0];
4101
+ if (first) return `Array<${inferExpressionType(first)}>`;
4102
+ return "Array<unknown>";
4103
+ }
4104
+ if (import_ts_morph13.Node.isConditionalExpression(node)) {
4105
+ const whenTrue = inferExpressionType(node.getWhenTrue());
4106
+ const whenFalse = inferExpressionType(node.getWhenFalse());
4107
+ if (whenTrue === whenFalse) return whenTrue;
4108
+ return `${whenTrue} | ${whenFalse}`;
4109
+ }
4110
+ if (import_ts_morph13.Node.isParenthesizedExpression(node)) {
4111
+ return inferExpressionType(node.getExpression());
4112
+ }
4113
+ if (import_ts_morph13.Node.isAsExpression(node)) {
4114
+ const typeNode = node.getTypeNode();
4115
+ if (typeNode) return typeNode.getText();
4116
+ }
4117
+ return "unknown";
4118
+ }
4119
+
4120
+ // src/emit/emit-api.ts
4121
+ var import_promises3 = require("fs/promises");
4122
+ var import_node_path7 = require("path");
4123
+
3891
4124
  // src/extension/registry.ts
4125
+ var import_node_path6 = require("path");
4126
+ var import_ts_morph14 = require("ts-morph");
3892
4127
  function resolveApiSlots(extensions) {
3893
4128
  let layer;
3894
4129
  let layerOwner;
@@ -3939,7 +4174,7 @@ function createExtensionContext(config, getRoutes, trackedInputs) {
3939
4174
  },
3940
4175
  project() {
3941
4176
  if (!project) {
3942
- project = new import_ts_morph13.Project({
4177
+ project = new import_ts_morph14.Project({
3943
4178
  skipAddingFilesFromTsConfig: true,
3944
4179
  skipLoadingLibFiles: true,
3945
4180
  skipFileDependencyResolution: true,
@@ -5673,36 +5908,64 @@ async function discoverInputFiles(config) {
5673
5908
  const matched = await (0, import_fast_glob3.default)(globs, { cwd, absolute: true, onlyFiles: true });
5674
5909
  return [...new Set(matched)].sort();
5675
5910
  }
5676
- async function computeInputsHash(config, extraInputs = []) {
5911
+ var READ_CONCURRENCY = 64;
5912
+ async function readAll(paths, read) {
5913
+ const out = /* @__PURE__ */ new Map();
5914
+ for (let i = 0; i < paths.length; i += READ_CONCURRENCY) {
5915
+ const batch = paths.slice(i, i + READ_CONCURRENCY);
5916
+ const texts = await Promise.all(batch.map(read));
5917
+ batch.forEach((path, j) => out.set(path, texts[j]));
5918
+ }
5919
+ return out;
5920
+ }
5921
+ async function readInputs(config) {
5922
+ const cwd = config.codegen.cwd;
5923
+ const tsconfigPath = resolveTsconfigPath(cwd, config.app?.tsconfig ?? void 0);
5924
+ const extended = loadDiscoveryTsconfig(tsconfigPath).files;
5925
+ const tsconfigFiles = extended.length > 0 ? extended : [tsconfigPath];
5926
+ const files = await discoverInputFiles(config);
5927
+ const contents = /* @__PURE__ */ new Map();
5928
+ const tsconfigs = await readAll(
5929
+ tsconfigFiles,
5930
+ (file) => (0, import_promises11.readFile)(file, "utf8").catch(() => null)
5931
+ );
5932
+ for (const [file, text] of tsconfigs) contents.set(file, text);
5933
+ for (const [file, text] of await readAll(files, (file2) => (0, import_promises11.readFile)(file2, "utf8"))) {
5934
+ contents.set(file, text);
5935
+ }
5936
+ return { tsconfigFiles, files, contents, tracked: /* @__PURE__ */ new Map() };
5937
+ }
5938
+ async function readInput(snapshot, path) {
5939
+ return snapshot.contents.get(path) ?? (0, import_promises11.readFile)(path, "utf8");
5940
+ }
5941
+ async function computeInputsHash(config, extraInputs = [], snapshot) {
5942
+ const inputs = snapshot ?? await readInputs(config);
5677
5943
  const hash = (0, import_node_crypto.createHash)("sha256");
5678
5944
  hash.update(`version:${VERSION}
5679
5945
  `);
5680
5946
  hash.update(`config:${serializeConfig(config)}
5681
5947
  `);
5682
5948
  const cwd = config.codegen.cwd;
5683
- const tsconfigPath = resolveTsconfigPath(cwd, config.app?.tsconfig ?? void 0);
5684
- const tsconfigFiles = loadDiscoveryTsconfig(tsconfigPath).files;
5685
- for (const file of tsconfigFiles.length > 0 ? tsconfigFiles : [tsconfigPath]) {
5686
- const contents = await (0, import_promises11.readFile)(file, "utf8").catch(() => null);
5949
+ for (const file of inputs.tsconfigFiles) {
5687
5950
  hash.update(`tsconfig:${(0, import_node_path15.relative)(cwd, file)}
5688
5951
  `);
5689
- hash.update(contents ?? " missing");
5952
+ hash.update(inputs.contents.get(file) ?? " missing");
5690
5953
  hash.update("\n");
5691
5954
  }
5692
- const globbed = await discoverInputFiles(config);
5693
- const globbedRelative = new Set(globbed.map((file) => (0, import_node_path15.relative)(cwd, file)));
5955
+ const globbedRelative = new Set(inputs.files.map((file) => (0, import_node_path15.relative)(cwd, file)));
5694
5956
  const extra = [...new Set(extraInputs)].filter((file) => !globbedRelative.has(file)).sort();
5695
- for (const file of globbed) {
5696
- const contents = await (0, import_promises11.readFile)(file, "utf8");
5957
+ for (const file of inputs.files) {
5697
5958
  hash.update(`file:${(0, import_node_path15.relative)(cwd, file)}
5698
5959
  `);
5699
- hash.update(contents);
5960
+ hash.update(inputs.contents.get(file));
5700
5961
  hash.update("\n");
5701
5962
  }
5963
+ const unread = extra.map((file) => (0, import_node_path15.join)(cwd, file)).filter((abs) => !inputs.tracked.has(abs));
5964
+ for (const [abs, text] of await readAll(unread, readTrackedInput)) inputs.tracked.set(abs, text);
5702
5965
  for (const file of extra) {
5703
5966
  hash.update(`extra:${file}
5704
5967
  `);
5705
- hash.update(await readTrackedInput((0, import_node_path15.join)(cwd, file)));
5968
+ hash.update(inputs.tracked.get((0, import_node_path15.join)(cwd, file)));
5706
5969
  hash.update("\n");
5707
5970
  }
5708
5971
  return hash.digest("hex");
@@ -5783,14 +6046,15 @@ function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint, differ
5783
6046
  async function generate(config, inputRoutes = [], entryPoint = "cli") {
5784
6047
  setCodegenDebug(config.debug);
5785
6048
  const manifest = await readManifest(config.codegen.outDir);
5786
- const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? []);
6049
+ const inputs = await readInputs(config);
6050
+ const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? [], inputs);
5787
6051
  if (await isManifestFresh(config.codegen.outDir, manifest, inputsHash)) {
5788
6052
  console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
5789
6053
  return;
5790
6054
  }
5791
6055
  const configHash = computeConfigHash(config);
5792
6056
  const configKeyHashes = computeConfigKeyHashes(config);
5793
- if (config.driftGuard && manifest?.entryPoint && manifest.entryPoint !== entryPoint && manifest.configHash && manifest.configHash !== configHash) {
6057
+ if (config.driftGuard && manifest?.version === VERSION && manifest?.entryPoint && manifest.entryPoint !== entryPoint && manifest.configHash && manifest.configHash !== configHash) {
5794
6058
  throw new DriftGuardError(
5795
6059
  driftGuardMessage(
5796
6060
  config.codegen.outDir,
@@ -5811,7 +6075,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
5811
6075
  }
5812
6076
  if (config.pages) {
5813
6077
  const pagesConfig = config.pages;
5814
- const pages = await discoverPageScopes(config);
6078
+ const pages = await discoverPageScopes(config, (path) => readInput(inputs, path));
5815
6079
  const sharedProps = discoverSharedPropsFromConfig(config);
5816
6080
  await emitPages(pages, config.codegen.outDir, {
5817
6081
  propsExport: pagesConfig.propsExport,
@@ -5861,7 +6125,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
5861
6125
  }
5862
6126
  const outputFiles = await listOutputFiles(config.codegen.outDir);
5863
6127
  const extraInputs = [...trackedInputs].sort();
5864
- const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs) : inputsHash;
6128
+ const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs, inputs) : inputsHash;
5865
6129
  await writeManifest(config.codegen.outDir, {
5866
6130
  version: VERSION,
5867
6131
  hash: recordedHash,
@@ -6044,13 +6308,15 @@ async function watch(config, onChange, options = {}) {
6044
6308
  pagesWatcher.on("unlink", schedulePagesRegenerate);
6045
6309
  let contractsDebounceTimer;
6046
6310
  const pendingChangedPaths = /* @__PURE__ */ new Set();
6311
+ let pendingAddedOrRemoved = false;
6047
6312
  const contractsWatcher = import_chokidar.default.watch((0, import_node_path18.join)(config.codegen.cwd, config.contracts.glob), {
6048
6313
  ignoreInitial: true,
6049
6314
  persistent: true,
6050
6315
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
6051
6316
  });
6052
- function scheduleContractsRegenerate(changedPath) {
6317
+ function scheduleContractsRegenerate(changedPath, addedOrRemoved = false) {
6053
6318
  if (typeof changedPath === "string") pendingChangedPaths.add(changedPath);
6319
+ if (addedOrRemoved) pendingAddedOrRemoved = true;
6054
6320
  if (contractsDebounceTimer !== void 0) {
6055
6321
  clearTimeout(contractsDebounceTimer);
6056
6322
  }
@@ -6058,8 +6324,12 @@ async function watch(config, onChange, options = {}) {
6058
6324
  contractsDebounceTimer = void 0;
6059
6325
  const changed = [...pendingChangedPaths];
6060
6326
  pendingChangedPaths.clear();
6327
+ const filesAddedOrRemoved = pendingAddedOrRemoved;
6328
+ pendingAddedOrRemoved = false;
6061
6329
  try {
6062
- const routes = await (await getDiscovery()).rediscover(changed);
6330
+ const routes = await (await getDiscovery()).rediscover(changed, {
6331
+ filesAddedOrRemoved
6332
+ });
6063
6333
  lastRoutes = routes;
6064
6334
  await generateAndTrack(routes);
6065
6335
  } catch (err) {
@@ -6071,20 +6341,25 @@ async function watch(config, onChange, options = {}) {
6071
6341
  onChange?.();
6072
6342
  }, config.contracts.debounceMs);
6073
6343
  }
6074
- contractsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
6344
+ contractsWatcher.on("add", (p) => scheduleContractsRegenerate(p, true));
6075
6345
  contractsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
6076
- contractsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
6346
+ contractsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p, true));
6077
6347
  const formsWatcher = import_chokidar.default.watch((0, import_node_path18.join)(config.codegen.cwd, config.forms.watch), {
6078
6348
  ignoreInitial: true,
6079
6349
  persistent: true,
6080
6350
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
6081
6351
  });
6082
- formsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
6352
+ formsWatcher.on("add", (p) => scheduleContractsRegenerate(p, true));
6083
6353
  formsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
6084
- formsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
6085
- trackedWatcher.on("add", (p) => scheduleContractsRegenerate(p));
6354
+ formsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p, true));
6355
+ trackedWatcher.on("add", (p) => scheduleContractsRegenerate(p, true));
6086
6356
  trackedWatcher.on("change", (p) => scheduleContractsRegenerate(p));
6087
- trackedWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
6357
+ trackedWatcher.on("unlink", (p) => scheduleContractsRegenerate(p, true));
6358
+ await Promise.all(
6359
+ [pagesWatcher, contractsWatcher, formsWatcher].map(
6360
+ (w) => new Promise((resolveReady) => w.once("ready", () => resolveReady()))
6361
+ )
6362
+ );
6088
6363
  return {
6089
6364
  close: async () => {
6090
6365
  if (pagesDebounceTimer !== void 0) {
@@ -6188,7 +6463,7 @@ function createChainModuleRenderer(opts) {
6188
6463
  }
6189
6464
 
6190
6465
  // src/extension/genui-sandbox-kit.ts
6191
- var import_node_fs2 = require("fs");
6466
+ var import_node_fs4 = require("fs");
6192
6467
  var import_node_path19 = require("path");
6193
6468
  var import_fast_glob4 = __toESM(require("fast-glob"), 1);
6194
6469
  var GENUI_KIT_MODULE = "@dudousxd/nestjs-agent-core/genui/kit";
@@ -6200,11 +6475,6 @@ var DEFAULT_KIT_DIRS = [
6200
6475
  "app/components/ui",
6201
6476
  "components/ui"
6202
6477
  ];
6203
- var LEGACY_OPTIONS = {
6204
- entry: "`entry: X` is now `components: X`",
6205
- include: "`include: G` is now `components: G` (globs, files, directories or packages)",
6206
- css: "`css: G` is now `styles: G` (the app's style entry files)"
6207
- };
6208
6478
  var MIN_CORE = "0.53.0";
6209
6479
  async function loadKitModule(specifier = GENUI_KIT_MODULE) {
6210
6480
  let mod;
@@ -6245,7 +6515,7 @@ function globBase(glob) {
6245
6515
  return base.join("/");
6246
6516
  }
6247
6517
  function resolveKitFilesLocally(root, options) {
6248
- const specs = options.components === void 0 ? DEFAULT_KIT_DIRS.filter((dir) => (0, import_node_fs2.existsSync)((0, import_node_path19.resolve)(root, dir))).slice(0, 1) : typeof options.components === "string" ? [options.components] : [...options.components];
6518
+ const specs = options.components === void 0 ? DEFAULT_KIT_DIRS.filter((dir) => (0, import_node_fs4.existsSync)((0, import_node_path19.resolve)(root, dir))).slice(0, 1) : typeof options.components === "string" ? [options.components] : [...options.components];
6249
6519
  const files = [];
6250
6520
  const dirs = /* @__PURE__ */ new Set();
6251
6521
  for (const spec of specs) {
@@ -6255,30 +6525,22 @@ function resolveKitFilesLocally(root, options) {
6255
6525
  continue;
6256
6526
  }
6257
6527
  const path = (0, import_node_path19.isAbsolute)(spec) ? spec : (0, import_node_path19.resolve)(root, spec);
6258
- if ((0, import_node_fs2.existsSync)(path) && (0, import_node_fs2.statSync)(path).isDirectory()) {
6528
+ if ((0, import_node_fs4.existsSync)(path) && (0, import_node_fs4.statSync)(path).isDirectory()) {
6259
6529
  files.push(...import_fast_glob4.default.sync("*.{tsx,jsx,ts,js}", { cwd: path, absolute: true, onlyFiles: true }));
6260
6530
  dirs.add(path);
6261
- } else if ((0, import_node_fs2.existsSync)(path)) {
6531
+ } else if ((0, import_node_fs4.existsSync)(path)) {
6262
6532
  files.push(path);
6263
6533
  dirs.add((0, import_node_path19.dirname)(path));
6264
6534
  }
6265
6535
  }
6266
6536
  if (options.wrapper !== void 0) {
6267
6537
  const wrapper = (0, import_node_path19.isAbsolute)(options.wrapper) ? options.wrapper : (0, import_node_path19.resolve)(root, options.wrapper);
6268
- if ((0, import_node_fs2.existsSync)(wrapper)) files.push(wrapper);
6538
+ if ((0, import_node_fs4.existsSync)(wrapper)) files.push(wrapper);
6269
6539
  }
6270
6540
  if (files.length === 0 && dirs.size === 0) return null;
6271
6541
  return { files: [...new Set(files)].sort(), dirs: [...dirs] };
6272
6542
  }
6273
6543
  function genuiSandboxKit(options = {}) {
6274
- const legacy = Object.keys(LEGACY_OPTIONS).filter(
6275
- (key) => options[key] !== void 0
6276
- );
6277
- if (legacy.length > 0) {
6278
- throw new CodegenError(
6279
- `genuiSandboxKit(): ${legacy.map((key) => `\`${key}\``).join(", ")} ${legacy.length > 1 ? "were" : "was"} removed \u2014 ${legacy.map((key) => LEGACY_OPTIONS[key]).join("; ")}. The options are now the agent's Vite plugin's: components, styles, wrapper, stylesDocs.`
6280
- );
6281
- }
6282
6544
  const output = options.output ?? DEFAULT_SANDBOX_KIT_OUTPUT;
6283
6545
  const source = {
6284
6546
  ...options.components !== void 0 ? { components: options.components } : {},
@@ -6322,7 +6584,7 @@ function genuiSandboxKit(options = {}) {
6322
6584
  }
6323
6585
  if (options.stylesDocs !== void 0 && !options.stylesDocs.includes("\n")) {
6324
6586
  const guide = (0, import_node_path19.isAbsolute)(options.stylesDocs) ? options.stylesDocs : (0, import_node_path19.resolve)(root, options.stylesDocs);
6325
- if ((0, import_node_fs2.existsSync)(guide)) ctx.trackInput(guide);
6587
+ if ((0, import_node_fs4.existsSync)(guide)) ctx.trackInput(guide);
6326
6588
  }
6327
6589
  ctx.trackInput((0, import_node_path19.isAbsolute)(output) ? output : (0, import_node_path19.resolve)(root, output));
6328
6590
  await generate2({
@@ -6340,7 +6602,7 @@ function genuiSandboxKit(options = {}) {
6340
6602
  }
6341
6603
 
6342
6604
  // src/index.ts
6343
- var VERSION = "0.29.0";
6605
+ var VERSION = "0.30.0";
6344
6606
  // Annotate the CommonJS export names for ESM import in node:
6345
6607
  0 && (module.exports = {
6346
6608
  CodegenError,