@dudousxd/nestjs-codegen 0.28.0 → 0.29.1

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