@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/cli/main.js CHANGED
@@ -100,11 +100,6 @@ function validateUserConfig(userConfig) {
100
100
  );
101
101
  }
102
102
  }
103
- if (userConfig.scopes && Object.keys(userConfig.scopes).length > 0) {
104
- console.warn(
105
- "[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)."
106
- );
107
- }
108
103
  }
109
104
  function isGlobInput(value) {
110
105
  if (typeof value === "string") return true;
@@ -152,7 +147,6 @@ function applyDefaults(userConfig, cwd) {
152
147
  glob: userConfig.contracts?.glob ?? "src/**/*.controller.ts",
153
148
  debounceMs: userConfig.contracts?.debounceMs ?? 500
154
149
  },
155
- scopes: userConfig.scopes ?? {},
156
150
  codegen: {
157
151
  outDir,
158
152
  cwd: resolvedCwd
@@ -247,7 +241,7 @@ async function discoverPages(opts) {
247
241
  const rel = relative2(opts.cwd, file);
248
242
  const nameRel = relative2(pagesBase, file).split(sep2).join("/");
249
243
  const name = `${prefix}${computeName(nameRel, opts.componentNameStrategy)}`;
250
- const source = await readFile(file, "utf8");
244
+ const source = await (opts.read ? opts.read(file) : readFile(file, "utf8"));
251
245
  const propsSource = extractPropsSource(source, opts.propsExport);
252
246
  out.push({ name, absolutePath: file, relativePath: rel, propsSource, scope });
253
247
  }
@@ -255,10 +249,14 @@ async function discoverPages(opts) {
255
249
  assertUniqueNames(out);
256
250
  return out;
257
251
  }
258
- async function discoverPageScopes(config) {
252
+ async function discoverPageScopes(config, read) {
259
253
  const pages = config.pages;
260
254
  if (!pages) return [];
261
- const common = { cwd: config.codegen.cwd, propsExport: pages.propsExport };
255
+ const common = {
256
+ cwd: config.codegen.cwd,
257
+ propsExport: pages.propsExport,
258
+ ...read ? { read } : {}
259
+ };
262
260
  const out = await discoverPages({
263
261
  ...common,
264
262
  glob: pages.glob,
@@ -329,305 +327,69 @@ function extractPropsSource(source, exportName) {
329
327
  }
330
328
 
331
329
  // src/discovery/shared-props.ts
332
- import { join as join3 } from "path";
333
- import { Node, Project, SyntaxKind } from "ts-morph";
334
- function discoverSharedProps(project, moduleEntry) {
335
- try {
336
- let sourceFile = project.getSourceFile(moduleEntry);
337
- if (!sourceFile) {
330
+ import { readFileSync as readFileSync2 } from "fs";
331
+ import { join as join4 } from "path";
332
+ import { Node as Node11, Project as Project4, SyntaxKind as SyntaxKind4 } from "ts-morph";
333
+
334
+ // src/discovery/contracts-fast.ts
335
+ import { existsSync } from "fs";
336
+ import { dirname as dirname2, join as join3, resolve as resolve3 } from "path";
337
+ import fg2 from "fast-glob";
338
+ import {
339
+ FileSystemRefreshResult as FileSystemRefreshResult3,
340
+ Node as Node10,
341
+ Project as Project3,
342
+ ts as ts4
343
+ } from "ts-morph";
344
+
345
+ // src/discovery/disk-fingerprints.ts
346
+ import { readFileSync, statSync } from "fs";
347
+ var DiskFingerprints = class {
348
+ stats = /* @__PURE__ */ new Map();
349
+ /** The paths whose disk content differs from `text`, or that no longer exist. */
350
+ changed(files) {
351
+ const changed = [];
352
+ for (const { path, text } of files) {
353
+ let key;
338
354
  try {
339
- sourceFile = project.addSourceFileAtPath(moduleEntry);
355
+ const stat2 = statSync(path);
356
+ key = `${stat2.mtimeMs}:${stat2.size}`;
340
357
  } catch {
341
- return null;
342
- }
343
- }
344
- const forRootCall = findForRootCall(sourceFile);
345
- if (!forRootCall) return null;
346
- const initializer = findShareInitializer(forRootCall);
347
- if (!initializer) return null;
348
- return extractShareType(initializer, sourceFile, project);
349
- } catch {
350
- return null;
351
- }
352
- }
353
- function discoverSharedPropsFromConfig(config) {
354
- if (!config.app?.moduleEntry) return null;
355
- try {
356
- const tsconfigPath = config.app.tsconfig ?? join3(config.codegen.cwd, "tsconfig.json");
357
- let project;
358
- try {
359
- project = new Project({
360
- tsConfigFilePath: tsconfigPath,
361
- skipAddingFilesFromTsConfig: true,
362
- skipLoadingLibFiles: true,
363
- skipFileDependencyResolution: true
364
- });
365
- } catch {
366
- project = new Project({
367
- skipAddingFilesFromTsConfig: true,
368
- skipLoadingLibFiles: true,
369
- skipFileDependencyResolution: true,
370
- compilerOptions: { allowJs: true, strict: false }
371
- });
372
- }
373
- return discoverSharedProps(project, config.app.moduleEntry);
374
- } catch {
375
- return null;
376
- }
377
- }
378
- function findForRootCall(sourceFile) {
379
- const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
380
- for (const call of callExpressions) {
381
- const expr = call.getExpression();
382
- if (!Node.isPropertyAccessExpression(expr)) continue;
383
- const methodName = expr.getName();
384
- const objectExpr = expr.getExpression();
385
- if (methodName === "forRoot" && Node.isIdentifier(objectExpr)) {
386
- const name = objectExpr.getText();
387
- if (name === "InertiaModule") {
388
- return call;
389
- }
390
- }
391
- }
392
- return null;
393
- }
394
- function findShareInitializer(forRootCall) {
395
- if (!Node.isCallExpression(forRootCall)) return null;
396
- const args = forRootCall.getArguments();
397
- const firstArg2 = args[0];
398
- if (!firstArg2 || !Node.isObjectLiteralExpression(firstArg2)) return null;
399
- for (const prop of firstArg2.getProperties()) {
400
- if (Node.isPropertyAssignment(prop) && prop.getName() === "share") {
401
- return prop.getInitializer() ?? null;
402
- }
403
- }
404
- return null;
405
- }
406
- function extractShareType(node, sourceFile, project) {
407
- if (Node.isIdentifier(node)) {
408
- const ref = resolveIdentifierToImportRef(node, sourceFile, project);
409
- if (ref) {
410
- return {
411
- typeString: `Awaited<ReturnType<typeof import('${ref.importPath}').${ref.exportName}>>`,
412
- properties: null,
413
- isImportRef: true
414
- };
415
- }
416
- }
417
- if (Node.isArrowFunction(node)) {
418
- const result = extractFromFunctionLike(node, sourceFile);
419
- return result ? { ...result, isImportRef: false } : null;
420
- }
421
- if (Node.isFunctionExpression(node)) {
422
- const result = extractFromFunctionLike(node, sourceFile);
423
- return result ? { ...result, isImportRef: false } : null;
424
- }
425
- if (Node.isObjectLiteralExpression(node)) {
426
- const result = extractFromObjectLiteral(node);
427
- return result ? { ...result, isImportRef: false } : null;
428
- }
429
- return null;
430
- }
431
- function resolveIdentifierToImportRef(id, sourceFile, project) {
432
- if (!Node.isIdentifier(id)) return null;
433
- const name = id.getText();
434
- const localFunc = sourceFile.getFunction(name);
435
- if (localFunc?.isExported()) {
436
- const filePath = sourceFile.getFilePath().replace(/\.ts$/, "");
437
- return { importPath: filePath, exportName: name };
438
- }
439
- const localVar = sourceFile.getVariableDeclaration(name);
440
- if (localVar?.isExported()) {
441
- const filePath = sourceFile.getFilePath().replace(/\.ts$/, "");
442
- return { importPath: filePath, exportName: name };
443
- }
444
- for (const imp of sourceFile.getImportDeclarations()) {
445
- for (const named of imp.getNamedImports()) {
446
- const importedName = named.getAliasNode()?.getText() ?? named.getName();
447
- if (importedName !== name) continue;
448
- const resolvedSource = imp.getModuleSpecifierSourceFile();
449
- if (!resolvedSource) continue;
450
- const originalName = named.getName();
451
- const fn = resolvedSource.getFunction(originalName);
452
- if (fn?.isExported()) {
453
- const filePath = resolvedSource.getFilePath().replace(/\.ts$/, "");
454
- return { importPath: filePath, exportName: originalName };
455
- }
456
- const v = resolvedSource.getVariableDeclaration(originalName);
457
- if (v?.isExported()) {
458
- const filePath = resolvedSource.getFilePath().replace(/\.ts$/, "");
459
- return { importPath: filePath, exportName: originalName };
460
- }
461
- }
462
- }
463
- return null;
464
- }
465
- function extractFromFunctionLike(node, _sourceFile) {
466
- const returnTypeNode = Node.isArrowFunction(node) || Node.isFunctionExpression(node) ? node.getReturnTypeNode() : null;
467
- if (returnTypeNode) {
468
- return extractFromReturnTypeAnnotation(returnTypeNode);
469
- }
470
- if (Node.isArrowFunction(node)) {
471
- const body = node.getBody();
472
- if (Node.isParenthesizedExpression(body)) {
473
- const inner = body.getExpression();
474
- if (Node.isObjectLiteralExpression(inner)) {
475
- return extractFromObjectLiteral(inner);
476
- }
477
- }
478
- if (Node.isObjectLiteralExpression(body)) {
479
- return extractFromObjectLiteral(body);
480
- }
481
- if (Node.isBlock(body)) {
482
- return extractFromBlockReturn(body);
483
- }
484
- }
485
- if (Node.isFunctionExpression(node)) {
486
- const body = node.getBody();
487
- if (Node.isBlock(body)) {
488
- return extractFromBlockReturn(body);
489
- }
490
- }
491
- return null;
492
- }
493
- function extractFromReturnTypeAnnotation(typeNode) {
494
- if (Node.isTypeReference(typeNode)) {
495
- const typeName = typeNode.getTypeName();
496
- if (Node.isIdentifier(typeName) && typeName.getText() === "Promise") {
497
- const typeArgs = typeNode.getTypeArguments();
498
- const firstArg2 = typeArgs[0];
499
- if (firstArg2) {
500
- return extractFromReturnTypeAnnotation(firstArg2);
358
+ this.stats.delete(path);
359
+ changed.push(path);
360
+ continue;
501
361
  }
502
- return null;
503
- }
504
- }
505
- if (Node.isTypeLiteral(typeNode)) {
506
- const properties = [];
507
- for (const member of typeNode.getMembers()) {
508
- if (Node.isPropertySignature(member)) {
509
- const name = member.getName();
510
- const memberTypeNode = member.getTypeNode();
511
- const type = memberTypeNode ? memberTypeNode.getText() : "unknown";
512
- properties.push({ name, type });
362
+ if (this.stats.get(path) === key) continue;
363
+ let disk;
364
+ try {
365
+ disk = readFileSync(path, "utf-8");
366
+ } catch {
367
+ disk = void 0;
513
368
  }
514
- }
515
- if (properties.length === 0) return null;
516
- const typeString = `{ ${properties.map((p) => `${p.name}: ${p.type}`).join("; ")} }`;
517
- return { typeString, properties, isImportRef: false };
518
- }
519
- return null;
520
- }
521
- function extractFromBlockReturn(block) {
522
- if (!Node.isBlock(block)) return null;
523
- const statements = block.getStatements();
524
- for (let i = statements.length - 1; i >= 0; i--) {
525
- const stmt = statements[i];
526
- if (!Node.isReturnStatement(stmt)) continue;
527
- const expr = stmt.getExpression();
528
- if (!expr) continue;
529
- if (Node.isObjectLiteralExpression(expr)) {
530
- return extractFromObjectLiteral(expr);
531
- }
532
- if (Node.isParenthesizedExpression(expr)) {
533
- const inner = expr.getExpression();
534
- if (Node.isObjectLiteralExpression(inner)) {
535
- return extractFromObjectLiteral(inner);
369
+ if (disk === text) {
370
+ this.stats.set(path, key);
371
+ } else {
372
+ this.stats.delete(path);
373
+ changed.push(path);
536
374
  }
537
375
  }
538
- break;
539
- }
540
- return null;
541
- }
542
- function extractFromObjectLiteral(objLiteral) {
543
- if (!Node.isObjectLiteralExpression(objLiteral)) return null;
544
- const properties = [];
545
- for (const prop of objLiteral.getProperties()) {
546
- if (!Node.isPropertyAssignment(prop)) continue;
547
- const name = prop.getName();
548
- const initializer = prop.getInitializer();
549
- if (!initializer) continue;
550
- const type = inferExpressionType(initializer);
551
- properties.push({ name, type });
552
- }
553
- if (properties.length === 0) return null;
554
- const typeString = `{ ${properties.map((p) => `${p.name}: ${p.type}`).join("; ")} }`;
555
- return { typeString, properties, isImportRef: false };
556
- }
557
- function inferExpressionType(node) {
558
- if (Node.isStringLiteral(node)) return "string";
559
- if (Node.isTemplateExpression(node) || Node.isNoSubstitutionTemplateLiteral(node))
560
- return "string";
561
- if (Node.isNumericLiteral(node)) return "number";
562
- if (node.getKind() === SyntaxKind.TrueKeyword || node.getKind() === SyntaxKind.FalseKeyword) {
563
- return "boolean";
564
- }
565
- if (node.getKind() === SyntaxKind.NullKeyword) return "null";
566
- if (Node.isIdentifier(node) && node.getText() === "undefined") return "undefined";
567
- if (Node.isObjectLiteralExpression(node)) {
568
- const props = node.getProperties();
569
- if (props.length === 0) return "Record<string, unknown>";
570
- const entries = [];
571
- for (const prop of props) {
572
- if (!Node.isPropertyAssignment(prop)) continue;
573
- const key = prop.getName();
574
- const init = prop.getInitializer();
575
- if (!init) continue;
576
- entries.push(`${key}: ${inferExpressionType(init)}`);
577
- }
578
- if (entries.length === 0) return "Record<string, unknown>";
579
- return `{ ${entries.join("; ")} }`;
376
+ return changed;
580
377
  }
581
- if (Node.isArrayLiteralExpression(node)) {
582
- const elements = node.getElements();
583
- if (elements.length === 0) return "Array<unknown>";
584
- const first = elements[0];
585
- if (first) return `Array<${inferExpressionType(first)}>`;
586
- return "Array<unknown>";
587
- }
588
- if (Node.isConditionalExpression(node)) {
589
- const whenTrue = inferExpressionType(node.getWhenTrue());
590
- const whenFalse = inferExpressionType(node.getWhenFalse());
591
- if (whenTrue === whenFalse) return whenTrue;
592
- return `${whenTrue} | ${whenFalse}`;
593
- }
594
- if (Node.isParenthesizedExpression(node)) {
595
- return inferExpressionType(node.getExpression());
596
- }
597
- if (Node.isAsExpression(node)) {
598
- const typeNode = node.getTypeNode();
599
- if (typeNode) return typeNode.getText();
378
+ /** Forget `path`, so its next check compares content again. */
379
+ forget(path) {
380
+ this.stats.delete(path);
600
381
  }
601
- return "unknown";
602
- }
603
-
604
- // src/emit/emit-api.ts
605
- import { mkdir, writeFile } from "fs/promises";
606
- import { isAbsolute as isAbsolute2, join as join5, relative as relative4 } from "path";
607
-
608
- // src/extension/registry.ts
609
- import { relative as relative3, resolve as resolve4 } from "path";
610
- import { Project as Project5 } from "ts-morph";
611
-
612
- // src/discovery/contracts-fast.ts
613
- import { existsSync } from "fs";
614
- import { dirname as dirname2, join as join4, resolve as resolve3 } from "path";
615
- import fg2 from "fast-glob";
616
- import {
617
- Node as Node11,
618
- Project as Project4,
619
- ts as ts3
620
- } from "ts-morph";
382
+ };
621
383
 
622
384
  // src/discovery/dto-type-resolver.ts
623
385
  import {
624
- Node as Node10,
625
- SyntaxKind as SyntaxKind4
386
+ Node as Node9,
387
+ SyntaxKind as SyntaxKind3
626
388
  } from "ts-morph";
627
389
 
628
390
  // src/discovery/dto-to-ir.ts
629
391
  import {
630
- Node as Node3
392
+ Node as Node2
631
393
  } from "ts-morph";
632
394
 
633
395
  // src/util/debug-log.ts
@@ -642,7 +404,7 @@ function debugWarn(message) {
642
404
  // src/discovery/type-ref-resolution.ts
643
405
  import { dirname, resolve as resolve2 } from "path";
644
406
  import {
645
- Node as Node2
407
+ Node
646
408
  } from "ts-morph";
647
409
  var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
648
410
  var _ctxByProject = /* @__PURE__ */ new WeakMap();
@@ -908,13 +670,13 @@ function resolveTypeRef(nodeOrName, sourceFile, project, opts) {
908
670
  name = nodeOrName;
909
671
  } else {
910
672
  const typeNode = nodeOrName;
911
- if (opts.unwrapContainers && Node2.isArrayTypeNode(typeNode)) {
673
+ if (opts.unwrapContainers && Node.isArrayTypeNode(typeNode)) {
912
674
  const inner = resolveTypeRef(typeNode.getElementTypeNode(), sourceFile, project, opts);
913
675
  return inner ? { ...inner, isArray: true } : null;
914
676
  }
915
- if (!Node2.isTypeReference(typeNode)) return null;
677
+ if (!Node.isTypeReference(typeNode)) return null;
916
678
  const typeName = typeNode.getTypeName();
917
- const refName = Node2.isIdentifier(typeName) ? typeName.getText() : null;
679
+ const refName = Node.isIdentifier(typeName) ? typeName.getText() : null;
918
680
  if (!refName) return null;
919
681
  if (opts.unwrapContainers && refName === "Promise") {
920
682
  const first = typeNode.getTypeArguments()[0];
@@ -1048,7 +810,7 @@ function buildProperty(prop, classFile, ctx) {
1048
810
  const dec = (n) => decorators.get(n);
1049
811
  const typeNode = prop.getTypeNode();
1050
812
  const typeText = typeNode?.getText() ?? "unknown";
1051
- const isArrayType = !!typeNode && Node3.isArrayTypeNode(typeNode);
813
+ const isArrayType = !!typeNode && Node2.isArrayTypeNode(typeNode);
1052
814
  const asField = (child) => applyPresence(
1053
815
  has("IsArray") || isArrayType ? { kind: "array", element: child } : child,
1054
816
  decorators
@@ -1262,22 +1024,22 @@ function firstArgText(decorator) {
1262
1024
  }
1263
1025
  function numericArg(decorator) {
1264
1026
  const arg = firstArg(decorator);
1265
- if (arg && Node3.isNumericLiteral(arg)) return arg.getText();
1027
+ if (arg && Node2.isNumericLiteral(arg)) return arg.getText();
1266
1028
  return null;
1267
1029
  }
1268
1030
  function numericArgs(decorator) {
1269
1031
  const args = decorator?.getArguments() ?? [];
1270
- const num = (n) => n && Node3.isNumericLiteral(n) ? n.getText() : null;
1032
+ const num = (n) => n && Node2.isNumericLiteral(n) ? n.getText() : null;
1271
1033
  return [num(args[0]), num(args[1])];
1272
1034
  }
1273
1035
  function messageRaw(decorator) {
1274
1036
  const args = decorator?.getArguments() ?? [];
1275
1037
  for (const arg of args) {
1276
- if (Node3.isObjectLiteralExpression(arg)) {
1038
+ if (Node2.isObjectLiteralExpression(arg)) {
1277
1039
  for (const prop of arg.getProperties()) {
1278
- if (Node3.isPropertyAssignment(prop) && prop.getName() === "message") {
1040
+ if (Node2.isPropertyAssignment(prop) && prop.getName() === "message") {
1279
1041
  const init = prop.getInitializer();
1280
- if (init && Node3.isStringLiteral(init)) return init.getText();
1042
+ if (init && Node2.isStringLiteral(init)) return init.getText();
1281
1043
  }
1282
1044
  }
1283
1045
  }
@@ -1286,30 +1048,30 @@ function messageRaw(decorator) {
1286
1048
  }
1287
1049
  function resolveDiscriminator(decorator) {
1288
1050
  const optsArg = decorator?.getArguments()[1];
1289
- if (!optsArg || !Node3.isObjectLiteralExpression(optsArg)) return null;
1051
+ if (!optsArg || !Node2.isObjectLiteralExpression(optsArg)) return null;
1290
1052
  let discProp;
1291
1053
  for (const prop of optsArg.getProperties()) {
1292
- if (Node3.isPropertyAssignment(prop) && prop.getName() === "discriminator") {
1054
+ if (Node2.isPropertyAssignment(prop) && prop.getName() === "discriminator") {
1293
1055
  discProp = prop.getInitializer();
1294
1056
  }
1295
1057
  }
1296
- if (!discProp || !Node3.isObjectLiteralExpression(discProp)) return null;
1058
+ if (!discProp || !Node2.isObjectLiteralExpression(discProp)) return null;
1297
1059
  let property = null;
1298
1060
  const subTypes = [];
1299
1061
  for (const prop of discProp.getProperties()) {
1300
- if (!Node3.isPropertyAssignment(prop)) continue;
1062
+ if (!Node2.isPropertyAssignment(prop)) continue;
1301
1063
  const name = prop.getName();
1302
1064
  const init = prop.getInitializer();
1303
1065
  if (!init) continue;
1304
- if (name === "property" && Node3.isStringLiteral(init)) {
1066
+ if (name === "property" && Node2.isStringLiteral(init)) {
1305
1067
  property = init.getLiteralValue();
1306
- } else if (name === "subTypes" && Node3.isArrayLiteralExpression(init)) {
1068
+ } else if (name === "subTypes" && Node2.isArrayLiteralExpression(init)) {
1307
1069
  for (const el of init.getElements()) {
1308
- if (!Node3.isObjectLiteralExpression(el)) continue;
1070
+ if (!Node2.isObjectLiteralExpression(el)) continue;
1309
1071
  for (const p of el.getProperties()) {
1310
- if (!Node3.isPropertyAssignment(p) || p.getName() !== "name") continue;
1072
+ if (!Node2.isPropertyAssignment(p) || p.getName() !== "name") continue;
1311
1073
  const nameInit = p.getInitializer();
1312
- if (nameInit && Node3.isIdentifier(nameInit)) subTypes.push(nameInit.getText());
1074
+ if (nameInit && Node2.isIdentifier(nameInit)) subTypes.push(nameInit.getText());
1313
1075
  }
1314
1076
  }
1315
1077
  }
@@ -1320,9 +1082,9 @@ function resolveDiscriminator(decorator) {
1320
1082
  function resolveTypeFactoryName(decorator) {
1321
1083
  const arg = firstArg(decorator);
1322
1084
  if (!arg) return null;
1323
- if (Node3.isArrowFunction(arg)) {
1085
+ if (Node2.isArrowFunction(arg)) {
1324
1086
  const body = arg.getBody();
1325
- if (Node3.isIdentifier(body)) return body.getText();
1087
+ if (Node2.isIdentifier(body)) return body.getText();
1326
1088
  }
1327
1089
  return null;
1328
1090
  }
@@ -1331,12 +1093,12 @@ function singularClassName(typeText) {
1331
1093
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(inner) ? inner : null;
1332
1094
  }
1333
1095
  function genericTypeArgNames(typeNode) {
1334
- if (!typeNode || !Node3.isTypeReference(typeNode)) return [];
1096
+ if (!typeNode || !Node2.isTypeReference(typeNode)) return [];
1335
1097
  const names = [];
1336
1098
  for (const arg of typeNode.getTypeArguments()) {
1337
- if (!Node3.isTypeReference(arg)) return [];
1099
+ if (!Node2.isTypeReference(arg)) return [];
1338
1100
  const tn = arg.getTypeName();
1339
- if (!Node3.isIdentifier(tn)) return [];
1101
+ if (!Node2.isIdentifier(tn)) return [];
1340
1102
  names.push(tn.getText());
1341
1103
  }
1342
1104
  return names;
@@ -1344,7 +1106,7 @@ function genericTypeArgNames(typeNode) {
1344
1106
  function enumSchemaFromDecorator(decorator, classFile, ctx) {
1345
1107
  const arg = firstArg(decorator);
1346
1108
  if (!arg) return null;
1347
- if (Node3.isIdentifier(arg)) {
1109
+ if (Node2.isIdentifier(arg)) {
1348
1110
  const name = arg.getText();
1349
1111
  const resolved = findType(name, classFile, ctx.project);
1350
1112
  if (resolved && resolved.kind === "enum") {
@@ -1358,12 +1120,12 @@ function enumSchemaFromDecorator(decorator, classFile, ctx) {
1358
1120
  }
1359
1121
  return { kind: "unknown", note: `@IsEnum(${name}): enum not resolvable to literals` };
1360
1122
  }
1361
- if (Node3.isObjectLiteralExpression(arg)) {
1123
+ if (Node2.isObjectLiteralExpression(arg)) {
1362
1124
  const values = [];
1363
1125
  for (const p of arg.getProperties()) {
1364
- if (!Node3.isPropertyAssignment(p)) continue;
1126
+ if (!Node2.isPropertyAssignment(p)) continue;
1365
1127
  const init = p.getInitializer();
1366
- if (init && Node3.isStringLiteral(init)) values.push(init.getText());
1128
+ if (init && Node2.isStringLiteral(init)) values.push(init.getText());
1367
1129
  }
1368
1130
  if (values.length > 0) return { kind: "enum", literals: values };
1369
1131
  }
@@ -1371,9 +1133,9 @@ function enumSchemaFromDecorator(decorator, classFile, ctx) {
1371
1133
  }
1372
1134
  function inSchemaFromDecorator(decorator) {
1373
1135
  const arg = firstArg(decorator);
1374
- if (arg && Node3.isArrayLiteralExpression(arg)) {
1136
+ if (arg && Node2.isArrayLiteralExpression(arg)) {
1375
1137
  const elements = arg.getElements();
1376
- const allStrings = elements.every((e) => Node3.isStringLiteral(e));
1138
+ const allStrings = elements.every((e) => Node2.isStringLiteral(e));
1377
1139
  if (allStrings && elements.length > 0) {
1378
1140
  return { kind: "enum", literals: elements.map((e) => e.getText()) };
1379
1141
  }
@@ -1389,13 +1151,13 @@ function inSchemaFromDecorator(decorator) {
1389
1151
 
1390
1152
  // src/discovery/filter-for.ts
1391
1153
  import {
1392
- Node as Node6
1154
+ Node as Node5
1393
1155
  } from "ts-morph";
1394
1156
 
1395
1157
  // src/discovery/filter-field-types.ts
1396
1158
  import {
1397
- Node as Node4,
1398
- SyntaxKind as SyntaxKind2
1159
+ Node as Node3,
1160
+ SyntaxKind
1399
1161
  } from "ts-morph";
1400
1162
 
1401
1163
  // src/discovery/enum-resolution.ts
@@ -1449,28 +1211,28 @@ function markNullable(r, nullable) {
1449
1211
  }
1450
1212
  var TRANSPARENT_TYPE_BRANDS = /* @__PURE__ */ new Set(["Opt", "Hidden"]);
1451
1213
  function classifyTypeNode(typeNode, sourceFile, project, opts) {
1452
- if (Node4.isUnionTypeNode(typeNode)) {
1214
+ if (Node3.isUnionTypeNode(typeNode)) {
1453
1215
  let nullable = false;
1454
1216
  const stringLits = [];
1455
1217
  const numberLits = [];
1456
1218
  const others = [];
1457
1219
  for (const member of typeNode.getTypeNodes()) {
1458
1220
  const kind = member.getKind();
1459
- if (kind === SyntaxKind2.NullKeyword || kind === SyntaxKind2.UndefinedKeyword) {
1221
+ if (kind === SyntaxKind.NullKeyword || kind === SyntaxKind.UndefinedKeyword) {
1460
1222
  nullable = true;
1461
1223
  continue;
1462
1224
  }
1463
- if (Node4.isLiteralTypeNode(member)) {
1225
+ if (Node3.isLiteralTypeNode(member)) {
1464
1226
  const lit = member.getLiteral();
1465
- if (Node4.isStringLiteral(lit)) {
1227
+ if (Node3.isStringLiteral(lit)) {
1466
1228
  stringLits.push(lit.getLiteralValue());
1467
1229
  continue;
1468
1230
  }
1469
- if (Node4.isNumericLiteral(lit)) {
1231
+ if (Node3.isNumericLiteral(lit)) {
1470
1232
  numberLits.push(lit.getText());
1471
1233
  continue;
1472
1234
  }
1473
- if (lit.getKind() === SyntaxKind2.NullKeyword) {
1235
+ if (lit.getKind() === SyntaxKind.NullKeyword) {
1474
1236
  nullable = true;
1475
1237
  continue;
1476
1238
  }
@@ -1490,19 +1252,19 @@ function classifyTypeNode(typeNode, sourceFile, project, opts) {
1490
1252
  return markNullable({ kind: "unknown" }, nullable);
1491
1253
  }
1492
1254
  switch (typeNode.getKind()) {
1493
- case SyntaxKind2.StringKeyword:
1255
+ case SyntaxKind.StringKeyword:
1494
1256
  return { kind: "string" };
1495
- case SyntaxKind2.NumberKeyword:
1257
+ case SyntaxKind.NumberKeyword:
1496
1258
  return { kind: "number" };
1497
- case SyntaxKind2.BooleanKeyword:
1259
+ case SyntaxKind.BooleanKeyword:
1498
1260
  return { kind: "boolean" };
1499
- case SyntaxKind2.AnyKeyword:
1500
- case SyntaxKind2.UnknownKeyword:
1261
+ case SyntaxKind.AnyKeyword:
1262
+ case SyntaxKind.UnknownKeyword:
1501
1263
  return { kind: "unknown" };
1502
1264
  default:
1503
1265
  break;
1504
1266
  }
1505
- if (Node4.isTypeReference(typeNode)) {
1267
+ if (Node3.isTypeReference(typeNode)) {
1506
1268
  const refName = typeNode.getTypeName().getText();
1507
1269
  if (refName === "Date") return { kind: "date" };
1508
1270
  if (refName === "Record" || refName === "Object") return { kind: "json" };
@@ -1520,25 +1282,25 @@ function classifyTypeNode(typeNode, sourceFile, project, opts) {
1520
1282
  if (typeRef) return { kind: "unknown", typeRef };
1521
1283
  return { kind: "unknown" };
1522
1284
  }
1523
- if (Node4.isTypeLiteral(typeNode)) return { kind: "json" };
1285
+ if (Node3.isTypeLiteral(typeNode)) return { kind: "json" };
1524
1286
  return { kind: "unknown" };
1525
1287
  }
1526
1288
  function enumFromDecoratorArgs(args, sourceFile, project) {
1527
1289
  for (const arg of args) {
1528
- if (Node4.isArrowFunction(arg)) {
1290
+ if (Node3.isArrowFunction(arg)) {
1529
1291
  const body = arg.getBody();
1530
- if (Node4.isIdentifier(body)) {
1292
+ if (Node3.isIdentifier(body)) {
1531
1293
  const en = resolveEnumValues(body.getText(), sourceFile, project);
1532
1294
  if (en) return en;
1533
1295
  }
1534
1296
  }
1535
- if (Node4.isObjectLiteralExpression(arg)) {
1297
+ if (Node3.isObjectLiteralExpression(arg)) {
1536
1298
  const itemsProp = arg.getProperty("items");
1537
- if (itemsProp && Node4.isPropertyAssignment(itemsProp)) {
1299
+ if (itemsProp && Node3.isPropertyAssignment(itemsProp)) {
1538
1300
  const init = itemsProp.getInitializer();
1539
- if (init && Node4.isArrowFunction(init)) {
1301
+ if (init && Node3.isArrowFunction(init)) {
1540
1302
  const body = init.getBody();
1541
- if (Node4.isIdentifier(body)) {
1303
+ if (Node3.isIdentifier(body)) {
1542
1304
  const en = resolveEnumValues(body.getText(), sourceFile, project);
1543
1305
  if (en) return en;
1544
1306
  }
@@ -1561,7 +1323,7 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
1561
1323
  return { kind: "string" };
1562
1324
  }
1563
1325
  for (const arg of args) {
1564
- if (Node4.isStringLiteral(arg)) {
1326
+ if (Node3.isStringLiteral(arg)) {
1565
1327
  const raw = arg.getLiteralValue();
1566
1328
  const kind = classifyTypeKeyword(raw);
1567
1329
  if (kind) {
@@ -1569,11 +1331,11 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
1569
1331
  return { kind };
1570
1332
  }
1571
1333
  }
1572
- if (Node4.isObjectLiteralExpression(arg)) {
1334
+ if (Node3.isObjectLiteralExpression(arg)) {
1573
1335
  const enumProp = arg.getProperty("enum");
1574
- if (enumProp && Node4.isPropertyAssignment(enumProp)) {
1336
+ if (enumProp && Node3.isPropertyAssignment(enumProp)) {
1575
1337
  const init = enumProp.getInitializer();
1576
- if (init && Node4.isIdentifier(init)) {
1338
+ if (init && Node3.isIdentifier(init)) {
1577
1339
  const en = resolveEnumValues(init.getText(), sourceFile, project);
1578
1340
  if (en) {
1579
1341
  return en.numeric ? { kind: "number", enumValues: en.values, numericEnum: true } : { kind: "string", enumValues: en.values };
@@ -1583,15 +1345,15 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
1583
1345
  }
1584
1346
  for (const key of ["columnType", "type"]) {
1585
1347
  const typeProp = arg.getProperty(key);
1586
- if (!typeProp || !Node4.isPropertyAssignment(typeProp)) continue;
1348
+ if (!typeProp || !Node3.isPropertyAssignment(typeProp)) continue;
1587
1349
  const init = typeProp.getInitializer();
1588
1350
  if (!init) continue;
1589
- if (Node4.isStringLiteral(init)) {
1351
+ if (Node3.isStringLiteral(init)) {
1590
1352
  const kind = classifyTypeKeyword(init.getLiteralValue());
1591
1353
  if (kind) return { kind };
1592
1354
  continue;
1593
1355
  }
1594
- if (Node4.isIdentifier(init)) {
1356
+ if (Node3.isIdentifier(init)) {
1595
1357
  const kind = classifyTypeKeyword(init.getText());
1596
1358
  if (kind) return { kind };
1597
1359
  }
@@ -1633,23 +1395,70 @@ function toFilterFieldType(name, r) {
1633
1395
  }
1634
1396
 
1635
1397
  // src/discovery/heritage.ts
1636
- import { Node as Node5, Project as Project2 } from "ts-morph";
1398
+ import {
1399
+ FileSystemRefreshResult,
1400
+ Node as Node4,
1401
+ Project
1402
+ } from "ts-morph";
1403
+
1404
+ // src/discovery/unresolved-imports.ts
1405
+ import { ts } from "ts-morph";
1406
+ var UnresolvedImports = class {
1407
+ read;
1408
+ /**
1409
+ * The files with an import that failed to resolve in `program` and now resolves
1410
+ * to a TypeScript file the program does not hold.
1411
+ */
1412
+ nowResolving(program) {
1413
+ if (this.read?.program !== program) {
1414
+ this.read = { program, imports: failedImports(program) };
1415
+ }
1416
+ const options = program.getCompilerOptions();
1417
+ const files = /* @__PURE__ */ new Set();
1418
+ for (const { specifier, from } of this.read.imports) {
1419
+ const file = ts.resolveModuleName(specifier, from, options, ts.sys).resolvedModule?.resolvedFileName;
1420
+ if (file !== void 0 && /\.(m|c)?tsx?$/.test(file) && !program.getSourceFile(file)) {
1421
+ files.add(from);
1422
+ }
1423
+ }
1424
+ return [...files];
1425
+ }
1426
+ };
1427
+ function failedImports(program) {
1428
+ const checker = program.getTypeChecker();
1429
+ const imports = [];
1430
+ for (const file of program.getSourceFiles()) {
1431
+ if (program.isSourceFileDefaultLibrary(file) || file.isDeclarationFile) continue;
1432
+ if (program.isSourceFileFromExternalLibrary(file)) continue;
1433
+ for (const statement of file.statements) {
1434
+ if (!ts.isImportDeclaration(statement) && !ts.isExportDeclaration(statement)) continue;
1435
+ const specifier = statement.moduleSpecifier;
1436
+ if (!specifier || !ts.isStringLiteral(specifier)) continue;
1437
+ if (!checker.getSymbolAtLocation(specifier)) {
1438
+ imports.push({ specifier: specifier.text, from: file.fileName });
1439
+ }
1440
+ }
1441
+ }
1442
+ return imports;
1443
+ }
1444
+
1445
+ // src/discovery/heritage.ts
1637
1446
  function resolveHeritageCall(node) {
1638
1447
  if (!node) return void 0;
1639
1448
  const expr = unwrapExpression(node);
1640
- if (Node5.isCallExpression(expr)) return expr;
1641
- if (!Node5.isIdentifier(expr)) return void 0;
1449
+ if (Node4.isCallExpression(expr)) return expr;
1450
+ if (!Node4.isIdentifier(expr)) return void 0;
1642
1451
  const decl = expr.getDefinitions().map((d) => d.getDeclarationNode()).find(
1643
- (n) => n !== void 0 && Node5.isVariableDeclaration(n)
1452
+ (n) => n !== void 0 && Node4.isVariableDeclaration(n)
1644
1453
  );
1645
1454
  const init = decl?.getInitializer();
1646
1455
  if (!init) return void 0;
1647
1456
  const unwrapped = unwrapExpression(init);
1648
- return Node5.isCallExpression(unwrapped) ? unwrapped : void 0;
1457
+ return Node4.isCallExpression(unwrapped) ? unwrapped : void 0;
1649
1458
  }
1650
1459
  function unwrapExpression(node) {
1651
1460
  let current = node;
1652
- while (Node5.isAsExpression(current) || Node5.isSatisfiesExpression(current) || Node5.isParenthesizedExpression(current) || Node5.isTypeAssertion(current)) {
1461
+ while (Node4.isAsExpression(current) || Node4.isSatisfiesExpression(current) || Node4.isParenthesizedExpression(current) || Node4.isTypeAssertion(current)) {
1653
1462
  current = current.getExpression();
1654
1463
  }
1655
1464
  return current;
@@ -1657,17 +1466,17 @@ function unwrapExpression(node) {
1657
1466
  function resolveReturnedClass(factoryDecl) {
1658
1467
  const body = factoryDecl.getBody();
1659
1468
  if (!body) return void 0;
1660
- const returns = body.getDescendants().filter((n) => Node5.isReturnStatement(n));
1469
+ const returns = body.getDescendants().filter((n) => Node4.isReturnStatement(n));
1661
1470
  for (const ret of returns) {
1662
1471
  const expr = ret.getExpression();
1663
1472
  if (!expr) continue;
1664
1473
  const inner = unwrapExpression(expr);
1665
- if (Node5.isClassExpression(inner)) {
1474
+ if (Node4.isClassExpression(inner)) {
1666
1475
  return inner;
1667
1476
  }
1668
- if (Node5.isIdentifier(inner)) {
1477
+ if (Node4.isIdentifier(inner)) {
1669
1478
  const name = inner.getText();
1670
- const decl = body.getDescendants().find((n) => Node5.isClassDeclaration(n) && n.getName() === name);
1479
+ const decl = body.getDescendants().find((n) => Node4.isClassDeclaration(n) && n.getName() === name);
1671
1480
  if (decl) return decl;
1672
1481
  }
1673
1482
  }
@@ -1675,10 +1484,10 @@ function resolveReturnedClass(factoryDecl) {
1675
1484
  }
1676
1485
  function factoryCalleeName(call) {
1677
1486
  const callee = unwrapExpression(call.getExpression());
1678
- if (Node5.isIdentifier(callee)) return callee;
1679
- if (Node5.isPropertyAccessExpression(callee)) {
1487
+ if (Node4.isIdentifier(callee)) return callee;
1488
+ if (Node4.isPropertyAccessExpression(callee)) {
1680
1489
  const name = callee.getNameNode();
1681
- return Node5.isIdentifier(name) ? name : void 0;
1490
+ return Node4.isIdentifier(name) ? name : void 0;
1682
1491
  }
1683
1492
  return void 0;
1684
1493
  }
@@ -1691,19 +1500,19 @@ function resolveFactoryFromName(name, seen) {
1691
1500
  seen.add(name);
1692
1501
  const decls = name.getDefinitions().map((d) => d.getDeclarationNode()).filter((n) => n !== void 0);
1693
1502
  for (const decl of decls) {
1694
- if (Node5.isFunctionDeclaration(decl) || Node5.isMethodDeclaration(decl)) return decl;
1695
- if (Node5.isPropertyAssignment(decl)) {
1503
+ if (Node4.isFunctionDeclaration(decl) || Node4.isMethodDeclaration(decl)) return decl;
1504
+ if (Node4.isPropertyAssignment(decl)) {
1696
1505
  const init = decl.getInitializer();
1697
1506
  const inner = init ? unwrapExpression(init) : void 0;
1698
- if (inner && Node5.isIdentifier(inner)) {
1507
+ if (inner && Node4.isIdentifier(inner)) {
1699
1508
  const resolved = resolveFactoryFromName(inner, seen);
1700
1509
  if (resolved) return resolved;
1701
1510
  }
1702
1511
  continue;
1703
1512
  }
1704
- if (Node5.isShorthandPropertyAssignment(decl)) {
1513
+ if (Node4.isShorthandPropertyAssignment(decl)) {
1705
1514
  const nameNode = decl.getNameNode();
1706
- const resolved = Node5.isIdentifier(nameNode) ? resolveFactoryFromName(nameNode, seen) : void 0;
1515
+ const resolved = Node4.isIdentifier(nameNode) ? resolveFactoryFromName(nameNode, seen) : void 0;
1707
1516
  if (resolved) return resolved;
1708
1517
  }
1709
1518
  }
@@ -1715,7 +1524,7 @@ function warnUnresolvedFactory(cls, calleeText, reason) {
1715
1524
  );
1716
1525
  }
1717
1526
  function resolveFactoryStaticClass(node) {
1718
- if (!Node5.isPropertyAccessExpression(node)) return void 0;
1527
+ if (!Node4.isPropertyAccessExpression(node)) return void 0;
1719
1528
  const call = resolveHeritageCall(node.getExpression());
1720
1529
  if (!call) return void 0;
1721
1530
  const factoryDecl = resolveFactoryDeclaration(call);
@@ -1723,13 +1532,13 @@ function resolveFactoryStaticClass(node) {
1723
1532
  const returnedClass = resolveReturnedClass(factoryDecl);
1724
1533
  if (!returnedClass) return void 0;
1725
1534
  const staticProp = returnedClass.getStaticProperties().find((p) => p.getName() === node.getName());
1726
- if (!staticProp || !Node5.isPropertyDeclaration(staticProp)) return void 0;
1535
+ if (!staticProp || !Node4.isPropertyDeclaration(staticProp)) return void 0;
1727
1536
  const init = staticProp.getInitializer();
1728
1537
  if (!init) return void 0;
1729
1538
  const inner = unwrapExpression(init);
1730
- if (Node5.isClassExpression(inner)) return inner;
1731
- if (!Node5.isIdentifier(inner)) return void 0;
1732
- return inner.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node5.isClassDeclaration(n));
1539
+ if (Node4.isClassExpression(inner)) return inner;
1540
+ if (!Node4.isIdentifier(inner)) return void 0;
1541
+ return inner.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node4.isClassDeclaration(n));
1733
1542
  }
1734
1543
  function resolveBaseClass(cls) {
1735
1544
  const heritage = cls.getExtends()?.getExpression();
@@ -1740,8 +1549,8 @@ function resolveBaseClass(cls) {
1740
1549
  return factoryDecl ? resolveReturnedClass(factoryDecl) : void 0;
1741
1550
  }
1742
1551
  const expr = unwrapExpression(heritage);
1743
- if (!Node5.isIdentifier(expr)) return void 0;
1744
- return expr.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node5.isClassDeclaration(n));
1552
+ if (!Node4.isIdentifier(expr)) return void 0;
1553
+ return expr.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node4.isClassDeclaration(n));
1745
1554
  }
1746
1555
  function collectMethodsThroughChain(returnedClass) {
1747
1556
  const byName = /* @__PURE__ */ new Map();
@@ -1788,7 +1597,7 @@ function resolveInheritedMethods(cls) {
1788
1597
  classArgs.push(positional);
1789
1598
  continue;
1790
1599
  }
1791
- if (!Node5.isObjectLiteralExpression(arg)) continue;
1600
+ if (!Node4.isObjectLiteralExpression(arg)) continue;
1792
1601
  for (const prop of arg.getProperties()) {
1793
1602
  const named = resolvePropertyClassReference(prop);
1794
1603
  if (!named) continue;
@@ -1810,8 +1619,8 @@ function resolveInheritedMethods(cls) {
1810
1619
  }
1811
1620
  function resolveClassReference(node) {
1812
1621
  const expr = unwrapExpression(node);
1813
- if (!Node5.isIdentifier(expr)) return void 0;
1814
- const decl = expr.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node5.isClassDeclaration(n));
1622
+ if (!Node4.isIdentifier(expr)) return void 0;
1623
+ const decl = expr.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node4.isClassDeclaration(n));
1815
1624
  if (!decl) return void 0;
1816
1625
  return {
1817
1626
  name: decl.getName() ?? expr.getText(),
@@ -1819,29 +1628,56 @@ function resolveClassReference(node) {
1819
1628
  };
1820
1629
  }
1821
1630
  function resolvePropertyClassReference(prop) {
1822
- if (Node5.isShorthandPropertyAssignment(prop)) {
1631
+ if (Node4.isShorthandPropertyAssignment(prop)) {
1823
1632
  const ref2 = resolveClassReference(prop.getNameNode());
1824
1633
  return ref2 ? { key: prop.getName(), ref: ref2 } : void 0;
1825
1634
  }
1826
- if (!Node5.isPropertyAssignment(prop)) return void 0;
1635
+ if (!Node4.isPropertyAssignment(prop)) return void 0;
1827
1636
  const init = prop.getInitializer();
1828
1637
  if (!init) return void 0;
1829
1638
  const ref = resolveClassReference(init);
1830
1639
  if (!ref) return void 0;
1831
1640
  const nameNode = prop.getNameNode();
1832
- const key = Node5.isStringLiteral(nameNode) ? nameNode.getLiteralValue() : prop.getName();
1641
+ const key = Node4.isStringLiteral(nameNode) ? nameNode.getLiteralValue() : prop.getName();
1833
1642
  return { key, ref };
1834
1643
  }
1835
1644
  var mixinTypeProject;
1645
+ var mixinFiles = new DiskFingerprints();
1646
+ var mixinStale = false;
1647
+ var mixinUnresolved = new UnresolvedImports();
1836
1648
  function getMixinTypeProject() {
1837
- mixinTypeProject ??= new Project2({
1838
- skipAddingFilesFromTsConfig: true,
1839
- compilerOptions: { strict: true }
1840
- });
1649
+ if (mixinTypeProject && mixinStale && !refreshMixinTypeProject(mixinTypeProject)) {
1650
+ mixinTypeProject = void 0;
1651
+ }
1652
+ mixinStale = false;
1653
+ if (!mixinTypeProject) {
1654
+ mixinTypeProject = new Project({
1655
+ skipAddingFilesFromTsConfig: true,
1656
+ compilerOptions: { strict: true }
1657
+ });
1658
+ mixinFiles = new DiskFingerprints();
1659
+ mixinUnresolved = new UnresolvedImports();
1660
+ }
1841
1661
  return mixinTypeProject;
1842
1662
  }
1843
1663
  function clearMixinTypeProject() {
1844
1664
  mixinTypeProject = void 0;
1665
+ mixinStale = false;
1666
+ }
1667
+ function invalidateMixinTypeProject() {
1668
+ mixinStale = true;
1669
+ }
1670
+ function refreshMixinTypeProject(project) {
1671
+ const program = project.getProgram().compilerObject;
1672
+ const files = program.getSourceFiles().filter((file) => !program.isSourceFileDefaultLibrary(file)).map((file) => ({ path: file.fileName, text: file.text }));
1673
+ const changed = mixinFiles.changed(files);
1674
+ if (changed.length === 0) return mixinUnresolved.nowResolving(program).length === 0;
1675
+ for (const path of changed) {
1676
+ const sf = project.getSourceFile(path);
1677
+ if (sf?.refreshFromFileSystemSync() !== FileSystemRefreshResult.Updated) return false;
1678
+ }
1679
+ project.resolveSourceFileDependencies();
1680
+ return true;
1845
1681
  }
1846
1682
  function resolveInstantiatedReturnType(cls, methodName) {
1847
1683
  const filePath = cls.getSourceFile().getFilePath();
@@ -1872,7 +1708,7 @@ function resolveClassRef(ref, project) {
1872
1708
  return file?.getClass(ref.name);
1873
1709
  }
1874
1710
  function classifyFilterForHint(typeInit) {
1875
- if (Node6.isStringLiteral(typeInit)) {
1711
+ if (Node5.isStringLiteral(typeInit)) {
1876
1712
  switch (typeInit.getLiteralValue()) {
1877
1713
  case "string":
1878
1714
  return { kind: "string" };
@@ -1886,10 +1722,10 @@ function classifyFilterForHint(typeInit) {
1886
1722
  return null;
1887
1723
  }
1888
1724
  }
1889
- if (Node6.isArrayLiteralExpression(typeInit)) {
1725
+ if (Node5.isArrayLiteralExpression(typeInit)) {
1890
1726
  const values = [];
1891
1727
  for (const el of typeInit.getElements()) {
1892
- if (!Node6.isStringLiteral(el)) return null;
1728
+ if (!Node5.isStringLiteral(el)) return null;
1893
1729
  values.push(el.getLiteralValue());
1894
1730
  }
1895
1731
  if (values.length === 0) return null;
@@ -1924,11 +1760,11 @@ function extractFilterForHints(classDecl, project) {
1924
1760
  if (!filterForDec) continue;
1925
1761
  const args = filterForDec.getArguments();
1926
1762
  const keyArg = args[0];
1927
- const inputKey = keyArg && Node6.isStringLiteral(keyArg) ? keyArg.getLiteralValue() : method.getName();
1763
+ const inputKey = keyArg && Node5.isStringLiteral(keyArg) ? keyArg.getLiteralValue() : method.getName();
1928
1764
  const optsArg = args[1];
1929
- if (optsArg && Node6.isObjectLiteralExpression(optsArg)) {
1765
+ if (optsArg && Node5.isObjectLiteralExpression(optsArg)) {
1930
1766
  const typeProp = optsArg.getProperty("type");
1931
- if (typeProp && Node6.isPropertyAssignment(typeProp)) {
1767
+ if (typeProp && Node5.isPropertyAssignment(typeProp)) {
1932
1768
  const typeInit = typeProp.getInitializer();
1933
1769
  if (typeInit) {
1934
1770
  const classified = classifyFilterForHint(typeInit);
@@ -1955,14 +1791,14 @@ function extractApplyFilterInfo(method, sourceFile, project, mixin, controllerCl
1955
1791
  const filterClassArg = args[0];
1956
1792
  if (!filterClassArg) continue;
1957
1793
  const factoryStaticClass = resolveFactoryStaticClass(filterClassArg);
1958
- if (!factoryStaticClass && !Node6.isIdentifier(filterClassArg)) continue;
1794
+ if (!factoryStaticClass && !Node5.isIdentifier(filterClassArg)) continue;
1959
1795
  let source = "query";
1960
1796
  const optionsArg = args[1];
1961
- if (optionsArg && Node6.isObjectLiteralExpression(optionsArg)) {
1797
+ if (optionsArg && Node5.isObjectLiteralExpression(optionsArg)) {
1962
1798
  const sourceProp = optionsArg.getProperty("source");
1963
- if (sourceProp && Node6.isPropertyAssignment(sourceProp)) {
1799
+ if (sourceProp && Node5.isPropertyAssignment(sourceProp)) {
1964
1800
  const init = sourceProp.getInitializer();
1965
- if (init && Node6.isStringLiteral(init) && init.getLiteralValue() === "body") {
1801
+ if (init && Node5.isStringLiteral(init) && init.getLiteralValue() === "body") {
1966
1802
  source = "body";
1967
1803
  }
1968
1804
  }
@@ -1971,7 +1807,7 @@ function extractApplyFilterInfo(method, sourceFile, project, mixin, controllerCl
1971
1807
  const resolved = factoryStaticClass ? void 0 : findType(filterClassName, declFile, project);
1972
1808
  const declaredClass = factoryStaticClass ?? (resolved?.kind === "class" ? resolved.decl : resolveLocalClassDeclaration(filterClassArg));
1973
1809
  const ownRoute = !mixin || (controllerClass ? method.getParent() === controllerClass : declFile.getFilePath() !== mixin.factoryFilePath);
1974
- const namedByRoute = ownRoute && Node6.isIdentifier(filterClassArg) ? declaredClass : void 0;
1810
+ const namedByRoute = ownRoute && Node5.isIdentifier(filterClassArg) ? declaredClass : void 0;
1975
1811
  for (const candidate of orderFilterCandidates({
1976
1812
  namedByRoute,
1977
1813
  supplied: resolveMixinFilterClass(mixin, project),
@@ -2049,22 +1885,22 @@ function resolveRelationEntity(prop, sourceFile, project) {
2049
1885
  const args = dec.getArguments();
2050
1886
  if (args.length === 0) continue;
2051
1887
  const arg = args[0];
2052
- if (Node6.isObjectLiteralExpression(arg)) {
1888
+ if (Node5.isObjectLiteralExpression(arg)) {
2053
1889
  const entityProp = arg.getProperty("entity");
2054
- if (entityProp && Node6.isPropertyAssignment(entityProp)) {
1890
+ if (entityProp && Node5.isPropertyAssignment(entityProp)) {
2055
1891
  const init = entityProp.getInitializer();
2056
- if (init && Node6.isArrowFunction(init)) {
1892
+ if (init && Node5.isArrowFunction(init)) {
2057
1893
  const body = init.getBody();
2058
- if (Node6.isIdentifier(body)) {
1894
+ if (Node5.isIdentifier(body)) {
2059
1895
  const resolved = findType(body.getText(), prop.getSourceFile(), project);
2060
1896
  if (resolved?.kind === "class") return resolved.decl;
2061
1897
  }
2062
1898
  }
2063
1899
  }
2064
1900
  }
2065
- if (Node6.isArrowFunction(arg)) {
1901
+ if (Node5.isArrowFunction(arg)) {
2066
1902
  const body = arg.getBody();
2067
- if (Node6.isIdentifier(body)) {
1903
+ if (Node5.isIdentifier(body)) {
2068
1904
  const resolved = findType(body.getText(), prop.getSourceFile(), project);
2069
1905
  if (resolved?.kind === "class") return resolved.decl;
2070
1906
  }
@@ -2083,36 +1919,36 @@ function extractClassPropertyTypes(classDecl, project) {
2083
1919
  return fields;
2084
1920
  }
2085
1921
  function resolveLocalClassDeclaration(identifier) {
2086
- if (!Node6.isIdentifier(identifier)) return void 0;
2087
- return identifier.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node6.isClassDeclaration(n));
1922
+ if (!Node5.isIdentifier(identifier)) return void 0;
1923
+ return identifier.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node5.isClassDeclaration(n));
2088
1924
  }
2089
1925
  function resolveDeclaredEntity(optionsArg, filterClass, project) {
2090
- if (!Node6.isObjectLiteralExpression(optionsArg)) return void 0;
1926
+ if (!Node5.isObjectLiteralExpression(optionsArg)) return void 0;
2091
1927
  const entityProp = optionsArg.getProperty("entity");
2092
- if (!entityProp || !Node6.isPropertyAssignment(entityProp)) return void 0;
1928
+ if (!entityProp || !Node5.isPropertyAssignment(entityProp)) return void 0;
2093
1929
  const entityInit = entityProp.getInitializer();
2094
- if (!entityInit || !Node6.isIdentifier(entityInit)) return void 0;
1930
+ if (!entityInit || !Node5.isIdentifier(entityInit)) return void 0;
2095
1931
  const resolved = findType(entityInit.getText(), filterClass.getSourceFile(), project);
2096
1932
  if (!resolved || resolved.kind !== "class") return void 0;
2097
1933
  return resolved.decl;
2098
1934
  }
2099
1935
  function readFieldNameList(optionsArg, key) {
2100
- if (!Node6.isObjectLiteralExpression(optionsArg)) return void 0;
1936
+ if (!Node5.isObjectLiteralExpression(optionsArg)) return void 0;
2101
1937
  const prop = optionsArg.getProperty(key);
2102
- if (!prop || !Node6.isPropertyAssignment(prop)) return void 0;
1938
+ if (!prop || !Node5.isPropertyAssignment(prop)) return void 0;
2103
1939
  const init = prop.getInitializer();
2104
- if (!init || !Node6.isArrayLiteralExpression(init)) return void 0;
1940
+ if (!init || !Node5.isArrayLiteralExpression(init)) return void 0;
2105
1941
  const names = /* @__PURE__ */ new Set();
2106
1942
  for (const el of init.getElements()) {
2107
- if (Node6.isStringLiteral(el)) {
1943
+ if (Node5.isStringLiteral(el)) {
2108
1944
  names.add(el.getLiteralValue());
2109
1945
  continue;
2110
1946
  }
2111
- if (Node6.isObjectLiteralExpression(el)) {
1947
+ if (Node5.isObjectLiteralExpression(el)) {
2112
1948
  const fieldProp = el.getProperty("field");
2113
- if (fieldProp && Node6.isPropertyAssignment(fieldProp)) {
1949
+ if (fieldProp && Node5.isPropertyAssignment(fieldProp)) {
2114
1950
  const fieldInit = fieldProp.getInitializer();
2115
- if (fieldInit && Node6.isStringLiteral(fieldInit)) {
1951
+ if (fieldInit && Node5.isStringLiteral(fieldInit)) {
2116
1952
  names.add(fieldInit.getLiteralValue());
2117
1953
  continue;
2118
1954
  }
@@ -2136,7 +1972,7 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
2136
1972
  const args = filterableDecorator.getArguments();
2137
1973
  if (args.length === 0) return [];
2138
1974
  const optionsArg = args[0];
2139
- if (!Node6.isObjectLiteralExpression(optionsArg)) return [];
1975
+ if (!Node5.isObjectLiteralExpression(optionsArg)) return [];
2140
1976
  const declaredEntity = resolveDeclaredEntity(optionsArg, filterClass, project);
2141
1977
  const entityDecl = preferDeclaredEntity ? declaredEntity ?? entityOverride : entityOverride ?? declaredEntity;
2142
1978
  if (!entityDecl) return [];
@@ -2147,17 +1983,17 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
2147
1983
  const relationsDecorator = filterClass.getDecorators().find((d) => d.getName() === "Relations");
2148
1984
  if (relationsDecorator) {
2149
1985
  const relArgs = relationsDecorator.getArguments();
2150
- if (relArgs.length > 0 && Node6.isObjectLiteralExpression(relArgs[0])) {
1986
+ if (relArgs.length > 0 && Node5.isObjectLiteralExpression(relArgs[0])) {
2151
1987
  for (const relProp of relArgs[0].getProperties()) {
2152
- if (!Node6.isPropertyAssignment(relProp)) continue;
1988
+ if (!Node5.isPropertyAssignment(relProp)) continue;
2153
1989
  const relInit = relProp.getInitializer();
2154
- if (!relInit || !Node6.isObjectLiteralExpression(relInit)) continue;
1990
+ if (!relInit || !Node5.isObjectLiteralExpression(relInit)) continue;
2155
1991
  const keysProp = relInit.getProperty("keys");
2156
- if (!keysProp || !Node6.isPropertyAssignment(keysProp)) continue;
1992
+ if (!keysProp || !Node5.isPropertyAssignment(keysProp)) continue;
2157
1993
  const keysInit = keysProp.getInitializer();
2158
- if (!keysInit || !Node6.isArrayLiteralExpression(keysInit)) continue;
1994
+ if (!keysInit || !Node5.isArrayLiteralExpression(keysInit)) continue;
2159
1995
  for (const el of keysInit.getElements()) {
2160
- if (Node6.isStringLiteral(el)) {
1996
+ if (Node5.isStringLiteral(el)) {
2161
1997
  fields.push(toFilterFieldType(el.getLiteralValue(), { kind: "unknown" }));
2162
1998
  }
2163
1999
  }
@@ -2168,15 +2004,15 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
2168
2004
  }
2169
2005
 
2170
2006
  // src/discovery/pipe-schema.ts
2171
- import { Node as Node9 } from "ts-morph";
2007
+ import { Node as Node8 } from "ts-morph";
2172
2008
 
2173
2009
  // src/discovery/schema-type-inference.ts
2174
2010
  import {
2175
- ts as ts2
2011
+ ts as ts3
2176
2012
  } from "ts-morph";
2177
2013
 
2178
2014
  // src/discovery/schema-type-program.ts
2179
- import { Node as Node7, Project as Project3, ts } from "ts-morph";
2015
+ import { FileSystemRefreshResult as FileSystemRefreshResult2, Node as Node6, Project as Project2, ts as ts2 } from "ts-morph";
2180
2016
  var PARAM_DECORATORS = /* @__PURE__ */ new Set(["Body", "Query", "Param"]);
2181
2017
  var SchemaTypeProgram = class {
2182
2018
  constructor(discovery) {
@@ -2186,10 +2022,10 @@ var SchemaTypeProgram = class {
2186
2022
  ...base,
2187
2023
  // A tsconfig-less consumer gets ES5 by default, whose lib has no `Map`/`Set`/
2188
2024
  // `Promise` for the schema libraries' declarations to lean on.
2189
- target: base.target ?? ts.ScriptTarget.ES2022,
2025
+ target: base.target ?? ts2.ScriptTarget.ES2022,
2190
2026
  // With neither set, resolve the way a bundler does: package `exports`
2191
2027
  // (`zod/v4`) and extensionless or `.js` relative imports all resolve.
2192
- ...base.module === void 0 && base.moduleResolution === void 0 ? { module: ts.ModuleKind.ESNext, moduleResolution: ts.ModuleResolutionKind.Bundler } : {},
2028
+ ...base.module === void 0 && base.moduleResolution === void 0 ? { module: ts2.ModuleKind.ESNext, moduleResolution: ts2.ModuleResolutionKind.Bundler } : {},
2193
2029
  // Without it `.nullable()` and `.optional()` would erase to the bare type.
2194
2030
  strictNullChecks: true,
2195
2031
  // No ambient `@types/*` (a tsconfig's `types: ['node']`): a schema does not
@@ -2197,17 +2033,16 @@ var SchemaTypeProgram = class {
2197
2033
  types: [],
2198
2034
  noEmit: true
2199
2035
  };
2200
- this.project = new Project3({
2036
+ this.project = new Project2({
2201
2037
  skipAddingFilesFromTsConfig: true,
2202
2038
  compilerOptions: this.options
2203
2039
  });
2204
- for (const sf of discovery.getSourceFiles()) this.request(sf.getFilePath(), { schema: true });
2205
- this.flush();
2040
+ this.requestDiscoveryFiles();
2206
2041
  }
2207
2042
  project;
2208
2043
  options;
2209
2044
  /** Parses a file that is not in discovery's Project, without adding it there. */
2210
- scratch = new Project3({
2045
+ scratch = new Project2({
2211
2046
  skipAddingFilesFromTsConfig: true,
2212
2047
  skipLoadingLibFiles: true,
2213
2048
  skipFileDependencyResolution: true
@@ -2215,6 +2050,53 @@ var SchemaTypeProgram = class {
2215
2050
  needs = /* @__PURE__ */ new Map();
2216
2051
  /** Pruned text per file, as last written into {@link project}. */
2217
2052
  written = /* @__PURE__ */ new Map();
2053
+ /** What {@link project} read from disk unpruned (packages, declaration files). */
2054
+ externalFiles = new DiskFingerprints();
2055
+ scratchFiles = new DiskFingerprints();
2056
+ /** The last pruning of each file, with the source text and needs it was pruned for. */
2057
+ pruned = /* @__PURE__ */ new Map();
2058
+ /**
2059
+ * Bring the program up to date with discovery's files and the disk, keeping the
2060
+ * lib and package declarations it has already parsed and checked. Leaves it
2061
+ * holding exactly the files a new program built now would hold, with the same
2062
+ * text. Returns false when a file the program read unpruned has changed on disk
2063
+ * (a package was reinstalled, a `.d.ts` was edited): the caller builds a new
2064
+ * program instead.
2065
+ */
2066
+ refresh() {
2067
+ const program = this.project.getProgram().compilerObject;
2068
+ const external = program.getSourceFiles().filter(
2069
+ (file) => !program.isSourceFileDefaultLibrary(file) && !this.written.has(file.fileName)
2070
+ ).map((file) => ({ path: file.fileName, text: file.text }));
2071
+ if (this.externalFiles.changed(external).length > 0) return false;
2072
+ const scratch = this.scratch.getSourceFiles().map((sf) => ({ path: sf.getFilePath(), text: sf.getFullText() }));
2073
+ for (const path of this.scratchFiles.changed(scratch)) {
2074
+ const sf = this.scratch.getSourceFile(path);
2075
+ if (sf?.refreshFromFileSystemSync() === FileSystemRefreshResult2.Deleted) {
2076
+ this.scratchFiles.forget(path);
2077
+ }
2078
+ }
2079
+ this.needs.clear();
2080
+ this.requestDiscoveryFiles();
2081
+ for (const path of [...this.written.keys()]) {
2082
+ if (this.needs.has(path)) continue;
2083
+ const sf = this.project.getSourceFile(path);
2084
+ if (sf) this.project.removeSourceFile(sf);
2085
+ this.written.delete(path);
2086
+ }
2087
+ return true;
2088
+ }
2089
+ /**
2090
+ * Everything discovery holds now (the controllers) goes in before the first
2091
+ * query: a file added afterwards discards the program and its checker, and
2092
+ * rebuilding them once per controller is exactly the cost this avoids.
2093
+ */
2094
+ requestDiscoveryFiles() {
2095
+ for (const sf of this.discovery.getSourceFiles()) {
2096
+ this.request(sf.getFilePath(), { schema: true });
2097
+ }
2098
+ this.flush();
2099
+ }
2218
2100
  /**
2219
2101
  * The typed copy of a discovery file, with its schema positions kept. Adding one
2220
2102
  * that was not there yet (a contract file discovery reached lazily) rebuilds the
@@ -2255,12 +2137,21 @@ var SchemaTypeProgram = class {
2255
2137
  while (this.queue.length > 0) {
2256
2138
  const path = this.queue.pop();
2257
2139
  const original = this.original(path);
2258
- if (!original) continue;
2259
- const needs = this.needs.get(path);
2260
- const text = this.prune(original, needs);
2140
+ if (!original) {
2141
+ if (this.written.has(path)) changed.set(path, void 0);
2142
+ continue;
2143
+ }
2144
+ const { text, requests } = this.pruneCached(path, original, this.needs.get(path));
2145
+ for (const { path: target, want } of requests) this.request(target, want);
2261
2146
  if (this.written.get(path) !== text) changed.set(path, text);
2262
2147
  }
2263
2148
  for (const [path, text] of changed) {
2149
+ if (text === void 0) {
2150
+ const sf = this.project.getSourceFile(path);
2151
+ if (sf) this.project.removeSourceFile(sf);
2152
+ this.written.delete(path);
2153
+ continue;
2154
+ }
2264
2155
  this.project.createSourceFile(path, text, { overwrite: true });
2265
2156
  this.written.set(path, text);
2266
2157
  }
@@ -2269,11 +2160,29 @@ var SchemaTypeProgram = class {
2269
2160
  original(path) {
2270
2161
  return this.discovery.getSourceFile(path) ?? this.scratch.getSourceFile(path) ?? this.scratch.addSourceFileAtPathIfExists(path);
2271
2162
  }
2163
+ /** {@link prune}, reusing the last result while the file's text and needs are the same. */
2164
+ pruneCached(path, sf, needs) {
2165
+ const source = sf.getFullText();
2166
+ const key = `${needs.schema}:${needs.all}:${[...needs.names].sort().join(",")}`;
2167
+ const last = this.pruned.get(path);
2168
+ if (last && last.source === source && last.needs === key) return last;
2169
+ const result = this.prune(sf, needs);
2170
+ if (result.resolved) this.pruned.set(path, { ...result, source, needs: key });
2171
+ else this.pruned.delete(path);
2172
+ return result;
2173
+ }
2272
2174
  /**
2273
2175
  * `sf`'s text with every import/re-export nothing in `needs` references blanked,
2274
- * requesting from each kept one's target the names it brings in.
2176
+ * and the names each kept one brings in from its target.
2275
2177
  */
2276
2178
  prune(sf, needs) {
2179
+ const requests = [];
2180
+ let resolved = true;
2181
+ const resolveFrom = (specifier) => {
2182
+ const target = this.resolve(specifier, sf);
2183
+ if (target === null) resolved = false;
2184
+ return target ?? void 0;
2185
+ };
2277
2186
  const locals = topLevelDeclarations(sf);
2278
2187
  const roots = [];
2279
2188
  if (needs.all) roots.push(...sf.getStatements());
@@ -2307,7 +2216,7 @@ var SchemaTypeProgram = class {
2307
2216
  const seen = new Set(queue);
2308
2217
  while (queue.length > 0) {
2309
2218
  const node = queue.pop();
2310
- const ids = Node7.isIdentifier(node) ? [node] : node.getDescendantsOfKind(ts.SyntaxKind.Identifier);
2219
+ const ids = Node6.isIdentifier(node) ? [node] : node.getDescendantsOfKind(ts2.SyntaxKind.Identifier);
2311
2220
  for (const id of ids) {
2312
2221
  const name = id.getText();
2313
2222
  if (referenced.has(name)) continue;
@@ -2326,7 +2235,7 @@ var SchemaTypeProgram = class {
2326
2235
  }
2327
2236
  };
2328
2237
  for (const decl of sf.getImportDeclarations()) {
2329
- const target = this.resolve(decl.getModuleSpecifierValue(), sf);
2238
+ const target = resolveFrom(decl.getModuleSpecifierValue());
2330
2239
  const namespace = decl.getNamespaceImport()?.getText();
2331
2240
  const def = decl.getDefaultImport()?.getText();
2332
2241
  const named = decl.getNamedImports().map((n) => ({
@@ -2340,21 +2249,24 @@ var SchemaTypeProgram = class {
2340
2249
  continue;
2341
2250
  }
2342
2251
  if (target) {
2343
- this.request(target, {
2344
- all: wantsNamespace,
2345
- names: [...wantsDefault ? ["default"] : [], ...named.map((n) => n.imported)]
2252
+ requests.push({
2253
+ path: target,
2254
+ want: {
2255
+ all: wantsNamespace,
2256
+ names: [...wantsDefault ? ["default"] : [], ...named.map((n) => n.imported)]
2257
+ }
2346
2258
  });
2347
2259
  }
2348
2260
  }
2349
2261
  for (const exp of sf.getExportDeclarations()) {
2350
2262
  const spec = exp.getModuleSpecifierValue();
2351
2263
  if (spec === void 0) continue;
2352
- const target = this.resolve(spec, sf);
2264
+ const target = resolveFrom(spec);
2353
2265
  if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) {
2354
2266
  if (!needs.all && unresolvedNames.length === 0) {
2355
2267
  blank(exp);
2356
2268
  } else if (target) {
2357
- this.request(target, { all: needs.all, names: unresolvedNames });
2269
+ requests.push({ path: target, want: { all: needs.all, names: unresolvedNames } });
2358
2270
  }
2359
2271
  continue;
2360
2272
  }
@@ -2362,22 +2274,24 @@ var SchemaTypeProgram = class {
2362
2274
  (n) => needs.all || needs.names.has((n.getAliasNode() ?? n.getNameNode()).getText())
2363
2275
  ).map((n) => n.getNameNode().getText());
2364
2276
  if (wanted.length === 0) blank(exp);
2365
- else if (target) this.request(target, { names: wanted });
2277
+ else if (target) requests.push({ path: target, want: { names: wanted } });
2366
2278
  }
2367
- return chars.join("");
2279
+ return { text: chars.join(""), requests, resolved };
2368
2280
  }
2369
2281
  /**
2370
- * The project source file a module specifier resolves to, or undefined for a
2371
- * package (read unpruned by the program itself) or an unresolvable one.
2282
+ * The project source file a module specifier resolves to; undefined for a
2283
+ * package or a declaration file (read unpruned by the program itself), null for
2284
+ * one that does not resolve.
2372
2285
  */
2373
2286
  resolve(specifier, from) {
2374
- const resolved = ts.resolveModuleName(
2287
+ const resolved = ts2.resolveModuleName(
2375
2288
  specifier,
2376
2289
  from.getFilePath(),
2377
2290
  this.options,
2378
- ts.sys
2291
+ ts2.sys
2379
2292
  ).resolvedModule;
2380
- if (!resolved || resolved.isExternalLibraryImport) return void 0;
2293
+ if (!resolved) return null;
2294
+ if (resolved.isExternalLibraryImport) return void 0;
2381
2295
  const file = resolved.resolvedFileName;
2382
2296
  if (file.endsWith(".d.ts") || !/\.(m|c)?tsx?$/.test(file)) return void 0;
2383
2297
  return file;
@@ -2386,20 +2300,20 @@ var SchemaTypeProgram = class {
2386
2300
  function schemaPositions(sf) {
2387
2301
  const roots = [];
2388
2302
  sf.forEachDescendant((node) => {
2389
- if (Node7.isDecorator(node)) {
2303
+ if (Node6.isDecorator(node)) {
2390
2304
  const name = node.getName();
2391
2305
  if (name === "ApplyContract") {
2392
2306
  roots.push(...node.getArguments());
2393
2307
  } else if (PARAM_DECORATORS.has(name)) {
2394
2308
  for (const arg of node.getArguments()) {
2395
- if (Node7.isNewExpression(arg) || Node7.isCallExpression(arg)) {
2309
+ if (Node6.isNewExpression(arg) || Node6.isCallExpression(arg)) {
2396
2310
  roots.push(...arg.getArguments());
2397
2311
  }
2398
2312
  }
2399
2313
  }
2400
- } else if (Node7.isCallExpression(node)) {
2314
+ } else if (Node6.isCallExpression(node)) {
2401
2315
  const callee = node.getExpression();
2402
- const name = Node7.isIdentifier(callee) ? callee.getText() : Node7.isPropertyAccessExpression(callee) ? callee.getName() : void 0;
2316
+ const name = Node6.isIdentifier(callee) ? callee.getText() : Node6.isPropertyAccessExpression(callee) ? callee.getName() : void 0;
2403
2317
  if (name === "defineContract") roots.push(...node.getArguments());
2404
2318
  }
2405
2319
  });
@@ -2432,8 +2346,13 @@ function defaultExport(sf) {
2432
2346
 
2433
2347
  // src/discovery/schema-type-inference.ts
2434
2348
  var programs = /* @__PURE__ */ new WeakMap();
2349
+ var stale = /* @__PURE__ */ new WeakSet();
2435
2350
  function programFor(discovery) {
2436
2351
  let program = programs.get(discovery);
2352
+ if (program && stale.has(discovery)) {
2353
+ stale.delete(discovery);
2354
+ if (!program.refresh()) program = void 0;
2355
+ }
2437
2356
  if (!program) {
2438
2357
  program = new SchemaTypeProgram(discovery);
2439
2358
  programs.set(discovery, program);
@@ -2442,6 +2361,10 @@ function programFor(discovery) {
2442
2361
  }
2443
2362
  function clearSchemaTypeProject(discovery) {
2444
2363
  programs.delete(discovery);
2364
+ stale.delete(discovery);
2365
+ }
2366
+ function invalidateSchemaTypeProject(discovery) {
2367
+ if (programs.has(discovery)) stale.add(discovery);
2445
2368
  }
2446
2369
  function counterpart(typedSf, node) {
2447
2370
  const start = node.getStart();
@@ -2505,7 +2428,7 @@ function objectKey(name) {
2505
2428
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
2506
2429
  }
2507
2430
  function isOptionalSymbol(sym) {
2508
- return (sym.getFlags() & ts2.SymbolFlags.Optional) !== 0;
2431
+ return (sym.getFlags() & ts3.SymbolFlags.Optional) !== 0;
2509
2432
  }
2510
2433
  function isSymbolKeyed(sym) {
2511
2434
  return sym.getName().startsWith("__@");
@@ -2666,11 +2589,11 @@ function acceptsUndefined(types) {
2666
2589
  }
2667
2590
 
2668
2591
  // src/discovery/zod-ast-to-ts.ts
2669
- import { Node as Node8, SyntaxKind as SyntaxKind3 } from "ts-morph";
2592
+ import { Node as Node7, SyntaxKind as SyntaxKind2 } from "ts-morph";
2670
2593
  function zodAstToTs(node) {
2671
- if (!Node8.isCallExpression(node)) return "unknown";
2594
+ if (!Node7.isCallExpression(node)) return "unknown";
2672
2595
  const expr = node.getExpression();
2673
- if (Node8.isPropertyAccessExpression(expr)) {
2596
+ if (Node7.isPropertyAccessExpression(expr)) {
2674
2597
  const methodName = expr.getName();
2675
2598
  const receiver = expr.getExpression();
2676
2599
  if (methodName === "optional") {
@@ -2694,17 +2617,17 @@ function zodAstToTs(node) {
2694
2617
  case "literal": {
2695
2618
  const lit = args[0];
2696
2619
  if (!lit) return "unknown";
2697
- if (Node8.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
2698
- if (Node8.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
2699
- if (lit.getKind() === SyntaxKind3.TrueKeyword) return "true";
2700
- if (lit.getKind() === SyntaxKind3.FalseKeyword) return "false";
2620
+ if (Node7.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
2621
+ if (Node7.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
2622
+ if (lit.getKind() === SyntaxKind2.TrueKeyword) return "true";
2623
+ if (lit.getKind() === SyntaxKind2.FalseKeyword) return "false";
2701
2624
  return "unknown";
2702
2625
  }
2703
2626
  case "enum": {
2704
2627
  const arrArg = args[0];
2705
- if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
2628
+ if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
2706
2629
  const members = arrArg.getElements().map(
2707
- (el) => Node8.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
2630
+ (el) => Node7.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
2708
2631
  );
2709
2632
  return members.join(" | ");
2710
2633
  }
@@ -2715,10 +2638,10 @@ function zodAstToTs(node) {
2715
2638
  }
2716
2639
  case "object": {
2717
2640
  const objArg = args[0];
2718
- if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
2641
+ if (!objArg || !Node7.isObjectLiteralExpression(objArg)) return "unknown";
2719
2642
  const lines = [];
2720
2643
  for (const prop of objArg.getProperties()) {
2721
- if (!Node8.isPropertyAssignment(prop)) continue;
2644
+ if (!Node7.isPropertyAssignment(prop)) continue;
2722
2645
  const key = prop.getName();
2723
2646
  const valNode = prop.getInitializer();
2724
2647
  if (!valNode) continue;
@@ -2730,7 +2653,7 @@ function zodAstToTs(node) {
2730
2653
  }
2731
2654
  case "union": {
2732
2655
  const arrArg = args[0];
2733
- if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
2656
+ if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
2734
2657
  return arrArg.getElements().map(zodAstToTs).join(" | ");
2735
2658
  }
2736
2659
  case "record": {
@@ -2740,7 +2663,7 @@ function zodAstToTs(node) {
2740
2663
  }
2741
2664
  case "tuple": {
2742
2665
  const arrArg = args[0];
2743
- if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
2666
+ if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
2744
2667
  return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
2745
2668
  }
2746
2669
  default:
@@ -2750,9 +2673,9 @@ function zodAstToTs(node) {
2750
2673
  return "unknown";
2751
2674
  }
2752
2675
  function isOptionalChain(node) {
2753
- if (!Node8.isCallExpression(node)) return false;
2676
+ if (!Node7.isCallExpression(node)) return false;
2754
2677
  const expr = node.getExpression();
2755
- return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
2678
+ return Node7.isPropertyAccessExpression(expr) && expr.getName() === "optional";
2756
2679
  }
2757
2680
  function memberType(val, position, project) {
2758
2681
  if (project) {
@@ -2764,13 +2687,13 @@ function memberType(val, position, project) {
2764
2687
  return zodAstToTs(val);
2765
2688
  }
2766
2689
  function parseDefineContractCall(callExpr, project) {
2767
- if (!Node8.isCallExpression(callExpr)) return null;
2690
+ if (!Node7.isCallExpression(callExpr)) return null;
2768
2691
  const callee = callExpr.getExpression();
2769
- const calleeName = Node8.isIdentifier(callee) ? callee.getText() : Node8.isPropertyAccessExpression(callee) ? callee.getName() : "";
2692
+ const calleeName = Node7.isIdentifier(callee) ? callee.getText() : Node7.isPropertyAccessExpression(callee) ? callee.getName() : "";
2770
2693
  if (calleeName !== "defineContract") return null;
2771
2694
  const args = callExpr.getArguments();
2772
2695
  const optsArg = args[0];
2773
- if (!optsArg || !Node8.isObjectLiteralExpression(optsArg)) return null;
2696
+ if (!optsArg || !Node7.isObjectLiteralExpression(optsArg)) return null;
2774
2697
  let query = null;
2775
2698
  let body = null;
2776
2699
  let response = "unknown";
@@ -2778,7 +2701,7 @@ function parseDefineContractCall(callExpr, project) {
2778
2701
  let bodyZodText = null;
2779
2702
  let queryZodText = null;
2780
2703
  for (const prop of optsArg.getProperties()) {
2781
- if (!Node8.isPropertyAssignment(prop)) continue;
2704
+ if (!Node7.isPropertyAssignment(prop)) continue;
2782
2705
  const propName = prop.getName();
2783
2706
  const val = prop.getInitializer();
2784
2707
  if (!val) continue;
@@ -2800,7 +2723,7 @@ function parseDefineContractCall(callExpr, project) {
2800
2723
  // src/discovery/pipe-schema.ts
2801
2724
  function pipeSchemaOf(decorator, project) {
2802
2725
  for (const pipe of decorator.getArguments()) {
2803
- if (!Node9.isNewExpression(pipe) && !Node9.isCallExpression(pipe)) continue;
2726
+ if (!Node8.isNewExpression(pipe) && !Node8.isCallExpression(pipe)) continue;
2804
2727
  for (const arg of pipe.getArguments()) {
2805
2728
  if (!couldBeSchema(arg)) continue;
2806
2729
  const inferred = inferSchema(arg, project);
@@ -2822,15 +2745,15 @@ function pipeSchemaOf(decorator, project) {
2822
2745
  return null;
2823
2746
  }
2824
2747
  function couldBeSchema(arg) {
2825
- return Node9.isIdentifier(arg) || Node9.isPropertyAccessExpression(arg) || Node9.isCallExpression(arg) || Node9.isAsExpression(arg) || Node9.isSatisfiesExpression(arg) || Node9.isParenthesizedExpression(arg);
2748
+ return Node8.isIdentifier(arg) || Node8.isPropertyAccessExpression(arg) || Node8.isCallExpression(arg) || Node8.isAsExpression(arg) || Node8.isSatisfiesExpression(arg) || Node8.isParenthesizedExpression(arg);
2826
2749
  }
2827
2750
  function syntacticZodType(arg, project) {
2828
2751
  let expr = arg;
2829
- if (Node9.isIdentifier(arg)) {
2752
+ if (Node8.isIdentifier(arg)) {
2830
2753
  const resolved = resolveImportedVariable(arg.getText(), arg.getSourceFile(), project);
2831
2754
  expr = resolved?.decl.getInitializer();
2832
2755
  }
2833
- if (!expr || !Node9.isCallExpression(expr)) return null;
2756
+ if (!expr || !Node8.isCallExpression(expr)) return null;
2834
2757
  const type = zodAstToTs(expr);
2835
2758
  return type === "unknown" ? null : type;
2836
2759
  }
@@ -2866,22 +2789,22 @@ var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
2866
2789
  ]);
2867
2790
  function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
2868
2791
  if (depth <= 0) return "unknown";
2869
- if (Node10.isArrayTypeNode(typeNode)) {
2792
+ if (Node9.isArrayTypeNode(typeNode)) {
2870
2793
  const elementType = typeNode.getElementTypeNode();
2871
2794
  return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth, subst)}>`;
2872
2795
  }
2873
- if (Node10.isUnionTypeNode(typeNode)) {
2796
+ if (Node9.isUnionTypeNode(typeNode)) {
2874
2797
  return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" | ");
2875
2798
  }
2876
- if (Node10.isIntersectionTypeNode(typeNode)) {
2799
+ if (Node9.isIntersectionTypeNode(typeNode)) {
2877
2800
  return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" & ");
2878
2801
  }
2879
- if (Node10.isParenthesizedTypeNode(typeNode)) {
2802
+ if (Node9.isParenthesizedTypeNode(typeNode)) {
2880
2803
  return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth, subst)})`;
2881
2804
  }
2882
- if (Node10.isTypeReference(typeNode)) {
2805
+ if (Node9.isTypeReference(typeNode)) {
2883
2806
  const typeName = typeNode.getTypeName();
2884
- const name = Node10.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
2807
+ const name = Node9.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
2885
2808
  const bound = subst.get(name);
2886
2809
  if (bound !== void 0) return bound;
2887
2810
  if (name === "string" || name === "number" || name === "boolean") return name;
@@ -2904,10 +2827,10 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
2904
2827
  dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
2905
2828
  return "unknown";
2906
2829
  }
2907
- if (Node10.isTypeLiteral(typeNode)) {
2830
+ if (Node9.isTypeLiteral(typeNode)) {
2908
2831
  const members = [];
2909
2832
  for (const member of typeNode.getMembers()) {
2910
- if (Node10.isPropertySignature(member)) {
2833
+ if (Node9.isPropertySignature(member)) {
2911
2834
  const memberTypeNode = member.getTypeNode();
2912
2835
  const memberType2 = memberTypeNode ? resolveTypeNodeToString(memberTypeNode, sourceFile, project, depth, subst) : "unknown";
2913
2836
  members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${memberType2}`);
@@ -2918,11 +2841,11 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
2918
2841
  return members.length > 0 ? `{ ${members.join("; ")} }` : "{}";
2919
2842
  }
2920
2843
  const kind = typeNode.getKind();
2921
- if (kind === SyntaxKind4.StringKeyword) return "string";
2922
- if (kind === SyntaxKind4.NumberKeyword) return "number";
2923
- if (kind === SyntaxKind4.BooleanKeyword) return "boolean";
2924
- if (kind === SyntaxKind4.UnknownKeyword) return "unknown";
2925
- if (kind === SyntaxKind4.AnyKeyword) return "unknown";
2844
+ if (kind === SyntaxKind3.StringKeyword) return "string";
2845
+ if (kind === SyntaxKind3.NumberKeyword) return "number";
2846
+ if (kind === SyntaxKind3.BooleanKeyword) return "boolean";
2847
+ if (kind === SyntaxKind3.UnknownKeyword) return "unknown";
2848
+ if (kind === SyntaxKind3.AnyKeyword) return "unknown";
2926
2849
  return typeNode.getText();
2927
2850
  }
2928
2851
  function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode, subst = /* @__PURE__ */ new Map()) {
@@ -2980,7 +2903,7 @@ function resolvePropertied(decl, sourceFile, project, depth, subst = /* @__PURE_
2980
2903
  }
2981
2904
  function namedField(decorator) {
2982
2905
  const first = decorator.getArguments()[0];
2983
- return first && Node10.isStringLiteral(first) ? first.getLiteralValue() : null;
2906
+ return first && Node9.isStringLiteral(first) ? first.getLiteralValue() : null;
2984
2907
  }
2985
2908
  function extractBodyType(method, sourceFile, project) {
2986
2909
  for (const param of method.getParameters()) {
@@ -3030,8 +2953,8 @@ function extractQueryType(method, sourceFile, project) {
3030
2953
  function isParamOptional(param) {
3031
2954
  if (param.hasQuestionToken() || param.hasInitializer()) return true;
3032
2955
  const typeNode = param.getTypeNode();
3033
- if (typeNode && Node10.isUnionTypeNode(typeNode)) {
3034
- return typeNode.getTypeNodes().some((t) => t.getKind() === SyntaxKind4.UndefinedKeyword);
2956
+ if (typeNode && Node9.isUnionTypeNode(typeNode)) {
2957
+ return typeNode.getTypeNodes().some((t) => t.getKind() === SyntaxKind3.UndefinedKeyword);
3035
2958
  }
3036
2959
  return false;
3037
2960
  }
@@ -3043,7 +2966,7 @@ function extractParamsType(method, sourceFile, project) {
3043
2966
  const paramArgs = paramDecorator.getArguments();
3044
2967
  if (paramArgs.length === 0) continue;
3045
2968
  const nameArg = paramArgs[0];
3046
- if (!Node10.isStringLiteral(nameArg)) continue;
2969
+ if (!Node9.isStringLiteral(nameArg)) continue;
3047
2970
  const paramName = nameArg.getLiteralValue();
3048
2971
  const typeNode = param.getTypeNode();
3049
2972
  const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
@@ -3080,28 +3003,28 @@ function extractUploadedFiles(method) {
3080
3003
  for (const decorator of method.getDecorators()) {
3081
3004
  if (decorator.getName() !== "UseInterceptors") continue;
3082
3005
  for (const arg of decorator.getArguments()) {
3083
- if (!Node10.isCallExpression(arg)) continue;
3006
+ if (!Node9.isCallExpression(arg)) continue;
3084
3007
  const interceptor = arg.getExpression().getText();
3085
3008
  const callArgs = arg.getArguments();
3086
3009
  const firstArg2 = callArgs[0];
3087
3010
  if (interceptor === "FileInterceptor") {
3088
- if (firstArg2 && Node10.isStringLiteral(firstArg2)) {
3011
+ if (firstArg2 && Node9.isStringLiteral(firstArg2)) {
3089
3012
  entries.push(`${firstArg2.getLiteralValue()}: ${FILE}`);
3090
3013
  multipart = true;
3091
3014
  }
3092
3015
  } else if (interceptor === "FilesInterceptor") {
3093
- if (firstArg2 && Node10.isStringLiteral(firstArg2)) {
3016
+ if (firstArg2 && Node9.isStringLiteral(firstArg2)) {
3094
3017
  entries.push(`${firstArg2.getLiteralValue()}: Array<${FILE}>`);
3095
3018
  multipart = true;
3096
3019
  }
3097
3020
  } else if (interceptor === "FileFieldsInterceptor") {
3098
- if (firstArg2 && Node10.isArrayLiteralExpression(firstArg2)) {
3021
+ if (firstArg2 && Node9.isArrayLiteralExpression(firstArg2)) {
3099
3022
  for (const el of firstArg2.getElements()) {
3100
- if (!Node10.isObjectLiteralExpression(el)) continue;
3023
+ if (!Node9.isObjectLiteralExpression(el)) continue;
3101
3024
  const nameProp = el.getProperty("name");
3102
- if (nameProp && Node10.isPropertyAssignment(nameProp)) {
3025
+ if (nameProp && Node9.isPropertyAssignment(nameProp)) {
3103
3026
  const init = nameProp.getInitializer();
3104
- if (init && Node10.isStringLiteral(init)) {
3027
+ if (init && Node9.isStringLiteral(init)) {
3105
3028
  entries.push(`${init.getLiteralValue()}: Array<${FILE}>`);
3106
3029
  }
3107
3030
  }
@@ -3121,13 +3044,13 @@ function extractResponseType(method, sourceFile, project) {
3121
3044
  if (apiResponseDecorator) {
3122
3045
  const args = apiResponseDecorator.getArguments();
3123
3046
  const optsArg = args[0];
3124
- if (optsArg && Node10.isObjectLiteralExpression(optsArg)) {
3047
+ if (optsArg && Node9.isObjectLiteralExpression(optsArg)) {
3125
3048
  for (const prop of optsArg.getProperties()) {
3126
- if (!Node10.isPropertyAssignment(prop)) continue;
3049
+ if (!Node9.isPropertyAssignment(prop)) continue;
3127
3050
  if (prop.getName() !== "type") continue;
3128
3051
  const val = prop.getInitializer();
3129
3052
  if (!val) continue;
3130
- if (Node10.isArrayLiteralExpression(val)) {
3053
+ if (Node9.isArrayLiteralExpression(val)) {
3131
3054
  const elements = val.getElements();
3132
3055
  const firstEl = elements[0];
3133
3056
  if (elements.length > 0 && firstEl !== void 0) {
@@ -3148,24 +3071,24 @@ function extractResponseType(method, sourceFile, project) {
3148
3071
  }
3149
3072
  function apiResponseStatus(decorator) {
3150
3073
  const optsArg = decorator.getArguments()[0];
3151
- if (!optsArg || !Node10.isObjectLiteralExpression(optsArg)) return null;
3074
+ if (!optsArg || !Node9.isObjectLiteralExpression(optsArg)) return null;
3152
3075
  for (const prop of optsArg.getProperties()) {
3153
- if (!Node10.isPropertyAssignment(prop)) continue;
3076
+ if (!Node9.isPropertyAssignment(prop)) continue;
3154
3077
  if (prop.getName() !== "status") continue;
3155
3078
  const val = prop.getInitializer();
3156
- if (val && Node10.isNumericLiteral(val)) return Number(val.getLiteralValue());
3079
+ if (val && Node9.isNumericLiteral(val)) return Number(val.getLiteralValue());
3157
3080
  }
3158
3081
  return null;
3159
3082
  }
3160
3083
  function apiResponseTypeNode(decorator) {
3161
3084
  const optsArg = decorator.getArguments()[0];
3162
- if (!optsArg || !Node10.isObjectLiteralExpression(optsArg)) return null;
3085
+ if (!optsArg || !Node9.isObjectLiteralExpression(optsArg)) return null;
3163
3086
  for (const prop of optsArg.getProperties()) {
3164
- if (!Node10.isPropertyAssignment(prop)) continue;
3087
+ if (!Node9.isPropertyAssignment(prop)) continue;
3165
3088
  if (prop.getName() !== "type") continue;
3166
3089
  const val = prop.getInitializer();
3167
3090
  if (!val) return null;
3168
- if (Node10.isArrayLiteralExpression(val)) {
3091
+ if (Node9.isArrayLiteralExpression(val)) {
3169
3092
  const first = val.getElements()[0];
3170
3093
  return first ? { node: first, isArray: true } : null;
3171
3094
  }
@@ -3183,7 +3106,7 @@ function extractErrorType(method, sourceFile, project) {
3183
3106
  const inner = resolveIdentifierToClassType(typeInfo.node, sourceFile, project, 3);
3184
3107
  const type = typeInfo.isArray ? `Array<${inner}>` : inner;
3185
3108
  let ref = null;
3186
- if (Node10.isIdentifier(typeInfo.node)) {
3109
+ if (Node9.isIdentifier(typeInfo.node)) {
3187
3110
  const name = typeInfo.node.getText();
3188
3111
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
3189
3112
  if (localDecl?.isExported()) {
@@ -3200,7 +3123,7 @@ function extractErrorType(method, sourceFile, project) {
3200
3123
  return null;
3201
3124
  }
3202
3125
  function resolveIdentifierToClassType(node, sourceFile, project, depth) {
3203
- if (!Node10.isIdentifier(node)) return "unknown";
3126
+ if (!Node9.isIdentifier(node)) return "unknown";
3204
3127
  const name = node.getText();
3205
3128
  const resolved = findType(name, sourceFile, project);
3206
3129
  if (resolved) {
@@ -3220,9 +3143,9 @@ var STREAM_ENVELOPES = /* @__PURE__ */ new Set(["MessageEvent", "MessageEventLik
3220
3143
  var BINARY_RESPONSE_TYPES = /* @__PURE__ */ new Set(["StreamableFile", "Buffer"]);
3221
3144
  function detectBinaryResponse(method) {
3222
3145
  const node = unwrapNamedContainer(method.getReturnTypeNode(), /* @__PURE__ */ new Set(["Promise"]));
3223
- if (!node || !Node10.isTypeReference(node)) return false;
3146
+ if (!node || !Node9.isTypeReference(node)) return false;
3224
3147
  const typeName = node.getTypeName();
3225
- const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
3148
+ const name = Node9.isIdentifier(typeName) ? typeName.getText() : "";
3226
3149
  return BINARY_RESPONSE_TYPES.has(name);
3227
3150
  }
3228
3151
  function detectStreamElement(method) {
@@ -3237,9 +3160,9 @@ function detectStreamElement(method) {
3237
3160
  return null;
3238
3161
  }
3239
3162
  function streamContainerElement(node) {
3240
- if (!node || !Node10.isTypeReference(node)) return null;
3163
+ if (!node || !Node9.isTypeReference(node)) return null;
3241
3164
  const typeName = node.getTypeName();
3242
- const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
3165
+ const name = Node9.isIdentifier(typeName) ? typeName.getText() : "";
3243
3166
  if (STREAM_CONTAINERS.has(name) || STREAM_CONTAINERS_GENERATOR.has(name)) {
3244
3167
  return node.getTypeArguments()[0] ?? null;
3245
3168
  }
@@ -3249,9 +3172,9 @@ function hasAsQueryDecorator(method) {
3249
3172
  return method.getDecorators().some((d) => d.getName() === "AsQuery");
3250
3173
  }
3251
3174
  function unwrapNamedContainer(node, names) {
3252
- if (!node || !Node10.isTypeReference(node)) return node;
3175
+ if (!node || !Node9.isTypeReference(node)) return node;
3253
3176
  const typeName = node.getTypeName();
3254
- const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
3177
+ const name = Node9.isIdentifier(typeName) ? typeName.getText() : "";
3255
3178
  if (names.has(name)) {
3256
3179
  return node.getTypeArguments()[0] ?? node;
3257
3180
  }
@@ -3298,11 +3221,11 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
3298
3221
  if (apiResp) {
3299
3222
  const args = apiResp.getArguments();
3300
3223
  const optsArg = args[0];
3301
- if (optsArg && Node10.isObjectLiteralExpression(optsArg)) {
3224
+ if (optsArg && Node9.isObjectLiteralExpression(optsArg)) {
3302
3225
  for (const prop of optsArg.getProperties()) {
3303
- if (Node10.isPropertyAssignment(prop) && prop.getName() === "type") {
3226
+ if (Node9.isPropertyAssignment(prop) && prop.getName() === "type") {
3304
3227
  const val = prop.getInitializer();
3305
- if (val && Node10.isIdentifier(val)) {
3228
+ if (val && Node9.isIdentifier(val)) {
3306
3229
  const name = val.getText();
3307
3230
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
3308
3231
  if (localDecl?.isExported()) {
@@ -3389,7 +3312,7 @@ async function discoverContractsFast(opts) {
3389
3312
  return routes;
3390
3313
  }
3391
3314
  function resolveTsconfigPath(cwd, tsconfig) {
3392
- return tsconfig ? resolve3(tsconfig) : join4(cwd, "tsconfig.json");
3315
+ return tsconfig ? resolve3(tsconfig) : join3(cwd, "tsconfig.json");
3393
3316
  }
3394
3317
  var NO_TSCONFIG_OPTIONS = {
3395
3318
  allowJs: true,
@@ -3398,25 +3321,25 @@ var NO_TSCONFIG_OPTIONS = {
3398
3321
  };
3399
3322
  function loadDiscoveryTsconfig(tsconfigPath) {
3400
3323
  if (!existsSync(tsconfigPath)) return { options: null, files: [] };
3401
- const read = ts3.readConfigFile(tsconfigPath, (path) => ts3.sys.readFile(path));
3324
+ const read = ts4.readConfigFile(tsconfigPath, (path) => ts4.sys.readFile(path));
3402
3325
  if (read.error) {
3403
3326
  return {
3404
3327
  options: null,
3405
3328
  files: [tsconfigPath],
3406
- error: ts3.flattenDiagnosticMessageText(read.error.messageText, " ")
3329
+ error: ts4.flattenDiagnosticMessageText(read.error.messageText, " ")
3407
3330
  };
3408
3331
  }
3409
3332
  const host = {
3410
- useCaseSensitiveFileNames: ts3.sys.useCaseSensitiveFileNames,
3333
+ useCaseSensitiveFileNames: ts4.sys.useCaseSensitiveFileNames,
3411
3334
  // The one line that keeps a tsconfig from being a filesystem walk.
3412
3335
  readDirectory: () => [],
3413
- fileExists: (path) => ts3.sys.fileExists(path),
3336
+ fileExists: (path) => ts4.sys.fileExists(path),
3414
3337
  // `extends` is resolved through the host, so a consumer whose `paths` live
3415
3338
  // in a base tsconfig keeps working.
3416
- readFile: (path) => ts3.sys.readFile(path)
3339
+ readFile: (path) => ts4.sys.readFile(path)
3417
3340
  };
3418
- const sourceFile = ts3.readJsonConfigFile(tsconfigPath, (path) => ts3.sys.readFile(path));
3419
- const parsed = ts3.parseJsonSourceFileConfigFileContent(
3341
+ const sourceFile = ts4.readJsonConfigFile(tsconfigPath, (path) => ts4.sys.readFile(path));
3342
+ const parsed = ts4.parseJsonSourceFileConfigFileContent(
3420
3343
  sourceFile,
3421
3344
  host,
3422
3345
  dirname2(tsconfigPath),
@@ -3439,7 +3362,7 @@ function createDiscoveryProject(tsconfigPath, tsconfig = loadDiscoveryTsconfig(t
3439
3362
  `[nestjs-codegen/fast] Could not load ${tsconfigPath}: ${tsconfig.error} \u2014 continuing WITHOUT its compiler options, so path alias imports (e.g. '@/...') will not resolve and controllers extending an alias-imported factory will contribute NO routes to the generated client.`
3440
3363
  );
3441
3364
  }
3442
- return new Project4({
3365
+ return new Project3({
3443
3366
  compilerOptions: tsconfig.options ?? { ...NO_TSCONFIG_OPTIONS },
3444
3367
  skipAddingFilesFromTsConfig: true,
3445
3368
  skipLoadingLibFiles: true,
@@ -3481,6 +3404,9 @@ var PersistentDiscovery = class _PersistentDiscovery {
3481
3404
  glob;
3482
3405
  /** Absolute paths of the controllers currently loaded as extraction roots. */
3483
3406
  controllerPaths = /* @__PURE__ */ new Set();
3407
+ /** Every file in {@link project}, as last read from disk. */
3408
+ files = new DiskFingerprints();
3409
+ unresolved = new UnresolvedImports();
3484
3410
  constructor(project, cwd, glob) {
3485
3411
  this.project = project;
3486
3412
  this.cwd = cwd;
@@ -3506,6 +3432,8 @@ var PersistentDiscovery = class _PersistentDiscovery {
3506
3432
  }
3507
3433
  /** Run the initial extraction (equivalent to a first `discoverContractsFast`). */
3508
3434
  discover() {
3435
+ clearMixinTypeProject();
3436
+ clearSchemaTypeProject(this.project);
3509
3437
  return this.runExtraction();
3510
3438
  }
3511
3439
  /**
@@ -3513,26 +3441,39 @@ var PersistentDiscovery = class _PersistentDiscovery {
3513
3441
  * from disk (controllers AND any lazily-loaded DTO/imported files), re-globs
3514
3442
  * to pick up added/removed controllers, clears the per-Project caches, then
3515
3443
  * re-extracts. `changedPaths` is a hint; correctness does not depend on it
3516
- * being exhaustive because re-globbing + refresh-on-presence covers the set.
3444
+ * being exhaustive because re-globbing + comparing every loaded file with the
3445
+ * disk covers the set.
3517
3446
  */
3518
- async rediscover(changedPaths) {
3519
- if (changedPaths) {
3520
- for (const p of changedPaths) {
3521
- const abs = resolve3(p);
3522
- const sf = this.project.getSourceFile(abs);
3523
- if (sf) {
3524
- await sf.refreshFromFileSystem();
3525
- }
3447
+ async rediscover(changedPaths, options = {}) {
3448
+ const hinted = new Set([...changedPaths ?? []].map((p) => resolve3(p)));
3449
+ const program = this.project.getProgram().compilerObject;
3450
+ const sources = program.getSourceFiles().filter(
3451
+ (file) => !program.isSourceFileDefaultLibrary(file) && !program.isSourceFileFromExternalLibrary(file)
3452
+ ).map((file) => ({ path: file.fileName, text: file.text }));
3453
+ for (const path of /* @__PURE__ */ new Set([...hinted, ...this.files.changed(sources)])) {
3454
+ const sf = this.project.getSourceFile(path);
3455
+ if (sf && await sf.refreshFromFileSystem() === FileSystemRefreshResult3.Deleted) {
3456
+ this.files.forget(path);
3526
3457
  }
3527
3458
  }
3459
+ const inProject = new Set(this.project.getSourceFiles().map((sf) => sf.getFilePath()));
3460
+ const definitions = this.project.getLanguageService().compilerObject.getProgram();
3461
+ for (const path of definitions ? this.unresolved.nowResolving(definitions) : []) {
3462
+ const sf = this.project.getSourceFile(path);
3463
+ if (!sf) continue;
3464
+ this.project.removeSourceFile(sf);
3465
+ if (inProject.has(path)) this.project.addSourceFileAtPathIfExists(path);
3466
+ }
3528
3467
  const globbed = new Set(
3529
3468
  await fg2(this.glob, { cwd: this.cwd, absolute: true, onlyFiles: true })
3530
3469
  );
3470
+ let controllersChanged = false;
3531
3471
  for (const f of globbed) {
3532
3472
  if (!this.controllerPaths.has(f)) {
3533
3473
  try {
3534
3474
  this.project.addSourceFileAtPath(f);
3535
3475
  this.controllerPaths.add(f);
3476
+ controllersChanged = true;
3536
3477
  } catch {
3537
3478
  }
3538
3479
  }
@@ -3542,25 +3483,31 @@ var PersistentDiscovery = class _PersistentDiscovery {
3542
3483
  const sf = this.project.getSourceFile(f);
3543
3484
  if (sf) this.project.removeSourceFile(sf);
3544
3485
  this.controllerPaths.delete(f);
3486
+ controllersChanged = true;
3545
3487
  }
3546
3488
  }
3489
+ if (controllersChanged || options.filesAddedOrRemoved) {
3490
+ clearMixinTypeProject();
3491
+ clearSchemaTypeProject(this.project);
3492
+ } else {
3493
+ invalidateMixinTypeProject();
3494
+ invalidateSchemaTypeProject(this.project);
3495
+ }
3547
3496
  return this.runExtraction();
3548
3497
  }
3549
3498
  /** Clear stale per-Project caches, then extract over the controller set. */
3550
3499
  runExtraction() {
3551
3500
  clearTypeResolutionCaches(this.project);
3552
3501
  clearEnumCache(this.project);
3553
- clearMixinTypeProject();
3554
- clearSchemaTypeProject(this.project);
3555
3502
  return extractRoutesFrom(this.project, this.controllerPaths);
3556
3503
  }
3557
3504
  };
3558
3505
  function decoratorStringArg(decoratorExpr) {
3559
3506
  if (!decoratorExpr) return void 0;
3560
- if (Node11.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
3561
- if (Node11.isArrayLiteralExpression(decoratorExpr)) {
3507
+ if (Node10.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
3508
+ if (Node10.isArrayLiteralExpression(decoratorExpr)) {
3562
3509
  const first = decoratorExpr.getElements()[0];
3563
- if (first && Node11.isStringLiteral(first)) return first.getLiteralValue();
3510
+ if (first && Node10.isStringLiteral(first)) return first.getLiteralValue();
3564
3511
  }
3565
3512
  return void 0;
3566
3513
  }
@@ -3681,9 +3628,9 @@ function extractContractRoute(args) {
3681
3628
  let contractDef = null;
3682
3629
  let bodyZodRef = null;
3683
3630
  let queryZodRef = null;
3684
- if (Node11.isCallExpression(firstDecoratorArg)) {
3631
+ if (Node10.isCallExpression(firstDecoratorArg)) {
3685
3632
  contractDef = parseDefineContractCall(firstDecoratorArg, project);
3686
- } else if (Node11.isIdentifier(firstDecoratorArg)) {
3633
+ } else if (Node10.isIdentifier(firstDecoratorArg)) {
3687
3634
  const identName = firstDecoratorArg.getText();
3688
3635
  const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
3689
3636
  if (!resolvedVar) {
@@ -3838,7 +3785,300 @@ function extractFromSourceFile(sourceFile, project) {
3838
3785
  return routes;
3839
3786
  }
3840
3787
 
3788
+ // src/discovery/shared-props.ts
3789
+ function discoverSharedProps(project, moduleEntry) {
3790
+ try {
3791
+ let sourceFile = project.getSourceFile(moduleEntry);
3792
+ if (!sourceFile) {
3793
+ try {
3794
+ sourceFile = project.addSourceFileAtPath(moduleEntry);
3795
+ } catch {
3796
+ return null;
3797
+ }
3798
+ }
3799
+ const forRootCall = findForRootCall(sourceFile);
3800
+ if (!forRootCall) return null;
3801
+ const initializer = findShareInitializer(forRootCall);
3802
+ if (!initializer) return null;
3803
+ return extractShareType(initializer, sourceFile, project);
3804
+ } catch {
3805
+ return null;
3806
+ }
3807
+ }
3808
+ function discoverSharedPropsFromConfig(config) {
3809
+ if (!config.app?.moduleEntry) return null;
3810
+ try {
3811
+ const tsconfigPath = config.app.tsconfig ?? join4(config.codegen.cwd, "tsconfig.json");
3812
+ let project;
3813
+ try {
3814
+ project = sharedPropsProject(tsconfigPath);
3815
+ } catch {
3816
+ project = new Project4({
3817
+ skipAddingFilesFromTsConfig: true,
3818
+ skipLoadingLibFiles: true,
3819
+ skipFileDependencyResolution: true,
3820
+ compilerOptions: { allowJs: true, strict: false }
3821
+ });
3822
+ }
3823
+ return discoverSharedProps(project, config.app.moduleEntry);
3824
+ } catch {
3825
+ return null;
3826
+ }
3827
+ }
3828
+ var parsedTsconfigs = /* @__PURE__ */ new Map();
3829
+ function readText(file) {
3830
+ try {
3831
+ return readFileSync2(file, "utf8");
3832
+ } catch {
3833
+ return "\0missing";
3834
+ }
3835
+ }
3836
+ function sharedPropsProject(tsconfigPath) {
3837
+ const text = loadDiscoveryTsconfig(tsconfigPath).files.map((file) => `${file}\0${readText(file)}`).join("\0");
3838
+ const flags = {
3839
+ skipAddingFilesFromTsConfig: true,
3840
+ skipLoadingLibFiles: true,
3841
+ skipFileDependencyResolution: true
3842
+ };
3843
+ const parsed = parsedTsconfigs.get(tsconfigPath);
3844
+ if (parsed?.text === text) return new Project4({ ...flags, compilerOptions: parsed.options });
3845
+ const project = new Project4({ ...flags, tsConfigFilePath: tsconfigPath });
3846
+ parsedTsconfigs.set(tsconfigPath, { text, options: project.getCompilerOptions() });
3847
+ return project;
3848
+ }
3849
+ function findForRootCall(sourceFile) {
3850
+ const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind4.CallExpression);
3851
+ for (const call of callExpressions) {
3852
+ const expr = call.getExpression();
3853
+ if (!Node11.isPropertyAccessExpression(expr)) continue;
3854
+ const methodName = expr.getName();
3855
+ const objectExpr = expr.getExpression();
3856
+ if (methodName === "forRoot" && Node11.isIdentifier(objectExpr)) {
3857
+ const name = objectExpr.getText();
3858
+ if (name === "InertiaModule") {
3859
+ return call;
3860
+ }
3861
+ }
3862
+ }
3863
+ return null;
3864
+ }
3865
+ function findShareInitializer(forRootCall) {
3866
+ if (!Node11.isCallExpression(forRootCall)) return null;
3867
+ const args = forRootCall.getArguments();
3868
+ const firstArg2 = args[0];
3869
+ if (!firstArg2 || !Node11.isObjectLiteralExpression(firstArg2)) return null;
3870
+ for (const prop of firstArg2.getProperties()) {
3871
+ if (Node11.isPropertyAssignment(prop) && prop.getName() === "share") {
3872
+ return prop.getInitializer() ?? null;
3873
+ }
3874
+ }
3875
+ return null;
3876
+ }
3877
+ function extractShareType(node, sourceFile, project) {
3878
+ if (Node11.isIdentifier(node)) {
3879
+ const ref = resolveIdentifierToImportRef(node, sourceFile, project);
3880
+ if (ref) {
3881
+ return {
3882
+ typeString: `Awaited<ReturnType<typeof import('${ref.importPath}').${ref.exportName}>>`,
3883
+ properties: null,
3884
+ isImportRef: true
3885
+ };
3886
+ }
3887
+ }
3888
+ if (Node11.isArrowFunction(node)) {
3889
+ const result = extractFromFunctionLike(node, sourceFile);
3890
+ return result ? { ...result, isImportRef: false } : null;
3891
+ }
3892
+ if (Node11.isFunctionExpression(node)) {
3893
+ const result = extractFromFunctionLike(node, sourceFile);
3894
+ return result ? { ...result, isImportRef: false } : null;
3895
+ }
3896
+ if (Node11.isObjectLiteralExpression(node)) {
3897
+ const result = extractFromObjectLiteral(node);
3898
+ return result ? { ...result, isImportRef: false } : null;
3899
+ }
3900
+ return null;
3901
+ }
3902
+ function resolveIdentifierToImportRef(id, sourceFile, project) {
3903
+ if (!Node11.isIdentifier(id)) return null;
3904
+ const name = id.getText();
3905
+ const localFunc = sourceFile.getFunction(name);
3906
+ if (localFunc?.isExported()) {
3907
+ const filePath = sourceFile.getFilePath().replace(/\.ts$/, "");
3908
+ return { importPath: filePath, exportName: name };
3909
+ }
3910
+ const localVar = sourceFile.getVariableDeclaration(name);
3911
+ if (localVar?.isExported()) {
3912
+ const filePath = sourceFile.getFilePath().replace(/\.ts$/, "");
3913
+ return { importPath: filePath, exportName: name };
3914
+ }
3915
+ for (const imp of sourceFile.getImportDeclarations()) {
3916
+ for (const named of imp.getNamedImports()) {
3917
+ const importedName = named.getAliasNode()?.getText() ?? named.getName();
3918
+ if (importedName !== name) continue;
3919
+ const resolvedSource = imp.getModuleSpecifierSourceFile();
3920
+ if (!resolvedSource) continue;
3921
+ const originalName = named.getName();
3922
+ const fn = resolvedSource.getFunction(originalName);
3923
+ if (fn?.isExported()) {
3924
+ const filePath = resolvedSource.getFilePath().replace(/\.ts$/, "");
3925
+ return { importPath: filePath, exportName: originalName };
3926
+ }
3927
+ const v = resolvedSource.getVariableDeclaration(originalName);
3928
+ if (v?.isExported()) {
3929
+ const filePath = resolvedSource.getFilePath().replace(/\.ts$/, "");
3930
+ return { importPath: filePath, exportName: originalName };
3931
+ }
3932
+ }
3933
+ }
3934
+ return null;
3935
+ }
3936
+ function extractFromFunctionLike(node, _sourceFile) {
3937
+ const returnTypeNode = Node11.isArrowFunction(node) || Node11.isFunctionExpression(node) ? node.getReturnTypeNode() : null;
3938
+ if (returnTypeNode) {
3939
+ return extractFromReturnTypeAnnotation(returnTypeNode);
3940
+ }
3941
+ if (Node11.isArrowFunction(node)) {
3942
+ const body = node.getBody();
3943
+ if (Node11.isParenthesizedExpression(body)) {
3944
+ const inner = body.getExpression();
3945
+ if (Node11.isObjectLiteralExpression(inner)) {
3946
+ return extractFromObjectLiteral(inner);
3947
+ }
3948
+ }
3949
+ if (Node11.isObjectLiteralExpression(body)) {
3950
+ return extractFromObjectLiteral(body);
3951
+ }
3952
+ if (Node11.isBlock(body)) {
3953
+ return extractFromBlockReturn(body);
3954
+ }
3955
+ }
3956
+ if (Node11.isFunctionExpression(node)) {
3957
+ const body = node.getBody();
3958
+ if (Node11.isBlock(body)) {
3959
+ return extractFromBlockReturn(body);
3960
+ }
3961
+ }
3962
+ return null;
3963
+ }
3964
+ function extractFromReturnTypeAnnotation(typeNode) {
3965
+ if (Node11.isTypeReference(typeNode)) {
3966
+ const typeName = typeNode.getTypeName();
3967
+ if (Node11.isIdentifier(typeName) && typeName.getText() === "Promise") {
3968
+ const typeArgs = typeNode.getTypeArguments();
3969
+ const firstArg2 = typeArgs[0];
3970
+ if (firstArg2) {
3971
+ return extractFromReturnTypeAnnotation(firstArg2);
3972
+ }
3973
+ return null;
3974
+ }
3975
+ }
3976
+ if (Node11.isTypeLiteral(typeNode)) {
3977
+ const properties = [];
3978
+ for (const member of typeNode.getMembers()) {
3979
+ if (Node11.isPropertySignature(member)) {
3980
+ const name = member.getName();
3981
+ const memberTypeNode = member.getTypeNode();
3982
+ const type = memberTypeNode ? memberTypeNode.getText() : "unknown";
3983
+ properties.push({ name, type });
3984
+ }
3985
+ }
3986
+ if (properties.length === 0) return null;
3987
+ const typeString = `{ ${properties.map((p) => `${p.name}: ${p.type}`).join("; ")} }`;
3988
+ return { typeString, properties, isImportRef: false };
3989
+ }
3990
+ return null;
3991
+ }
3992
+ function extractFromBlockReturn(block) {
3993
+ if (!Node11.isBlock(block)) return null;
3994
+ const statements = block.getStatements();
3995
+ for (let i = statements.length - 1; i >= 0; i--) {
3996
+ const stmt = statements[i];
3997
+ if (!Node11.isReturnStatement(stmt)) continue;
3998
+ const expr = stmt.getExpression();
3999
+ if (!expr) continue;
4000
+ if (Node11.isObjectLiteralExpression(expr)) {
4001
+ return extractFromObjectLiteral(expr);
4002
+ }
4003
+ if (Node11.isParenthesizedExpression(expr)) {
4004
+ const inner = expr.getExpression();
4005
+ if (Node11.isObjectLiteralExpression(inner)) {
4006
+ return extractFromObjectLiteral(inner);
4007
+ }
4008
+ }
4009
+ break;
4010
+ }
4011
+ return null;
4012
+ }
4013
+ function extractFromObjectLiteral(objLiteral) {
4014
+ if (!Node11.isObjectLiteralExpression(objLiteral)) return null;
4015
+ const properties = [];
4016
+ for (const prop of objLiteral.getProperties()) {
4017
+ if (!Node11.isPropertyAssignment(prop)) continue;
4018
+ const name = prop.getName();
4019
+ const initializer = prop.getInitializer();
4020
+ if (!initializer) continue;
4021
+ const type = inferExpressionType(initializer);
4022
+ properties.push({ name, type });
4023
+ }
4024
+ if (properties.length === 0) return null;
4025
+ const typeString = `{ ${properties.map((p) => `${p.name}: ${p.type}`).join("; ")} }`;
4026
+ return { typeString, properties, isImportRef: false };
4027
+ }
4028
+ function inferExpressionType(node) {
4029
+ if (Node11.isStringLiteral(node)) return "string";
4030
+ if (Node11.isTemplateExpression(node) || Node11.isNoSubstitutionTemplateLiteral(node))
4031
+ return "string";
4032
+ if (Node11.isNumericLiteral(node)) return "number";
4033
+ if (node.getKind() === SyntaxKind4.TrueKeyword || node.getKind() === SyntaxKind4.FalseKeyword) {
4034
+ return "boolean";
4035
+ }
4036
+ if (node.getKind() === SyntaxKind4.NullKeyword) return "null";
4037
+ if (Node11.isIdentifier(node) && node.getText() === "undefined") return "undefined";
4038
+ if (Node11.isObjectLiteralExpression(node)) {
4039
+ const props = node.getProperties();
4040
+ if (props.length === 0) return "Record<string, unknown>";
4041
+ const entries = [];
4042
+ for (const prop of props) {
4043
+ if (!Node11.isPropertyAssignment(prop)) continue;
4044
+ const key = prop.getName();
4045
+ const init = prop.getInitializer();
4046
+ if (!init) continue;
4047
+ entries.push(`${key}: ${inferExpressionType(init)}`);
4048
+ }
4049
+ if (entries.length === 0) return "Record<string, unknown>";
4050
+ return `{ ${entries.join("; ")} }`;
4051
+ }
4052
+ if (Node11.isArrayLiteralExpression(node)) {
4053
+ const elements = node.getElements();
4054
+ if (elements.length === 0) return "Array<unknown>";
4055
+ const first = elements[0];
4056
+ if (first) return `Array<${inferExpressionType(first)}>`;
4057
+ return "Array<unknown>";
4058
+ }
4059
+ if (Node11.isConditionalExpression(node)) {
4060
+ const whenTrue = inferExpressionType(node.getWhenTrue());
4061
+ const whenFalse = inferExpressionType(node.getWhenFalse());
4062
+ if (whenTrue === whenFalse) return whenTrue;
4063
+ return `${whenTrue} | ${whenFalse}`;
4064
+ }
4065
+ if (Node11.isParenthesizedExpression(node)) {
4066
+ return inferExpressionType(node.getExpression());
4067
+ }
4068
+ if (Node11.isAsExpression(node)) {
4069
+ const typeNode = node.getTypeNode();
4070
+ if (typeNode) return typeNode.getText();
4071
+ }
4072
+ return "unknown";
4073
+ }
4074
+
4075
+ // src/emit/emit-api.ts
4076
+ import { mkdir, writeFile } from "fs/promises";
4077
+ import { isAbsolute as isAbsolute2, join as join5, relative as relative4 } from "path";
4078
+
3841
4079
  // src/extension/registry.ts
4080
+ import { relative as relative3, resolve as resolve4 } from "path";
4081
+ import { Project as Project5 } from "ts-morph";
3842
4082
  function resolveApiSlots(extensions) {
3843
4083
  let layer;
3844
4084
  let layerOwner;
@@ -5620,36 +5860,64 @@ async function discoverInputFiles(config) {
5620
5860
  const matched = await fg3(globs, { cwd, absolute: true, onlyFiles: true });
5621
5861
  return [...new Set(matched)].sort();
5622
5862
  }
5623
- async function computeInputsHash(config, extraInputs = []) {
5863
+ var READ_CONCURRENCY = 64;
5864
+ async function readAll(paths, read) {
5865
+ const out = /* @__PURE__ */ new Map();
5866
+ for (let i = 0; i < paths.length; i += READ_CONCURRENCY) {
5867
+ const batch = paths.slice(i, i + READ_CONCURRENCY);
5868
+ const texts = await Promise.all(batch.map(read));
5869
+ batch.forEach((path, j) => out.set(path, texts[j]));
5870
+ }
5871
+ return out;
5872
+ }
5873
+ async function readInputs(config) {
5874
+ const cwd = config.codegen.cwd;
5875
+ const tsconfigPath = resolveTsconfigPath(cwd, config.app?.tsconfig ?? void 0);
5876
+ const extended = loadDiscoveryTsconfig(tsconfigPath).files;
5877
+ const tsconfigFiles = extended.length > 0 ? extended : [tsconfigPath];
5878
+ const files = await discoverInputFiles(config);
5879
+ const contents = /* @__PURE__ */ new Map();
5880
+ const tsconfigs = await readAll(
5881
+ tsconfigFiles,
5882
+ (file) => readFile2(file, "utf8").catch(() => null)
5883
+ );
5884
+ for (const [file, text] of tsconfigs) contents.set(file, text);
5885
+ for (const [file, text] of await readAll(files, (file2) => readFile2(file2, "utf8"))) {
5886
+ contents.set(file, text);
5887
+ }
5888
+ return { tsconfigFiles, files, contents, tracked: /* @__PURE__ */ new Map() };
5889
+ }
5890
+ async function readInput(snapshot, path) {
5891
+ return snapshot.contents.get(path) ?? readFile2(path, "utf8");
5892
+ }
5893
+ async function computeInputsHash(config, extraInputs = [], snapshot) {
5894
+ const inputs = snapshot ?? await readInputs(config);
5624
5895
  const hash = createHash("sha256");
5625
5896
  hash.update(`version:${VERSION}
5626
5897
  `);
5627
5898
  hash.update(`config:${serializeConfig(config)}
5628
5899
  `);
5629
5900
  const cwd = config.codegen.cwd;
5630
- const tsconfigPath = resolveTsconfigPath(cwd, config.app?.tsconfig ?? void 0);
5631
- const tsconfigFiles = loadDiscoveryTsconfig(tsconfigPath).files;
5632
- for (const file of tsconfigFiles.length > 0 ? tsconfigFiles : [tsconfigPath]) {
5633
- const contents = await readFile2(file, "utf8").catch(() => null);
5901
+ for (const file of inputs.tsconfigFiles) {
5634
5902
  hash.update(`tsconfig:${relative7(cwd, file)}
5635
5903
  `);
5636
- hash.update(contents ?? " missing");
5904
+ hash.update(inputs.contents.get(file) ?? " missing");
5637
5905
  hash.update("\n");
5638
5906
  }
5639
- const globbed = await discoverInputFiles(config);
5640
- const globbedRelative = new Set(globbed.map((file) => relative7(cwd, file)));
5907
+ const globbedRelative = new Set(inputs.files.map((file) => relative7(cwd, file)));
5641
5908
  const extra = [...new Set(extraInputs)].filter((file) => !globbedRelative.has(file)).sort();
5642
- for (const file of globbed) {
5643
- const contents = await readFile2(file, "utf8");
5909
+ for (const file of inputs.files) {
5644
5910
  hash.update(`file:${relative7(cwd, file)}
5645
5911
  `);
5646
- hash.update(contents);
5912
+ hash.update(inputs.contents.get(file));
5647
5913
  hash.update("\n");
5648
5914
  }
5915
+ const unread = extra.map((file) => join13(cwd, file)).filter((abs) => !inputs.tracked.has(abs));
5916
+ for (const [abs, text] of await readAll(unread, readTrackedInput)) inputs.tracked.set(abs, text);
5649
5917
  for (const file of extra) {
5650
5918
  hash.update(`extra:${file}
5651
5919
  `);
5652
- hash.update(await readTrackedInput(join13(cwd, file)));
5920
+ hash.update(inputs.tracked.get(join13(cwd, file)));
5653
5921
  hash.update("\n");
5654
5922
  }
5655
5923
  return hash.digest("hex");
@@ -5730,14 +5998,15 @@ function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint, differ
5730
5998
  async function generate(config, inputRoutes = [], entryPoint = "cli") {
5731
5999
  setCodegenDebug(config.debug);
5732
6000
  const manifest = await readManifest(config.codegen.outDir);
5733
- const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? []);
6001
+ const inputs = await readInputs(config);
6002
+ const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? [], inputs);
5734
6003
  if (await isManifestFresh(config.codegen.outDir, manifest, inputsHash)) {
5735
6004
  console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
5736
6005
  return;
5737
6006
  }
5738
6007
  const configHash = computeConfigHash(config);
5739
6008
  const configKeyHashes = computeConfigKeyHashes(config);
5740
- if (config.driftGuard && manifest?.entryPoint && manifest.entryPoint !== entryPoint && manifest.configHash && manifest.configHash !== configHash) {
6009
+ if (config.driftGuard && manifest?.version === VERSION && manifest?.entryPoint && manifest.entryPoint !== entryPoint && manifest.configHash && manifest.configHash !== configHash) {
5741
6010
  throw new DriftGuardError(
5742
6011
  driftGuardMessage(
5743
6012
  config.codegen.outDir,
@@ -5758,7 +6027,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
5758
6027
  }
5759
6028
  if (config.pages) {
5760
6029
  const pagesConfig = config.pages;
5761
- const pages = await discoverPageScopes(config);
6030
+ const pages = await discoverPageScopes(config, (path) => readInput(inputs, path));
5762
6031
  const sharedProps = discoverSharedPropsFromConfig(config);
5763
6032
  await emitPages(pages, config.codegen.outDir, {
5764
6033
  propsExport: pagesConfig.propsExport,
@@ -5808,7 +6077,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
5808
6077
  }
5809
6078
  const outputFiles = await listOutputFiles(config.codegen.outDir);
5810
6079
  const extraInputs = [...trackedInputs].sort();
5811
- const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs) : inputsHash;
6080
+ const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs, inputs) : inputsHash;
5812
6081
  await writeManifest(config.codegen.outDir, {
5813
6082
  version: VERSION,
5814
6083
  hash: recordedHash,
@@ -5991,13 +6260,15 @@ async function watch(config, onChange, options = {}) {
5991
6260
  pagesWatcher.on("unlink", schedulePagesRegenerate);
5992
6261
  let contractsDebounceTimer;
5993
6262
  const pendingChangedPaths = /* @__PURE__ */ new Set();
6263
+ let pendingAddedOrRemoved = false;
5994
6264
  const contractsWatcher = chokidar.watch(join16(config.codegen.cwd, config.contracts.glob), {
5995
6265
  ignoreInitial: true,
5996
6266
  persistent: true,
5997
6267
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
5998
6268
  });
5999
- function scheduleContractsRegenerate(changedPath) {
6269
+ function scheduleContractsRegenerate(changedPath, addedOrRemoved = false) {
6000
6270
  if (typeof changedPath === "string") pendingChangedPaths.add(changedPath);
6271
+ if (addedOrRemoved) pendingAddedOrRemoved = true;
6001
6272
  if (contractsDebounceTimer !== void 0) {
6002
6273
  clearTimeout(contractsDebounceTimer);
6003
6274
  }
@@ -6005,8 +6276,12 @@ async function watch(config, onChange, options = {}) {
6005
6276
  contractsDebounceTimer = void 0;
6006
6277
  const changed = [...pendingChangedPaths];
6007
6278
  pendingChangedPaths.clear();
6279
+ const filesAddedOrRemoved = pendingAddedOrRemoved;
6280
+ pendingAddedOrRemoved = false;
6008
6281
  try {
6009
- const routes = await (await getDiscovery()).rediscover(changed);
6282
+ const routes = await (await getDiscovery()).rediscover(changed, {
6283
+ filesAddedOrRemoved
6284
+ });
6010
6285
  lastRoutes = routes;
6011
6286
  await generateAndTrack(routes);
6012
6287
  } catch (err) {
@@ -6018,20 +6293,25 @@ async function watch(config, onChange, options = {}) {
6018
6293
  onChange?.();
6019
6294
  }, config.contracts.debounceMs);
6020
6295
  }
6021
- contractsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
6296
+ contractsWatcher.on("add", (p) => scheduleContractsRegenerate(p, true));
6022
6297
  contractsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
6023
- contractsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
6298
+ contractsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p, true));
6024
6299
  const formsWatcher = chokidar.watch(join16(config.codegen.cwd, config.forms.watch), {
6025
6300
  ignoreInitial: true,
6026
6301
  persistent: true,
6027
6302
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
6028
6303
  });
6029
- formsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
6304
+ formsWatcher.on("add", (p) => scheduleContractsRegenerate(p, true));
6030
6305
  formsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
6031
- formsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
6032
- trackedWatcher.on("add", (p) => scheduleContractsRegenerate(p));
6306
+ formsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p, true));
6307
+ trackedWatcher.on("add", (p) => scheduleContractsRegenerate(p, true));
6033
6308
  trackedWatcher.on("change", (p) => scheduleContractsRegenerate(p));
6034
- trackedWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
6309
+ trackedWatcher.on("unlink", (p) => scheduleContractsRegenerate(p, true));
6310
+ await Promise.all(
6311
+ [pagesWatcher, contractsWatcher, formsWatcher].map(
6312
+ (w) => new Promise((resolveReady) => w.once("ready", () => resolveReady()))
6313
+ )
6314
+ );
6035
6315
  return {
6036
6316
  close: async () => {
6037
6317
  if (pagesDebounceTimer !== void 0) {
@@ -6052,7 +6332,7 @@ async function watch(config, onChange, options = {}) {
6052
6332
  }
6053
6333
 
6054
6334
  // src/index.ts
6055
- var VERSION = "0.29.0";
6335
+ var VERSION = "0.30.0";
6056
6336
 
6057
6337
  // src/cli/codegen.ts
6058
6338
  async function runCodegen(opts = {}) {
@@ -6080,22 +6360,22 @@ async function runCodegen(opts = {}) {
6080
6360
 
6081
6361
  // src/cli/doctor.ts
6082
6362
  import { execFileSync as execFileSync2 } from "child_process";
6083
- import { appendFileSync, existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
6363
+ import { appendFileSync, existsSync as existsSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
6084
6364
  import { join as join18 } from "path";
6085
6365
 
6086
6366
  // src/cli/init.ts
6087
6367
  import { execFileSync } from "child_process";
6088
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
6368
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
6089
6369
  import { access as access2, mkdir as mkdir11, readFile as readFile5, writeFile as writeFile11 } from "fs/promises";
6090
6370
  import { join as join17 } from "path";
6091
6371
  import { createInterface } from "readline";
6092
6372
 
6093
6373
  // src/cli/patch-utils.ts
6094
- import { readFileSync, writeFileSync } from "fs";
6374
+ import { readFileSync as readFileSync3, writeFileSync } from "fs";
6095
6375
  function patchJsonFile(filePath, mutator, parse = (raw) => raw) {
6096
6376
  let raw;
6097
6377
  try {
6098
- raw = readFileSync(filePath, "utf8");
6378
+ raw = readFileSync3(filePath, "utf8");
6099
6379
  } catch {
6100
6380
  return "skipped";
6101
6381
  }
@@ -6313,7 +6593,7 @@ async function patchPackageJsonScripts(cwd, scripts) {
6313
6593
  function patchAppModule(filePath, rootView) {
6314
6594
  let content;
6315
6595
  try {
6316
- content = readFileSync2(filePath, "utf8");
6596
+ content = readFileSync4(filePath, "utf8");
6317
6597
  } catch {
6318
6598
  return "skipped";
6319
6599
  }
@@ -6354,7 +6634,7 @@ ${indent}HomeController,${content.slice(bracketPos)}`;
6354
6634
  function patchMainTs(filePath) {
6355
6635
  let content;
6356
6636
  try {
6357
- content = readFileSync2(filePath, "utf8");
6637
+ content = readFileSync4(filePath, "utf8");
6358
6638
  } catch {
6359
6639
  return "skipped";
6360
6640
  }
@@ -6838,14 +7118,14 @@ function checkFileExists(cwd, file) {
6838
7118
  }
6839
7119
  function readJson(path) {
6840
7120
  try {
6841
- const raw = readFileSync3(path, "utf8").replace(/\/\/.*$/gm, "");
7121
+ const raw = readFileSync5(path, "utf8").replace(/\/\/.*$/gm, "");
6842
7122
  return JSON.parse(raw);
6843
7123
  } catch {
6844
7124
  return null;
6845
7125
  }
6846
7126
  }
6847
7127
  function writeJsonField(filePath, dotPath, value) {
6848
- const raw = readFileSync3(filePath, "utf8");
7128
+ const raw = readFileSync5(filePath, "utf8");
6849
7129
  const stripped = raw.replace(/\/\/.*$/gm, "");
6850
7130
  const obj = JSON.parse(stripped);
6851
7131
  let target = obj;
@@ -7054,7 +7334,7 @@ async function runDoctor(opts) {
7054
7334
  }
7055
7335
  });
7056
7336
  if (checkFileExists(cwd, "vite.config.ts")) {
7057
- const viteContent = readFileSync3(join18(cwd, "vite.config.ts"), "utf8");
7337
+ const viteContent = readFileSync5(join18(cwd, "vite.config.ts"), "utf8");
7058
7338
  checks.push({
7059
7339
  name: "vite.config.ts has resolve.alias",
7060
7340
  pass: viteContent.includes("resolve") && viteContent.includes("alias"),
@@ -7120,7 +7400,7 @@ async function runDoctor(opts) {
7120
7400
  }
7121
7401
  if (checkFileExists(cwd, ".gitignore")) {
7122
7402
  const gitignorePath = join18(cwd, ".gitignore");
7123
- const gitignore = readFileSync3(gitignorePath, "utf8");
7403
+ const gitignore = readFileSync5(gitignorePath, "utf8");
7124
7404
  checks.push({
7125
7405
  name: ".gitignore includes .nestjs-inertia/",
7126
7406
  pass: gitignore.includes(".nestjs-inertia"),