@dudousxd/nestjs-codegen 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -105,11 +105,6 @@ function validateUserConfig(userConfig) {
105
105
  );
106
106
  }
107
107
  }
108
- if (userConfig.scopes && Object.keys(userConfig.scopes).length > 0) {
109
- console.warn(
110
- "[nestjs-codegen] The top-level `scopes` option has no effect and is deprecated. For several Inertia apps use `pages.scopes` (one entry per InertiaModule.forFeature scope); for several controller sets use one `contracts.glob` (e.g. a brace glob)."
111
- );
112
- }
113
108
  }
114
109
  function isGlobInput(value) {
115
110
  if (typeof value === "string") return true;
@@ -157,7 +152,6 @@ function applyDefaults(userConfig, cwd) {
157
152
  glob: userConfig.contracts?.glob ?? "src/**/*.controller.ts",
158
153
  debounceMs: userConfig.contracts?.debounceMs ?? 500
159
154
  },
160
- scopes: userConfig.scopes ?? {},
161
155
  codegen: {
162
156
  outDir,
163
157
  cwd: resolvedCwd
@@ -252,7 +246,7 @@ async function discoverPages(opts) {
252
246
  const rel = relative2(opts.cwd, file);
253
247
  const nameRel = relative2(pagesBase, file).split(sep2).join("/");
254
248
  const name = `${prefix}${computeName(nameRel, opts.componentNameStrategy)}`;
255
- const source = await readFile(file, "utf8");
249
+ const source = await (opts.read ? opts.read(file) : readFile(file, "utf8"));
256
250
  const propsSource = extractPropsSource(source, opts.propsExport);
257
251
  out.push({ name, absolutePath: file, relativePath: rel, propsSource, scope });
258
252
  }
@@ -260,10 +254,14 @@ async function discoverPages(opts) {
260
254
  assertUniqueNames(out);
261
255
  return out;
262
256
  }
263
- async function discoverPageScopes(config) {
257
+ async function discoverPageScopes(config, read) {
264
258
  const pages = config.pages;
265
259
  if (!pages) return [];
266
- const common = { cwd: config.codegen.cwd, propsExport: pages.propsExport };
260
+ const common = {
261
+ cwd: config.codegen.cwd,
262
+ propsExport: pages.propsExport,
263
+ ...read ? { read } : {}
264
+ };
267
265
  const out = await discoverPages({
268
266
  ...common,
269
267
  glob: pages.glob,
@@ -334,305 +332,69 @@ function extractPropsSource(source, exportName) {
334
332
  }
335
333
 
336
334
  // src/discovery/shared-props.ts
337
- import { join as join3 } from "path";
338
- import { Node, Project, SyntaxKind } from "ts-morph";
339
- function discoverSharedProps(project, moduleEntry) {
340
- try {
341
- let sourceFile = project.getSourceFile(moduleEntry);
342
- if (!sourceFile) {
335
+ import { readFileSync as readFileSync2 } from "fs";
336
+ import { join as join4 } from "path";
337
+ import { Node as Node11, Project as Project4, SyntaxKind as SyntaxKind4 } from "ts-morph";
338
+
339
+ // src/discovery/contracts-fast.ts
340
+ import { existsSync } from "fs";
341
+ import { dirname as dirname2, join as join3, resolve as resolve3 } from "path";
342
+ import fg2 from "fast-glob";
343
+ import {
344
+ FileSystemRefreshResult as FileSystemRefreshResult3,
345
+ Node as Node10,
346
+ Project as Project3,
347
+ ts as ts4
348
+ } from "ts-morph";
349
+
350
+ // src/discovery/disk-fingerprints.ts
351
+ import { readFileSync, statSync } from "fs";
352
+ var DiskFingerprints = class {
353
+ stats = /* @__PURE__ */ new Map();
354
+ /** The paths whose disk content differs from `text`, or that no longer exist. */
355
+ changed(files) {
356
+ const changed = [];
357
+ for (const { path, text } of files) {
358
+ let key;
343
359
  try {
344
- sourceFile = project.addSourceFileAtPath(moduleEntry);
360
+ const stat2 = statSync(path);
361
+ key = `${stat2.mtimeMs}:${stat2.size}`;
345
362
  } catch {
346
- return null;
347
- }
348
- }
349
- const forRootCall = findForRootCall(sourceFile);
350
- if (!forRootCall) return null;
351
- const initializer = findShareInitializer(forRootCall);
352
- if (!initializer) return null;
353
- return extractShareType(initializer, sourceFile, project);
354
- } catch {
355
- return null;
356
- }
357
- }
358
- function discoverSharedPropsFromConfig(config) {
359
- if (!config.app?.moduleEntry) return null;
360
- try {
361
- const tsconfigPath = config.app.tsconfig ?? join3(config.codegen.cwd, "tsconfig.json");
362
- let project;
363
- try {
364
- project = new Project({
365
- tsConfigFilePath: tsconfigPath,
366
- skipAddingFilesFromTsConfig: true,
367
- skipLoadingLibFiles: true,
368
- skipFileDependencyResolution: true
369
- });
370
- } catch {
371
- project = new Project({
372
- skipAddingFilesFromTsConfig: true,
373
- skipLoadingLibFiles: true,
374
- skipFileDependencyResolution: true,
375
- compilerOptions: { allowJs: true, strict: false }
376
- });
377
- }
378
- return discoverSharedProps(project, config.app.moduleEntry);
379
- } catch {
380
- return null;
381
- }
382
- }
383
- function findForRootCall(sourceFile) {
384
- const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
385
- for (const call of callExpressions) {
386
- const expr = call.getExpression();
387
- if (!Node.isPropertyAccessExpression(expr)) continue;
388
- const methodName = expr.getName();
389
- const objectExpr = expr.getExpression();
390
- if (methodName === "forRoot" && Node.isIdentifier(objectExpr)) {
391
- const name = objectExpr.getText();
392
- if (name === "InertiaModule") {
393
- return call;
394
- }
395
- }
396
- }
397
- return null;
398
- }
399
- function findShareInitializer(forRootCall) {
400
- if (!Node.isCallExpression(forRootCall)) return null;
401
- const args = forRootCall.getArguments();
402
- const firstArg2 = args[0];
403
- if (!firstArg2 || !Node.isObjectLiteralExpression(firstArg2)) return null;
404
- for (const prop of firstArg2.getProperties()) {
405
- if (Node.isPropertyAssignment(prop) && prop.getName() === "share") {
406
- return prop.getInitializer() ?? null;
407
- }
408
- }
409
- return null;
410
- }
411
- function extractShareType(node, sourceFile, project) {
412
- if (Node.isIdentifier(node)) {
413
- const ref = resolveIdentifierToImportRef(node, sourceFile, project);
414
- if (ref) {
415
- return {
416
- typeString: `Awaited<ReturnType<typeof import('${ref.importPath}').${ref.exportName}>>`,
417
- properties: null,
418
- isImportRef: true
419
- };
420
- }
421
- }
422
- if (Node.isArrowFunction(node)) {
423
- const result = extractFromFunctionLike(node, sourceFile);
424
- return result ? { ...result, isImportRef: false } : null;
425
- }
426
- if (Node.isFunctionExpression(node)) {
427
- const result = extractFromFunctionLike(node, sourceFile);
428
- return result ? { ...result, isImportRef: false } : null;
429
- }
430
- if (Node.isObjectLiteralExpression(node)) {
431
- const result = extractFromObjectLiteral(node);
432
- return result ? { ...result, isImportRef: false } : null;
433
- }
434
- return null;
435
- }
436
- function resolveIdentifierToImportRef(id, sourceFile, project) {
437
- if (!Node.isIdentifier(id)) return null;
438
- const name = id.getText();
439
- const localFunc = sourceFile.getFunction(name);
440
- if (localFunc?.isExported()) {
441
- const filePath = sourceFile.getFilePath().replace(/\.ts$/, "");
442
- return { importPath: filePath, exportName: name };
443
- }
444
- const localVar = sourceFile.getVariableDeclaration(name);
445
- if (localVar?.isExported()) {
446
- const filePath = sourceFile.getFilePath().replace(/\.ts$/, "");
447
- return { importPath: filePath, exportName: name };
448
- }
449
- for (const imp of sourceFile.getImportDeclarations()) {
450
- for (const named of imp.getNamedImports()) {
451
- const importedName = named.getAliasNode()?.getText() ?? named.getName();
452
- if (importedName !== name) continue;
453
- const resolvedSource = imp.getModuleSpecifierSourceFile();
454
- if (!resolvedSource) continue;
455
- const originalName = named.getName();
456
- const fn = resolvedSource.getFunction(originalName);
457
- if (fn?.isExported()) {
458
- const filePath = resolvedSource.getFilePath().replace(/\.ts$/, "");
459
- return { importPath: filePath, exportName: originalName };
460
- }
461
- const v = resolvedSource.getVariableDeclaration(originalName);
462
- if (v?.isExported()) {
463
- const filePath = resolvedSource.getFilePath().replace(/\.ts$/, "");
464
- return { importPath: filePath, exportName: originalName };
465
- }
466
- }
467
- }
468
- return null;
469
- }
470
- function extractFromFunctionLike(node, _sourceFile) {
471
- const returnTypeNode = Node.isArrowFunction(node) || Node.isFunctionExpression(node) ? node.getReturnTypeNode() : null;
472
- if (returnTypeNode) {
473
- return extractFromReturnTypeAnnotation(returnTypeNode);
474
- }
475
- if (Node.isArrowFunction(node)) {
476
- const body = node.getBody();
477
- if (Node.isParenthesizedExpression(body)) {
478
- const inner = body.getExpression();
479
- if (Node.isObjectLiteralExpression(inner)) {
480
- return extractFromObjectLiteral(inner);
481
- }
482
- }
483
- if (Node.isObjectLiteralExpression(body)) {
484
- return extractFromObjectLiteral(body);
485
- }
486
- if (Node.isBlock(body)) {
487
- return extractFromBlockReturn(body);
488
- }
489
- }
490
- if (Node.isFunctionExpression(node)) {
491
- const body = node.getBody();
492
- if (Node.isBlock(body)) {
493
- return extractFromBlockReturn(body);
494
- }
495
- }
496
- return null;
497
- }
498
- function extractFromReturnTypeAnnotation(typeNode) {
499
- if (Node.isTypeReference(typeNode)) {
500
- const typeName = typeNode.getTypeName();
501
- if (Node.isIdentifier(typeName) && typeName.getText() === "Promise") {
502
- const typeArgs = typeNode.getTypeArguments();
503
- const firstArg2 = typeArgs[0];
504
- if (firstArg2) {
505
- return extractFromReturnTypeAnnotation(firstArg2);
363
+ this.stats.delete(path);
364
+ changed.push(path);
365
+ continue;
506
366
  }
507
- return null;
508
- }
509
- }
510
- if (Node.isTypeLiteral(typeNode)) {
511
- const properties = [];
512
- for (const member of typeNode.getMembers()) {
513
- if (Node.isPropertySignature(member)) {
514
- const name = member.getName();
515
- const memberTypeNode = member.getTypeNode();
516
- const type = memberTypeNode ? memberTypeNode.getText() : "unknown";
517
- properties.push({ name, type });
367
+ if (this.stats.get(path) === key) continue;
368
+ let disk;
369
+ try {
370
+ disk = readFileSync(path, "utf-8");
371
+ } catch {
372
+ disk = void 0;
518
373
  }
519
- }
520
- if (properties.length === 0) return null;
521
- const typeString = `{ ${properties.map((p) => `${p.name}: ${p.type}`).join("; ")} }`;
522
- return { typeString, properties, isImportRef: false };
523
- }
524
- return null;
525
- }
526
- function extractFromBlockReturn(block) {
527
- if (!Node.isBlock(block)) return null;
528
- const statements = block.getStatements();
529
- for (let i = statements.length - 1; i >= 0; i--) {
530
- const stmt = statements[i];
531
- if (!Node.isReturnStatement(stmt)) continue;
532
- const expr = stmt.getExpression();
533
- if (!expr) continue;
534
- if (Node.isObjectLiteralExpression(expr)) {
535
- return extractFromObjectLiteral(expr);
536
- }
537
- if (Node.isParenthesizedExpression(expr)) {
538
- const inner = expr.getExpression();
539
- if (Node.isObjectLiteralExpression(inner)) {
540
- return extractFromObjectLiteral(inner);
374
+ if (disk === text) {
375
+ this.stats.set(path, key);
376
+ } else {
377
+ this.stats.delete(path);
378
+ changed.push(path);
541
379
  }
542
380
  }
543
- break;
381
+ return changed;
544
382
  }
545
- return null;
546
- }
547
- function extractFromObjectLiteral(objLiteral) {
548
- if (!Node.isObjectLiteralExpression(objLiteral)) return null;
549
- const properties = [];
550
- for (const prop of objLiteral.getProperties()) {
551
- if (!Node.isPropertyAssignment(prop)) continue;
552
- const name = prop.getName();
553
- const initializer = prop.getInitializer();
554
- if (!initializer) continue;
555
- const type = inferExpressionType(initializer);
556
- properties.push({ name, type });
383
+ /** Forget `path`, so its next check compares content again. */
384
+ forget(path) {
385
+ this.stats.delete(path);
557
386
  }
558
- if (properties.length === 0) return null;
559
- const typeString = `{ ${properties.map((p) => `${p.name}: ${p.type}`).join("; ")} }`;
560
- return { typeString, properties, isImportRef: false };
561
- }
562
- function inferExpressionType(node) {
563
- if (Node.isStringLiteral(node)) return "string";
564
- if (Node.isTemplateExpression(node) || Node.isNoSubstitutionTemplateLiteral(node))
565
- return "string";
566
- if (Node.isNumericLiteral(node)) return "number";
567
- if (node.getKind() === SyntaxKind.TrueKeyword || node.getKind() === SyntaxKind.FalseKeyword) {
568
- return "boolean";
569
- }
570
- if (node.getKind() === SyntaxKind.NullKeyword) return "null";
571
- if (Node.isIdentifier(node) && node.getText() === "undefined") return "undefined";
572
- if (Node.isObjectLiteralExpression(node)) {
573
- const props = node.getProperties();
574
- if (props.length === 0) return "Record<string, unknown>";
575
- const entries = [];
576
- for (const prop of props) {
577
- if (!Node.isPropertyAssignment(prop)) continue;
578
- const key = prop.getName();
579
- const init = prop.getInitializer();
580
- if (!init) continue;
581
- entries.push(`${key}: ${inferExpressionType(init)}`);
582
- }
583
- if (entries.length === 0) return "Record<string, unknown>";
584
- return `{ ${entries.join("; ")} }`;
585
- }
586
- if (Node.isArrayLiteralExpression(node)) {
587
- const elements = node.getElements();
588
- if (elements.length === 0) return "Array<unknown>";
589
- const first = elements[0];
590
- if (first) return `Array<${inferExpressionType(first)}>`;
591
- return "Array<unknown>";
592
- }
593
- if (Node.isConditionalExpression(node)) {
594
- const whenTrue = inferExpressionType(node.getWhenTrue());
595
- const whenFalse = inferExpressionType(node.getWhenFalse());
596
- if (whenTrue === whenFalse) return whenTrue;
597
- return `${whenTrue} | ${whenFalse}`;
598
- }
599
- if (Node.isParenthesizedExpression(node)) {
600
- return inferExpressionType(node.getExpression());
601
- }
602
- if (Node.isAsExpression(node)) {
603
- const typeNode = node.getTypeNode();
604
- if (typeNode) return typeNode.getText();
605
- }
606
- return "unknown";
607
- }
608
-
609
- // src/emit/emit-api.ts
610
- import { mkdir, writeFile } from "fs/promises";
611
- import { isAbsolute as isAbsolute2, join as join5, relative as relative4 } from "path";
612
-
613
- // src/extension/registry.ts
614
- import { relative as relative3, resolve as resolve4 } from "path";
615
- import { Project as Project5 } from "ts-morph";
616
-
617
- // src/discovery/contracts-fast.ts
618
- import { existsSync } from "fs";
619
- import { dirname as dirname2, join as join4, resolve as resolve3 } from "path";
620
- import fg2 from "fast-glob";
621
- import {
622
- Node as Node11,
623
- Project as Project4,
624
- ts as ts3
625
- } from "ts-morph";
387
+ };
626
388
 
627
389
  // src/discovery/dto-type-resolver.ts
628
390
  import {
629
- Node as Node10,
630
- SyntaxKind as SyntaxKind4
391
+ Node as Node9,
392
+ SyntaxKind as SyntaxKind3
631
393
  } from "ts-morph";
632
394
 
633
395
  // src/discovery/dto-to-ir.ts
634
396
  import {
635
- Node as Node3
397
+ Node as Node2
636
398
  } from "ts-morph";
637
399
 
638
400
  // src/util/debug-log.ts
@@ -647,7 +409,7 @@ function debugWarn(message) {
647
409
  // src/discovery/type-ref-resolution.ts
648
410
  import { dirname, resolve as resolve2 } from "path";
649
411
  import {
650
- Node as Node2
412
+ Node
651
413
  } from "ts-morph";
652
414
  var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
653
415
  var _ctxByProject = /* @__PURE__ */ new WeakMap();
@@ -913,13 +675,13 @@ function resolveTypeRef(nodeOrName, sourceFile, project, opts) {
913
675
  name = nodeOrName;
914
676
  } else {
915
677
  const typeNode = nodeOrName;
916
- if (opts.unwrapContainers && Node2.isArrayTypeNode(typeNode)) {
678
+ if (opts.unwrapContainers && Node.isArrayTypeNode(typeNode)) {
917
679
  const inner = resolveTypeRef(typeNode.getElementTypeNode(), sourceFile, project, opts);
918
680
  return inner ? { ...inner, isArray: true } : null;
919
681
  }
920
- if (!Node2.isTypeReference(typeNode)) return null;
682
+ if (!Node.isTypeReference(typeNode)) return null;
921
683
  const typeName = typeNode.getTypeName();
922
- const refName = Node2.isIdentifier(typeName) ? typeName.getText() : null;
684
+ const refName = Node.isIdentifier(typeName) ? typeName.getText() : null;
923
685
  if (!refName) return null;
924
686
  if (opts.unwrapContainers && refName === "Promise") {
925
687
  const first = typeNode.getTypeArguments()[0];
@@ -1053,7 +815,7 @@ function buildProperty(prop, classFile, ctx) {
1053
815
  const dec = (n) => decorators.get(n);
1054
816
  const typeNode = prop.getTypeNode();
1055
817
  const typeText = typeNode?.getText() ?? "unknown";
1056
- const isArrayType = !!typeNode && Node3.isArrayTypeNode(typeNode);
818
+ const isArrayType = !!typeNode && Node2.isArrayTypeNode(typeNode);
1057
819
  const asField = (child) => applyPresence(
1058
820
  has("IsArray") || isArrayType ? { kind: "array", element: child } : child,
1059
821
  decorators
@@ -1267,22 +1029,22 @@ function firstArgText(decorator) {
1267
1029
  }
1268
1030
  function numericArg(decorator) {
1269
1031
  const arg = firstArg(decorator);
1270
- if (arg && Node3.isNumericLiteral(arg)) return arg.getText();
1032
+ if (arg && Node2.isNumericLiteral(arg)) return arg.getText();
1271
1033
  return null;
1272
1034
  }
1273
1035
  function numericArgs(decorator) {
1274
1036
  const args = decorator?.getArguments() ?? [];
1275
- const num = (n) => n && Node3.isNumericLiteral(n) ? n.getText() : null;
1037
+ const num = (n) => n && Node2.isNumericLiteral(n) ? n.getText() : null;
1276
1038
  return [num(args[0]), num(args[1])];
1277
1039
  }
1278
1040
  function messageRaw(decorator) {
1279
1041
  const args = decorator?.getArguments() ?? [];
1280
1042
  for (const arg of args) {
1281
- if (Node3.isObjectLiteralExpression(arg)) {
1043
+ if (Node2.isObjectLiteralExpression(arg)) {
1282
1044
  for (const prop of arg.getProperties()) {
1283
- if (Node3.isPropertyAssignment(prop) && prop.getName() === "message") {
1045
+ if (Node2.isPropertyAssignment(prop) && prop.getName() === "message") {
1284
1046
  const init = prop.getInitializer();
1285
- if (init && Node3.isStringLiteral(init)) return init.getText();
1047
+ if (init && Node2.isStringLiteral(init)) return init.getText();
1286
1048
  }
1287
1049
  }
1288
1050
  }
@@ -1291,30 +1053,30 @@ function messageRaw(decorator) {
1291
1053
  }
1292
1054
  function resolveDiscriminator(decorator) {
1293
1055
  const optsArg = decorator?.getArguments()[1];
1294
- if (!optsArg || !Node3.isObjectLiteralExpression(optsArg)) return null;
1056
+ if (!optsArg || !Node2.isObjectLiteralExpression(optsArg)) return null;
1295
1057
  let discProp;
1296
1058
  for (const prop of optsArg.getProperties()) {
1297
- if (Node3.isPropertyAssignment(prop) && prop.getName() === "discriminator") {
1059
+ if (Node2.isPropertyAssignment(prop) && prop.getName() === "discriminator") {
1298
1060
  discProp = prop.getInitializer();
1299
1061
  }
1300
1062
  }
1301
- if (!discProp || !Node3.isObjectLiteralExpression(discProp)) return null;
1063
+ if (!discProp || !Node2.isObjectLiteralExpression(discProp)) return null;
1302
1064
  let property = null;
1303
1065
  const subTypes = [];
1304
1066
  for (const prop of discProp.getProperties()) {
1305
- if (!Node3.isPropertyAssignment(prop)) continue;
1067
+ if (!Node2.isPropertyAssignment(prop)) continue;
1306
1068
  const name = prop.getName();
1307
1069
  const init = prop.getInitializer();
1308
1070
  if (!init) continue;
1309
- if (name === "property" && Node3.isStringLiteral(init)) {
1071
+ if (name === "property" && Node2.isStringLiteral(init)) {
1310
1072
  property = init.getLiteralValue();
1311
- } else if (name === "subTypes" && Node3.isArrayLiteralExpression(init)) {
1073
+ } else if (name === "subTypes" && Node2.isArrayLiteralExpression(init)) {
1312
1074
  for (const el of init.getElements()) {
1313
- if (!Node3.isObjectLiteralExpression(el)) continue;
1075
+ if (!Node2.isObjectLiteralExpression(el)) continue;
1314
1076
  for (const p of el.getProperties()) {
1315
- if (!Node3.isPropertyAssignment(p) || p.getName() !== "name") continue;
1077
+ if (!Node2.isPropertyAssignment(p) || p.getName() !== "name") continue;
1316
1078
  const nameInit = p.getInitializer();
1317
- if (nameInit && Node3.isIdentifier(nameInit)) subTypes.push(nameInit.getText());
1079
+ if (nameInit && Node2.isIdentifier(nameInit)) subTypes.push(nameInit.getText());
1318
1080
  }
1319
1081
  }
1320
1082
  }
@@ -1325,9 +1087,9 @@ function resolveDiscriminator(decorator) {
1325
1087
  function resolveTypeFactoryName(decorator) {
1326
1088
  const arg = firstArg(decorator);
1327
1089
  if (!arg) return null;
1328
- if (Node3.isArrowFunction(arg)) {
1090
+ if (Node2.isArrowFunction(arg)) {
1329
1091
  const body = arg.getBody();
1330
- if (Node3.isIdentifier(body)) return body.getText();
1092
+ if (Node2.isIdentifier(body)) return body.getText();
1331
1093
  }
1332
1094
  return null;
1333
1095
  }
@@ -1336,12 +1098,12 @@ function singularClassName(typeText) {
1336
1098
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(inner) ? inner : null;
1337
1099
  }
1338
1100
  function genericTypeArgNames(typeNode) {
1339
- if (!typeNode || !Node3.isTypeReference(typeNode)) return [];
1101
+ if (!typeNode || !Node2.isTypeReference(typeNode)) return [];
1340
1102
  const names = [];
1341
1103
  for (const arg of typeNode.getTypeArguments()) {
1342
- if (!Node3.isTypeReference(arg)) return [];
1104
+ if (!Node2.isTypeReference(arg)) return [];
1343
1105
  const tn = arg.getTypeName();
1344
- if (!Node3.isIdentifier(tn)) return [];
1106
+ if (!Node2.isIdentifier(tn)) return [];
1345
1107
  names.push(tn.getText());
1346
1108
  }
1347
1109
  return names;
@@ -1349,7 +1111,7 @@ function genericTypeArgNames(typeNode) {
1349
1111
  function enumSchemaFromDecorator(decorator, classFile, ctx) {
1350
1112
  const arg = firstArg(decorator);
1351
1113
  if (!arg) return null;
1352
- if (Node3.isIdentifier(arg)) {
1114
+ if (Node2.isIdentifier(arg)) {
1353
1115
  const name = arg.getText();
1354
1116
  const resolved = findType(name, classFile, ctx.project);
1355
1117
  if (resolved && resolved.kind === "enum") {
@@ -1363,12 +1125,12 @@ function enumSchemaFromDecorator(decorator, classFile, ctx) {
1363
1125
  }
1364
1126
  return { kind: "unknown", note: `@IsEnum(${name}): enum not resolvable to literals` };
1365
1127
  }
1366
- if (Node3.isObjectLiteralExpression(arg)) {
1128
+ if (Node2.isObjectLiteralExpression(arg)) {
1367
1129
  const values = [];
1368
1130
  for (const p of arg.getProperties()) {
1369
- if (!Node3.isPropertyAssignment(p)) continue;
1131
+ if (!Node2.isPropertyAssignment(p)) continue;
1370
1132
  const init = p.getInitializer();
1371
- if (init && Node3.isStringLiteral(init)) values.push(init.getText());
1133
+ if (init && Node2.isStringLiteral(init)) values.push(init.getText());
1372
1134
  }
1373
1135
  if (values.length > 0) return { kind: "enum", literals: values };
1374
1136
  }
@@ -1376,9 +1138,9 @@ function enumSchemaFromDecorator(decorator, classFile, ctx) {
1376
1138
  }
1377
1139
  function inSchemaFromDecorator(decorator) {
1378
1140
  const arg = firstArg(decorator);
1379
- if (arg && Node3.isArrayLiteralExpression(arg)) {
1141
+ if (arg && Node2.isArrayLiteralExpression(arg)) {
1380
1142
  const elements = arg.getElements();
1381
- const allStrings = elements.every((e) => Node3.isStringLiteral(e));
1143
+ const allStrings = elements.every((e) => Node2.isStringLiteral(e));
1382
1144
  if (allStrings && elements.length > 0) {
1383
1145
  return { kind: "enum", literals: elements.map((e) => e.getText()) };
1384
1146
  }
@@ -1394,13 +1156,13 @@ function inSchemaFromDecorator(decorator) {
1394
1156
 
1395
1157
  // src/discovery/filter-for.ts
1396
1158
  import {
1397
- Node as Node6
1159
+ Node as Node5
1398
1160
  } from "ts-morph";
1399
1161
 
1400
1162
  // src/discovery/filter-field-types.ts
1401
1163
  import {
1402
- Node as Node4,
1403
- SyntaxKind as SyntaxKind2
1164
+ Node as Node3,
1165
+ SyntaxKind
1404
1166
  } from "ts-morph";
1405
1167
 
1406
1168
  // src/discovery/enum-resolution.ts
@@ -1454,28 +1216,28 @@ function markNullable(r, nullable) {
1454
1216
  }
1455
1217
  var TRANSPARENT_TYPE_BRANDS = /* @__PURE__ */ new Set(["Opt", "Hidden"]);
1456
1218
  function classifyTypeNode(typeNode, sourceFile, project, opts) {
1457
- if (Node4.isUnionTypeNode(typeNode)) {
1219
+ if (Node3.isUnionTypeNode(typeNode)) {
1458
1220
  let nullable = false;
1459
1221
  const stringLits = [];
1460
1222
  const numberLits = [];
1461
1223
  const others = [];
1462
1224
  for (const member of typeNode.getTypeNodes()) {
1463
1225
  const kind = member.getKind();
1464
- if (kind === SyntaxKind2.NullKeyword || kind === SyntaxKind2.UndefinedKeyword) {
1226
+ if (kind === SyntaxKind.NullKeyword || kind === SyntaxKind.UndefinedKeyword) {
1465
1227
  nullable = true;
1466
1228
  continue;
1467
1229
  }
1468
- if (Node4.isLiteralTypeNode(member)) {
1230
+ if (Node3.isLiteralTypeNode(member)) {
1469
1231
  const lit = member.getLiteral();
1470
- if (Node4.isStringLiteral(lit)) {
1232
+ if (Node3.isStringLiteral(lit)) {
1471
1233
  stringLits.push(lit.getLiteralValue());
1472
1234
  continue;
1473
1235
  }
1474
- if (Node4.isNumericLiteral(lit)) {
1236
+ if (Node3.isNumericLiteral(lit)) {
1475
1237
  numberLits.push(lit.getText());
1476
1238
  continue;
1477
1239
  }
1478
- if (lit.getKind() === SyntaxKind2.NullKeyword) {
1240
+ if (lit.getKind() === SyntaxKind.NullKeyword) {
1479
1241
  nullable = true;
1480
1242
  continue;
1481
1243
  }
@@ -1495,19 +1257,19 @@ function classifyTypeNode(typeNode, sourceFile, project, opts) {
1495
1257
  return markNullable({ kind: "unknown" }, nullable);
1496
1258
  }
1497
1259
  switch (typeNode.getKind()) {
1498
- case SyntaxKind2.StringKeyword:
1260
+ case SyntaxKind.StringKeyword:
1499
1261
  return { kind: "string" };
1500
- case SyntaxKind2.NumberKeyword:
1262
+ case SyntaxKind.NumberKeyword:
1501
1263
  return { kind: "number" };
1502
- case SyntaxKind2.BooleanKeyword:
1264
+ case SyntaxKind.BooleanKeyword:
1503
1265
  return { kind: "boolean" };
1504
- case SyntaxKind2.AnyKeyword:
1505
- case SyntaxKind2.UnknownKeyword:
1266
+ case SyntaxKind.AnyKeyword:
1267
+ case SyntaxKind.UnknownKeyword:
1506
1268
  return { kind: "unknown" };
1507
1269
  default:
1508
1270
  break;
1509
1271
  }
1510
- if (Node4.isTypeReference(typeNode)) {
1272
+ if (Node3.isTypeReference(typeNode)) {
1511
1273
  const refName = typeNode.getTypeName().getText();
1512
1274
  if (refName === "Date") return { kind: "date" };
1513
1275
  if (refName === "Record" || refName === "Object") return { kind: "json" };
@@ -1525,25 +1287,25 @@ function classifyTypeNode(typeNode, sourceFile, project, opts) {
1525
1287
  if (typeRef) return { kind: "unknown", typeRef };
1526
1288
  return { kind: "unknown" };
1527
1289
  }
1528
- if (Node4.isTypeLiteral(typeNode)) return { kind: "json" };
1290
+ if (Node3.isTypeLiteral(typeNode)) return { kind: "json" };
1529
1291
  return { kind: "unknown" };
1530
1292
  }
1531
1293
  function enumFromDecoratorArgs(args, sourceFile, project) {
1532
1294
  for (const arg of args) {
1533
- if (Node4.isArrowFunction(arg)) {
1295
+ if (Node3.isArrowFunction(arg)) {
1534
1296
  const body = arg.getBody();
1535
- if (Node4.isIdentifier(body)) {
1297
+ if (Node3.isIdentifier(body)) {
1536
1298
  const en = resolveEnumValues(body.getText(), sourceFile, project);
1537
1299
  if (en) return en;
1538
1300
  }
1539
1301
  }
1540
- if (Node4.isObjectLiteralExpression(arg)) {
1302
+ if (Node3.isObjectLiteralExpression(arg)) {
1541
1303
  const itemsProp = arg.getProperty("items");
1542
- if (itemsProp && Node4.isPropertyAssignment(itemsProp)) {
1304
+ if (itemsProp && Node3.isPropertyAssignment(itemsProp)) {
1543
1305
  const init = itemsProp.getInitializer();
1544
- if (init && Node4.isArrowFunction(init)) {
1306
+ if (init && Node3.isArrowFunction(init)) {
1545
1307
  const body = init.getBody();
1546
- if (Node4.isIdentifier(body)) {
1308
+ if (Node3.isIdentifier(body)) {
1547
1309
  const en = resolveEnumValues(body.getText(), sourceFile, project);
1548
1310
  if (en) return en;
1549
1311
  }
@@ -1566,7 +1328,7 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
1566
1328
  return { kind: "string" };
1567
1329
  }
1568
1330
  for (const arg of args) {
1569
- if (Node4.isStringLiteral(arg)) {
1331
+ if (Node3.isStringLiteral(arg)) {
1570
1332
  const raw = arg.getLiteralValue();
1571
1333
  const kind = classifyTypeKeyword(raw);
1572
1334
  if (kind) {
@@ -1574,11 +1336,11 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
1574
1336
  return { kind };
1575
1337
  }
1576
1338
  }
1577
- if (Node4.isObjectLiteralExpression(arg)) {
1339
+ if (Node3.isObjectLiteralExpression(arg)) {
1578
1340
  const enumProp = arg.getProperty("enum");
1579
- if (enumProp && Node4.isPropertyAssignment(enumProp)) {
1341
+ if (enumProp && Node3.isPropertyAssignment(enumProp)) {
1580
1342
  const init = enumProp.getInitializer();
1581
- if (init && Node4.isIdentifier(init)) {
1343
+ if (init && Node3.isIdentifier(init)) {
1582
1344
  const en = resolveEnumValues(init.getText(), sourceFile, project);
1583
1345
  if (en) {
1584
1346
  return en.numeric ? { kind: "number", enumValues: en.values, numericEnum: true } : { kind: "string", enumValues: en.values };
@@ -1588,15 +1350,15 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
1588
1350
  }
1589
1351
  for (const key of ["columnType", "type"]) {
1590
1352
  const typeProp = arg.getProperty(key);
1591
- if (!typeProp || !Node4.isPropertyAssignment(typeProp)) continue;
1353
+ if (!typeProp || !Node3.isPropertyAssignment(typeProp)) continue;
1592
1354
  const init = typeProp.getInitializer();
1593
1355
  if (!init) continue;
1594
- if (Node4.isStringLiteral(init)) {
1356
+ if (Node3.isStringLiteral(init)) {
1595
1357
  const kind = classifyTypeKeyword(init.getLiteralValue());
1596
1358
  if (kind) return { kind };
1597
1359
  continue;
1598
1360
  }
1599
- if (Node4.isIdentifier(init)) {
1361
+ if (Node3.isIdentifier(init)) {
1600
1362
  const kind = classifyTypeKeyword(init.getText());
1601
1363
  if (kind) return { kind };
1602
1364
  }
@@ -1638,23 +1400,70 @@ function toFilterFieldType(name, r) {
1638
1400
  }
1639
1401
 
1640
1402
  // src/discovery/heritage.ts
1641
- import { Node as Node5, Project as Project2 } from "ts-morph";
1403
+ import {
1404
+ FileSystemRefreshResult,
1405
+ Node as Node4,
1406
+ Project
1407
+ } from "ts-morph";
1408
+
1409
+ // src/discovery/unresolved-imports.ts
1410
+ import { ts } from "ts-morph";
1411
+ var UnresolvedImports = class {
1412
+ read;
1413
+ /**
1414
+ * The files with an import that failed to resolve in `program` and now resolves
1415
+ * to a TypeScript file the program does not hold.
1416
+ */
1417
+ nowResolving(program) {
1418
+ if (this.read?.program !== program) {
1419
+ this.read = { program, imports: failedImports(program) };
1420
+ }
1421
+ const options = program.getCompilerOptions();
1422
+ const files = /* @__PURE__ */ new Set();
1423
+ for (const { specifier, from } of this.read.imports) {
1424
+ const file = ts.resolveModuleName(specifier, from, options, ts.sys).resolvedModule?.resolvedFileName;
1425
+ if (file !== void 0 && /\.(m|c)?tsx?$/.test(file) && !program.getSourceFile(file)) {
1426
+ files.add(from);
1427
+ }
1428
+ }
1429
+ return [...files];
1430
+ }
1431
+ };
1432
+ function failedImports(program) {
1433
+ const checker = program.getTypeChecker();
1434
+ const imports = [];
1435
+ for (const file of program.getSourceFiles()) {
1436
+ if (program.isSourceFileDefaultLibrary(file) || file.isDeclarationFile) continue;
1437
+ if (program.isSourceFileFromExternalLibrary(file)) continue;
1438
+ for (const statement of file.statements) {
1439
+ if (!ts.isImportDeclaration(statement) && !ts.isExportDeclaration(statement)) continue;
1440
+ const specifier = statement.moduleSpecifier;
1441
+ if (!specifier || !ts.isStringLiteral(specifier)) continue;
1442
+ if (!checker.getSymbolAtLocation(specifier)) {
1443
+ imports.push({ specifier: specifier.text, from: file.fileName });
1444
+ }
1445
+ }
1446
+ }
1447
+ return imports;
1448
+ }
1449
+
1450
+ // src/discovery/heritage.ts
1642
1451
  function resolveHeritageCall(node) {
1643
1452
  if (!node) return void 0;
1644
1453
  const expr = unwrapExpression(node);
1645
- if (Node5.isCallExpression(expr)) return expr;
1646
- if (!Node5.isIdentifier(expr)) return void 0;
1454
+ if (Node4.isCallExpression(expr)) return expr;
1455
+ if (!Node4.isIdentifier(expr)) return void 0;
1647
1456
  const decl = expr.getDefinitions().map((d) => d.getDeclarationNode()).find(
1648
- (n) => n !== void 0 && Node5.isVariableDeclaration(n)
1457
+ (n) => n !== void 0 && Node4.isVariableDeclaration(n)
1649
1458
  );
1650
1459
  const init = decl?.getInitializer();
1651
1460
  if (!init) return void 0;
1652
1461
  const unwrapped = unwrapExpression(init);
1653
- return Node5.isCallExpression(unwrapped) ? unwrapped : void 0;
1462
+ return Node4.isCallExpression(unwrapped) ? unwrapped : void 0;
1654
1463
  }
1655
1464
  function unwrapExpression(node) {
1656
1465
  let current = node;
1657
- while (Node5.isAsExpression(current) || Node5.isSatisfiesExpression(current) || Node5.isParenthesizedExpression(current) || Node5.isTypeAssertion(current)) {
1466
+ while (Node4.isAsExpression(current) || Node4.isSatisfiesExpression(current) || Node4.isParenthesizedExpression(current) || Node4.isTypeAssertion(current)) {
1658
1467
  current = current.getExpression();
1659
1468
  }
1660
1469
  return current;
@@ -1662,17 +1471,17 @@ function unwrapExpression(node) {
1662
1471
  function resolveReturnedClass(factoryDecl) {
1663
1472
  const body = factoryDecl.getBody();
1664
1473
  if (!body) return void 0;
1665
- const returns = body.getDescendants().filter((n) => Node5.isReturnStatement(n));
1474
+ const returns = body.getDescendants().filter((n) => Node4.isReturnStatement(n));
1666
1475
  for (const ret of returns) {
1667
1476
  const expr = ret.getExpression();
1668
1477
  if (!expr) continue;
1669
1478
  const inner = unwrapExpression(expr);
1670
- if (Node5.isClassExpression(inner)) {
1479
+ if (Node4.isClassExpression(inner)) {
1671
1480
  return inner;
1672
1481
  }
1673
- if (Node5.isIdentifier(inner)) {
1482
+ if (Node4.isIdentifier(inner)) {
1674
1483
  const name = inner.getText();
1675
- const decl = body.getDescendants().find((n) => Node5.isClassDeclaration(n) && n.getName() === name);
1484
+ const decl = body.getDescendants().find((n) => Node4.isClassDeclaration(n) && n.getName() === name);
1676
1485
  if (decl) return decl;
1677
1486
  }
1678
1487
  }
@@ -1680,10 +1489,10 @@ function resolveReturnedClass(factoryDecl) {
1680
1489
  }
1681
1490
  function factoryCalleeName(call) {
1682
1491
  const callee = unwrapExpression(call.getExpression());
1683
- if (Node5.isIdentifier(callee)) return callee;
1684
- if (Node5.isPropertyAccessExpression(callee)) {
1492
+ if (Node4.isIdentifier(callee)) return callee;
1493
+ if (Node4.isPropertyAccessExpression(callee)) {
1685
1494
  const name = callee.getNameNode();
1686
- return Node5.isIdentifier(name) ? name : void 0;
1495
+ return Node4.isIdentifier(name) ? name : void 0;
1687
1496
  }
1688
1497
  return void 0;
1689
1498
  }
@@ -1696,19 +1505,19 @@ function resolveFactoryFromName(name, seen) {
1696
1505
  seen.add(name);
1697
1506
  const decls = name.getDefinitions().map((d) => d.getDeclarationNode()).filter((n) => n !== void 0);
1698
1507
  for (const decl of decls) {
1699
- if (Node5.isFunctionDeclaration(decl) || Node5.isMethodDeclaration(decl)) return decl;
1700
- if (Node5.isPropertyAssignment(decl)) {
1508
+ if (Node4.isFunctionDeclaration(decl) || Node4.isMethodDeclaration(decl)) return decl;
1509
+ if (Node4.isPropertyAssignment(decl)) {
1701
1510
  const init = decl.getInitializer();
1702
1511
  const inner = init ? unwrapExpression(init) : void 0;
1703
- if (inner && Node5.isIdentifier(inner)) {
1512
+ if (inner && Node4.isIdentifier(inner)) {
1704
1513
  const resolved = resolveFactoryFromName(inner, seen);
1705
1514
  if (resolved) return resolved;
1706
1515
  }
1707
1516
  continue;
1708
1517
  }
1709
- if (Node5.isShorthandPropertyAssignment(decl)) {
1518
+ if (Node4.isShorthandPropertyAssignment(decl)) {
1710
1519
  const nameNode = decl.getNameNode();
1711
- const resolved = Node5.isIdentifier(nameNode) ? resolveFactoryFromName(nameNode, seen) : void 0;
1520
+ const resolved = Node4.isIdentifier(nameNode) ? resolveFactoryFromName(nameNode, seen) : void 0;
1712
1521
  if (resolved) return resolved;
1713
1522
  }
1714
1523
  }
@@ -1720,7 +1529,7 @@ function warnUnresolvedFactory(cls, calleeText, reason) {
1720
1529
  );
1721
1530
  }
1722
1531
  function resolveFactoryStaticClass(node) {
1723
- if (!Node5.isPropertyAccessExpression(node)) return void 0;
1532
+ if (!Node4.isPropertyAccessExpression(node)) return void 0;
1724
1533
  const call = resolveHeritageCall(node.getExpression());
1725
1534
  if (!call) return void 0;
1726
1535
  const factoryDecl = resolveFactoryDeclaration(call);
@@ -1728,13 +1537,13 @@ function resolveFactoryStaticClass(node) {
1728
1537
  const returnedClass = resolveReturnedClass(factoryDecl);
1729
1538
  if (!returnedClass) return void 0;
1730
1539
  const staticProp = returnedClass.getStaticProperties().find((p) => p.getName() === node.getName());
1731
- if (!staticProp || !Node5.isPropertyDeclaration(staticProp)) return void 0;
1540
+ if (!staticProp || !Node4.isPropertyDeclaration(staticProp)) return void 0;
1732
1541
  const init = staticProp.getInitializer();
1733
1542
  if (!init) return void 0;
1734
1543
  const inner = unwrapExpression(init);
1735
- if (Node5.isClassExpression(inner)) return inner;
1736
- if (!Node5.isIdentifier(inner)) return void 0;
1737
- return inner.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node5.isClassDeclaration(n));
1544
+ if (Node4.isClassExpression(inner)) return inner;
1545
+ if (!Node4.isIdentifier(inner)) return void 0;
1546
+ return inner.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node4.isClassDeclaration(n));
1738
1547
  }
1739
1548
  function resolveBaseClass(cls) {
1740
1549
  const heritage = cls.getExtends()?.getExpression();
@@ -1745,8 +1554,8 @@ function resolveBaseClass(cls) {
1745
1554
  return factoryDecl ? resolveReturnedClass(factoryDecl) : void 0;
1746
1555
  }
1747
1556
  const expr = unwrapExpression(heritage);
1748
- if (!Node5.isIdentifier(expr)) return void 0;
1749
- return expr.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node5.isClassDeclaration(n));
1557
+ if (!Node4.isIdentifier(expr)) return void 0;
1558
+ return expr.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node4.isClassDeclaration(n));
1750
1559
  }
1751
1560
  function collectMethodsThroughChain(returnedClass) {
1752
1561
  const byName = /* @__PURE__ */ new Map();
@@ -1793,7 +1602,7 @@ function resolveInheritedMethods(cls) {
1793
1602
  classArgs.push(positional);
1794
1603
  continue;
1795
1604
  }
1796
- if (!Node5.isObjectLiteralExpression(arg)) continue;
1605
+ if (!Node4.isObjectLiteralExpression(arg)) continue;
1797
1606
  for (const prop of arg.getProperties()) {
1798
1607
  const named = resolvePropertyClassReference(prop);
1799
1608
  if (!named) continue;
@@ -1815,8 +1624,8 @@ function resolveInheritedMethods(cls) {
1815
1624
  }
1816
1625
  function resolveClassReference(node) {
1817
1626
  const expr = unwrapExpression(node);
1818
- if (!Node5.isIdentifier(expr)) return void 0;
1819
- const decl = expr.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node5.isClassDeclaration(n));
1627
+ if (!Node4.isIdentifier(expr)) return void 0;
1628
+ const decl = expr.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node4.isClassDeclaration(n));
1820
1629
  if (!decl) return void 0;
1821
1630
  return {
1822
1631
  name: decl.getName() ?? expr.getText(),
@@ -1824,29 +1633,56 @@ function resolveClassReference(node) {
1824
1633
  };
1825
1634
  }
1826
1635
  function resolvePropertyClassReference(prop) {
1827
- if (Node5.isShorthandPropertyAssignment(prop)) {
1636
+ if (Node4.isShorthandPropertyAssignment(prop)) {
1828
1637
  const ref2 = resolveClassReference(prop.getNameNode());
1829
1638
  return ref2 ? { key: prop.getName(), ref: ref2 } : void 0;
1830
1639
  }
1831
- if (!Node5.isPropertyAssignment(prop)) return void 0;
1640
+ if (!Node4.isPropertyAssignment(prop)) return void 0;
1832
1641
  const init = prop.getInitializer();
1833
1642
  if (!init) return void 0;
1834
1643
  const ref = resolveClassReference(init);
1835
1644
  if (!ref) return void 0;
1836
1645
  const nameNode = prop.getNameNode();
1837
- const key = Node5.isStringLiteral(nameNode) ? nameNode.getLiteralValue() : prop.getName();
1646
+ const key = Node4.isStringLiteral(nameNode) ? nameNode.getLiteralValue() : prop.getName();
1838
1647
  return { key, ref };
1839
1648
  }
1840
1649
  var mixinTypeProject;
1650
+ var mixinFiles = new DiskFingerprints();
1651
+ var mixinStale = false;
1652
+ var mixinUnresolved = new UnresolvedImports();
1841
1653
  function getMixinTypeProject() {
1842
- mixinTypeProject ??= new Project2({
1843
- skipAddingFilesFromTsConfig: true,
1844
- compilerOptions: { strict: true }
1845
- });
1654
+ if (mixinTypeProject && mixinStale && !refreshMixinTypeProject(mixinTypeProject)) {
1655
+ mixinTypeProject = void 0;
1656
+ }
1657
+ mixinStale = false;
1658
+ if (!mixinTypeProject) {
1659
+ mixinTypeProject = new Project({
1660
+ skipAddingFilesFromTsConfig: true,
1661
+ compilerOptions: { strict: true }
1662
+ });
1663
+ mixinFiles = new DiskFingerprints();
1664
+ mixinUnresolved = new UnresolvedImports();
1665
+ }
1846
1666
  return mixinTypeProject;
1847
1667
  }
1848
1668
  function clearMixinTypeProject() {
1849
1669
  mixinTypeProject = void 0;
1670
+ mixinStale = false;
1671
+ }
1672
+ function invalidateMixinTypeProject() {
1673
+ mixinStale = true;
1674
+ }
1675
+ function refreshMixinTypeProject(project) {
1676
+ const program = project.getProgram().compilerObject;
1677
+ const files = program.getSourceFiles().filter((file) => !program.isSourceFileDefaultLibrary(file)).map((file) => ({ path: file.fileName, text: file.text }));
1678
+ const changed = mixinFiles.changed(files);
1679
+ if (changed.length === 0) return mixinUnresolved.nowResolving(program).length === 0;
1680
+ for (const path of changed) {
1681
+ const sf = project.getSourceFile(path);
1682
+ if (sf?.refreshFromFileSystemSync() !== FileSystemRefreshResult.Updated) return false;
1683
+ }
1684
+ project.resolveSourceFileDependencies();
1685
+ return true;
1850
1686
  }
1851
1687
  function resolveInstantiatedReturnType(cls, methodName) {
1852
1688
  const filePath = cls.getSourceFile().getFilePath();
@@ -1877,7 +1713,7 @@ function resolveClassRef(ref, project) {
1877
1713
  return file?.getClass(ref.name);
1878
1714
  }
1879
1715
  function classifyFilterForHint(typeInit) {
1880
- if (Node6.isStringLiteral(typeInit)) {
1716
+ if (Node5.isStringLiteral(typeInit)) {
1881
1717
  switch (typeInit.getLiteralValue()) {
1882
1718
  case "string":
1883
1719
  return { kind: "string" };
@@ -1891,10 +1727,10 @@ function classifyFilterForHint(typeInit) {
1891
1727
  return null;
1892
1728
  }
1893
1729
  }
1894
- if (Node6.isArrayLiteralExpression(typeInit)) {
1730
+ if (Node5.isArrayLiteralExpression(typeInit)) {
1895
1731
  const values = [];
1896
1732
  for (const el of typeInit.getElements()) {
1897
- if (!Node6.isStringLiteral(el)) return null;
1733
+ if (!Node5.isStringLiteral(el)) return null;
1898
1734
  values.push(el.getLiteralValue());
1899
1735
  }
1900
1736
  if (values.length === 0) return null;
@@ -1929,11 +1765,11 @@ function extractFilterForHints(classDecl, project) {
1929
1765
  if (!filterForDec) continue;
1930
1766
  const args = filterForDec.getArguments();
1931
1767
  const keyArg = args[0];
1932
- const inputKey = keyArg && Node6.isStringLiteral(keyArg) ? keyArg.getLiteralValue() : method.getName();
1768
+ const inputKey = keyArg && Node5.isStringLiteral(keyArg) ? keyArg.getLiteralValue() : method.getName();
1933
1769
  const optsArg = args[1];
1934
- if (optsArg && Node6.isObjectLiteralExpression(optsArg)) {
1770
+ if (optsArg && Node5.isObjectLiteralExpression(optsArg)) {
1935
1771
  const typeProp = optsArg.getProperty("type");
1936
- if (typeProp && Node6.isPropertyAssignment(typeProp)) {
1772
+ if (typeProp && Node5.isPropertyAssignment(typeProp)) {
1937
1773
  const typeInit = typeProp.getInitializer();
1938
1774
  if (typeInit) {
1939
1775
  const classified = classifyFilterForHint(typeInit);
@@ -1960,14 +1796,14 @@ function extractApplyFilterInfo(method, sourceFile, project, mixin, controllerCl
1960
1796
  const filterClassArg = args[0];
1961
1797
  if (!filterClassArg) continue;
1962
1798
  const factoryStaticClass = resolveFactoryStaticClass(filterClassArg);
1963
- if (!factoryStaticClass && !Node6.isIdentifier(filterClassArg)) continue;
1799
+ if (!factoryStaticClass && !Node5.isIdentifier(filterClassArg)) continue;
1964
1800
  let source = "query";
1965
1801
  const optionsArg = args[1];
1966
- if (optionsArg && Node6.isObjectLiteralExpression(optionsArg)) {
1802
+ if (optionsArg && Node5.isObjectLiteralExpression(optionsArg)) {
1967
1803
  const sourceProp = optionsArg.getProperty("source");
1968
- if (sourceProp && Node6.isPropertyAssignment(sourceProp)) {
1804
+ if (sourceProp && Node5.isPropertyAssignment(sourceProp)) {
1969
1805
  const init = sourceProp.getInitializer();
1970
- if (init && Node6.isStringLiteral(init) && init.getLiteralValue() === "body") {
1806
+ if (init && Node5.isStringLiteral(init) && init.getLiteralValue() === "body") {
1971
1807
  source = "body";
1972
1808
  }
1973
1809
  }
@@ -1976,7 +1812,7 @@ function extractApplyFilterInfo(method, sourceFile, project, mixin, controllerCl
1976
1812
  const resolved = factoryStaticClass ? void 0 : findType(filterClassName, declFile, project);
1977
1813
  const declaredClass = factoryStaticClass ?? (resolved?.kind === "class" ? resolved.decl : resolveLocalClassDeclaration(filterClassArg));
1978
1814
  const ownRoute = !mixin || (controllerClass ? method.getParent() === controllerClass : declFile.getFilePath() !== mixin.factoryFilePath);
1979
- const namedByRoute = ownRoute && Node6.isIdentifier(filterClassArg) ? declaredClass : void 0;
1815
+ const namedByRoute = ownRoute && Node5.isIdentifier(filterClassArg) ? declaredClass : void 0;
1980
1816
  for (const candidate of orderFilterCandidates({
1981
1817
  namedByRoute,
1982
1818
  supplied: resolveMixinFilterClass(mixin, project),
@@ -2054,22 +1890,22 @@ function resolveRelationEntity(prop, sourceFile, project) {
2054
1890
  const args = dec.getArguments();
2055
1891
  if (args.length === 0) continue;
2056
1892
  const arg = args[0];
2057
- if (Node6.isObjectLiteralExpression(arg)) {
1893
+ if (Node5.isObjectLiteralExpression(arg)) {
2058
1894
  const entityProp = arg.getProperty("entity");
2059
- if (entityProp && Node6.isPropertyAssignment(entityProp)) {
1895
+ if (entityProp && Node5.isPropertyAssignment(entityProp)) {
2060
1896
  const init = entityProp.getInitializer();
2061
- if (init && Node6.isArrowFunction(init)) {
1897
+ if (init && Node5.isArrowFunction(init)) {
2062
1898
  const body = init.getBody();
2063
- if (Node6.isIdentifier(body)) {
1899
+ if (Node5.isIdentifier(body)) {
2064
1900
  const resolved = findType(body.getText(), prop.getSourceFile(), project);
2065
1901
  if (resolved?.kind === "class") return resolved.decl;
2066
1902
  }
2067
1903
  }
2068
1904
  }
2069
1905
  }
2070
- if (Node6.isArrowFunction(arg)) {
1906
+ if (Node5.isArrowFunction(arg)) {
2071
1907
  const body = arg.getBody();
2072
- if (Node6.isIdentifier(body)) {
1908
+ if (Node5.isIdentifier(body)) {
2073
1909
  const resolved = findType(body.getText(), prop.getSourceFile(), project);
2074
1910
  if (resolved?.kind === "class") return resolved.decl;
2075
1911
  }
@@ -2088,36 +1924,36 @@ function extractClassPropertyTypes(classDecl, project) {
2088
1924
  return fields;
2089
1925
  }
2090
1926
  function resolveLocalClassDeclaration(identifier) {
2091
- if (!Node6.isIdentifier(identifier)) return void 0;
2092
- return identifier.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node6.isClassDeclaration(n));
1927
+ if (!Node5.isIdentifier(identifier)) return void 0;
1928
+ return identifier.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node5.isClassDeclaration(n));
2093
1929
  }
2094
1930
  function resolveDeclaredEntity(optionsArg, filterClass, project) {
2095
- if (!Node6.isObjectLiteralExpression(optionsArg)) return void 0;
1931
+ if (!Node5.isObjectLiteralExpression(optionsArg)) return void 0;
2096
1932
  const entityProp = optionsArg.getProperty("entity");
2097
- if (!entityProp || !Node6.isPropertyAssignment(entityProp)) return void 0;
1933
+ if (!entityProp || !Node5.isPropertyAssignment(entityProp)) return void 0;
2098
1934
  const entityInit = entityProp.getInitializer();
2099
- if (!entityInit || !Node6.isIdentifier(entityInit)) return void 0;
1935
+ if (!entityInit || !Node5.isIdentifier(entityInit)) return void 0;
2100
1936
  const resolved = findType(entityInit.getText(), filterClass.getSourceFile(), project);
2101
1937
  if (!resolved || resolved.kind !== "class") return void 0;
2102
1938
  return resolved.decl;
2103
1939
  }
2104
1940
  function readFieldNameList(optionsArg, key) {
2105
- if (!Node6.isObjectLiteralExpression(optionsArg)) return void 0;
1941
+ if (!Node5.isObjectLiteralExpression(optionsArg)) return void 0;
2106
1942
  const prop = optionsArg.getProperty(key);
2107
- if (!prop || !Node6.isPropertyAssignment(prop)) return void 0;
1943
+ if (!prop || !Node5.isPropertyAssignment(prop)) return void 0;
2108
1944
  const init = prop.getInitializer();
2109
- if (!init || !Node6.isArrayLiteralExpression(init)) return void 0;
1945
+ if (!init || !Node5.isArrayLiteralExpression(init)) return void 0;
2110
1946
  const names = /* @__PURE__ */ new Set();
2111
1947
  for (const el of init.getElements()) {
2112
- if (Node6.isStringLiteral(el)) {
1948
+ if (Node5.isStringLiteral(el)) {
2113
1949
  names.add(el.getLiteralValue());
2114
1950
  continue;
2115
1951
  }
2116
- if (Node6.isObjectLiteralExpression(el)) {
1952
+ if (Node5.isObjectLiteralExpression(el)) {
2117
1953
  const fieldProp = el.getProperty("field");
2118
- if (fieldProp && Node6.isPropertyAssignment(fieldProp)) {
1954
+ if (fieldProp && Node5.isPropertyAssignment(fieldProp)) {
2119
1955
  const fieldInit = fieldProp.getInitializer();
2120
- if (fieldInit && Node6.isStringLiteral(fieldInit)) {
1956
+ if (fieldInit && Node5.isStringLiteral(fieldInit)) {
2121
1957
  names.add(fieldInit.getLiteralValue());
2122
1958
  continue;
2123
1959
  }
@@ -2141,7 +1977,7 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
2141
1977
  const args = filterableDecorator.getArguments();
2142
1978
  if (args.length === 0) return [];
2143
1979
  const optionsArg = args[0];
2144
- if (!Node6.isObjectLiteralExpression(optionsArg)) return [];
1980
+ if (!Node5.isObjectLiteralExpression(optionsArg)) return [];
2145
1981
  const declaredEntity = resolveDeclaredEntity(optionsArg, filterClass, project);
2146
1982
  const entityDecl = preferDeclaredEntity ? declaredEntity ?? entityOverride : entityOverride ?? declaredEntity;
2147
1983
  if (!entityDecl) return [];
@@ -2152,17 +1988,17 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
2152
1988
  const relationsDecorator = filterClass.getDecorators().find((d) => d.getName() === "Relations");
2153
1989
  if (relationsDecorator) {
2154
1990
  const relArgs = relationsDecorator.getArguments();
2155
- if (relArgs.length > 0 && Node6.isObjectLiteralExpression(relArgs[0])) {
1991
+ if (relArgs.length > 0 && Node5.isObjectLiteralExpression(relArgs[0])) {
2156
1992
  for (const relProp of relArgs[0].getProperties()) {
2157
- if (!Node6.isPropertyAssignment(relProp)) continue;
1993
+ if (!Node5.isPropertyAssignment(relProp)) continue;
2158
1994
  const relInit = relProp.getInitializer();
2159
- if (!relInit || !Node6.isObjectLiteralExpression(relInit)) continue;
1995
+ if (!relInit || !Node5.isObjectLiteralExpression(relInit)) continue;
2160
1996
  const keysProp = relInit.getProperty("keys");
2161
- if (!keysProp || !Node6.isPropertyAssignment(keysProp)) continue;
1997
+ if (!keysProp || !Node5.isPropertyAssignment(keysProp)) continue;
2162
1998
  const keysInit = keysProp.getInitializer();
2163
- if (!keysInit || !Node6.isArrayLiteralExpression(keysInit)) continue;
1999
+ if (!keysInit || !Node5.isArrayLiteralExpression(keysInit)) continue;
2164
2000
  for (const el of keysInit.getElements()) {
2165
- if (Node6.isStringLiteral(el)) {
2001
+ if (Node5.isStringLiteral(el)) {
2166
2002
  fields.push(toFilterFieldType(el.getLiteralValue(), { kind: "unknown" }));
2167
2003
  }
2168
2004
  }
@@ -2173,15 +2009,15 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
2173
2009
  }
2174
2010
 
2175
2011
  // src/discovery/pipe-schema.ts
2176
- import { Node as Node9 } from "ts-morph";
2012
+ import { Node as Node8 } from "ts-morph";
2177
2013
 
2178
2014
  // src/discovery/schema-type-inference.ts
2179
2015
  import {
2180
- ts as ts2
2016
+ ts as ts3
2181
2017
  } from "ts-morph";
2182
2018
 
2183
2019
  // src/discovery/schema-type-program.ts
2184
- import { Node as Node7, Project as Project3, ts } from "ts-morph";
2020
+ import { FileSystemRefreshResult as FileSystemRefreshResult2, Node as Node6, Project as Project2, ts as ts2 } from "ts-morph";
2185
2021
  var PARAM_DECORATORS = /* @__PURE__ */ new Set(["Body", "Query", "Param"]);
2186
2022
  var SchemaTypeProgram = class {
2187
2023
  constructor(discovery) {
@@ -2191,10 +2027,10 @@ var SchemaTypeProgram = class {
2191
2027
  ...base,
2192
2028
  // A tsconfig-less consumer gets ES5 by default, whose lib has no `Map`/`Set`/
2193
2029
  // `Promise` for the schema libraries' declarations to lean on.
2194
- target: base.target ?? ts.ScriptTarget.ES2022,
2030
+ target: base.target ?? ts2.ScriptTarget.ES2022,
2195
2031
  // With neither set, resolve the way a bundler does: package `exports`
2196
2032
  // (`zod/v4`) and extensionless or `.js` relative imports all resolve.
2197
- ...base.module === void 0 && base.moduleResolution === void 0 ? { module: ts.ModuleKind.ESNext, moduleResolution: ts.ModuleResolutionKind.Bundler } : {},
2033
+ ...base.module === void 0 && base.moduleResolution === void 0 ? { module: ts2.ModuleKind.ESNext, moduleResolution: ts2.ModuleResolutionKind.Bundler } : {},
2198
2034
  // Without it `.nullable()` and `.optional()` would erase to the bare type.
2199
2035
  strictNullChecks: true,
2200
2036
  // No ambient `@types/*` (a tsconfig's `types: ['node']`): a schema does not
@@ -2202,17 +2038,16 @@ var SchemaTypeProgram = class {
2202
2038
  types: [],
2203
2039
  noEmit: true
2204
2040
  };
2205
- this.project = new Project3({
2041
+ this.project = new Project2({
2206
2042
  skipAddingFilesFromTsConfig: true,
2207
2043
  compilerOptions: this.options
2208
2044
  });
2209
- for (const sf of discovery.getSourceFiles()) this.request(sf.getFilePath(), { schema: true });
2210
- this.flush();
2045
+ this.requestDiscoveryFiles();
2211
2046
  }
2212
2047
  project;
2213
2048
  options;
2214
2049
  /** Parses a file that is not in discovery's Project, without adding it there. */
2215
- scratch = new Project3({
2050
+ scratch = new Project2({
2216
2051
  skipAddingFilesFromTsConfig: true,
2217
2052
  skipLoadingLibFiles: true,
2218
2053
  skipFileDependencyResolution: true
@@ -2220,6 +2055,53 @@ var SchemaTypeProgram = class {
2220
2055
  needs = /* @__PURE__ */ new Map();
2221
2056
  /** Pruned text per file, as last written into {@link project}. */
2222
2057
  written = /* @__PURE__ */ new Map();
2058
+ /** What {@link project} read from disk unpruned (packages, declaration files). */
2059
+ externalFiles = new DiskFingerprints();
2060
+ scratchFiles = new DiskFingerprints();
2061
+ /** The last pruning of each file, with the source text and needs it was pruned for. */
2062
+ pruned = /* @__PURE__ */ new Map();
2063
+ /**
2064
+ * Bring the program up to date with discovery's files and the disk, keeping the
2065
+ * lib and package declarations it has already parsed and checked. Leaves it
2066
+ * holding exactly the files a new program built now would hold, with the same
2067
+ * text. Returns false when a file the program read unpruned has changed on disk
2068
+ * (a package was reinstalled, a `.d.ts` was edited): the caller builds a new
2069
+ * program instead.
2070
+ */
2071
+ refresh() {
2072
+ const program = this.project.getProgram().compilerObject;
2073
+ const external = program.getSourceFiles().filter(
2074
+ (file) => !program.isSourceFileDefaultLibrary(file) && !this.written.has(file.fileName)
2075
+ ).map((file) => ({ path: file.fileName, text: file.text }));
2076
+ if (this.externalFiles.changed(external).length > 0) return false;
2077
+ const scratch = this.scratch.getSourceFiles().map((sf) => ({ path: sf.getFilePath(), text: sf.getFullText() }));
2078
+ for (const path of this.scratchFiles.changed(scratch)) {
2079
+ const sf = this.scratch.getSourceFile(path);
2080
+ if (sf?.refreshFromFileSystemSync() === FileSystemRefreshResult2.Deleted) {
2081
+ this.scratchFiles.forget(path);
2082
+ }
2083
+ }
2084
+ this.needs.clear();
2085
+ this.requestDiscoveryFiles();
2086
+ for (const path of [...this.written.keys()]) {
2087
+ if (this.needs.has(path)) continue;
2088
+ const sf = this.project.getSourceFile(path);
2089
+ if (sf) this.project.removeSourceFile(sf);
2090
+ this.written.delete(path);
2091
+ }
2092
+ return true;
2093
+ }
2094
+ /**
2095
+ * Everything discovery holds now (the controllers) goes in before the first
2096
+ * query: a file added afterwards discards the program and its checker, and
2097
+ * rebuilding them once per controller is exactly the cost this avoids.
2098
+ */
2099
+ requestDiscoveryFiles() {
2100
+ for (const sf of this.discovery.getSourceFiles()) {
2101
+ this.request(sf.getFilePath(), { schema: true });
2102
+ }
2103
+ this.flush();
2104
+ }
2223
2105
  /**
2224
2106
  * The typed copy of a discovery file, with its schema positions kept. Adding one
2225
2107
  * that was not there yet (a contract file discovery reached lazily) rebuilds the
@@ -2260,12 +2142,21 @@ var SchemaTypeProgram = class {
2260
2142
  while (this.queue.length > 0) {
2261
2143
  const path = this.queue.pop();
2262
2144
  const original = this.original(path);
2263
- if (!original) continue;
2264
- const needs = this.needs.get(path);
2265
- const text = this.prune(original, needs);
2145
+ if (!original) {
2146
+ if (this.written.has(path)) changed.set(path, void 0);
2147
+ continue;
2148
+ }
2149
+ const { text, requests } = this.pruneCached(path, original, this.needs.get(path));
2150
+ for (const { path: target, want } of requests) this.request(target, want);
2266
2151
  if (this.written.get(path) !== text) changed.set(path, text);
2267
2152
  }
2268
2153
  for (const [path, text] of changed) {
2154
+ if (text === void 0) {
2155
+ const sf = this.project.getSourceFile(path);
2156
+ if (sf) this.project.removeSourceFile(sf);
2157
+ this.written.delete(path);
2158
+ continue;
2159
+ }
2269
2160
  this.project.createSourceFile(path, text, { overwrite: true });
2270
2161
  this.written.set(path, text);
2271
2162
  }
@@ -2274,11 +2165,29 @@ var SchemaTypeProgram = class {
2274
2165
  original(path) {
2275
2166
  return this.discovery.getSourceFile(path) ?? this.scratch.getSourceFile(path) ?? this.scratch.addSourceFileAtPathIfExists(path);
2276
2167
  }
2168
+ /** {@link prune}, reusing the last result while the file's text and needs are the same. */
2169
+ pruneCached(path, sf, needs) {
2170
+ const source = sf.getFullText();
2171
+ const key = `${needs.schema}:${needs.all}:${[...needs.names].sort().join(",")}`;
2172
+ const last = this.pruned.get(path);
2173
+ if (last && last.source === source && last.needs === key) return last;
2174
+ const result = this.prune(sf, needs);
2175
+ if (result.resolved) this.pruned.set(path, { ...result, source, needs: key });
2176
+ else this.pruned.delete(path);
2177
+ return result;
2178
+ }
2277
2179
  /**
2278
2180
  * `sf`'s text with every import/re-export nothing in `needs` references blanked,
2279
- * requesting from each kept one's target the names it brings in.
2181
+ * and the names each kept one brings in from its target.
2280
2182
  */
2281
2183
  prune(sf, needs) {
2184
+ const requests = [];
2185
+ let resolved = true;
2186
+ const resolveFrom = (specifier) => {
2187
+ const target = this.resolve(specifier, sf);
2188
+ if (target === null) resolved = false;
2189
+ return target ?? void 0;
2190
+ };
2282
2191
  const locals = topLevelDeclarations(sf);
2283
2192
  const roots = [];
2284
2193
  if (needs.all) roots.push(...sf.getStatements());
@@ -2312,7 +2221,7 @@ var SchemaTypeProgram = class {
2312
2221
  const seen = new Set(queue);
2313
2222
  while (queue.length > 0) {
2314
2223
  const node = queue.pop();
2315
- const ids = Node7.isIdentifier(node) ? [node] : node.getDescendantsOfKind(ts.SyntaxKind.Identifier);
2224
+ const ids = Node6.isIdentifier(node) ? [node] : node.getDescendantsOfKind(ts2.SyntaxKind.Identifier);
2316
2225
  for (const id of ids) {
2317
2226
  const name = id.getText();
2318
2227
  if (referenced.has(name)) continue;
@@ -2331,7 +2240,7 @@ var SchemaTypeProgram = class {
2331
2240
  }
2332
2241
  };
2333
2242
  for (const decl of sf.getImportDeclarations()) {
2334
- const target = this.resolve(decl.getModuleSpecifierValue(), sf);
2243
+ const target = resolveFrom(decl.getModuleSpecifierValue());
2335
2244
  const namespace = decl.getNamespaceImport()?.getText();
2336
2245
  const def = decl.getDefaultImport()?.getText();
2337
2246
  const named = decl.getNamedImports().map((n) => ({
@@ -2345,21 +2254,24 @@ var SchemaTypeProgram = class {
2345
2254
  continue;
2346
2255
  }
2347
2256
  if (target) {
2348
- this.request(target, {
2349
- all: wantsNamespace,
2350
- names: [...wantsDefault ? ["default"] : [], ...named.map((n) => n.imported)]
2257
+ requests.push({
2258
+ path: target,
2259
+ want: {
2260
+ all: wantsNamespace,
2261
+ names: [...wantsDefault ? ["default"] : [], ...named.map((n) => n.imported)]
2262
+ }
2351
2263
  });
2352
2264
  }
2353
2265
  }
2354
2266
  for (const exp of sf.getExportDeclarations()) {
2355
2267
  const spec = exp.getModuleSpecifierValue();
2356
2268
  if (spec === void 0) continue;
2357
- const target = this.resolve(spec, sf);
2269
+ const target = resolveFrom(spec);
2358
2270
  if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) {
2359
2271
  if (!needs.all && unresolvedNames.length === 0) {
2360
2272
  blank(exp);
2361
2273
  } else if (target) {
2362
- this.request(target, { all: needs.all, names: unresolvedNames });
2274
+ requests.push({ path: target, want: { all: needs.all, names: unresolvedNames } });
2363
2275
  }
2364
2276
  continue;
2365
2277
  }
@@ -2367,22 +2279,24 @@ var SchemaTypeProgram = class {
2367
2279
  (n) => needs.all || needs.names.has((n.getAliasNode() ?? n.getNameNode()).getText())
2368
2280
  ).map((n) => n.getNameNode().getText());
2369
2281
  if (wanted.length === 0) blank(exp);
2370
- else if (target) this.request(target, { names: wanted });
2282
+ else if (target) requests.push({ path: target, want: { names: wanted } });
2371
2283
  }
2372
- return chars.join("");
2284
+ return { text: chars.join(""), requests, resolved };
2373
2285
  }
2374
2286
  /**
2375
- * The project source file a module specifier resolves to, or undefined for a
2376
- * package (read unpruned by the program itself) or an unresolvable one.
2287
+ * The project source file a module specifier resolves to; undefined for a
2288
+ * package or a declaration file (read unpruned by the program itself), null for
2289
+ * one that does not resolve.
2377
2290
  */
2378
2291
  resolve(specifier, from) {
2379
- const resolved = ts.resolveModuleName(
2292
+ const resolved = ts2.resolveModuleName(
2380
2293
  specifier,
2381
2294
  from.getFilePath(),
2382
2295
  this.options,
2383
- ts.sys
2296
+ ts2.sys
2384
2297
  ).resolvedModule;
2385
- if (!resolved || resolved.isExternalLibraryImport) return void 0;
2298
+ if (!resolved) return null;
2299
+ if (resolved.isExternalLibraryImport) return void 0;
2386
2300
  const file = resolved.resolvedFileName;
2387
2301
  if (file.endsWith(".d.ts") || !/\.(m|c)?tsx?$/.test(file)) return void 0;
2388
2302
  return file;
@@ -2391,20 +2305,20 @@ var SchemaTypeProgram = class {
2391
2305
  function schemaPositions(sf) {
2392
2306
  const roots = [];
2393
2307
  sf.forEachDescendant((node) => {
2394
- if (Node7.isDecorator(node)) {
2308
+ if (Node6.isDecorator(node)) {
2395
2309
  const name = node.getName();
2396
2310
  if (name === "ApplyContract") {
2397
2311
  roots.push(...node.getArguments());
2398
2312
  } else if (PARAM_DECORATORS.has(name)) {
2399
2313
  for (const arg of node.getArguments()) {
2400
- if (Node7.isNewExpression(arg) || Node7.isCallExpression(arg)) {
2314
+ if (Node6.isNewExpression(arg) || Node6.isCallExpression(arg)) {
2401
2315
  roots.push(...arg.getArguments());
2402
2316
  }
2403
2317
  }
2404
2318
  }
2405
- } else if (Node7.isCallExpression(node)) {
2319
+ } else if (Node6.isCallExpression(node)) {
2406
2320
  const callee = node.getExpression();
2407
- const name = Node7.isIdentifier(callee) ? callee.getText() : Node7.isPropertyAccessExpression(callee) ? callee.getName() : void 0;
2321
+ const name = Node6.isIdentifier(callee) ? callee.getText() : Node6.isPropertyAccessExpression(callee) ? callee.getName() : void 0;
2408
2322
  if (name === "defineContract") roots.push(...node.getArguments());
2409
2323
  }
2410
2324
  });
@@ -2437,8 +2351,13 @@ function defaultExport(sf) {
2437
2351
 
2438
2352
  // src/discovery/schema-type-inference.ts
2439
2353
  var programs = /* @__PURE__ */ new WeakMap();
2354
+ var stale = /* @__PURE__ */ new WeakSet();
2440
2355
  function programFor(discovery) {
2441
2356
  let program = programs.get(discovery);
2357
+ if (program && stale.has(discovery)) {
2358
+ stale.delete(discovery);
2359
+ if (!program.refresh()) program = void 0;
2360
+ }
2442
2361
  if (!program) {
2443
2362
  program = new SchemaTypeProgram(discovery);
2444
2363
  programs.set(discovery, program);
@@ -2447,6 +2366,10 @@ function programFor(discovery) {
2447
2366
  }
2448
2367
  function clearSchemaTypeProject(discovery) {
2449
2368
  programs.delete(discovery);
2369
+ stale.delete(discovery);
2370
+ }
2371
+ function invalidateSchemaTypeProject(discovery) {
2372
+ if (programs.has(discovery)) stale.add(discovery);
2450
2373
  }
2451
2374
  function counterpart(typedSf, node) {
2452
2375
  const start = node.getStart();
@@ -2510,7 +2433,7 @@ function objectKey(name) {
2510
2433
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
2511
2434
  }
2512
2435
  function isOptionalSymbol(sym) {
2513
- return (sym.getFlags() & ts2.SymbolFlags.Optional) !== 0;
2436
+ return (sym.getFlags() & ts3.SymbolFlags.Optional) !== 0;
2514
2437
  }
2515
2438
  function isSymbolKeyed(sym) {
2516
2439
  return sym.getName().startsWith("__@");
@@ -2671,11 +2594,11 @@ function acceptsUndefined(types) {
2671
2594
  }
2672
2595
 
2673
2596
  // src/discovery/zod-ast-to-ts.ts
2674
- import { Node as Node8, SyntaxKind as SyntaxKind3 } from "ts-morph";
2597
+ import { Node as Node7, SyntaxKind as SyntaxKind2 } from "ts-morph";
2675
2598
  function zodAstToTs(node) {
2676
- if (!Node8.isCallExpression(node)) return "unknown";
2599
+ if (!Node7.isCallExpression(node)) return "unknown";
2677
2600
  const expr = node.getExpression();
2678
- if (Node8.isPropertyAccessExpression(expr)) {
2601
+ if (Node7.isPropertyAccessExpression(expr)) {
2679
2602
  const methodName = expr.getName();
2680
2603
  const receiver = expr.getExpression();
2681
2604
  if (methodName === "optional") {
@@ -2699,17 +2622,17 @@ function zodAstToTs(node) {
2699
2622
  case "literal": {
2700
2623
  const lit = args[0];
2701
2624
  if (!lit) return "unknown";
2702
- if (Node8.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
2703
- if (Node8.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
2704
- if (lit.getKind() === SyntaxKind3.TrueKeyword) return "true";
2705
- if (lit.getKind() === SyntaxKind3.FalseKeyword) return "false";
2625
+ if (Node7.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
2626
+ if (Node7.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
2627
+ if (lit.getKind() === SyntaxKind2.TrueKeyword) return "true";
2628
+ if (lit.getKind() === SyntaxKind2.FalseKeyword) return "false";
2706
2629
  return "unknown";
2707
2630
  }
2708
2631
  case "enum": {
2709
2632
  const arrArg = args[0];
2710
- if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
2633
+ if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
2711
2634
  const members = arrArg.getElements().map(
2712
- (el) => Node8.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
2635
+ (el) => Node7.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
2713
2636
  );
2714
2637
  return members.join(" | ");
2715
2638
  }
@@ -2720,10 +2643,10 @@ function zodAstToTs(node) {
2720
2643
  }
2721
2644
  case "object": {
2722
2645
  const objArg = args[0];
2723
- if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
2646
+ if (!objArg || !Node7.isObjectLiteralExpression(objArg)) return "unknown";
2724
2647
  const lines = [];
2725
2648
  for (const prop of objArg.getProperties()) {
2726
- if (!Node8.isPropertyAssignment(prop)) continue;
2649
+ if (!Node7.isPropertyAssignment(prop)) continue;
2727
2650
  const key = prop.getName();
2728
2651
  const valNode = prop.getInitializer();
2729
2652
  if (!valNode) continue;
@@ -2735,7 +2658,7 @@ function zodAstToTs(node) {
2735
2658
  }
2736
2659
  case "union": {
2737
2660
  const arrArg = args[0];
2738
- if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
2661
+ if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
2739
2662
  return arrArg.getElements().map(zodAstToTs).join(" | ");
2740
2663
  }
2741
2664
  case "record": {
@@ -2745,7 +2668,7 @@ function zodAstToTs(node) {
2745
2668
  }
2746
2669
  case "tuple": {
2747
2670
  const arrArg = args[0];
2748
- if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
2671
+ if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
2749
2672
  return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
2750
2673
  }
2751
2674
  default:
@@ -2755,9 +2678,9 @@ function zodAstToTs(node) {
2755
2678
  return "unknown";
2756
2679
  }
2757
2680
  function isOptionalChain(node) {
2758
- if (!Node8.isCallExpression(node)) return false;
2681
+ if (!Node7.isCallExpression(node)) return false;
2759
2682
  const expr = node.getExpression();
2760
- return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
2683
+ return Node7.isPropertyAccessExpression(expr) && expr.getName() === "optional";
2761
2684
  }
2762
2685
  function memberType(val, position, project) {
2763
2686
  if (project) {
@@ -2769,13 +2692,13 @@ function memberType(val, position, project) {
2769
2692
  return zodAstToTs(val);
2770
2693
  }
2771
2694
  function parseDefineContractCall(callExpr, project) {
2772
- if (!Node8.isCallExpression(callExpr)) return null;
2695
+ if (!Node7.isCallExpression(callExpr)) return null;
2773
2696
  const callee = callExpr.getExpression();
2774
- const calleeName = Node8.isIdentifier(callee) ? callee.getText() : Node8.isPropertyAccessExpression(callee) ? callee.getName() : "";
2697
+ const calleeName = Node7.isIdentifier(callee) ? callee.getText() : Node7.isPropertyAccessExpression(callee) ? callee.getName() : "";
2775
2698
  if (calleeName !== "defineContract") return null;
2776
2699
  const args = callExpr.getArguments();
2777
2700
  const optsArg = args[0];
2778
- if (!optsArg || !Node8.isObjectLiteralExpression(optsArg)) return null;
2701
+ if (!optsArg || !Node7.isObjectLiteralExpression(optsArg)) return null;
2779
2702
  let query = null;
2780
2703
  let body = null;
2781
2704
  let response = "unknown";
@@ -2783,7 +2706,7 @@ function parseDefineContractCall(callExpr, project) {
2783
2706
  let bodyZodText = null;
2784
2707
  let queryZodText = null;
2785
2708
  for (const prop of optsArg.getProperties()) {
2786
- if (!Node8.isPropertyAssignment(prop)) continue;
2709
+ if (!Node7.isPropertyAssignment(prop)) continue;
2787
2710
  const propName = prop.getName();
2788
2711
  const val = prop.getInitializer();
2789
2712
  if (!val) continue;
@@ -2805,7 +2728,7 @@ function parseDefineContractCall(callExpr, project) {
2805
2728
  // src/discovery/pipe-schema.ts
2806
2729
  function pipeSchemaOf(decorator, project) {
2807
2730
  for (const pipe of decorator.getArguments()) {
2808
- if (!Node9.isNewExpression(pipe) && !Node9.isCallExpression(pipe)) continue;
2731
+ if (!Node8.isNewExpression(pipe) && !Node8.isCallExpression(pipe)) continue;
2809
2732
  for (const arg of pipe.getArguments()) {
2810
2733
  if (!couldBeSchema(arg)) continue;
2811
2734
  const inferred = inferSchema(arg, project);
@@ -2827,15 +2750,15 @@ function pipeSchemaOf(decorator, project) {
2827
2750
  return null;
2828
2751
  }
2829
2752
  function couldBeSchema(arg) {
2830
- return Node9.isIdentifier(arg) || Node9.isPropertyAccessExpression(arg) || Node9.isCallExpression(arg) || Node9.isAsExpression(arg) || Node9.isSatisfiesExpression(arg) || Node9.isParenthesizedExpression(arg);
2753
+ return Node8.isIdentifier(arg) || Node8.isPropertyAccessExpression(arg) || Node8.isCallExpression(arg) || Node8.isAsExpression(arg) || Node8.isSatisfiesExpression(arg) || Node8.isParenthesizedExpression(arg);
2831
2754
  }
2832
2755
  function syntacticZodType(arg, project) {
2833
2756
  let expr = arg;
2834
- if (Node9.isIdentifier(arg)) {
2757
+ if (Node8.isIdentifier(arg)) {
2835
2758
  const resolved = resolveImportedVariable(arg.getText(), arg.getSourceFile(), project);
2836
2759
  expr = resolved?.decl.getInitializer();
2837
2760
  }
2838
- if (!expr || !Node9.isCallExpression(expr)) return null;
2761
+ if (!expr || !Node8.isCallExpression(expr)) return null;
2839
2762
  const type = zodAstToTs(expr);
2840
2763
  return type === "unknown" ? null : type;
2841
2764
  }
@@ -2871,22 +2794,22 @@ var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
2871
2794
  ]);
2872
2795
  function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
2873
2796
  if (depth <= 0) return "unknown";
2874
- if (Node10.isArrayTypeNode(typeNode)) {
2797
+ if (Node9.isArrayTypeNode(typeNode)) {
2875
2798
  const elementType = typeNode.getElementTypeNode();
2876
2799
  return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth, subst)}>`;
2877
2800
  }
2878
- if (Node10.isUnionTypeNode(typeNode)) {
2801
+ if (Node9.isUnionTypeNode(typeNode)) {
2879
2802
  return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" | ");
2880
2803
  }
2881
- if (Node10.isIntersectionTypeNode(typeNode)) {
2804
+ if (Node9.isIntersectionTypeNode(typeNode)) {
2882
2805
  return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" & ");
2883
2806
  }
2884
- if (Node10.isParenthesizedTypeNode(typeNode)) {
2807
+ if (Node9.isParenthesizedTypeNode(typeNode)) {
2885
2808
  return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth, subst)})`;
2886
2809
  }
2887
- if (Node10.isTypeReference(typeNode)) {
2810
+ if (Node9.isTypeReference(typeNode)) {
2888
2811
  const typeName = typeNode.getTypeName();
2889
- const name = Node10.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
2812
+ const name = Node9.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
2890
2813
  const bound = subst.get(name);
2891
2814
  if (bound !== void 0) return bound;
2892
2815
  if (name === "string" || name === "number" || name === "boolean") return name;
@@ -2909,10 +2832,10 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
2909
2832
  dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
2910
2833
  return "unknown";
2911
2834
  }
2912
- if (Node10.isTypeLiteral(typeNode)) {
2835
+ if (Node9.isTypeLiteral(typeNode)) {
2913
2836
  const members = [];
2914
2837
  for (const member of typeNode.getMembers()) {
2915
- if (Node10.isPropertySignature(member)) {
2838
+ if (Node9.isPropertySignature(member)) {
2916
2839
  const memberTypeNode = member.getTypeNode();
2917
2840
  const memberType2 = memberTypeNode ? resolveTypeNodeToString(memberTypeNode, sourceFile, project, depth, subst) : "unknown";
2918
2841
  members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${memberType2}`);
@@ -2923,11 +2846,11 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
2923
2846
  return members.length > 0 ? `{ ${members.join("; ")} }` : "{}";
2924
2847
  }
2925
2848
  const kind = typeNode.getKind();
2926
- if (kind === SyntaxKind4.StringKeyword) return "string";
2927
- if (kind === SyntaxKind4.NumberKeyword) return "number";
2928
- if (kind === SyntaxKind4.BooleanKeyword) return "boolean";
2929
- if (kind === SyntaxKind4.UnknownKeyword) return "unknown";
2930
- if (kind === SyntaxKind4.AnyKeyword) return "unknown";
2849
+ if (kind === SyntaxKind3.StringKeyword) return "string";
2850
+ if (kind === SyntaxKind3.NumberKeyword) return "number";
2851
+ if (kind === SyntaxKind3.BooleanKeyword) return "boolean";
2852
+ if (kind === SyntaxKind3.UnknownKeyword) return "unknown";
2853
+ if (kind === SyntaxKind3.AnyKeyword) return "unknown";
2931
2854
  return typeNode.getText();
2932
2855
  }
2933
2856
  function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode, subst = /* @__PURE__ */ new Map()) {
@@ -2985,7 +2908,7 @@ function resolvePropertied(decl, sourceFile, project, depth, subst = /* @__PURE_
2985
2908
  }
2986
2909
  function namedField(decorator) {
2987
2910
  const first = decorator.getArguments()[0];
2988
- return first && Node10.isStringLiteral(first) ? first.getLiteralValue() : null;
2911
+ return first && Node9.isStringLiteral(first) ? first.getLiteralValue() : null;
2989
2912
  }
2990
2913
  function extractBodyType(method, sourceFile, project) {
2991
2914
  for (const param of method.getParameters()) {
@@ -3035,8 +2958,8 @@ function extractQueryType(method, sourceFile, project) {
3035
2958
  function isParamOptional(param) {
3036
2959
  if (param.hasQuestionToken() || param.hasInitializer()) return true;
3037
2960
  const typeNode = param.getTypeNode();
3038
- if (typeNode && Node10.isUnionTypeNode(typeNode)) {
3039
- return typeNode.getTypeNodes().some((t) => t.getKind() === SyntaxKind4.UndefinedKeyword);
2961
+ if (typeNode && Node9.isUnionTypeNode(typeNode)) {
2962
+ return typeNode.getTypeNodes().some((t) => t.getKind() === SyntaxKind3.UndefinedKeyword);
3040
2963
  }
3041
2964
  return false;
3042
2965
  }
@@ -3048,7 +2971,7 @@ function extractParamsType(method, sourceFile, project) {
3048
2971
  const paramArgs = paramDecorator.getArguments();
3049
2972
  if (paramArgs.length === 0) continue;
3050
2973
  const nameArg = paramArgs[0];
3051
- if (!Node10.isStringLiteral(nameArg)) continue;
2974
+ if (!Node9.isStringLiteral(nameArg)) continue;
3052
2975
  const paramName = nameArg.getLiteralValue();
3053
2976
  const typeNode = param.getTypeNode();
3054
2977
  const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
@@ -3085,28 +3008,28 @@ function extractUploadedFiles(method) {
3085
3008
  for (const decorator of method.getDecorators()) {
3086
3009
  if (decorator.getName() !== "UseInterceptors") continue;
3087
3010
  for (const arg of decorator.getArguments()) {
3088
- if (!Node10.isCallExpression(arg)) continue;
3011
+ if (!Node9.isCallExpression(arg)) continue;
3089
3012
  const interceptor = arg.getExpression().getText();
3090
3013
  const callArgs = arg.getArguments();
3091
3014
  const firstArg2 = callArgs[0];
3092
3015
  if (interceptor === "FileInterceptor") {
3093
- if (firstArg2 && Node10.isStringLiteral(firstArg2)) {
3016
+ if (firstArg2 && Node9.isStringLiteral(firstArg2)) {
3094
3017
  entries.push(`${firstArg2.getLiteralValue()}: ${FILE}`);
3095
3018
  multipart = true;
3096
3019
  }
3097
3020
  } else if (interceptor === "FilesInterceptor") {
3098
- if (firstArg2 && Node10.isStringLiteral(firstArg2)) {
3021
+ if (firstArg2 && Node9.isStringLiteral(firstArg2)) {
3099
3022
  entries.push(`${firstArg2.getLiteralValue()}: Array<${FILE}>`);
3100
3023
  multipart = true;
3101
3024
  }
3102
3025
  } else if (interceptor === "FileFieldsInterceptor") {
3103
- if (firstArg2 && Node10.isArrayLiteralExpression(firstArg2)) {
3026
+ if (firstArg2 && Node9.isArrayLiteralExpression(firstArg2)) {
3104
3027
  for (const el of firstArg2.getElements()) {
3105
- if (!Node10.isObjectLiteralExpression(el)) continue;
3028
+ if (!Node9.isObjectLiteralExpression(el)) continue;
3106
3029
  const nameProp = el.getProperty("name");
3107
- if (nameProp && Node10.isPropertyAssignment(nameProp)) {
3030
+ if (nameProp && Node9.isPropertyAssignment(nameProp)) {
3108
3031
  const init = nameProp.getInitializer();
3109
- if (init && Node10.isStringLiteral(init)) {
3032
+ if (init && Node9.isStringLiteral(init)) {
3110
3033
  entries.push(`${init.getLiteralValue()}: Array<${FILE}>`);
3111
3034
  }
3112
3035
  }
@@ -3126,13 +3049,13 @@ function extractResponseType(method, sourceFile, project) {
3126
3049
  if (apiResponseDecorator) {
3127
3050
  const args = apiResponseDecorator.getArguments();
3128
3051
  const optsArg = args[0];
3129
- if (optsArg && Node10.isObjectLiteralExpression(optsArg)) {
3052
+ if (optsArg && Node9.isObjectLiteralExpression(optsArg)) {
3130
3053
  for (const prop of optsArg.getProperties()) {
3131
- if (!Node10.isPropertyAssignment(prop)) continue;
3054
+ if (!Node9.isPropertyAssignment(prop)) continue;
3132
3055
  if (prop.getName() !== "type") continue;
3133
3056
  const val = prop.getInitializer();
3134
3057
  if (!val) continue;
3135
- if (Node10.isArrayLiteralExpression(val)) {
3058
+ if (Node9.isArrayLiteralExpression(val)) {
3136
3059
  const elements = val.getElements();
3137
3060
  const firstEl = elements[0];
3138
3061
  if (elements.length > 0 && firstEl !== void 0) {
@@ -3153,24 +3076,24 @@ function extractResponseType(method, sourceFile, project) {
3153
3076
  }
3154
3077
  function apiResponseStatus(decorator) {
3155
3078
  const optsArg = decorator.getArguments()[0];
3156
- if (!optsArg || !Node10.isObjectLiteralExpression(optsArg)) return null;
3079
+ if (!optsArg || !Node9.isObjectLiteralExpression(optsArg)) return null;
3157
3080
  for (const prop of optsArg.getProperties()) {
3158
- if (!Node10.isPropertyAssignment(prop)) continue;
3081
+ if (!Node9.isPropertyAssignment(prop)) continue;
3159
3082
  if (prop.getName() !== "status") continue;
3160
3083
  const val = prop.getInitializer();
3161
- if (val && Node10.isNumericLiteral(val)) return Number(val.getLiteralValue());
3084
+ if (val && Node9.isNumericLiteral(val)) return Number(val.getLiteralValue());
3162
3085
  }
3163
3086
  return null;
3164
3087
  }
3165
3088
  function apiResponseTypeNode(decorator) {
3166
3089
  const optsArg = decorator.getArguments()[0];
3167
- if (!optsArg || !Node10.isObjectLiteralExpression(optsArg)) return null;
3090
+ if (!optsArg || !Node9.isObjectLiteralExpression(optsArg)) return null;
3168
3091
  for (const prop of optsArg.getProperties()) {
3169
- if (!Node10.isPropertyAssignment(prop)) continue;
3092
+ if (!Node9.isPropertyAssignment(prop)) continue;
3170
3093
  if (prop.getName() !== "type") continue;
3171
3094
  const val = prop.getInitializer();
3172
3095
  if (!val) return null;
3173
- if (Node10.isArrayLiteralExpression(val)) {
3096
+ if (Node9.isArrayLiteralExpression(val)) {
3174
3097
  const first = val.getElements()[0];
3175
3098
  return first ? { node: first, isArray: true } : null;
3176
3099
  }
@@ -3188,7 +3111,7 @@ function extractErrorType(method, sourceFile, project) {
3188
3111
  const inner = resolveIdentifierToClassType(typeInfo.node, sourceFile, project, 3);
3189
3112
  const type = typeInfo.isArray ? `Array<${inner}>` : inner;
3190
3113
  let ref = null;
3191
- if (Node10.isIdentifier(typeInfo.node)) {
3114
+ if (Node9.isIdentifier(typeInfo.node)) {
3192
3115
  const name = typeInfo.node.getText();
3193
3116
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
3194
3117
  if (localDecl?.isExported()) {
@@ -3205,7 +3128,7 @@ function extractErrorType(method, sourceFile, project) {
3205
3128
  return null;
3206
3129
  }
3207
3130
  function resolveIdentifierToClassType(node, sourceFile, project, depth) {
3208
- if (!Node10.isIdentifier(node)) return "unknown";
3131
+ if (!Node9.isIdentifier(node)) return "unknown";
3209
3132
  const name = node.getText();
3210
3133
  const resolved = findType(name, sourceFile, project);
3211
3134
  if (resolved) {
@@ -3225,9 +3148,9 @@ var STREAM_ENVELOPES = /* @__PURE__ */ new Set(["MessageEvent", "MessageEventLik
3225
3148
  var BINARY_RESPONSE_TYPES = /* @__PURE__ */ new Set(["StreamableFile", "Buffer"]);
3226
3149
  function detectBinaryResponse(method) {
3227
3150
  const node = unwrapNamedContainer(method.getReturnTypeNode(), /* @__PURE__ */ new Set(["Promise"]));
3228
- if (!node || !Node10.isTypeReference(node)) return false;
3151
+ if (!node || !Node9.isTypeReference(node)) return false;
3229
3152
  const typeName = node.getTypeName();
3230
- const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
3153
+ const name = Node9.isIdentifier(typeName) ? typeName.getText() : "";
3231
3154
  return BINARY_RESPONSE_TYPES.has(name);
3232
3155
  }
3233
3156
  function detectStreamElement(method) {
@@ -3242,9 +3165,9 @@ function detectStreamElement(method) {
3242
3165
  return null;
3243
3166
  }
3244
3167
  function streamContainerElement(node) {
3245
- if (!node || !Node10.isTypeReference(node)) return null;
3168
+ if (!node || !Node9.isTypeReference(node)) return null;
3246
3169
  const typeName = node.getTypeName();
3247
- const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
3170
+ const name = Node9.isIdentifier(typeName) ? typeName.getText() : "";
3248
3171
  if (STREAM_CONTAINERS.has(name) || STREAM_CONTAINERS_GENERATOR.has(name)) {
3249
3172
  return node.getTypeArguments()[0] ?? null;
3250
3173
  }
@@ -3254,9 +3177,9 @@ function hasAsQueryDecorator(method) {
3254
3177
  return method.getDecorators().some((d) => d.getName() === "AsQuery");
3255
3178
  }
3256
3179
  function unwrapNamedContainer(node, names) {
3257
- if (!node || !Node10.isTypeReference(node)) return node;
3180
+ if (!node || !Node9.isTypeReference(node)) return node;
3258
3181
  const typeName = node.getTypeName();
3259
- const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
3182
+ const name = Node9.isIdentifier(typeName) ? typeName.getText() : "";
3260
3183
  if (names.has(name)) {
3261
3184
  return node.getTypeArguments()[0] ?? node;
3262
3185
  }
@@ -3303,11 +3226,11 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
3303
3226
  if (apiResp) {
3304
3227
  const args = apiResp.getArguments();
3305
3228
  const optsArg = args[0];
3306
- if (optsArg && Node10.isObjectLiteralExpression(optsArg)) {
3229
+ if (optsArg && Node9.isObjectLiteralExpression(optsArg)) {
3307
3230
  for (const prop of optsArg.getProperties()) {
3308
- if (Node10.isPropertyAssignment(prop) && prop.getName() === "type") {
3231
+ if (Node9.isPropertyAssignment(prop) && prop.getName() === "type") {
3309
3232
  const val = prop.getInitializer();
3310
- if (val && Node10.isIdentifier(val)) {
3233
+ if (val && Node9.isIdentifier(val)) {
3311
3234
  const name = val.getText();
3312
3235
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
3313
3236
  if (localDecl?.isExported()) {
@@ -3394,7 +3317,7 @@ async function discoverContractsFast(opts) {
3394
3317
  return routes;
3395
3318
  }
3396
3319
  function resolveTsconfigPath(cwd, tsconfig) {
3397
- return tsconfig ? resolve3(tsconfig) : join4(cwd, "tsconfig.json");
3320
+ return tsconfig ? resolve3(tsconfig) : join3(cwd, "tsconfig.json");
3398
3321
  }
3399
3322
  var NO_TSCONFIG_OPTIONS = {
3400
3323
  allowJs: true,
@@ -3403,25 +3326,25 @@ var NO_TSCONFIG_OPTIONS = {
3403
3326
  };
3404
3327
  function loadDiscoveryTsconfig(tsconfigPath) {
3405
3328
  if (!existsSync(tsconfigPath)) return { options: null, files: [] };
3406
- const read = ts3.readConfigFile(tsconfigPath, (path) => ts3.sys.readFile(path));
3329
+ const read = ts4.readConfigFile(tsconfigPath, (path) => ts4.sys.readFile(path));
3407
3330
  if (read.error) {
3408
3331
  return {
3409
3332
  options: null,
3410
3333
  files: [tsconfigPath],
3411
- error: ts3.flattenDiagnosticMessageText(read.error.messageText, " ")
3334
+ error: ts4.flattenDiagnosticMessageText(read.error.messageText, " ")
3412
3335
  };
3413
3336
  }
3414
3337
  const host = {
3415
- useCaseSensitiveFileNames: ts3.sys.useCaseSensitiveFileNames,
3338
+ useCaseSensitiveFileNames: ts4.sys.useCaseSensitiveFileNames,
3416
3339
  // The one line that keeps a tsconfig from being a filesystem walk.
3417
3340
  readDirectory: () => [],
3418
- fileExists: (path) => ts3.sys.fileExists(path),
3341
+ fileExists: (path) => ts4.sys.fileExists(path),
3419
3342
  // `extends` is resolved through the host, so a consumer whose `paths` live
3420
3343
  // in a base tsconfig keeps working.
3421
- readFile: (path) => ts3.sys.readFile(path)
3344
+ readFile: (path) => ts4.sys.readFile(path)
3422
3345
  };
3423
- const sourceFile = ts3.readJsonConfigFile(tsconfigPath, (path) => ts3.sys.readFile(path));
3424
- const parsed = ts3.parseJsonSourceFileConfigFileContent(
3346
+ const sourceFile = ts4.readJsonConfigFile(tsconfigPath, (path) => ts4.sys.readFile(path));
3347
+ const parsed = ts4.parseJsonSourceFileConfigFileContent(
3425
3348
  sourceFile,
3426
3349
  host,
3427
3350
  dirname2(tsconfigPath),
@@ -3444,7 +3367,7 @@ function createDiscoveryProject(tsconfigPath, tsconfig = loadDiscoveryTsconfig(t
3444
3367
  `[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.`
3445
3368
  );
3446
3369
  }
3447
- return new Project4({
3370
+ return new Project3({
3448
3371
  compilerOptions: tsconfig.options ?? { ...NO_TSCONFIG_OPTIONS },
3449
3372
  skipAddingFilesFromTsConfig: true,
3450
3373
  skipLoadingLibFiles: true,
@@ -3486,6 +3409,9 @@ var PersistentDiscovery = class _PersistentDiscovery {
3486
3409
  glob;
3487
3410
  /** Absolute paths of the controllers currently loaded as extraction roots. */
3488
3411
  controllerPaths = /* @__PURE__ */ new Set();
3412
+ /** Every file in {@link project}, as last read from disk. */
3413
+ files = new DiskFingerprints();
3414
+ unresolved = new UnresolvedImports();
3489
3415
  constructor(project, cwd, glob) {
3490
3416
  this.project = project;
3491
3417
  this.cwd = cwd;
@@ -3511,6 +3437,8 @@ var PersistentDiscovery = class _PersistentDiscovery {
3511
3437
  }
3512
3438
  /** Run the initial extraction (equivalent to a first `discoverContractsFast`). */
3513
3439
  discover() {
3440
+ clearMixinTypeProject();
3441
+ clearSchemaTypeProject(this.project);
3514
3442
  return this.runExtraction();
3515
3443
  }
3516
3444
  /**
@@ -3518,26 +3446,39 @@ var PersistentDiscovery = class _PersistentDiscovery {
3518
3446
  * from disk (controllers AND any lazily-loaded DTO/imported files), re-globs
3519
3447
  * to pick up added/removed controllers, clears the per-Project caches, then
3520
3448
  * re-extracts. `changedPaths` is a hint; correctness does not depend on it
3521
- * being exhaustive because re-globbing + refresh-on-presence covers the set.
3449
+ * being exhaustive because re-globbing + comparing every loaded file with the
3450
+ * disk covers the set.
3522
3451
  */
3523
- async rediscover(changedPaths) {
3524
- if (changedPaths) {
3525
- for (const p of changedPaths) {
3526
- const abs = resolve3(p);
3527
- const sf = this.project.getSourceFile(abs);
3528
- if (sf) {
3529
- await sf.refreshFromFileSystem();
3530
- }
3452
+ async rediscover(changedPaths, options = {}) {
3453
+ const hinted = new Set([...changedPaths ?? []].map((p) => resolve3(p)));
3454
+ const program = this.project.getProgram().compilerObject;
3455
+ const sources = program.getSourceFiles().filter(
3456
+ (file) => !program.isSourceFileDefaultLibrary(file) && !program.isSourceFileFromExternalLibrary(file)
3457
+ ).map((file) => ({ path: file.fileName, text: file.text }));
3458
+ for (const path of /* @__PURE__ */ new Set([...hinted, ...this.files.changed(sources)])) {
3459
+ const sf = this.project.getSourceFile(path);
3460
+ if (sf && await sf.refreshFromFileSystem() === FileSystemRefreshResult3.Deleted) {
3461
+ this.files.forget(path);
3531
3462
  }
3532
3463
  }
3464
+ const inProject = new Set(this.project.getSourceFiles().map((sf) => sf.getFilePath()));
3465
+ const definitions = this.project.getLanguageService().compilerObject.getProgram();
3466
+ for (const path of definitions ? this.unresolved.nowResolving(definitions) : []) {
3467
+ const sf = this.project.getSourceFile(path);
3468
+ if (!sf) continue;
3469
+ this.project.removeSourceFile(sf);
3470
+ if (inProject.has(path)) this.project.addSourceFileAtPathIfExists(path);
3471
+ }
3533
3472
  const globbed = new Set(
3534
3473
  await fg2(this.glob, { cwd: this.cwd, absolute: true, onlyFiles: true })
3535
3474
  );
3475
+ let controllersChanged = false;
3536
3476
  for (const f of globbed) {
3537
3477
  if (!this.controllerPaths.has(f)) {
3538
3478
  try {
3539
3479
  this.project.addSourceFileAtPath(f);
3540
3480
  this.controllerPaths.add(f);
3481
+ controllersChanged = true;
3541
3482
  } catch {
3542
3483
  }
3543
3484
  }
@@ -3547,25 +3488,31 @@ var PersistentDiscovery = class _PersistentDiscovery {
3547
3488
  const sf = this.project.getSourceFile(f);
3548
3489
  if (sf) this.project.removeSourceFile(sf);
3549
3490
  this.controllerPaths.delete(f);
3491
+ controllersChanged = true;
3550
3492
  }
3551
3493
  }
3494
+ if (controllersChanged || options.filesAddedOrRemoved) {
3495
+ clearMixinTypeProject();
3496
+ clearSchemaTypeProject(this.project);
3497
+ } else {
3498
+ invalidateMixinTypeProject();
3499
+ invalidateSchemaTypeProject(this.project);
3500
+ }
3552
3501
  return this.runExtraction();
3553
3502
  }
3554
3503
  /** Clear stale per-Project caches, then extract over the controller set. */
3555
3504
  runExtraction() {
3556
3505
  clearTypeResolutionCaches(this.project);
3557
3506
  clearEnumCache(this.project);
3558
- clearMixinTypeProject();
3559
- clearSchemaTypeProject(this.project);
3560
3507
  return extractRoutesFrom(this.project, this.controllerPaths);
3561
3508
  }
3562
3509
  };
3563
3510
  function decoratorStringArg(decoratorExpr) {
3564
3511
  if (!decoratorExpr) return void 0;
3565
- if (Node11.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
3566
- if (Node11.isArrayLiteralExpression(decoratorExpr)) {
3512
+ if (Node10.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
3513
+ if (Node10.isArrayLiteralExpression(decoratorExpr)) {
3567
3514
  const first = decoratorExpr.getElements()[0];
3568
- if (first && Node11.isStringLiteral(first)) return first.getLiteralValue();
3515
+ if (first && Node10.isStringLiteral(first)) return first.getLiteralValue();
3569
3516
  }
3570
3517
  return void 0;
3571
3518
  }
@@ -3686,9 +3633,9 @@ function extractContractRoute(args) {
3686
3633
  let contractDef = null;
3687
3634
  let bodyZodRef = null;
3688
3635
  let queryZodRef = null;
3689
- if (Node11.isCallExpression(firstDecoratorArg)) {
3636
+ if (Node10.isCallExpression(firstDecoratorArg)) {
3690
3637
  contractDef = parseDefineContractCall(firstDecoratorArg, project);
3691
- } else if (Node11.isIdentifier(firstDecoratorArg)) {
3638
+ } else if (Node10.isIdentifier(firstDecoratorArg)) {
3692
3639
  const identName = firstDecoratorArg.getText();
3693
3640
  const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
3694
3641
  if (!resolvedVar) {
@@ -3843,7 +3790,300 @@ function extractFromSourceFile(sourceFile, project) {
3843
3790
  return routes;
3844
3791
  }
3845
3792
 
3793
+ // src/discovery/shared-props.ts
3794
+ function discoverSharedProps(project, moduleEntry) {
3795
+ try {
3796
+ let sourceFile = project.getSourceFile(moduleEntry);
3797
+ if (!sourceFile) {
3798
+ try {
3799
+ sourceFile = project.addSourceFileAtPath(moduleEntry);
3800
+ } catch {
3801
+ return null;
3802
+ }
3803
+ }
3804
+ const forRootCall = findForRootCall(sourceFile);
3805
+ if (!forRootCall) return null;
3806
+ const initializer = findShareInitializer(forRootCall);
3807
+ if (!initializer) return null;
3808
+ return extractShareType(initializer, sourceFile, project);
3809
+ } catch {
3810
+ return null;
3811
+ }
3812
+ }
3813
+ function discoverSharedPropsFromConfig(config) {
3814
+ if (!config.app?.moduleEntry) return null;
3815
+ try {
3816
+ const tsconfigPath = config.app.tsconfig ?? join4(config.codegen.cwd, "tsconfig.json");
3817
+ let project;
3818
+ try {
3819
+ project = sharedPropsProject(tsconfigPath);
3820
+ } catch {
3821
+ project = new Project4({
3822
+ skipAddingFilesFromTsConfig: true,
3823
+ skipLoadingLibFiles: true,
3824
+ skipFileDependencyResolution: true,
3825
+ compilerOptions: { allowJs: true, strict: false }
3826
+ });
3827
+ }
3828
+ return discoverSharedProps(project, config.app.moduleEntry);
3829
+ } catch {
3830
+ return null;
3831
+ }
3832
+ }
3833
+ var parsedTsconfigs = /* @__PURE__ */ new Map();
3834
+ function readText(file) {
3835
+ try {
3836
+ return readFileSync2(file, "utf8");
3837
+ } catch {
3838
+ return "\0missing";
3839
+ }
3840
+ }
3841
+ function sharedPropsProject(tsconfigPath) {
3842
+ const text = loadDiscoveryTsconfig(tsconfigPath).files.map((file) => `${file}\0${readText(file)}`).join("\0");
3843
+ const flags = {
3844
+ skipAddingFilesFromTsConfig: true,
3845
+ skipLoadingLibFiles: true,
3846
+ skipFileDependencyResolution: true
3847
+ };
3848
+ const parsed = parsedTsconfigs.get(tsconfigPath);
3849
+ if (parsed?.text === text) return new Project4({ ...flags, compilerOptions: parsed.options });
3850
+ const project = new Project4({ ...flags, tsConfigFilePath: tsconfigPath });
3851
+ parsedTsconfigs.set(tsconfigPath, { text, options: project.getCompilerOptions() });
3852
+ return project;
3853
+ }
3854
+ function findForRootCall(sourceFile) {
3855
+ const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind4.CallExpression);
3856
+ for (const call of callExpressions) {
3857
+ const expr = call.getExpression();
3858
+ if (!Node11.isPropertyAccessExpression(expr)) continue;
3859
+ const methodName = expr.getName();
3860
+ const objectExpr = expr.getExpression();
3861
+ if (methodName === "forRoot" && Node11.isIdentifier(objectExpr)) {
3862
+ const name = objectExpr.getText();
3863
+ if (name === "InertiaModule") {
3864
+ return call;
3865
+ }
3866
+ }
3867
+ }
3868
+ return null;
3869
+ }
3870
+ function findShareInitializer(forRootCall) {
3871
+ if (!Node11.isCallExpression(forRootCall)) return null;
3872
+ const args = forRootCall.getArguments();
3873
+ const firstArg2 = args[0];
3874
+ if (!firstArg2 || !Node11.isObjectLiteralExpression(firstArg2)) return null;
3875
+ for (const prop of firstArg2.getProperties()) {
3876
+ if (Node11.isPropertyAssignment(prop) && prop.getName() === "share") {
3877
+ return prop.getInitializer() ?? null;
3878
+ }
3879
+ }
3880
+ return null;
3881
+ }
3882
+ function extractShareType(node, sourceFile, project) {
3883
+ if (Node11.isIdentifier(node)) {
3884
+ const ref = resolveIdentifierToImportRef(node, sourceFile, project);
3885
+ if (ref) {
3886
+ return {
3887
+ typeString: `Awaited<ReturnType<typeof import('${ref.importPath}').${ref.exportName}>>`,
3888
+ properties: null,
3889
+ isImportRef: true
3890
+ };
3891
+ }
3892
+ }
3893
+ if (Node11.isArrowFunction(node)) {
3894
+ const result = extractFromFunctionLike(node, sourceFile);
3895
+ return result ? { ...result, isImportRef: false } : null;
3896
+ }
3897
+ if (Node11.isFunctionExpression(node)) {
3898
+ const result = extractFromFunctionLike(node, sourceFile);
3899
+ return result ? { ...result, isImportRef: false } : null;
3900
+ }
3901
+ if (Node11.isObjectLiteralExpression(node)) {
3902
+ const result = extractFromObjectLiteral(node);
3903
+ return result ? { ...result, isImportRef: false } : null;
3904
+ }
3905
+ return null;
3906
+ }
3907
+ function resolveIdentifierToImportRef(id, sourceFile, project) {
3908
+ if (!Node11.isIdentifier(id)) return null;
3909
+ const name = id.getText();
3910
+ const localFunc = sourceFile.getFunction(name);
3911
+ if (localFunc?.isExported()) {
3912
+ const filePath = sourceFile.getFilePath().replace(/\.ts$/, "");
3913
+ return { importPath: filePath, exportName: name };
3914
+ }
3915
+ const localVar = sourceFile.getVariableDeclaration(name);
3916
+ if (localVar?.isExported()) {
3917
+ const filePath = sourceFile.getFilePath().replace(/\.ts$/, "");
3918
+ return { importPath: filePath, exportName: name };
3919
+ }
3920
+ for (const imp of sourceFile.getImportDeclarations()) {
3921
+ for (const named of imp.getNamedImports()) {
3922
+ const importedName = named.getAliasNode()?.getText() ?? named.getName();
3923
+ if (importedName !== name) continue;
3924
+ const resolvedSource = imp.getModuleSpecifierSourceFile();
3925
+ if (!resolvedSource) continue;
3926
+ const originalName = named.getName();
3927
+ const fn = resolvedSource.getFunction(originalName);
3928
+ if (fn?.isExported()) {
3929
+ const filePath = resolvedSource.getFilePath().replace(/\.ts$/, "");
3930
+ return { importPath: filePath, exportName: originalName };
3931
+ }
3932
+ const v = resolvedSource.getVariableDeclaration(originalName);
3933
+ if (v?.isExported()) {
3934
+ const filePath = resolvedSource.getFilePath().replace(/\.ts$/, "");
3935
+ return { importPath: filePath, exportName: originalName };
3936
+ }
3937
+ }
3938
+ }
3939
+ return null;
3940
+ }
3941
+ function extractFromFunctionLike(node, _sourceFile) {
3942
+ const returnTypeNode = Node11.isArrowFunction(node) || Node11.isFunctionExpression(node) ? node.getReturnTypeNode() : null;
3943
+ if (returnTypeNode) {
3944
+ return extractFromReturnTypeAnnotation(returnTypeNode);
3945
+ }
3946
+ if (Node11.isArrowFunction(node)) {
3947
+ const body = node.getBody();
3948
+ if (Node11.isParenthesizedExpression(body)) {
3949
+ const inner = body.getExpression();
3950
+ if (Node11.isObjectLiteralExpression(inner)) {
3951
+ return extractFromObjectLiteral(inner);
3952
+ }
3953
+ }
3954
+ if (Node11.isObjectLiteralExpression(body)) {
3955
+ return extractFromObjectLiteral(body);
3956
+ }
3957
+ if (Node11.isBlock(body)) {
3958
+ return extractFromBlockReturn(body);
3959
+ }
3960
+ }
3961
+ if (Node11.isFunctionExpression(node)) {
3962
+ const body = node.getBody();
3963
+ if (Node11.isBlock(body)) {
3964
+ return extractFromBlockReturn(body);
3965
+ }
3966
+ }
3967
+ return null;
3968
+ }
3969
+ function extractFromReturnTypeAnnotation(typeNode) {
3970
+ if (Node11.isTypeReference(typeNode)) {
3971
+ const typeName = typeNode.getTypeName();
3972
+ if (Node11.isIdentifier(typeName) && typeName.getText() === "Promise") {
3973
+ const typeArgs = typeNode.getTypeArguments();
3974
+ const firstArg2 = typeArgs[0];
3975
+ if (firstArg2) {
3976
+ return extractFromReturnTypeAnnotation(firstArg2);
3977
+ }
3978
+ return null;
3979
+ }
3980
+ }
3981
+ if (Node11.isTypeLiteral(typeNode)) {
3982
+ const properties = [];
3983
+ for (const member of typeNode.getMembers()) {
3984
+ if (Node11.isPropertySignature(member)) {
3985
+ const name = member.getName();
3986
+ const memberTypeNode = member.getTypeNode();
3987
+ const type = memberTypeNode ? memberTypeNode.getText() : "unknown";
3988
+ properties.push({ name, type });
3989
+ }
3990
+ }
3991
+ if (properties.length === 0) return null;
3992
+ const typeString = `{ ${properties.map((p) => `${p.name}: ${p.type}`).join("; ")} }`;
3993
+ return { typeString, properties, isImportRef: false };
3994
+ }
3995
+ return null;
3996
+ }
3997
+ function extractFromBlockReturn(block) {
3998
+ if (!Node11.isBlock(block)) return null;
3999
+ const statements = block.getStatements();
4000
+ for (let i = statements.length - 1; i >= 0; i--) {
4001
+ const stmt = statements[i];
4002
+ if (!Node11.isReturnStatement(stmt)) continue;
4003
+ const expr = stmt.getExpression();
4004
+ if (!expr) continue;
4005
+ if (Node11.isObjectLiteralExpression(expr)) {
4006
+ return extractFromObjectLiteral(expr);
4007
+ }
4008
+ if (Node11.isParenthesizedExpression(expr)) {
4009
+ const inner = expr.getExpression();
4010
+ if (Node11.isObjectLiteralExpression(inner)) {
4011
+ return extractFromObjectLiteral(inner);
4012
+ }
4013
+ }
4014
+ break;
4015
+ }
4016
+ return null;
4017
+ }
4018
+ function extractFromObjectLiteral(objLiteral) {
4019
+ if (!Node11.isObjectLiteralExpression(objLiteral)) return null;
4020
+ const properties = [];
4021
+ for (const prop of objLiteral.getProperties()) {
4022
+ if (!Node11.isPropertyAssignment(prop)) continue;
4023
+ const name = prop.getName();
4024
+ const initializer = prop.getInitializer();
4025
+ if (!initializer) continue;
4026
+ const type = inferExpressionType(initializer);
4027
+ properties.push({ name, type });
4028
+ }
4029
+ if (properties.length === 0) return null;
4030
+ const typeString = `{ ${properties.map((p) => `${p.name}: ${p.type}`).join("; ")} }`;
4031
+ return { typeString, properties, isImportRef: false };
4032
+ }
4033
+ function inferExpressionType(node) {
4034
+ if (Node11.isStringLiteral(node)) return "string";
4035
+ if (Node11.isTemplateExpression(node) || Node11.isNoSubstitutionTemplateLiteral(node))
4036
+ return "string";
4037
+ if (Node11.isNumericLiteral(node)) return "number";
4038
+ if (node.getKind() === SyntaxKind4.TrueKeyword || node.getKind() === SyntaxKind4.FalseKeyword) {
4039
+ return "boolean";
4040
+ }
4041
+ if (node.getKind() === SyntaxKind4.NullKeyword) return "null";
4042
+ if (Node11.isIdentifier(node) && node.getText() === "undefined") return "undefined";
4043
+ if (Node11.isObjectLiteralExpression(node)) {
4044
+ const props = node.getProperties();
4045
+ if (props.length === 0) return "Record<string, unknown>";
4046
+ const entries = [];
4047
+ for (const prop of props) {
4048
+ if (!Node11.isPropertyAssignment(prop)) continue;
4049
+ const key = prop.getName();
4050
+ const init = prop.getInitializer();
4051
+ if (!init) continue;
4052
+ entries.push(`${key}: ${inferExpressionType(init)}`);
4053
+ }
4054
+ if (entries.length === 0) return "Record<string, unknown>";
4055
+ return `{ ${entries.join("; ")} }`;
4056
+ }
4057
+ if (Node11.isArrayLiteralExpression(node)) {
4058
+ const elements = node.getElements();
4059
+ if (elements.length === 0) return "Array<unknown>";
4060
+ const first = elements[0];
4061
+ if (first) return `Array<${inferExpressionType(first)}>`;
4062
+ return "Array<unknown>";
4063
+ }
4064
+ if (Node11.isConditionalExpression(node)) {
4065
+ const whenTrue = inferExpressionType(node.getWhenTrue());
4066
+ const whenFalse = inferExpressionType(node.getWhenFalse());
4067
+ if (whenTrue === whenFalse) return whenTrue;
4068
+ return `${whenTrue} | ${whenFalse}`;
4069
+ }
4070
+ if (Node11.isParenthesizedExpression(node)) {
4071
+ return inferExpressionType(node.getExpression());
4072
+ }
4073
+ if (Node11.isAsExpression(node)) {
4074
+ const typeNode = node.getTypeNode();
4075
+ if (typeNode) return typeNode.getText();
4076
+ }
4077
+ return "unknown";
4078
+ }
4079
+
4080
+ // src/emit/emit-api.ts
4081
+ import { mkdir, writeFile } from "fs/promises";
4082
+ import { isAbsolute as isAbsolute2, join as join5, relative as relative4 } from "path";
4083
+
3846
4084
  // src/extension/registry.ts
4085
+ import { relative as relative3, resolve as resolve4 } from "path";
4086
+ import { Project as Project5 } from "ts-morph";
3847
4087
  function resolveApiSlots(extensions) {
3848
4088
  let layer;
3849
4089
  let layerOwner;
@@ -5628,36 +5868,64 @@ async function discoverInputFiles(config) {
5628
5868
  const matched = await fg3(globs, { cwd, absolute: true, onlyFiles: true });
5629
5869
  return [...new Set(matched)].sort();
5630
5870
  }
5631
- async function computeInputsHash(config, extraInputs = []) {
5871
+ var READ_CONCURRENCY = 64;
5872
+ async function readAll(paths, read) {
5873
+ const out = /* @__PURE__ */ new Map();
5874
+ for (let i = 0; i < paths.length; i += READ_CONCURRENCY) {
5875
+ const batch = paths.slice(i, i + READ_CONCURRENCY);
5876
+ const texts = await Promise.all(batch.map(read));
5877
+ batch.forEach((path, j) => out.set(path, texts[j]));
5878
+ }
5879
+ return out;
5880
+ }
5881
+ async function readInputs(config) {
5882
+ const cwd = config.codegen.cwd;
5883
+ const tsconfigPath = resolveTsconfigPath(cwd, config.app?.tsconfig ?? void 0);
5884
+ const extended = loadDiscoveryTsconfig(tsconfigPath).files;
5885
+ const tsconfigFiles = extended.length > 0 ? extended : [tsconfigPath];
5886
+ const files = await discoverInputFiles(config);
5887
+ const contents = /* @__PURE__ */ new Map();
5888
+ const tsconfigs = await readAll(
5889
+ tsconfigFiles,
5890
+ (file) => readFile2(file, "utf8").catch(() => null)
5891
+ );
5892
+ for (const [file, text] of tsconfigs) contents.set(file, text);
5893
+ for (const [file, text] of await readAll(files, (file2) => readFile2(file2, "utf8"))) {
5894
+ contents.set(file, text);
5895
+ }
5896
+ return { tsconfigFiles, files, contents, tracked: /* @__PURE__ */ new Map() };
5897
+ }
5898
+ async function readInput(snapshot, path) {
5899
+ return snapshot.contents.get(path) ?? readFile2(path, "utf8");
5900
+ }
5901
+ async function computeInputsHash(config, extraInputs = [], snapshot) {
5902
+ const inputs = snapshot ?? await readInputs(config);
5632
5903
  const hash = createHash("sha256");
5633
5904
  hash.update(`version:${VERSION}
5634
5905
  `);
5635
5906
  hash.update(`config:${serializeConfig(config)}
5636
5907
  `);
5637
5908
  const cwd = config.codegen.cwd;
5638
- const tsconfigPath = resolveTsconfigPath(cwd, config.app?.tsconfig ?? void 0);
5639
- const tsconfigFiles = loadDiscoveryTsconfig(tsconfigPath).files;
5640
- for (const file of tsconfigFiles.length > 0 ? tsconfigFiles : [tsconfigPath]) {
5641
- const contents = await readFile2(file, "utf8").catch(() => null);
5909
+ for (const file of inputs.tsconfigFiles) {
5642
5910
  hash.update(`tsconfig:${relative7(cwd, file)}
5643
5911
  `);
5644
- hash.update(contents ?? " missing");
5912
+ hash.update(inputs.contents.get(file) ?? " missing");
5645
5913
  hash.update("\n");
5646
5914
  }
5647
- const globbed = await discoverInputFiles(config);
5648
- const globbedRelative = new Set(globbed.map((file) => relative7(cwd, file)));
5915
+ const globbedRelative = new Set(inputs.files.map((file) => relative7(cwd, file)));
5649
5916
  const extra = [...new Set(extraInputs)].filter((file) => !globbedRelative.has(file)).sort();
5650
- for (const file of globbed) {
5651
- const contents = await readFile2(file, "utf8");
5917
+ for (const file of inputs.files) {
5652
5918
  hash.update(`file:${relative7(cwd, file)}
5653
5919
  `);
5654
- hash.update(contents);
5920
+ hash.update(inputs.contents.get(file));
5655
5921
  hash.update("\n");
5656
5922
  }
5923
+ const unread = extra.map((file) => join13(cwd, file)).filter((abs) => !inputs.tracked.has(abs));
5924
+ for (const [abs, text] of await readAll(unread, readTrackedInput)) inputs.tracked.set(abs, text);
5657
5925
  for (const file of extra) {
5658
5926
  hash.update(`extra:${file}
5659
5927
  `);
5660
- hash.update(await readTrackedInput(join13(cwd, file)));
5928
+ hash.update(inputs.tracked.get(join13(cwd, file)));
5661
5929
  hash.update("\n");
5662
5930
  }
5663
5931
  return hash.digest("hex");
@@ -5738,14 +6006,15 @@ function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint, differ
5738
6006
  async function generate(config, inputRoutes = [], entryPoint = "cli") {
5739
6007
  setCodegenDebug(config.debug);
5740
6008
  const manifest = await readManifest(config.codegen.outDir);
5741
- const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? []);
6009
+ const inputs = await readInputs(config);
6010
+ const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? [], inputs);
5742
6011
  if (await isManifestFresh(config.codegen.outDir, manifest, inputsHash)) {
5743
6012
  console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
5744
6013
  return;
5745
6014
  }
5746
6015
  const configHash = computeConfigHash(config);
5747
6016
  const configKeyHashes = computeConfigKeyHashes(config);
5748
- if (config.driftGuard && manifest?.entryPoint && manifest.entryPoint !== entryPoint && manifest.configHash && manifest.configHash !== configHash) {
6017
+ if (config.driftGuard && manifest?.version === VERSION && manifest?.entryPoint && manifest.entryPoint !== entryPoint && manifest.configHash && manifest.configHash !== configHash) {
5749
6018
  throw new DriftGuardError(
5750
6019
  driftGuardMessage(
5751
6020
  config.codegen.outDir,
@@ -5766,7 +6035,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
5766
6035
  }
5767
6036
  if (config.pages) {
5768
6037
  const pagesConfig = config.pages;
5769
- const pages = await discoverPageScopes(config);
6038
+ const pages = await discoverPageScopes(config, (path) => readInput(inputs, path));
5770
6039
  const sharedProps = discoverSharedPropsFromConfig(config);
5771
6040
  await emitPages(pages, config.codegen.outDir, {
5772
6041
  propsExport: pagesConfig.propsExport,
@@ -5816,7 +6085,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
5816
6085
  }
5817
6086
  const outputFiles = await listOutputFiles(config.codegen.outDir);
5818
6087
  const extraInputs = [...trackedInputs].sort();
5819
- const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs) : inputsHash;
6088
+ const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs, inputs) : inputsHash;
5820
6089
  await writeManifest(config.codegen.outDir, {
5821
6090
  version: VERSION,
5822
6091
  hash: recordedHash,
@@ -5999,13 +6268,15 @@ async function watch(config, onChange, options = {}) {
5999
6268
  pagesWatcher.on("unlink", schedulePagesRegenerate);
6000
6269
  let contractsDebounceTimer;
6001
6270
  const pendingChangedPaths = /* @__PURE__ */ new Set();
6271
+ let pendingAddedOrRemoved = false;
6002
6272
  const contractsWatcher = chokidar.watch(join16(config.codegen.cwd, config.contracts.glob), {
6003
6273
  ignoreInitial: true,
6004
6274
  persistent: true,
6005
6275
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
6006
6276
  });
6007
- function scheduleContractsRegenerate(changedPath) {
6277
+ function scheduleContractsRegenerate(changedPath, addedOrRemoved = false) {
6008
6278
  if (typeof changedPath === "string") pendingChangedPaths.add(changedPath);
6279
+ if (addedOrRemoved) pendingAddedOrRemoved = true;
6009
6280
  if (contractsDebounceTimer !== void 0) {
6010
6281
  clearTimeout(contractsDebounceTimer);
6011
6282
  }
@@ -6013,8 +6284,12 @@ async function watch(config, onChange, options = {}) {
6013
6284
  contractsDebounceTimer = void 0;
6014
6285
  const changed = [...pendingChangedPaths];
6015
6286
  pendingChangedPaths.clear();
6287
+ const filesAddedOrRemoved = pendingAddedOrRemoved;
6288
+ pendingAddedOrRemoved = false;
6016
6289
  try {
6017
- const routes = await (await getDiscovery()).rediscover(changed);
6290
+ const routes = await (await getDiscovery()).rediscover(changed, {
6291
+ filesAddedOrRemoved
6292
+ });
6018
6293
  lastRoutes = routes;
6019
6294
  await generateAndTrack(routes);
6020
6295
  } catch (err) {
@@ -6026,20 +6301,25 @@ async function watch(config, onChange, options = {}) {
6026
6301
  onChange?.();
6027
6302
  }, config.contracts.debounceMs);
6028
6303
  }
6029
- contractsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
6304
+ contractsWatcher.on("add", (p) => scheduleContractsRegenerate(p, true));
6030
6305
  contractsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
6031
- contractsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
6306
+ contractsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p, true));
6032
6307
  const formsWatcher = chokidar.watch(join16(config.codegen.cwd, config.forms.watch), {
6033
6308
  ignoreInitial: true,
6034
6309
  persistent: true,
6035
6310
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
6036
6311
  });
6037
- formsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
6312
+ formsWatcher.on("add", (p) => scheduleContractsRegenerate(p, true));
6038
6313
  formsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
6039
- formsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
6040
- trackedWatcher.on("add", (p) => scheduleContractsRegenerate(p));
6314
+ formsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p, true));
6315
+ trackedWatcher.on("add", (p) => scheduleContractsRegenerate(p, true));
6041
6316
  trackedWatcher.on("change", (p) => scheduleContractsRegenerate(p));
6042
- trackedWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
6317
+ trackedWatcher.on("unlink", (p) => scheduleContractsRegenerate(p, true));
6318
+ await Promise.all(
6319
+ [pagesWatcher, contractsWatcher, formsWatcher].map(
6320
+ (w) => new Promise((resolveReady) => w.once("ready", () => resolveReady()))
6321
+ )
6322
+ );
6043
6323
  return {
6044
6324
  close: async () => {
6045
6325
  if (pagesDebounceTimer !== void 0) {
@@ -6143,7 +6423,7 @@ function createChainModuleRenderer(opts) {
6143
6423
  }
6144
6424
 
6145
6425
  // src/extension/genui-sandbox-kit.ts
6146
- import { existsSync as existsSync2, statSync } from "fs";
6426
+ import { existsSync as existsSync2, statSync as statSync2 } from "fs";
6147
6427
  import { dirname as dirname5, isAbsolute as isAbsolute3, resolve as resolve6 } from "path";
6148
6428
  import fg4 from "fast-glob";
6149
6429
  var GENUI_KIT_MODULE = "@dudousxd/nestjs-agent-core/genui/kit";
@@ -6155,11 +6435,6 @@ var DEFAULT_KIT_DIRS = [
6155
6435
  "app/components/ui",
6156
6436
  "components/ui"
6157
6437
  ];
6158
- var LEGACY_OPTIONS = {
6159
- entry: "`entry: X` is now `components: X`",
6160
- include: "`include: G` is now `components: G` (globs, files, directories or packages)",
6161
- css: "`css: G` is now `styles: G` (the app's style entry files)"
6162
- };
6163
6438
  var MIN_CORE = "0.53.0";
6164
6439
  async function loadKitModule(specifier = GENUI_KIT_MODULE) {
6165
6440
  let mod;
@@ -6210,7 +6485,7 @@ function resolveKitFilesLocally(root, options) {
6210
6485
  continue;
6211
6486
  }
6212
6487
  const path = isAbsolute3(spec) ? spec : resolve6(root, spec);
6213
- if (existsSync2(path) && statSync(path).isDirectory()) {
6488
+ if (existsSync2(path) && statSync2(path).isDirectory()) {
6214
6489
  files.push(...fg4.sync("*.{tsx,jsx,ts,js}", { cwd: path, absolute: true, onlyFiles: true }));
6215
6490
  dirs.add(path);
6216
6491
  } else if (existsSync2(path)) {
@@ -6226,14 +6501,6 @@ function resolveKitFilesLocally(root, options) {
6226
6501
  return { files: [...new Set(files)].sort(), dirs: [...dirs] };
6227
6502
  }
6228
6503
  function genuiSandboxKit(options = {}) {
6229
- const legacy = Object.keys(LEGACY_OPTIONS).filter(
6230
- (key) => options[key] !== void 0
6231
- );
6232
- if (legacy.length > 0) {
6233
- throw new CodegenError(
6234
- `genuiSandboxKit(): ${legacy.map((key) => `\`${key}\``).join(", ")} ${legacy.length > 1 ? "were" : "was"} removed \u2014 ${legacy.map((key) => LEGACY_OPTIONS[key]).join("; ")}. The options are now the agent's Vite plugin's: components, styles, wrapper, stylesDocs.`
6235
- );
6236
- }
6237
6504
  const output = options.output ?? DEFAULT_SANDBOX_KIT_OUTPUT;
6238
6505
  const source = {
6239
6506
  ...options.components !== void 0 ? { components: options.components } : {},
@@ -6295,7 +6562,7 @@ function genuiSandboxKit(options = {}) {
6295
6562
  }
6296
6563
 
6297
6564
  // src/index.ts
6298
- var VERSION = "0.29.0";
6565
+ var VERSION = "0.30.0";
6299
6566
  export {
6300
6567
  CodegenError,
6301
6568
  ConfigError,