@dudousxd/nestjs-codegen 0.28.0 → 0.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -252,7 +252,7 @@ async function discoverPages(opts) {
252
252
  const rel = relative2(opts.cwd, file);
253
253
  const nameRel = relative2(pagesBase, file).split(sep2).join("/");
254
254
  const name = `${prefix}${computeName(nameRel, opts.componentNameStrategy)}`;
255
- const source = await readFile(file, "utf8");
255
+ const source = await (opts.read ? opts.read(file) : readFile(file, "utf8"));
256
256
  const propsSource = extractPropsSource(source, opts.propsExport);
257
257
  out.push({ name, absolutePath: file, relativePath: rel, propsSource, scope });
258
258
  }
@@ -260,10 +260,14 @@ async function discoverPages(opts) {
260
260
  assertUniqueNames(out);
261
261
  return out;
262
262
  }
263
- async function discoverPageScopes(config) {
263
+ async function discoverPageScopes(config, read) {
264
264
  const pages = config.pages;
265
265
  if (!pages) return [];
266
- const common = { cwd: config.codegen.cwd, propsExport: pages.propsExport };
266
+ const common = {
267
+ cwd: config.codegen.cwd,
268
+ propsExport: pages.propsExport,
269
+ ...read ? { read } : {}
270
+ };
267
271
  const out = await discoverPages({
268
272
  ...common,
269
273
  glob: pages.glob,
@@ -334,305 +338,69 @@ function extractPropsSource(source, exportName) {
334
338
  }
335
339
 
336
340
  // 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) {
341
+ import { readFileSync as readFileSync2 } from "fs";
342
+ import { join as join4 } from "path";
343
+ import { Node as Node11, Project as Project4, SyntaxKind as SyntaxKind4 } from "ts-morph";
344
+
345
+ // src/discovery/contracts-fast.ts
346
+ import { existsSync } from "fs";
347
+ import { dirname as dirname2, join as join3, resolve as resolve3 } from "path";
348
+ import fg2 from "fast-glob";
349
+ import {
350
+ FileSystemRefreshResult as FileSystemRefreshResult3,
351
+ Node as Node10,
352
+ Project as Project3,
353
+ ts as ts4
354
+ } from "ts-morph";
355
+
356
+ // src/discovery/disk-fingerprints.ts
357
+ import { readFileSync, statSync } from "fs";
358
+ var DiskFingerprints = class {
359
+ stats = /* @__PURE__ */ new Map();
360
+ /** The paths whose disk content differs from `text`, or that no longer exist. */
361
+ changed(files) {
362
+ const changed = [];
363
+ for (const { path, text } of files) {
364
+ let key;
343
365
  try {
344
- sourceFile = project.addSourceFileAtPath(moduleEntry);
366
+ const stat2 = statSync(path);
367
+ key = `${stat2.mtimeMs}:${stat2.size}`;
345
368
  } 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);
369
+ this.stats.delete(path);
370
+ changed.push(path);
371
+ continue;
506
372
  }
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 });
373
+ if (this.stats.get(path) === key) continue;
374
+ let disk;
375
+ try {
376
+ disk = readFileSync(path, "utf-8");
377
+ } catch {
378
+ disk = void 0;
518
379
  }
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);
380
+ if (disk === text) {
381
+ this.stats.set(path, key);
382
+ } else {
383
+ this.stats.delete(path);
384
+ changed.push(path);
541
385
  }
542
386
  }
543
- break;
544
- }
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 });
557
- }
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());
387
+ return changed;
601
388
  }
602
- if (Node.isAsExpression(node)) {
603
- const typeNode = node.getTypeNode();
604
- if (typeNode) return typeNode.getText();
389
+ /** Forget `path`, so its next check compares content again. */
390
+ forget(path) {
391
+ this.stats.delete(path);
605
392
  }
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";
393
+ };
626
394
 
627
395
  // src/discovery/dto-type-resolver.ts
628
396
  import {
629
- Node as Node10,
630
- SyntaxKind as SyntaxKind4
397
+ Node as Node9,
398
+ SyntaxKind as SyntaxKind3
631
399
  } from "ts-morph";
632
400
 
633
401
  // src/discovery/dto-to-ir.ts
634
402
  import {
635
- Node as Node3
403
+ Node as Node2
636
404
  } from "ts-morph";
637
405
 
638
406
  // src/util/debug-log.ts
@@ -647,7 +415,7 @@ function debugWarn(message) {
647
415
  // src/discovery/type-ref-resolution.ts
648
416
  import { dirname, resolve as resolve2 } from "path";
649
417
  import {
650
- Node as Node2
418
+ Node
651
419
  } from "ts-morph";
652
420
  var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
653
421
  var _ctxByProject = /* @__PURE__ */ new WeakMap();
@@ -913,13 +681,13 @@ function resolveTypeRef(nodeOrName, sourceFile, project, opts) {
913
681
  name = nodeOrName;
914
682
  } else {
915
683
  const typeNode = nodeOrName;
916
- if (opts.unwrapContainers && Node2.isArrayTypeNode(typeNode)) {
684
+ if (opts.unwrapContainers && Node.isArrayTypeNode(typeNode)) {
917
685
  const inner = resolveTypeRef(typeNode.getElementTypeNode(), sourceFile, project, opts);
918
686
  return inner ? { ...inner, isArray: true } : null;
919
687
  }
920
- if (!Node2.isTypeReference(typeNode)) return null;
688
+ if (!Node.isTypeReference(typeNode)) return null;
921
689
  const typeName = typeNode.getTypeName();
922
- const refName = Node2.isIdentifier(typeName) ? typeName.getText() : null;
690
+ const refName = Node.isIdentifier(typeName) ? typeName.getText() : null;
923
691
  if (!refName) return null;
924
692
  if (opts.unwrapContainers && refName === "Promise") {
925
693
  const first = typeNode.getTypeArguments()[0];
@@ -1053,7 +821,7 @@ function buildProperty(prop, classFile, ctx) {
1053
821
  const dec = (n) => decorators.get(n);
1054
822
  const typeNode = prop.getTypeNode();
1055
823
  const typeText = typeNode?.getText() ?? "unknown";
1056
- const isArrayType = !!typeNode && Node3.isArrayTypeNode(typeNode);
824
+ const isArrayType = !!typeNode && Node2.isArrayTypeNode(typeNode);
1057
825
  const asField = (child) => applyPresence(
1058
826
  has("IsArray") || isArrayType ? { kind: "array", element: child } : child,
1059
827
  decorators
@@ -1267,22 +1035,22 @@ function firstArgText(decorator) {
1267
1035
  }
1268
1036
  function numericArg(decorator) {
1269
1037
  const arg = firstArg(decorator);
1270
- if (arg && Node3.isNumericLiteral(arg)) return arg.getText();
1038
+ if (arg && Node2.isNumericLiteral(arg)) return arg.getText();
1271
1039
  return null;
1272
1040
  }
1273
1041
  function numericArgs(decorator) {
1274
1042
  const args = decorator?.getArguments() ?? [];
1275
- const num = (n) => n && Node3.isNumericLiteral(n) ? n.getText() : null;
1043
+ const num = (n) => n && Node2.isNumericLiteral(n) ? n.getText() : null;
1276
1044
  return [num(args[0]), num(args[1])];
1277
1045
  }
1278
1046
  function messageRaw(decorator) {
1279
1047
  const args = decorator?.getArguments() ?? [];
1280
1048
  for (const arg of args) {
1281
- if (Node3.isObjectLiteralExpression(arg)) {
1049
+ if (Node2.isObjectLiteralExpression(arg)) {
1282
1050
  for (const prop of arg.getProperties()) {
1283
- if (Node3.isPropertyAssignment(prop) && prop.getName() === "message") {
1051
+ if (Node2.isPropertyAssignment(prop) && prop.getName() === "message") {
1284
1052
  const init = prop.getInitializer();
1285
- if (init && Node3.isStringLiteral(init)) return init.getText();
1053
+ if (init && Node2.isStringLiteral(init)) return init.getText();
1286
1054
  }
1287
1055
  }
1288
1056
  }
@@ -1291,30 +1059,30 @@ function messageRaw(decorator) {
1291
1059
  }
1292
1060
  function resolveDiscriminator(decorator) {
1293
1061
  const optsArg = decorator?.getArguments()[1];
1294
- if (!optsArg || !Node3.isObjectLiteralExpression(optsArg)) return null;
1062
+ if (!optsArg || !Node2.isObjectLiteralExpression(optsArg)) return null;
1295
1063
  let discProp;
1296
1064
  for (const prop of optsArg.getProperties()) {
1297
- if (Node3.isPropertyAssignment(prop) && prop.getName() === "discriminator") {
1065
+ if (Node2.isPropertyAssignment(prop) && prop.getName() === "discriminator") {
1298
1066
  discProp = prop.getInitializer();
1299
1067
  }
1300
1068
  }
1301
- if (!discProp || !Node3.isObjectLiteralExpression(discProp)) return null;
1069
+ if (!discProp || !Node2.isObjectLiteralExpression(discProp)) return null;
1302
1070
  let property = null;
1303
1071
  const subTypes = [];
1304
1072
  for (const prop of discProp.getProperties()) {
1305
- if (!Node3.isPropertyAssignment(prop)) continue;
1073
+ if (!Node2.isPropertyAssignment(prop)) continue;
1306
1074
  const name = prop.getName();
1307
1075
  const init = prop.getInitializer();
1308
1076
  if (!init) continue;
1309
- if (name === "property" && Node3.isStringLiteral(init)) {
1077
+ if (name === "property" && Node2.isStringLiteral(init)) {
1310
1078
  property = init.getLiteralValue();
1311
- } else if (name === "subTypes" && Node3.isArrayLiteralExpression(init)) {
1079
+ } else if (name === "subTypes" && Node2.isArrayLiteralExpression(init)) {
1312
1080
  for (const el of init.getElements()) {
1313
- if (!Node3.isObjectLiteralExpression(el)) continue;
1081
+ if (!Node2.isObjectLiteralExpression(el)) continue;
1314
1082
  for (const p of el.getProperties()) {
1315
- if (!Node3.isPropertyAssignment(p) || p.getName() !== "name") continue;
1083
+ if (!Node2.isPropertyAssignment(p) || p.getName() !== "name") continue;
1316
1084
  const nameInit = p.getInitializer();
1317
- if (nameInit && Node3.isIdentifier(nameInit)) subTypes.push(nameInit.getText());
1085
+ if (nameInit && Node2.isIdentifier(nameInit)) subTypes.push(nameInit.getText());
1318
1086
  }
1319
1087
  }
1320
1088
  }
@@ -1325,9 +1093,9 @@ function resolveDiscriminator(decorator) {
1325
1093
  function resolveTypeFactoryName(decorator) {
1326
1094
  const arg = firstArg(decorator);
1327
1095
  if (!arg) return null;
1328
- if (Node3.isArrowFunction(arg)) {
1096
+ if (Node2.isArrowFunction(arg)) {
1329
1097
  const body = arg.getBody();
1330
- if (Node3.isIdentifier(body)) return body.getText();
1098
+ if (Node2.isIdentifier(body)) return body.getText();
1331
1099
  }
1332
1100
  return null;
1333
1101
  }
@@ -1336,12 +1104,12 @@ function singularClassName(typeText) {
1336
1104
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(inner) ? inner : null;
1337
1105
  }
1338
1106
  function genericTypeArgNames(typeNode) {
1339
- if (!typeNode || !Node3.isTypeReference(typeNode)) return [];
1107
+ if (!typeNode || !Node2.isTypeReference(typeNode)) return [];
1340
1108
  const names = [];
1341
1109
  for (const arg of typeNode.getTypeArguments()) {
1342
- if (!Node3.isTypeReference(arg)) return [];
1110
+ if (!Node2.isTypeReference(arg)) return [];
1343
1111
  const tn = arg.getTypeName();
1344
- if (!Node3.isIdentifier(tn)) return [];
1112
+ if (!Node2.isIdentifier(tn)) return [];
1345
1113
  names.push(tn.getText());
1346
1114
  }
1347
1115
  return names;
@@ -1349,7 +1117,7 @@ function genericTypeArgNames(typeNode) {
1349
1117
  function enumSchemaFromDecorator(decorator, classFile, ctx) {
1350
1118
  const arg = firstArg(decorator);
1351
1119
  if (!arg) return null;
1352
- if (Node3.isIdentifier(arg)) {
1120
+ if (Node2.isIdentifier(arg)) {
1353
1121
  const name = arg.getText();
1354
1122
  const resolved = findType(name, classFile, ctx.project);
1355
1123
  if (resolved && resolved.kind === "enum") {
@@ -1363,12 +1131,12 @@ function enumSchemaFromDecorator(decorator, classFile, ctx) {
1363
1131
  }
1364
1132
  return { kind: "unknown", note: `@IsEnum(${name}): enum not resolvable to literals` };
1365
1133
  }
1366
- if (Node3.isObjectLiteralExpression(arg)) {
1134
+ if (Node2.isObjectLiteralExpression(arg)) {
1367
1135
  const values = [];
1368
1136
  for (const p of arg.getProperties()) {
1369
- if (!Node3.isPropertyAssignment(p)) continue;
1137
+ if (!Node2.isPropertyAssignment(p)) continue;
1370
1138
  const init = p.getInitializer();
1371
- if (init && Node3.isStringLiteral(init)) values.push(init.getText());
1139
+ if (init && Node2.isStringLiteral(init)) values.push(init.getText());
1372
1140
  }
1373
1141
  if (values.length > 0) return { kind: "enum", literals: values };
1374
1142
  }
@@ -1376,9 +1144,9 @@ function enumSchemaFromDecorator(decorator, classFile, ctx) {
1376
1144
  }
1377
1145
  function inSchemaFromDecorator(decorator) {
1378
1146
  const arg = firstArg(decorator);
1379
- if (arg && Node3.isArrayLiteralExpression(arg)) {
1147
+ if (arg && Node2.isArrayLiteralExpression(arg)) {
1380
1148
  const elements = arg.getElements();
1381
- const allStrings = elements.every((e) => Node3.isStringLiteral(e));
1149
+ const allStrings = elements.every((e) => Node2.isStringLiteral(e));
1382
1150
  if (allStrings && elements.length > 0) {
1383
1151
  return { kind: "enum", literals: elements.map((e) => e.getText()) };
1384
1152
  }
@@ -1394,13 +1162,13 @@ function inSchemaFromDecorator(decorator) {
1394
1162
 
1395
1163
  // src/discovery/filter-for.ts
1396
1164
  import {
1397
- Node as Node6
1165
+ Node as Node5
1398
1166
  } from "ts-morph";
1399
1167
 
1400
1168
  // src/discovery/filter-field-types.ts
1401
1169
  import {
1402
- Node as Node4,
1403
- SyntaxKind as SyntaxKind2
1170
+ Node as Node3,
1171
+ SyntaxKind
1404
1172
  } from "ts-morph";
1405
1173
 
1406
1174
  // src/discovery/enum-resolution.ts
@@ -1454,28 +1222,28 @@ function markNullable(r, nullable) {
1454
1222
  }
1455
1223
  var TRANSPARENT_TYPE_BRANDS = /* @__PURE__ */ new Set(["Opt", "Hidden"]);
1456
1224
  function classifyTypeNode(typeNode, sourceFile, project, opts) {
1457
- if (Node4.isUnionTypeNode(typeNode)) {
1225
+ if (Node3.isUnionTypeNode(typeNode)) {
1458
1226
  let nullable = false;
1459
1227
  const stringLits = [];
1460
1228
  const numberLits = [];
1461
1229
  const others = [];
1462
1230
  for (const member of typeNode.getTypeNodes()) {
1463
1231
  const kind = member.getKind();
1464
- if (kind === SyntaxKind2.NullKeyword || kind === SyntaxKind2.UndefinedKeyword) {
1232
+ if (kind === SyntaxKind.NullKeyword || kind === SyntaxKind.UndefinedKeyword) {
1465
1233
  nullable = true;
1466
1234
  continue;
1467
1235
  }
1468
- if (Node4.isLiteralTypeNode(member)) {
1236
+ if (Node3.isLiteralTypeNode(member)) {
1469
1237
  const lit = member.getLiteral();
1470
- if (Node4.isStringLiteral(lit)) {
1238
+ if (Node3.isStringLiteral(lit)) {
1471
1239
  stringLits.push(lit.getLiteralValue());
1472
1240
  continue;
1473
1241
  }
1474
- if (Node4.isNumericLiteral(lit)) {
1242
+ if (Node3.isNumericLiteral(lit)) {
1475
1243
  numberLits.push(lit.getText());
1476
1244
  continue;
1477
1245
  }
1478
- if (lit.getKind() === SyntaxKind2.NullKeyword) {
1246
+ if (lit.getKind() === SyntaxKind.NullKeyword) {
1479
1247
  nullable = true;
1480
1248
  continue;
1481
1249
  }
@@ -1495,19 +1263,19 @@ function classifyTypeNode(typeNode, sourceFile, project, opts) {
1495
1263
  return markNullable({ kind: "unknown" }, nullable);
1496
1264
  }
1497
1265
  switch (typeNode.getKind()) {
1498
- case SyntaxKind2.StringKeyword:
1266
+ case SyntaxKind.StringKeyword:
1499
1267
  return { kind: "string" };
1500
- case SyntaxKind2.NumberKeyword:
1268
+ case SyntaxKind.NumberKeyword:
1501
1269
  return { kind: "number" };
1502
- case SyntaxKind2.BooleanKeyword:
1270
+ case SyntaxKind.BooleanKeyword:
1503
1271
  return { kind: "boolean" };
1504
- case SyntaxKind2.AnyKeyword:
1505
- case SyntaxKind2.UnknownKeyword:
1272
+ case SyntaxKind.AnyKeyword:
1273
+ case SyntaxKind.UnknownKeyword:
1506
1274
  return { kind: "unknown" };
1507
1275
  default:
1508
1276
  break;
1509
1277
  }
1510
- if (Node4.isTypeReference(typeNode)) {
1278
+ if (Node3.isTypeReference(typeNode)) {
1511
1279
  const refName = typeNode.getTypeName().getText();
1512
1280
  if (refName === "Date") return { kind: "date" };
1513
1281
  if (refName === "Record" || refName === "Object") return { kind: "json" };
@@ -1525,25 +1293,25 @@ function classifyTypeNode(typeNode, sourceFile, project, opts) {
1525
1293
  if (typeRef) return { kind: "unknown", typeRef };
1526
1294
  return { kind: "unknown" };
1527
1295
  }
1528
- if (Node4.isTypeLiteral(typeNode)) return { kind: "json" };
1296
+ if (Node3.isTypeLiteral(typeNode)) return { kind: "json" };
1529
1297
  return { kind: "unknown" };
1530
1298
  }
1531
1299
  function enumFromDecoratorArgs(args, sourceFile, project) {
1532
1300
  for (const arg of args) {
1533
- if (Node4.isArrowFunction(arg)) {
1301
+ if (Node3.isArrowFunction(arg)) {
1534
1302
  const body = arg.getBody();
1535
- if (Node4.isIdentifier(body)) {
1303
+ if (Node3.isIdentifier(body)) {
1536
1304
  const en = resolveEnumValues(body.getText(), sourceFile, project);
1537
1305
  if (en) return en;
1538
1306
  }
1539
1307
  }
1540
- if (Node4.isObjectLiteralExpression(arg)) {
1308
+ if (Node3.isObjectLiteralExpression(arg)) {
1541
1309
  const itemsProp = arg.getProperty("items");
1542
- if (itemsProp && Node4.isPropertyAssignment(itemsProp)) {
1310
+ if (itemsProp && Node3.isPropertyAssignment(itemsProp)) {
1543
1311
  const init = itemsProp.getInitializer();
1544
- if (init && Node4.isArrowFunction(init)) {
1312
+ if (init && Node3.isArrowFunction(init)) {
1545
1313
  const body = init.getBody();
1546
- if (Node4.isIdentifier(body)) {
1314
+ if (Node3.isIdentifier(body)) {
1547
1315
  const en = resolveEnumValues(body.getText(), sourceFile, project);
1548
1316
  if (en) return en;
1549
1317
  }
@@ -1566,7 +1334,7 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
1566
1334
  return { kind: "string" };
1567
1335
  }
1568
1336
  for (const arg of args) {
1569
- if (Node4.isStringLiteral(arg)) {
1337
+ if (Node3.isStringLiteral(arg)) {
1570
1338
  const raw = arg.getLiteralValue();
1571
1339
  const kind = classifyTypeKeyword(raw);
1572
1340
  if (kind) {
@@ -1574,11 +1342,11 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
1574
1342
  return { kind };
1575
1343
  }
1576
1344
  }
1577
- if (Node4.isObjectLiteralExpression(arg)) {
1345
+ if (Node3.isObjectLiteralExpression(arg)) {
1578
1346
  const enumProp = arg.getProperty("enum");
1579
- if (enumProp && Node4.isPropertyAssignment(enumProp)) {
1347
+ if (enumProp && Node3.isPropertyAssignment(enumProp)) {
1580
1348
  const init = enumProp.getInitializer();
1581
- if (init && Node4.isIdentifier(init)) {
1349
+ if (init && Node3.isIdentifier(init)) {
1582
1350
  const en = resolveEnumValues(init.getText(), sourceFile, project);
1583
1351
  if (en) {
1584
1352
  return en.numeric ? { kind: "number", enumValues: en.values, numericEnum: true } : { kind: "string", enumValues: en.values };
@@ -1588,15 +1356,15 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
1588
1356
  }
1589
1357
  for (const key of ["columnType", "type"]) {
1590
1358
  const typeProp = arg.getProperty(key);
1591
- if (!typeProp || !Node4.isPropertyAssignment(typeProp)) continue;
1359
+ if (!typeProp || !Node3.isPropertyAssignment(typeProp)) continue;
1592
1360
  const init = typeProp.getInitializer();
1593
1361
  if (!init) continue;
1594
- if (Node4.isStringLiteral(init)) {
1362
+ if (Node3.isStringLiteral(init)) {
1595
1363
  const kind = classifyTypeKeyword(init.getLiteralValue());
1596
1364
  if (kind) return { kind };
1597
1365
  continue;
1598
1366
  }
1599
- if (Node4.isIdentifier(init)) {
1367
+ if (Node3.isIdentifier(init)) {
1600
1368
  const kind = classifyTypeKeyword(init.getText());
1601
1369
  if (kind) return { kind };
1602
1370
  }
@@ -1638,23 +1406,70 @@ function toFilterFieldType(name, r) {
1638
1406
  }
1639
1407
 
1640
1408
  // src/discovery/heritage.ts
1641
- import { Node as Node5, Project as Project2 } from "ts-morph";
1642
- function resolveHeritageCall(node) {
1643
- if (!node) return void 0;
1644
- const expr = unwrapExpression(node);
1645
- if (Node5.isCallExpression(expr)) return expr;
1646
- if (!Node5.isIdentifier(expr)) return void 0;
1647
- const decl = expr.getDefinitions().map((d) => d.getDeclarationNode()).find(
1648
- (n) => n !== void 0 && Node5.isVariableDeclaration(n)
1649
- );
1650
- const init = decl?.getInitializer();
1409
+ import {
1410
+ FileSystemRefreshResult,
1411
+ Node as Node4,
1412
+ Project
1413
+ } from "ts-morph";
1414
+
1415
+ // src/discovery/unresolved-imports.ts
1416
+ import { ts } from "ts-morph";
1417
+ var UnresolvedImports = class {
1418
+ read;
1419
+ /**
1420
+ * The files with an import that failed to resolve in `program` and now resolves
1421
+ * to a TypeScript file the program does not hold.
1422
+ */
1423
+ nowResolving(program) {
1424
+ if (this.read?.program !== program) {
1425
+ this.read = { program, imports: failedImports(program) };
1426
+ }
1427
+ const options = program.getCompilerOptions();
1428
+ const files = /* @__PURE__ */ new Set();
1429
+ for (const { specifier, from } of this.read.imports) {
1430
+ const file = ts.resolveModuleName(specifier, from, options, ts.sys).resolvedModule?.resolvedFileName;
1431
+ if (file !== void 0 && /\.(m|c)?tsx?$/.test(file) && !program.getSourceFile(file)) {
1432
+ files.add(from);
1433
+ }
1434
+ }
1435
+ return [...files];
1436
+ }
1437
+ };
1438
+ function failedImports(program) {
1439
+ const checker = program.getTypeChecker();
1440
+ const imports = [];
1441
+ for (const file of program.getSourceFiles()) {
1442
+ if (program.isSourceFileDefaultLibrary(file) || file.isDeclarationFile) continue;
1443
+ if (program.isSourceFileFromExternalLibrary(file)) continue;
1444
+ for (const statement of file.statements) {
1445
+ if (!ts.isImportDeclaration(statement) && !ts.isExportDeclaration(statement)) continue;
1446
+ const specifier = statement.moduleSpecifier;
1447
+ if (!specifier || !ts.isStringLiteral(specifier)) continue;
1448
+ if (!checker.getSymbolAtLocation(specifier)) {
1449
+ imports.push({ specifier: specifier.text, from: file.fileName });
1450
+ }
1451
+ }
1452
+ }
1453
+ return imports;
1454
+ }
1455
+
1456
+ // src/discovery/heritage.ts
1457
+ function resolveHeritageCall(node) {
1458
+ if (!node) return void 0;
1459
+ const expr = unwrapExpression(node);
1460
+ if (Node4.isCallExpression(expr)) return expr;
1461
+ if (!Node4.isIdentifier(expr)) return void 0;
1462
+ const decl = expr.getDefinitions().map((d) => d.getDeclarationNode()).find(
1463
+ (n) => n !== void 0 && Node4.isVariableDeclaration(n)
1464
+ );
1465
+ const init = decl?.getInitializer();
1651
1466
  if (!init) return void 0;
1652
1467
  const unwrapped = unwrapExpression(init);
1653
- return Node5.isCallExpression(unwrapped) ? unwrapped : void 0;
1468
+ return Node4.isCallExpression(unwrapped) ? unwrapped : void 0;
1654
1469
  }
1655
1470
  function unwrapExpression(node) {
1656
1471
  let current = node;
1657
- while (Node5.isAsExpression(current) || Node5.isSatisfiesExpression(current) || Node5.isParenthesizedExpression(current) || Node5.isTypeAssertion(current)) {
1472
+ while (Node4.isAsExpression(current) || Node4.isSatisfiesExpression(current) || Node4.isParenthesizedExpression(current) || Node4.isTypeAssertion(current)) {
1658
1473
  current = current.getExpression();
1659
1474
  }
1660
1475
  return current;
@@ -1662,17 +1477,17 @@ function unwrapExpression(node) {
1662
1477
  function resolveReturnedClass(factoryDecl) {
1663
1478
  const body = factoryDecl.getBody();
1664
1479
  if (!body) return void 0;
1665
- const returns = body.getDescendants().filter((n) => Node5.isReturnStatement(n));
1480
+ const returns = body.getDescendants().filter((n) => Node4.isReturnStatement(n));
1666
1481
  for (const ret of returns) {
1667
1482
  const expr = ret.getExpression();
1668
1483
  if (!expr) continue;
1669
1484
  const inner = unwrapExpression(expr);
1670
- if (Node5.isClassExpression(inner)) {
1485
+ if (Node4.isClassExpression(inner)) {
1671
1486
  return inner;
1672
1487
  }
1673
- if (Node5.isIdentifier(inner)) {
1488
+ if (Node4.isIdentifier(inner)) {
1674
1489
  const name = inner.getText();
1675
- const decl = body.getDescendants().find((n) => Node5.isClassDeclaration(n) && n.getName() === name);
1490
+ const decl = body.getDescendants().find((n) => Node4.isClassDeclaration(n) && n.getName() === name);
1676
1491
  if (decl) return decl;
1677
1492
  }
1678
1493
  }
@@ -1680,10 +1495,10 @@ function resolveReturnedClass(factoryDecl) {
1680
1495
  }
1681
1496
  function factoryCalleeName(call) {
1682
1497
  const callee = unwrapExpression(call.getExpression());
1683
- if (Node5.isIdentifier(callee)) return callee;
1684
- if (Node5.isPropertyAccessExpression(callee)) {
1498
+ if (Node4.isIdentifier(callee)) return callee;
1499
+ if (Node4.isPropertyAccessExpression(callee)) {
1685
1500
  const name = callee.getNameNode();
1686
- return Node5.isIdentifier(name) ? name : void 0;
1501
+ return Node4.isIdentifier(name) ? name : void 0;
1687
1502
  }
1688
1503
  return void 0;
1689
1504
  }
@@ -1696,19 +1511,19 @@ function resolveFactoryFromName(name, seen) {
1696
1511
  seen.add(name);
1697
1512
  const decls = name.getDefinitions().map((d) => d.getDeclarationNode()).filter((n) => n !== void 0);
1698
1513
  for (const decl of decls) {
1699
- if (Node5.isFunctionDeclaration(decl) || Node5.isMethodDeclaration(decl)) return decl;
1700
- if (Node5.isPropertyAssignment(decl)) {
1514
+ if (Node4.isFunctionDeclaration(decl) || Node4.isMethodDeclaration(decl)) return decl;
1515
+ if (Node4.isPropertyAssignment(decl)) {
1701
1516
  const init = decl.getInitializer();
1702
1517
  const inner = init ? unwrapExpression(init) : void 0;
1703
- if (inner && Node5.isIdentifier(inner)) {
1518
+ if (inner && Node4.isIdentifier(inner)) {
1704
1519
  const resolved = resolveFactoryFromName(inner, seen);
1705
1520
  if (resolved) return resolved;
1706
1521
  }
1707
1522
  continue;
1708
1523
  }
1709
- if (Node5.isShorthandPropertyAssignment(decl)) {
1524
+ if (Node4.isShorthandPropertyAssignment(decl)) {
1710
1525
  const nameNode = decl.getNameNode();
1711
- const resolved = Node5.isIdentifier(nameNode) ? resolveFactoryFromName(nameNode, seen) : void 0;
1526
+ const resolved = Node4.isIdentifier(nameNode) ? resolveFactoryFromName(nameNode, seen) : void 0;
1712
1527
  if (resolved) return resolved;
1713
1528
  }
1714
1529
  }
@@ -1720,7 +1535,7 @@ function warnUnresolvedFactory(cls, calleeText, reason) {
1720
1535
  );
1721
1536
  }
1722
1537
  function resolveFactoryStaticClass(node) {
1723
- if (!Node5.isPropertyAccessExpression(node)) return void 0;
1538
+ if (!Node4.isPropertyAccessExpression(node)) return void 0;
1724
1539
  const call = resolveHeritageCall(node.getExpression());
1725
1540
  if (!call) return void 0;
1726
1541
  const factoryDecl = resolveFactoryDeclaration(call);
@@ -1728,13 +1543,13 @@ function resolveFactoryStaticClass(node) {
1728
1543
  const returnedClass = resolveReturnedClass(factoryDecl);
1729
1544
  if (!returnedClass) return void 0;
1730
1545
  const staticProp = returnedClass.getStaticProperties().find((p) => p.getName() === node.getName());
1731
- if (!staticProp || !Node5.isPropertyDeclaration(staticProp)) return void 0;
1546
+ if (!staticProp || !Node4.isPropertyDeclaration(staticProp)) return void 0;
1732
1547
  const init = staticProp.getInitializer();
1733
1548
  if (!init) return void 0;
1734
1549
  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));
1550
+ if (Node4.isClassExpression(inner)) return inner;
1551
+ if (!Node4.isIdentifier(inner)) return void 0;
1552
+ return inner.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node4.isClassDeclaration(n));
1738
1553
  }
1739
1554
  function resolveBaseClass(cls) {
1740
1555
  const heritage = cls.getExtends()?.getExpression();
@@ -1745,8 +1560,8 @@ function resolveBaseClass(cls) {
1745
1560
  return factoryDecl ? resolveReturnedClass(factoryDecl) : void 0;
1746
1561
  }
1747
1562
  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));
1563
+ if (!Node4.isIdentifier(expr)) return void 0;
1564
+ return expr.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node4.isClassDeclaration(n));
1750
1565
  }
1751
1566
  function collectMethodsThroughChain(returnedClass) {
1752
1567
  const byName = /* @__PURE__ */ new Map();
@@ -1793,7 +1608,7 @@ function resolveInheritedMethods(cls) {
1793
1608
  classArgs.push(positional);
1794
1609
  continue;
1795
1610
  }
1796
- if (!Node5.isObjectLiteralExpression(arg)) continue;
1611
+ if (!Node4.isObjectLiteralExpression(arg)) continue;
1797
1612
  for (const prop of arg.getProperties()) {
1798
1613
  const named = resolvePropertyClassReference(prop);
1799
1614
  if (!named) continue;
@@ -1815,8 +1630,8 @@ function resolveInheritedMethods(cls) {
1815
1630
  }
1816
1631
  function resolveClassReference(node) {
1817
1632
  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));
1633
+ if (!Node4.isIdentifier(expr)) return void 0;
1634
+ const decl = expr.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node4.isClassDeclaration(n));
1820
1635
  if (!decl) return void 0;
1821
1636
  return {
1822
1637
  name: decl.getName() ?? expr.getText(),
@@ -1824,29 +1639,56 @@ function resolveClassReference(node) {
1824
1639
  };
1825
1640
  }
1826
1641
  function resolvePropertyClassReference(prop) {
1827
- if (Node5.isShorthandPropertyAssignment(prop)) {
1642
+ if (Node4.isShorthandPropertyAssignment(prop)) {
1828
1643
  const ref2 = resolveClassReference(prop.getNameNode());
1829
1644
  return ref2 ? { key: prop.getName(), ref: ref2 } : void 0;
1830
1645
  }
1831
- if (!Node5.isPropertyAssignment(prop)) return void 0;
1646
+ if (!Node4.isPropertyAssignment(prop)) return void 0;
1832
1647
  const init = prop.getInitializer();
1833
1648
  if (!init) return void 0;
1834
1649
  const ref = resolveClassReference(init);
1835
1650
  if (!ref) return void 0;
1836
1651
  const nameNode = prop.getNameNode();
1837
- const key = Node5.isStringLiteral(nameNode) ? nameNode.getLiteralValue() : prop.getName();
1652
+ const key = Node4.isStringLiteral(nameNode) ? nameNode.getLiteralValue() : prop.getName();
1838
1653
  return { key, ref };
1839
1654
  }
1840
1655
  var mixinTypeProject;
1656
+ var mixinFiles = new DiskFingerprints();
1657
+ var mixinStale = false;
1658
+ var mixinUnresolved = new UnresolvedImports();
1841
1659
  function getMixinTypeProject() {
1842
- mixinTypeProject ??= new Project2({
1843
- skipAddingFilesFromTsConfig: true,
1844
- compilerOptions: { strict: true }
1845
- });
1660
+ if (mixinTypeProject && mixinStale && !refreshMixinTypeProject(mixinTypeProject)) {
1661
+ mixinTypeProject = void 0;
1662
+ }
1663
+ mixinStale = false;
1664
+ if (!mixinTypeProject) {
1665
+ mixinTypeProject = new Project({
1666
+ skipAddingFilesFromTsConfig: true,
1667
+ compilerOptions: { strict: true }
1668
+ });
1669
+ mixinFiles = new DiskFingerprints();
1670
+ mixinUnresolved = new UnresolvedImports();
1671
+ }
1846
1672
  return mixinTypeProject;
1847
1673
  }
1848
1674
  function clearMixinTypeProject() {
1849
1675
  mixinTypeProject = void 0;
1676
+ mixinStale = false;
1677
+ }
1678
+ function invalidateMixinTypeProject() {
1679
+ mixinStale = true;
1680
+ }
1681
+ function refreshMixinTypeProject(project) {
1682
+ const program = project.getProgram().compilerObject;
1683
+ const files = program.getSourceFiles().filter((file) => !program.isSourceFileDefaultLibrary(file)).map((file) => ({ path: file.fileName, text: file.text }));
1684
+ const changed = mixinFiles.changed(files);
1685
+ if (changed.length === 0) return mixinUnresolved.nowResolving(program).length === 0;
1686
+ for (const path of changed) {
1687
+ const sf = project.getSourceFile(path);
1688
+ if (sf?.refreshFromFileSystemSync() !== FileSystemRefreshResult.Updated) return false;
1689
+ }
1690
+ project.resolveSourceFileDependencies();
1691
+ return true;
1850
1692
  }
1851
1693
  function resolveInstantiatedReturnType(cls, methodName) {
1852
1694
  const filePath = cls.getSourceFile().getFilePath();
@@ -1877,7 +1719,7 @@ function resolveClassRef(ref, project) {
1877
1719
  return file?.getClass(ref.name);
1878
1720
  }
1879
1721
  function classifyFilterForHint(typeInit) {
1880
- if (Node6.isStringLiteral(typeInit)) {
1722
+ if (Node5.isStringLiteral(typeInit)) {
1881
1723
  switch (typeInit.getLiteralValue()) {
1882
1724
  case "string":
1883
1725
  return { kind: "string" };
@@ -1891,10 +1733,10 @@ function classifyFilterForHint(typeInit) {
1891
1733
  return null;
1892
1734
  }
1893
1735
  }
1894
- if (Node6.isArrayLiteralExpression(typeInit)) {
1736
+ if (Node5.isArrayLiteralExpression(typeInit)) {
1895
1737
  const values = [];
1896
1738
  for (const el of typeInit.getElements()) {
1897
- if (!Node6.isStringLiteral(el)) return null;
1739
+ if (!Node5.isStringLiteral(el)) return null;
1898
1740
  values.push(el.getLiteralValue());
1899
1741
  }
1900
1742
  if (values.length === 0) return null;
@@ -1929,11 +1771,11 @@ function extractFilterForHints(classDecl, project) {
1929
1771
  if (!filterForDec) continue;
1930
1772
  const args = filterForDec.getArguments();
1931
1773
  const keyArg = args[0];
1932
- const inputKey = keyArg && Node6.isStringLiteral(keyArg) ? keyArg.getLiteralValue() : method.getName();
1774
+ const inputKey = keyArg && Node5.isStringLiteral(keyArg) ? keyArg.getLiteralValue() : method.getName();
1933
1775
  const optsArg = args[1];
1934
- if (optsArg && Node6.isObjectLiteralExpression(optsArg)) {
1776
+ if (optsArg && Node5.isObjectLiteralExpression(optsArg)) {
1935
1777
  const typeProp = optsArg.getProperty("type");
1936
- if (typeProp && Node6.isPropertyAssignment(typeProp)) {
1778
+ if (typeProp && Node5.isPropertyAssignment(typeProp)) {
1937
1779
  const typeInit = typeProp.getInitializer();
1938
1780
  if (typeInit) {
1939
1781
  const classified = classifyFilterForHint(typeInit);
@@ -1960,14 +1802,14 @@ function extractApplyFilterInfo(method, sourceFile, project, mixin, controllerCl
1960
1802
  const filterClassArg = args[0];
1961
1803
  if (!filterClassArg) continue;
1962
1804
  const factoryStaticClass = resolveFactoryStaticClass(filterClassArg);
1963
- if (!factoryStaticClass && !Node6.isIdentifier(filterClassArg)) continue;
1805
+ if (!factoryStaticClass && !Node5.isIdentifier(filterClassArg)) continue;
1964
1806
  let source = "query";
1965
1807
  const optionsArg = args[1];
1966
- if (optionsArg && Node6.isObjectLiteralExpression(optionsArg)) {
1808
+ if (optionsArg && Node5.isObjectLiteralExpression(optionsArg)) {
1967
1809
  const sourceProp = optionsArg.getProperty("source");
1968
- if (sourceProp && Node6.isPropertyAssignment(sourceProp)) {
1810
+ if (sourceProp && Node5.isPropertyAssignment(sourceProp)) {
1969
1811
  const init = sourceProp.getInitializer();
1970
- if (init && Node6.isStringLiteral(init) && init.getLiteralValue() === "body") {
1812
+ if (init && Node5.isStringLiteral(init) && init.getLiteralValue() === "body") {
1971
1813
  source = "body";
1972
1814
  }
1973
1815
  }
@@ -1976,7 +1818,7 @@ function extractApplyFilterInfo(method, sourceFile, project, mixin, controllerCl
1976
1818
  const resolved = factoryStaticClass ? void 0 : findType(filterClassName, declFile, project);
1977
1819
  const declaredClass = factoryStaticClass ?? (resolved?.kind === "class" ? resolved.decl : resolveLocalClassDeclaration(filterClassArg));
1978
1820
  const ownRoute = !mixin || (controllerClass ? method.getParent() === controllerClass : declFile.getFilePath() !== mixin.factoryFilePath);
1979
- const namedByRoute = ownRoute && Node6.isIdentifier(filterClassArg) ? declaredClass : void 0;
1821
+ const namedByRoute = ownRoute && Node5.isIdentifier(filterClassArg) ? declaredClass : void 0;
1980
1822
  for (const candidate of orderFilterCandidates({
1981
1823
  namedByRoute,
1982
1824
  supplied: resolveMixinFilterClass(mixin, project),
@@ -2054,22 +1896,22 @@ function resolveRelationEntity(prop, sourceFile, project) {
2054
1896
  const args = dec.getArguments();
2055
1897
  if (args.length === 0) continue;
2056
1898
  const arg = args[0];
2057
- if (Node6.isObjectLiteralExpression(arg)) {
1899
+ if (Node5.isObjectLiteralExpression(arg)) {
2058
1900
  const entityProp = arg.getProperty("entity");
2059
- if (entityProp && Node6.isPropertyAssignment(entityProp)) {
1901
+ if (entityProp && Node5.isPropertyAssignment(entityProp)) {
2060
1902
  const init = entityProp.getInitializer();
2061
- if (init && Node6.isArrowFunction(init)) {
1903
+ if (init && Node5.isArrowFunction(init)) {
2062
1904
  const body = init.getBody();
2063
- if (Node6.isIdentifier(body)) {
1905
+ if (Node5.isIdentifier(body)) {
2064
1906
  const resolved = findType(body.getText(), prop.getSourceFile(), project);
2065
1907
  if (resolved?.kind === "class") return resolved.decl;
2066
1908
  }
2067
1909
  }
2068
1910
  }
2069
1911
  }
2070
- if (Node6.isArrowFunction(arg)) {
1912
+ if (Node5.isArrowFunction(arg)) {
2071
1913
  const body = arg.getBody();
2072
- if (Node6.isIdentifier(body)) {
1914
+ if (Node5.isIdentifier(body)) {
2073
1915
  const resolved = findType(body.getText(), prop.getSourceFile(), project);
2074
1916
  if (resolved?.kind === "class") return resolved.decl;
2075
1917
  }
@@ -2088,36 +1930,36 @@ function extractClassPropertyTypes(classDecl, project) {
2088
1930
  return fields;
2089
1931
  }
2090
1932
  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));
1933
+ if (!Node5.isIdentifier(identifier)) return void 0;
1934
+ return identifier.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node5.isClassDeclaration(n));
2093
1935
  }
2094
1936
  function resolveDeclaredEntity(optionsArg, filterClass, project) {
2095
- if (!Node6.isObjectLiteralExpression(optionsArg)) return void 0;
1937
+ if (!Node5.isObjectLiteralExpression(optionsArg)) return void 0;
2096
1938
  const entityProp = optionsArg.getProperty("entity");
2097
- if (!entityProp || !Node6.isPropertyAssignment(entityProp)) return void 0;
1939
+ if (!entityProp || !Node5.isPropertyAssignment(entityProp)) return void 0;
2098
1940
  const entityInit = entityProp.getInitializer();
2099
- if (!entityInit || !Node6.isIdentifier(entityInit)) return void 0;
1941
+ if (!entityInit || !Node5.isIdentifier(entityInit)) return void 0;
2100
1942
  const resolved = findType(entityInit.getText(), filterClass.getSourceFile(), project);
2101
1943
  if (!resolved || resolved.kind !== "class") return void 0;
2102
1944
  return resolved.decl;
2103
1945
  }
2104
1946
  function readFieldNameList(optionsArg, key) {
2105
- if (!Node6.isObjectLiteralExpression(optionsArg)) return void 0;
1947
+ if (!Node5.isObjectLiteralExpression(optionsArg)) return void 0;
2106
1948
  const prop = optionsArg.getProperty(key);
2107
- if (!prop || !Node6.isPropertyAssignment(prop)) return void 0;
1949
+ if (!prop || !Node5.isPropertyAssignment(prop)) return void 0;
2108
1950
  const init = prop.getInitializer();
2109
- if (!init || !Node6.isArrayLiteralExpression(init)) return void 0;
1951
+ if (!init || !Node5.isArrayLiteralExpression(init)) return void 0;
2110
1952
  const names = /* @__PURE__ */ new Set();
2111
1953
  for (const el of init.getElements()) {
2112
- if (Node6.isStringLiteral(el)) {
1954
+ if (Node5.isStringLiteral(el)) {
2113
1955
  names.add(el.getLiteralValue());
2114
1956
  continue;
2115
1957
  }
2116
- if (Node6.isObjectLiteralExpression(el)) {
1958
+ if (Node5.isObjectLiteralExpression(el)) {
2117
1959
  const fieldProp = el.getProperty("field");
2118
- if (fieldProp && Node6.isPropertyAssignment(fieldProp)) {
1960
+ if (fieldProp && Node5.isPropertyAssignment(fieldProp)) {
2119
1961
  const fieldInit = fieldProp.getInitializer();
2120
- if (fieldInit && Node6.isStringLiteral(fieldInit)) {
1962
+ if (fieldInit && Node5.isStringLiteral(fieldInit)) {
2121
1963
  names.add(fieldInit.getLiteralValue());
2122
1964
  continue;
2123
1965
  }
@@ -2141,7 +1983,7 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
2141
1983
  const args = filterableDecorator.getArguments();
2142
1984
  if (args.length === 0) return [];
2143
1985
  const optionsArg = args[0];
2144
- if (!Node6.isObjectLiteralExpression(optionsArg)) return [];
1986
+ if (!Node5.isObjectLiteralExpression(optionsArg)) return [];
2145
1987
  const declaredEntity = resolveDeclaredEntity(optionsArg, filterClass, project);
2146
1988
  const entityDecl = preferDeclaredEntity ? declaredEntity ?? entityOverride : entityOverride ?? declaredEntity;
2147
1989
  if (!entityDecl) return [];
@@ -2152,17 +1994,17 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
2152
1994
  const relationsDecorator = filterClass.getDecorators().find((d) => d.getName() === "Relations");
2153
1995
  if (relationsDecorator) {
2154
1996
  const relArgs = relationsDecorator.getArguments();
2155
- if (relArgs.length > 0 && Node6.isObjectLiteralExpression(relArgs[0])) {
1997
+ if (relArgs.length > 0 && Node5.isObjectLiteralExpression(relArgs[0])) {
2156
1998
  for (const relProp of relArgs[0].getProperties()) {
2157
- if (!Node6.isPropertyAssignment(relProp)) continue;
1999
+ if (!Node5.isPropertyAssignment(relProp)) continue;
2158
2000
  const relInit = relProp.getInitializer();
2159
- if (!relInit || !Node6.isObjectLiteralExpression(relInit)) continue;
2001
+ if (!relInit || !Node5.isObjectLiteralExpression(relInit)) continue;
2160
2002
  const keysProp = relInit.getProperty("keys");
2161
- if (!keysProp || !Node6.isPropertyAssignment(keysProp)) continue;
2003
+ if (!keysProp || !Node5.isPropertyAssignment(keysProp)) continue;
2162
2004
  const keysInit = keysProp.getInitializer();
2163
- if (!keysInit || !Node6.isArrayLiteralExpression(keysInit)) continue;
2005
+ if (!keysInit || !Node5.isArrayLiteralExpression(keysInit)) continue;
2164
2006
  for (const el of keysInit.getElements()) {
2165
- if (Node6.isStringLiteral(el)) {
2007
+ if (Node5.isStringLiteral(el)) {
2166
2008
  fields.push(toFilterFieldType(el.getLiteralValue(), { kind: "unknown" }));
2167
2009
  }
2168
2010
  }
@@ -2173,15 +2015,15 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
2173
2015
  }
2174
2016
 
2175
2017
  // src/discovery/pipe-schema.ts
2176
- import { Node as Node9 } from "ts-morph";
2018
+ import { Node as Node8 } from "ts-morph";
2177
2019
 
2178
2020
  // src/discovery/schema-type-inference.ts
2179
2021
  import {
2180
- ts as ts2
2022
+ ts as ts3
2181
2023
  } from "ts-morph";
2182
2024
 
2183
2025
  // src/discovery/schema-type-program.ts
2184
- import { Node as Node7, Project as Project3, ts } from "ts-morph";
2026
+ import { FileSystemRefreshResult as FileSystemRefreshResult2, Node as Node6, Project as Project2, ts as ts2 } from "ts-morph";
2185
2027
  var PARAM_DECORATORS = /* @__PURE__ */ new Set(["Body", "Query", "Param"]);
2186
2028
  var SchemaTypeProgram = class {
2187
2029
  constructor(discovery) {
@@ -2191,10 +2033,10 @@ var SchemaTypeProgram = class {
2191
2033
  ...base,
2192
2034
  // A tsconfig-less consumer gets ES5 by default, whose lib has no `Map`/`Set`/
2193
2035
  // `Promise` for the schema libraries' declarations to lean on.
2194
- target: base.target ?? ts.ScriptTarget.ES2022,
2036
+ target: base.target ?? ts2.ScriptTarget.ES2022,
2195
2037
  // With neither set, resolve the way a bundler does: package `exports`
2196
2038
  // (`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 } : {},
2039
+ ...base.module === void 0 && base.moduleResolution === void 0 ? { module: ts2.ModuleKind.ESNext, moduleResolution: ts2.ModuleResolutionKind.Bundler } : {},
2198
2040
  // Without it `.nullable()` and `.optional()` would erase to the bare type.
2199
2041
  strictNullChecks: true,
2200
2042
  // No ambient `@types/*` (a tsconfig's `types: ['node']`): a schema does not
@@ -2202,17 +2044,16 @@ var SchemaTypeProgram = class {
2202
2044
  types: [],
2203
2045
  noEmit: true
2204
2046
  };
2205
- this.project = new Project3({
2047
+ this.project = new Project2({
2206
2048
  skipAddingFilesFromTsConfig: true,
2207
2049
  compilerOptions: this.options
2208
2050
  });
2209
- for (const sf of discovery.getSourceFiles()) this.request(sf.getFilePath(), { schema: true });
2210
- this.flush();
2051
+ this.requestDiscoveryFiles();
2211
2052
  }
2212
2053
  project;
2213
2054
  options;
2214
2055
  /** Parses a file that is not in discovery's Project, without adding it there. */
2215
- scratch = new Project3({
2056
+ scratch = new Project2({
2216
2057
  skipAddingFilesFromTsConfig: true,
2217
2058
  skipLoadingLibFiles: true,
2218
2059
  skipFileDependencyResolution: true
@@ -2220,6 +2061,53 @@ var SchemaTypeProgram = class {
2220
2061
  needs = /* @__PURE__ */ new Map();
2221
2062
  /** Pruned text per file, as last written into {@link project}. */
2222
2063
  written = /* @__PURE__ */ new Map();
2064
+ /** What {@link project} read from disk unpruned (packages, declaration files). */
2065
+ externalFiles = new DiskFingerprints();
2066
+ scratchFiles = new DiskFingerprints();
2067
+ /** The last pruning of each file, with the source text and needs it was pruned for. */
2068
+ pruned = /* @__PURE__ */ new Map();
2069
+ /**
2070
+ * Bring the program up to date with discovery's files and the disk, keeping the
2071
+ * lib and package declarations it has already parsed and checked. Leaves it
2072
+ * holding exactly the files a new program built now would hold, with the same
2073
+ * text. Returns false when a file the program read unpruned has changed on disk
2074
+ * (a package was reinstalled, a `.d.ts` was edited): the caller builds a new
2075
+ * program instead.
2076
+ */
2077
+ refresh() {
2078
+ const program = this.project.getProgram().compilerObject;
2079
+ const external = program.getSourceFiles().filter(
2080
+ (file) => !program.isSourceFileDefaultLibrary(file) && !this.written.has(file.fileName)
2081
+ ).map((file) => ({ path: file.fileName, text: file.text }));
2082
+ if (this.externalFiles.changed(external).length > 0) return false;
2083
+ const scratch = this.scratch.getSourceFiles().map((sf) => ({ path: sf.getFilePath(), text: sf.getFullText() }));
2084
+ for (const path of this.scratchFiles.changed(scratch)) {
2085
+ const sf = this.scratch.getSourceFile(path);
2086
+ if (sf?.refreshFromFileSystemSync() === FileSystemRefreshResult2.Deleted) {
2087
+ this.scratchFiles.forget(path);
2088
+ }
2089
+ }
2090
+ this.needs.clear();
2091
+ this.requestDiscoveryFiles();
2092
+ for (const path of [...this.written.keys()]) {
2093
+ if (this.needs.has(path)) continue;
2094
+ const sf = this.project.getSourceFile(path);
2095
+ if (sf) this.project.removeSourceFile(sf);
2096
+ this.written.delete(path);
2097
+ }
2098
+ return true;
2099
+ }
2100
+ /**
2101
+ * Everything discovery holds now (the controllers) goes in before the first
2102
+ * query: a file added afterwards discards the program and its checker, and
2103
+ * rebuilding them once per controller is exactly the cost this avoids.
2104
+ */
2105
+ requestDiscoveryFiles() {
2106
+ for (const sf of this.discovery.getSourceFiles()) {
2107
+ this.request(sf.getFilePath(), { schema: true });
2108
+ }
2109
+ this.flush();
2110
+ }
2223
2111
  /**
2224
2112
  * The typed copy of a discovery file, with its schema positions kept. Adding one
2225
2113
  * that was not there yet (a contract file discovery reached lazily) rebuilds the
@@ -2260,12 +2148,21 @@ var SchemaTypeProgram = class {
2260
2148
  while (this.queue.length > 0) {
2261
2149
  const path = this.queue.pop();
2262
2150
  const original = this.original(path);
2263
- if (!original) continue;
2264
- const needs = this.needs.get(path);
2265
- const text = this.prune(original, needs);
2151
+ if (!original) {
2152
+ if (this.written.has(path)) changed.set(path, void 0);
2153
+ continue;
2154
+ }
2155
+ const { text, requests } = this.pruneCached(path, original, this.needs.get(path));
2156
+ for (const { path: target, want } of requests) this.request(target, want);
2266
2157
  if (this.written.get(path) !== text) changed.set(path, text);
2267
2158
  }
2268
2159
  for (const [path, text] of changed) {
2160
+ if (text === void 0) {
2161
+ const sf = this.project.getSourceFile(path);
2162
+ if (sf) this.project.removeSourceFile(sf);
2163
+ this.written.delete(path);
2164
+ continue;
2165
+ }
2269
2166
  this.project.createSourceFile(path, text, { overwrite: true });
2270
2167
  this.written.set(path, text);
2271
2168
  }
@@ -2274,11 +2171,29 @@ var SchemaTypeProgram = class {
2274
2171
  original(path) {
2275
2172
  return this.discovery.getSourceFile(path) ?? this.scratch.getSourceFile(path) ?? this.scratch.addSourceFileAtPathIfExists(path);
2276
2173
  }
2174
+ /** {@link prune}, reusing the last result while the file's text and needs are the same. */
2175
+ pruneCached(path, sf, needs) {
2176
+ const source = sf.getFullText();
2177
+ const key = `${needs.schema}:${needs.all}:${[...needs.names].sort().join(",")}`;
2178
+ const last = this.pruned.get(path);
2179
+ if (last && last.source === source && last.needs === key) return last;
2180
+ const result = this.prune(sf, needs);
2181
+ if (result.resolved) this.pruned.set(path, { ...result, source, needs: key });
2182
+ else this.pruned.delete(path);
2183
+ return result;
2184
+ }
2277
2185
  /**
2278
2186
  * `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.
2187
+ * and the names each kept one brings in from its target.
2280
2188
  */
2281
2189
  prune(sf, needs) {
2190
+ const requests = [];
2191
+ let resolved = true;
2192
+ const resolveFrom = (specifier) => {
2193
+ const target = this.resolve(specifier, sf);
2194
+ if (target === null) resolved = false;
2195
+ return target ?? void 0;
2196
+ };
2282
2197
  const locals = topLevelDeclarations(sf);
2283
2198
  const roots = [];
2284
2199
  if (needs.all) roots.push(...sf.getStatements());
@@ -2312,7 +2227,7 @@ var SchemaTypeProgram = class {
2312
2227
  const seen = new Set(queue);
2313
2228
  while (queue.length > 0) {
2314
2229
  const node = queue.pop();
2315
- const ids = Node7.isIdentifier(node) ? [node] : node.getDescendantsOfKind(ts.SyntaxKind.Identifier);
2230
+ const ids = Node6.isIdentifier(node) ? [node] : node.getDescendantsOfKind(ts2.SyntaxKind.Identifier);
2316
2231
  for (const id of ids) {
2317
2232
  const name = id.getText();
2318
2233
  if (referenced.has(name)) continue;
@@ -2331,7 +2246,7 @@ var SchemaTypeProgram = class {
2331
2246
  }
2332
2247
  };
2333
2248
  for (const decl of sf.getImportDeclarations()) {
2334
- const target = this.resolve(decl.getModuleSpecifierValue(), sf);
2249
+ const target = resolveFrom(decl.getModuleSpecifierValue());
2335
2250
  const namespace = decl.getNamespaceImport()?.getText();
2336
2251
  const def = decl.getDefaultImport()?.getText();
2337
2252
  const named = decl.getNamedImports().map((n) => ({
@@ -2345,21 +2260,24 @@ var SchemaTypeProgram = class {
2345
2260
  continue;
2346
2261
  }
2347
2262
  if (target) {
2348
- this.request(target, {
2349
- all: wantsNamespace,
2350
- names: [...wantsDefault ? ["default"] : [], ...named.map((n) => n.imported)]
2263
+ requests.push({
2264
+ path: target,
2265
+ want: {
2266
+ all: wantsNamespace,
2267
+ names: [...wantsDefault ? ["default"] : [], ...named.map((n) => n.imported)]
2268
+ }
2351
2269
  });
2352
2270
  }
2353
2271
  }
2354
2272
  for (const exp of sf.getExportDeclarations()) {
2355
2273
  const spec = exp.getModuleSpecifierValue();
2356
2274
  if (spec === void 0) continue;
2357
- const target = this.resolve(spec, sf);
2275
+ const target = resolveFrom(spec);
2358
2276
  if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) {
2359
2277
  if (!needs.all && unresolvedNames.length === 0) {
2360
2278
  blank(exp);
2361
2279
  } else if (target) {
2362
- this.request(target, { all: needs.all, names: unresolvedNames });
2280
+ requests.push({ path: target, want: { all: needs.all, names: unresolvedNames } });
2363
2281
  }
2364
2282
  continue;
2365
2283
  }
@@ -2367,22 +2285,24 @@ var SchemaTypeProgram = class {
2367
2285
  (n) => needs.all || needs.names.has((n.getAliasNode() ?? n.getNameNode()).getText())
2368
2286
  ).map((n) => n.getNameNode().getText());
2369
2287
  if (wanted.length === 0) blank(exp);
2370
- else if (target) this.request(target, { names: wanted });
2288
+ else if (target) requests.push({ path: target, want: { names: wanted } });
2371
2289
  }
2372
- return chars.join("");
2290
+ return { text: chars.join(""), requests, resolved };
2373
2291
  }
2374
2292
  /**
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.
2293
+ * The project source file a module specifier resolves to; undefined for a
2294
+ * package or a declaration file (read unpruned by the program itself), null for
2295
+ * one that does not resolve.
2377
2296
  */
2378
2297
  resolve(specifier, from) {
2379
- const resolved = ts.resolveModuleName(
2298
+ const resolved = ts2.resolveModuleName(
2380
2299
  specifier,
2381
2300
  from.getFilePath(),
2382
2301
  this.options,
2383
- ts.sys
2302
+ ts2.sys
2384
2303
  ).resolvedModule;
2385
- if (!resolved || resolved.isExternalLibraryImport) return void 0;
2304
+ if (!resolved) return null;
2305
+ if (resolved.isExternalLibraryImport) return void 0;
2386
2306
  const file = resolved.resolvedFileName;
2387
2307
  if (file.endsWith(".d.ts") || !/\.(m|c)?tsx?$/.test(file)) return void 0;
2388
2308
  return file;
@@ -2391,20 +2311,20 @@ var SchemaTypeProgram = class {
2391
2311
  function schemaPositions(sf) {
2392
2312
  const roots = [];
2393
2313
  sf.forEachDescendant((node) => {
2394
- if (Node7.isDecorator(node)) {
2314
+ if (Node6.isDecorator(node)) {
2395
2315
  const name = node.getName();
2396
2316
  if (name === "ApplyContract") {
2397
2317
  roots.push(...node.getArguments());
2398
2318
  } else if (PARAM_DECORATORS.has(name)) {
2399
2319
  for (const arg of node.getArguments()) {
2400
- if (Node7.isNewExpression(arg) || Node7.isCallExpression(arg)) {
2320
+ if (Node6.isNewExpression(arg) || Node6.isCallExpression(arg)) {
2401
2321
  roots.push(...arg.getArguments());
2402
2322
  }
2403
2323
  }
2404
2324
  }
2405
- } else if (Node7.isCallExpression(node)) {
2325
+ } else if (Node6.isCallExpression(node)) {
2406
2326
  const callee = node.getExpression();
2407
- const name = Node7.isIdentifier(callee) ? callee.getText() : Node7.isPropertyAccessExpression(callee) ? callee.getName() : void 0;
2327
+ const name = Node6.isIdentifier(callee) ? callee.getText() : Node6.isPropertyAccessExpression(callee) ? callee.getName() : void 0;
2408
2328
  if (name === "defineContract") roots.push(...node.getArguments());
2409
2329
  }
2410
2330
  });
@@ -2437,8 +2357,13 @@ function defaultExport(sf) {
2437
2357
 
2438
2358
  // src/discovery/schema-type-inference.ts
2439
2359
  var programs = /* @__PURE__ */ new WeakMap();
2360
+ var stale = /* @__PURE__ */ new WeakSet();
2440
2361
  function programFor(discovery) {
2441
2362
  let program = programs.get(discovery);
2363
+ if (program && stale.has(discovery)) {
2364
+ stale.delete(discovery);
2365
+ if (!program.refresh()) program = void 0;
2366
+ }
2442
2367
  if (!program) {
2443
2368
  program = new SchemaTypeProgram(discovery);
2444
2369
  programs.set(discovery, program);
@@ -2447,6 +2372,10 @@ function programFor(discovery) {
2447
2372
  }
2448
2373
  function clearSchemaTypeProject(discovery) {
2449
2374
  programs.delete(discovery);
2375
+ stale.delete(discovery);
2376
+ }
2377
+ function invalidateSchemaTypeProject(discovery) {
2378
+ if (programs.has(discovery)) stale.add(discovery);
2450
2379
  }
2451
2380
  function counterpart(typedSf, node) {
2452
2381
  const start = node.getStart();
@@ -2510,7 +2439,7 @@ function objectKey(name) {
2510
2439
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
2511
2440
  }
2512
2441
  function isOptionalSymbol(sym) {
2513
- return (sym.getFlags() & ts2.SymbolFlags.Optional) !== 0;
2442
+ return (sym.getFlags() & ts3.SymbolFlags.Optional) !== 0;
2514
2443
  }
2515
2444
  function isSymbolKeyed(sym) {
2516
2445
  return sym.getName().startsWith("__@");
@@ -2671,11 +2600,11 @@ function acceptsUndefined(types) {
2671
2600
  }
2672
2601
 
2673
2602
  // src/discovery/zod-ast-to-ts.ts
2674
- import { Node as Node8, SyntaxKind as SyntaxKind3 } from "ts-morph";
2603
+ import { Node as Node7, SyntaxKind as SyntaxKind2 } from "ts-morph";
2675
2604
  function zodAstToTs(node) {
2676
- if (!Node8.isCallExpression(node)) return "unknown";
2605
+ if (!Node7.isCallExpression(node)) return "unknown";
2677
2606
  const expr = node.getExpression();
2678
- if (Node8.isPropertyAccessExpression(expr)) {
2607
+ if (Node7.isPropertyAccessExpression(expr)) {
2679
2608
  const methodName = expr.getName();
2680
2609
  const receiver = expr.getExpression();
2681
2610
  if (methodName === "optional") {
@@ -2699,17 +2628,17 @@ function zodAstToTs(node) {
2699
2628
  case "literal": {
2700
2629
  const lit = args[0];
2701
2630
  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";
2631
+ if (Node7.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
2632
+ if (Node7.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
2633
+ if (lit.getKind() === SyntaxKind2.TrueKeyword) return "true";
2634
+ if (lit.getKind() === SyntaxKind2.FalseKeyword) return "false";
2706
2635
  return "unknown";
2707
2636
  }
2708
2637
  case "enum": {
2709
2638
  const arrArg = args[0];
2710
- if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
2639
+ if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
2711
2640
  const members = arrArg.getElements().map(
2712
- (el) => Node8.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
2641
+ (el) => Node7.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
2713
2642
  );
2714
2643
  return members.join(" | ");
2715
2644
  }
@@ -2720,10 +2649,10 @@ function zodAstToTs(node) {
2720
2649
  }
2721
2650
  case "object": {
2722
2651
  const objArg = args[0];
2723
- if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
2652
+ if (!objArg || !Node7.isObjectLiteralExpression(objArg)) return "unknown";
2724
2653
  const lines = [];
2725
2654
  for (const prop of objArg.getProperties()) {
2726
- if (!Node8.isPropertyAssignment(prop)) continue;
2655
+ if (!Node7.isPropertyAssignment(prop)) continue;
2727
2656
  const key = prop.getName();
2728
2657
  const valNode = prop.getInitializer();
2729
2658
  if (!valNode) continue;
@@ -2735,7 +2664,7 @@ function zodAstToTs(node) {
2735
2664
  }
2736
2665
  case "union": {
2737
2666
  const arrArg = args[0];
2738
- if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
2667
+ if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
2739
2668
  return arrArg.getElements().map(zodAstToTs).join(" | ");
2740
2669
  }
2741
2670
  case "record": {
@@ -2745,7 +2674,7 @@ function zodAstToTs(node) {
2745
2674
  }
2746
2675
  case "tuple": {
2747
2676
  const arrArg = args[0];
2748
- if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
2677
+ if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
2749
2678
  return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
2750
2679
  }
2751
2680
  default:
@@ -2755,9 +2684,9 @@ function zodAstToTs(node) {
2755
2684
  return "unknown";
2756
2685
  }
2757
2686
  function isOptionalChain(node) {
2758
- if (!Node8.isCallExpression(node)) return false;
2687
+ if (!Node7.isCallExpression(node)) return false;
2759
2688
  const expr = node.getExpression();
2760
- return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
2689
+ return Node7.isPropertyAccessExpression(expr) && expr.getName() === "optional";
2761
2690
  }
2762
2691
  function memberType(val, position, project) {
2763
2692
  if (project) {
@@ -2769,13 +2698,13 @@ function memberType(val, position, project) {
2769
2698
  return zodAstToTs(val);
2770
2699
  }
2771
2700
  function parseDefineContractCall(callExpr, project) {
2772
- if (!Node8.isCallExpression(callExpr)) return null;
2701
+ if (!Node7.isCallExpression(callExpr)) return null;
2773
2702
  const callee = callExpr.getExpression();
2774
- const calleeName = Node8.isIdentifier(callee) ? callee.getText() : Node8.isPropertyAccessExpression(callee) ? callee.getName() : "";
2703
+ const calleeName = Node7.isIdentifier(callee) ? callee.getText() : Node7.isPropertyAccessExpression(callee) ? callee.getName() : "";
2775
2704
  if (calleeName !== "defineContract") return null;
2776
2705
  const args = callExpr.getArguments();
2777
2706
  const optsArg = args[0];
2778
- if (!optsArg || !Node8.isObjectLiteralExpression(optsArg)) return null;
2707
+ if (!optsArg || !Node7.isObjectLiteralExpression(optsArg)) return null;
2779
2708
  let query = null;
2780
2709
  let body = null;
2781
2710
  let response = "unknown";
@@ -2783,7 +2712,7 @@ function parseDefineContractCall(callExpr, project) {
2783
2712
  let bodyZodText = null;
2784
2713
  let queryZodText = null;
2785
2714
  for (const prop of optsArg.getProperties()) {
2786
- if (!Node8.isPropertyAssignment(prop)) continue;
2715
+ if (!Node7.isPropertyAssignment(prop)) continue;
2787
2716
  const propName = prop.getName();
2788
2717
  const val = prop.getInitializer();
2789
2718
  if (!val) continue;
@@ -2805,7 +2734,7 @@ function parseDefineContractCall(callExpr, project) {
2805
2734
  // src/discovery/pipe-schema.ts
2806
2735
  function pipeSchemaOf(decorator, project) {
2807
2736
  for (const pipe of decorator.getArguments()) {
2808
- if (!Node9.isNewExpression(pipe) && !Node9.isCallExpression(pipe)) continue;
2737
+ if (!Node8.isNewExpression(pipe) && !Node8.isCallExpression(pipe)) continue;
2809
2738
  for (const arg of pipe.getArguments()) {
2810
2739
  if (!couldBeSchema(arg)) continue;
2811
2740
  const inferred = inferSchema(arg, project);
@@ -2827,15 +2756,15 @@ function pipeSchemaOf(decorator, project) {
2827
2756
  return null;
2828
2757
  }
2829
2758
  function couldBeSchema(arg) {
2830
- return Node9.isIdentifier(arg) || Node9.isPropertyAccessExpression(arg) || Node9.isCallExpression(arg) || Node9.isAsExpression(arg) || Node9.isSatisfiesExpression(arg) || Node9.isParenthesizedExpression(arg);
2759
+ return Node8.isIdentifier(arg) || Node8.isPropertyAccessExpression(arg) || Node8.isCallExpression(arg) || Node8.isAsExpression(arg) || Node8.isSatisfiesExpression(arg) || Node8.isParenthesizedExpression(arg);
2831
2760
  }
2832
2761
  function syntacticZodType(arg, project) {
2833
2762
  let expr = arg;
2834
- if (Node9.isIdentifier(arg)) {
2763
+ if (Node8.isIdentifier(arg)) {
2835
2764
  const resolved = resolveImportedVariable(arg.getText(), arg.getSourceFile(), project);
2836
2765
  expr = resolved?.decl.getInitializer();
2837
2766
  }
2838
- if (!expr || !Node9.isCallExpression(expr)) return null;
2767
+ if (!expr || !Node8.isCallExpression(expr)) return null;
2839
2768
  const type = zodAstToTs(expr);
2840
2769
  return type === "unknown" ? null : type;
2841
2770
  }
@@ -2871,22 +2800,22 @@ var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
2871
2800
  ]);
2872
2801
  function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
2873
2802
  if (depth <= 0) return "unknown";
2874
- if (Node10.isArrayTypeNode(typeNode)) {
2803
+ if (Node9.isArrayTypeNode(typeNode)) {
2875
2804
  const elementType = typeNode.getElementTypeNode();
2876
2805
  return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth, subst)}>`;
2877
2806
  }
2878
- if (Node10.isUnionTypeNode(typeNode)) {
2807
+ if (Node9.isUnionTypeNode(typeNode)) {
2879
2808
  return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" | ");
2880
2809
  }
2881
- if (Node10.isIntersectionTypeNode(typeNode)) {
2810
+ if (Node9.isIntersectionTypeNode(typeNode)) {
2882
2811
  return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" & ");
2883
2812
  }
2884
- if (Node10.isParenthesizedTypeNode(typeNode)) {
2813
+ if (Node9.isParenthesizedTypeNode(typeNode)) {
2885
2814
  return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth, subst)})`;
2886
2815
  }
2887
- if (Node10.isTypeReference(typeNode)) {
2816
+ if (Node9.isTypeReference(typeNode)) {
2888
2817
  const typeName = typeNode.getTypeName();
2889
- const name = Node10.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
2818
+ const name = Node9.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
2890
2819
  const bound = subst.get(name);
2891
2820
  if (bound !== void 0) return bound;
2892
2821
  if (name === "string" || name === "number" || name === "boolean") return name;
@@ -2909,10 +2838,10 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
2909
2838
  dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
2910
2839
  return "unknown";
2911
2840
  }
2912
- if (Node10.isTypeLiteral(typeNode)) {
2841
+ if (Node9.isTypeLiteral(typeNode)) {
2913
2842
  const members = [];
2914
2843
  for (const member of typeNode.getMembers()) {
2915
- if (Node10.isPropertySignature(member)) {
2844
+ if (Node9.isPropertySignature(member)) {
2916
2845
  const memberTypeNode = member.getTypeNode();
2917
2846
  const memberType2 = memberTypeNode ? resolveTypeNodeToString(memberTypeNode, sourceFile, project, depth, subst) : "unknown";
2918
2847
  members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${memberType2}`);
@@ -2923,11 +2852,11 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
2923
2852
  return members.length > 0 ? `{ ${members.join("; ")} }` : "{}";
2924
2853
  }
2925
2854
  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";
2855
+ if (kind === SyntaxKind3.StringKeyword) return "string";
2856
+ if (kind === SyntaxKind3.NumberKeyword) return "number";
2857
+ if (kind === SyntaxKind3.BooleanKeyword) return "boolean";
2858
+ if (kind === SyntaxKind3.UnknownKeyword) return "unknown";
2859
+ if (kind === SyntaxKind3.AnyKeyword) return "unknown";
2931
2860
  return typeNode.getText();
2932
2861
  }
2933
2862
  function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode, subst = /* @__PURE__ */ new Map()) {
@@ -2985,7 +2914,7 @@ function resolvePropertied(decl, sourceFile, project, depth, subst = /* @__PURE_
2985
2914
  }
2986
2915
  function namedField(decorator) {
2987
2916
  const first = decorator.getArguments()[0];
2988
- return first && Node10.isStringLiteral(first) ? first.getLiteralValue() : null;
2917
+ return first && Node9.isStringLiteral(first) ? first.getLiteralValue() : null;
2989
2918
  }
2990
2919
  function extractBodyType(method, sourceFile, project) {
2991
2920
  for (const param of method.getParameters()) {
@@ -3035,8 +2964,8 @@ function extractQueryType(method, sourceFile, project) {
3035
2964
  function isParamOptional(param) {
3036
2965
  if (param.hasQuestionToken() || param.hasInitializer()) return true;
3037
2966
  const typeNode = param.getTypeNode();
3038
- if (typeNode && Node10.isUnionTypeNode(typeNode)) {
3039
- return typeNode.getTypeNodes().some((t) => t.getKind() === SyntaxKind4.UndefinedKeyword);
2967
+ if (typeNode && Node9.isUnionTypeNode(typeNode)) {
2968
+ return typeNode.getTypeNodes().some((t) => t.getKind() === SyntaxKind3.UndefinedKeyword);
3040
2969
  }
3041
2970
  return false;
3042
2971
  }
@@ -3048,7 +2977,7 @@ function extractParamsType(method, sourceFile, project) {
3048
2977
  const paramArgs = paramDecorator.getArguments();
3049
2978
  if (paramArgs.length === 0) continue;
3050
2979
  const nameArg = paramArgs[0];
3051
- if (!Node10.isStringLiteral(nameArg)) continue;
2980
+ if (!Node9.isStringLiteral(nameArg)) continue;
3052
2981
  const paramName = nameArg.getLiteralValue();
3053
2982
  const typeNode = param.getTypeNode();
3054
2983
  const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
@@ -3085,28 +3014,28 @@ function extractUploadedFiles(method) {
3085
3014
  for (const decorator of method.getDecorators()) {
3086
3015
  if (decorator.getName() !== "UseInterceptors") continue;
3087
3016
  for (const arg of decorator.getArguments()) {
3088
- if (!Node10.isCallExpression(arg)) continue;
3017
+ if (!Node9.isCallExpression(arg)) continue;
3089
3018
  const interceptor = arg.getExpression().getText();
3090
3019
  const callArgs = arg.getArguments();
3091
3020
  const firstArg2 = callArgs[0];
3092
3021
  if (interceptor === "FileInterceptor") {
3093
- if (firstArg2 && Node10.isStringLiteral(firstArg2)) {
3022
+ if (firstArg2 && Node9.isStringLiteral(firstArg2)) {
3094
3023
  entries.push(`${firstArg2.getLiteralValue()}: ${FILE}`);
3095
3024
  multipart = true;
3096
3025
  }
3097
3026
  } else if (interceptor === "FilesInterceptor") {
3098
- if (firstArg2 && Node10.isStringLiteral(firstArg2)) {
3027
+ if (firstArg2 && Node9.isStringLiteral(firstArg2)) {
3099
3028
  entries.push(`${firstArg2.getLiteralValue()}: Array<${FILE}>`);
3100
3029
  multipart = true;
3101
3030
  }
3102
3031
  } else if (interceptor === "FileFieldsInterceptor") {
3103
- if (firstArg2 && Node10.isArrayLiteralExpression(firstArg2)) {
3032
+ if (firstArg2 && Node9.isArrayLiteralExpression(firstArg2)) {
3104
3033
  for (const el of firstArg2.getElements()) {
3105
- if (!Node10.isObjectLiteralExpression(el)) continue;
3034
+ if (!Node9.isObjectLiteralExpression(el)) continue;
3106
3035
  const nameProp = el.getProperty("name");
3107
- if (nameProp && Node10.isPropertyAssignment(nameProp)) {
3036
+ if (nameProp && Node9.isPropertyAssignment(nameProp)) {
3108
3037
  const init = nameProp.getInitializer();
3109
- if (init && Node10.isStringLiteral(init)) {
3038
+ if (init && Node9.isStringLiteral(init)) {
3110
3039
  entries.push(`${init.getLiteralValue()}: Array<${FILE}>`);
3111
3040
  }
3112
3041
  }
@@ -3126,13 +3055,13 @@ function extractResponseType(method, sourceFile, project) {
3126
3055
  if (apiResponseDecorator) {
3127
3056
  const args = apiResponseDecorator.getArguments();
3128
3057
  const optsArg = args[0];
3129
- if (optsArg && Node10.isObjectLiteralExpression(optsArg)) {
3058
+ if (optsArg && Node9.isObjectLiteralExpression(optsArg)) {
3130
3059
  for (const prop of optsArg.getProperties()) {
3131
- if (!Node10.isPropertyAssignment(prop)) continue;
3060
+ if (!Node9.isPropertyAssignment(prop)) continue;
3132
3061
  if (prop.getName() !== "type") continue;
3133
3062
  const val = prop.getInitializer();
3134
3063
  if (!val) continue;
3135
- if (Node10.isArrayLiteralExpression(val)) {
3064
+ if (Node9.isArrayLiteralExpression(val)) {
3136
3065
  const elements = val.getElements();
3137
3066
  const firstEl = elements[0];
3138
3067
  if (elements.length > 0 && firstEl !== void 0) {
@@ -3153,24 +3082,24 @@ function extractResponseType(method, sourceFile, project) {
3153
3082
  }
3154
3083
  function apiResponseStatus(decorator) {
3155
3084
  const optsArg = decorator.getArguments()[0];
3156
- if (!optsArg || !Node10.isObjectLiteralExpression(optsArg)) return null;
3085
+ if (!optsArg || !Node9.isObjectLiteralExpression(optsArg)) return null;
3157
3086
  for (const prop of optsArg.getProperties()) {
3158
- if (!Node10.isPropertyAssignment(prop)) continue;
3087
+ if (!Node9.isPropertyAssignment(prop)) continue;
3159
3088
  if (prop.getName() !== "status") continue;
3160
3089
  const val = prop.getInitializer();
3161
- if (val && Node10.isNumericLiteral(val)) return Number(val.getLiteralValue());
3090
+ if (val && Node9.isNumericLiteral(val)) return Number(val.getLiteralValue());
3162
3091
  }
3163
3092
  return null;
3164
3093
  }
3165
3094
  function apiResponseTypeNode(decorator) {
3166
3095
  const optsArg = decorator.getArguments()[0];
3167
- if (!optsArg || !Node10.isObjectLiteralExpression(optsArg)) return null;
3096
+ if (!optsArg || !Node9.isObjectLiteralExpression(optsArg)) return null;
3168
3097
  for (const prop of optsArg.getProperties()) {
3169
- if (!Node10.isPropertyAssignment(prop)) continue;
3098
+ if (!Node9.isPropertyAssignment(prop)) continue;
3170
3099
  if (prop.getName() !== "type") continue;
3171
3100
  const val = prop.getInitializer();
3172
3101
  if (!val) return null;
3173
- if (Node10.isArrayLiteralExpression(val)) {
3102
+ if (Node9.isArrayLiteralExpression(val)) {
3174
3103
  const first = val.getElements()[0];
3175
3104
  return first ? { node: first, isArray: true } : null;
3176
3105
  }
@@ -3188,7 +3117,7 @@ function extractErrorType(method, sourceFile, project) {
3188
3117
  const inner = resolveIdentifierToClassType(typeInfo.node, sourceFile, project, 3);
3189
3118
  const type = typeInfo.isArray ? `Array<${inner}>` : inner;
3190
3119
  let ref = null;
3191
- if (Node10.isIdentifier(typeInfo.node)) {
3120
+ if (Node9.isIdentifier(typeInfo.node)) {
3192
3121
  const name = typeInfo.node.getText();
3193
3122
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
3194
3123
  if (localDecl?.isExported()) {
@@ -3205,7 +3134,7 @@ function extractErrorType(method, sourceFile, project) {
3205
3134
  return null;
3206
3135
  }
3207
3136
  function resolveIdentifierToClassType(node, sourceFile, project, depth) {
3208
- if (!Node10.isIdentifier(node)) return "unknown";
3137
+ if (!Node9.isIdentifier(node)) return "unknown";
3209
3138
  const name = node.getText();
3210
3139
  const resolved = findType(name, sourceFile, project);
3211
3140
  if (resolved) {
@@ -3225,9 +3154,9 @@ var STREAM_ENVELOPES = /* @__PURE__ */ new Set(["MessageEvent", "MessageEventLik
3225
3154
  var BINARY_RESPONSE_TYPES = /* @__PURE__ */ new Set(["StreamableFile", "Buffer"]);
3226
3155
  function detectBinaryResponse(method) {
3227
3156
  const node = unwrapNamedContainer(method.getReturnTypeNode(), /* @__PURE__ */ new Set(["Promise"]));
3228
- if (!node || !Node10.isTypeReference(node)) return false;
3157
+ if (!node || !Node9.isTypeReference(node)) return false;
3229
3158
  const typeName = node.getTypeName();
3230
- const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
3159
+ const name = Node9.isIdentifier(typeName) ? typeName.getText() : "";
3231
3160
  return BINARY_RESPONSE_TYPES.has(name);
3232
3161
  }
3233
3162
  function detectStreamElement(method) {
@@ -3242,9 +3171,9 @@ function detectStreamElement(method) {
3242
3171
  return null;
3243
3172
  }
3244
3173
  function streamContainerElement(node) {
3245
- if (!node || !Node10.isTypeReference(node)) return null;
3174
+ if (!node || !Node9.isTypeReference(node)) return null;
3246
3175
  const typeName = node.getTypeName();
3247
- const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
3176
+ const name = Node9.isIdentifier(typeName) ? typeName.getText() : "";
3248
3177
  if (STREAM_CONTAINERS.has(name) || STREAM_CONTAINERS_GENERATOR.has(name)) {
3249
3178
  return node.getTypeArguments()[0] ?? null;
3250
3179
  }
@@ -3254,9 +3183,9 @@ function hasAsQueryDecorator(method) {
3254
3183
  return method.getDecorators().some((d) => d.getName() === "AsQuery");
3255
3184
  }
3256
3185
  function unwrapNamedContainer(node, names) {
3257
- if (!node || !Node10.isTypeReference(node)) return node;
3186
+ if (!node || !Node9.isTypeReference(node)) return node;
3258
3187
  const typeName = node.getTypeName();
3259
- const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
3188
+ const name = Node9.isIdentifier(typeName) ? typeName.getText() : "";
3260
3189
  if (names.has(name)) {
3261
3190
  return node.getTypeArguments()[0] ?? node;
3262
3191
  }
@@ -3303,11 +3232,11 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
3303
3232
  if (apiResp) {
3304
3233
  const args = apiResp.getArguments();
3305
3234
  const optsArg = args[0];
3306
- if (optsArg && Node10.isObjectLiteralExpression(optsArg)) {
3235
+ if (optsArg && Node9.isObjectLiteralExpression(optsArg)) {
3307
3236
  for (const prop of optsArg.getProperties()) {
3308
- if (Node10.isPropertyAssignment(prop) && prop.getName() === "type") {
3237
+ if (Node9.isPropertyAssignment(prop) && prop.getName() === "type") {
3309
3238
  const val = prop.getInitializer();
3310
- if (val && Node10.isIdentifier(val)) {
3239
+ if (val && Node9.isIdentifier(val)) {
3311
3240
  const name = val.getText();
3312
3241
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
3313
3242
  if (localDecl?.isExported()) {
@@ -3394,7 +3323,7 @@ async function discoverContractsFast(opts) {
3394
3323
  return routes;
3395
3324
  }
3396
3325
  function resolveTsconfigPath(cwd, tsconfig) {
3397
- return tsconfig ? resolve3(tsconfig) : join4(cwd, "tsconfig.json");
3326
+ return tsconfig ? resolve3(tsconfig) : join3(cwd, "tsconfig.json");
3398
3327
  }
3399
3328
  var NO_TSCONFIG_OPTIONS = {
3400
3329
  allowJs: true,
@@ -3403,25 +3332,25 @@ var NO_TSCONFIG_OPTIONS = {
3403
3332
  };
3404
3333
  function loadDiscoveryTsconfig(tsconfigPath) {
3405
3334
  if (!existsSync(tsconfigPath)) return { options: null, files: [] };
3406
- const read = ts3.readConfigFile(tsconfigPath, (path) => ts3.sys.readFile(path));
3335
+ const read = ts4.readConfigFile(tsconfigPath, (path) => ts4.sys.readFile(path));
3407
3336
  if (read.error) {
3408
3337
  return {
3409
3338
  options: null,
3410
3339
  files: [tsconfigPath],
3411
- error: ts3.flattenDiagnosticMessageText(read.error.messageText, " ")
3340
+ error: ts4.flattenDiagnosticMessageText(read.error.messageText, " ")
3412
3341
  };
3413
3342
  }
3414
3343
  const host = {
3415
- useCaseSensitiveFileNames: ts3.sys.useCaseSensitiveFileNames,
3344
+ useCaseSensitiveFileNames: ts4.sys.useCaseSensitiveFileNames,
3416
3345
  // The one line that keeps a tsconfig from being a filesystem walk.
3417
3346
  readDirectory: () => [],
3418
- fileExists: (path) => ts3.sys.fileExists(path),
3347
+ fileExists: (path) => ts4.sys.fileExists(path),
3419
3348
  // `extends` is resolved through the host, so a consumer whose `paths` live
3420
3349
  // in a base tsconfig keeps working.
3421
- readFile: (path) => ts3.sys.readFile(path)
3350
+ readFile: (path) => ts4.sys.readFile(path)
3422
3351
  };
3423
- const sourceFile = ts3.readJsonConfigFile(tsconfigPath, (path) => ts3.sys.readFile(path));
3424
- const parsed = ts3.parseJsonSourceFileConfigFileContent(
3352
+ const sourceFile = ts4.readJsonConfigFile(tsconfigPath, (path) => ts4.sys.readFile(path));
3353
+ const parsed = ts4.parseJsonSourceFileConfigFileContent(
3425
3354
  sourceFile,
3426
3355
  host,
3427
3356
  dirname2(tsconfigPath),
@@ -3444,7 +3373,7 @@ function createDiscoveryProject(tsconfigPath, tsconfig = loadDiscoveryTsconfig(t
3444
3373
  `[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
3374
  );
3446
3375
  }
3447
- return new Project4({
3376
+ return new Project3({
3448
3377
  compilerOptions: tsconfig.options ?? { ...NO_TSCONFIG_OPTIONS },
3449
3378
  skipAddingFilesFromTsConfig: true,
3450
3379
  skipLoadingLibFiles: true,
@@ -3486,6 +3415,9 @@ var PersistentDiscovery = class _PersistentDiscovery {
3486
3415
  glob;
3487
3416
  /** Absolute paths of the controllers currently loaded as extraction roots. */
3488
3417
  controllerPaths = /* @__PURE__ */ new Set();
3418
+ /** Every file in {@link project}, as last read from disk. */
3419
+ files = new DiskFingerprints();
3420
+ unresolved = new UnresolvedImports();
3489
3421
  constructor(project, cwd, glob) {
3490
3422
  this.project = project;
3491
3423
  this.cwd = cwd;
@@ -3511,6 +3443,8 @@ var PersistentDiscovery = class _PersistentDiscovery {
3511
3443
  }
3512
3444
  /** Run the initial extraction (equivalent to a first `discoverContractsFast`). */
3513
3445
  discover() {
3446
+ clearMixinTypeProject();
3447
+ clearSchemaTypeProject(this.project);
3514
3448
  return this.runExtraction();
3515
3449
  }
3516
3450
  /**
@@ -3518,26 +3452,39 @@ var PersistentDiscovery = class _PersistentDiscovery {
3518
3452
  * from disk (controllers AND any lazily-loaded DTO/imported files), re-globs
3519
3453
  * to pick up added/removed controllers, clears the per-Project caches, then
3520
3454
  * re-extracts. `changedPaths` is a hint; correctness does not depend on it
3521
- * being exhaustive because re-globbing + refresh-on-presence covers the set.
3455
+ * being exhaustive because re-globbing + comparing every loaded file with the
3456
+ * disk covers the set.
3522
3457
  */
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
- }
3458
+ async rediscover(changedPaths, options = {}) {
3459
+ const hinted = new Set([...changedPaths ?? []].map((p) => resolve3(p)));
3460
+ const program = this.project.getProgram().compilerObject;
3461
+ const sources = program.getSourceFiles().filter(
3462
+ (file) => !program.isSourceFileDefaultLibrary(file) && !program.isSourceFileFromExternalLibrary(file)
3463
+ ).map((file) => ({ path: file.fileName, text: file.text }));
3464
+ for (const path of /* @__PURE__ */ new Set([...hinted, ...this.files.changed(sources)])) {
3465
+ const sf = this.project.getSourceFile(path);
3466
+ if (sf && await sf.refreshFromFileSystem() === FileSystemRefreshResult3.Deleted) {
3467
+ this.files.forget(path);
3531
3468
  }
3532
3469
  }
3470
+ const inProject = new Set(this.project.getSourceFiles().map((sf) => sf.getFilePath()));
3471
+ const definitions = this.project.getLanguageService().compilerObject.getProgram();
3472
+ for (const path of definitions ? this.unresolved.nowResolving(definitions) : []) {
3473
+ const sf = this.project.getSourceFile(path);
3474
+ if (!sf) continue;
3475
+ this.project.removeSourceFile(sf);
3476
+ if (inProject.has(path)) this.project.addSourceFileAtPathIfExists(path);
3477
+ }
3533
3478
  const globbed = new Set(
3534
3479
  await fg2(this.glob, { cwd: this.cwd, absolute: true, onlyFiles: true })
3535
3480
  );
3481
+ let controllersChanged = false;
3536
3482
  for (const f of globbed) {
3537
3483
  if (!this.controllerPaths.has(f)) {
3538
3484
  try {
3539
3485
  this.project.addSourceFileAtPath(f);
3540
3486
  this.controllerPaths.add(f);
3487
+ controllersChanged = true;
3541
3488
  } catch {
3542
3489
  }
3543
3490
  }
@@ -3547,25 +3494,31 @@ var PersistentDiscovery = class _PersistentDiscovery {
3547
3494
  const sf = this.project.getSourceFile(f);
3548
3495
  if (sf) this.project.removeSourceFile(sf);
3549
3496
  this.controllerPaths.delete(f);
3497
+ controllersChanged = true;
3550
3498
  }
3551
3499
  }
3500
+ if (controllersChanged || options.filesAddedOrRemoved) {
3501
+ clearMixinTypeProject();
3502
+ clearSchemaTypeProject(this.project);
3503
+ } else {
3504
+ invalidateMixinTypeProject();
3505
+ invalidateSchemaTypeProject(this.project);
3506
+ }
3552
3507
  return this.runExtraction();
3553
3508
  }
3554
3509
  /** Clear stale per-Project caches, then extract over the controller set. */
3555
3510
  runExtraction() {
3556
3511
  clearTypeResolutionCaches(this.project);
3557
3512
  clearEnumCache(this.project);
3558
- clearMixinTypeProject();
3559
- clearSchemaTypeProject(this.project);
3560
3513
  return extractRoutesFrom(this.project, this.controllerPaths);
3561
3514
  }
3562
3515
  };
3563
3516
  function decoratorStringArg(decoratorExpr) {
3564
3517
  if (!decoratorExpr) return void 0;
3565
- if (Node11.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
3566
- if (Node11.isArrayLiteralExpression(decoratorExpr)) {
3518
+ if (Node10.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
3519
+ if (Node10.isArrayLiteralExpression(decoratorExpr)) {
3567
3520
  const first = decoratorExpr.getElements()[0];
3568
- if (first && Node11.isStringLiteral(first)) return first.getLiteralValue();
3521
+ if (first && Node10.isStringLiteral(first)) return first.getLiteralValue();
3569
3522
  }
3570
3523
  return void 0;
3571
3524
  }
@@ -3686,9 +3639,9 @@ function extractContractRoute(args) {
3686
3639
  let contractDef = null;
3687
3640
  let bodyZodRef = null;
3688
3641
  let queryZodRef = null;
3689
- if (Node11.isCallExpression(firstDecoratorArg)) {
3642
+ if (Node10.isCallExpression(firstDecoratorArg)) {
3690
3643
  contractDef = parseDefineContractCall(firstDecoratorArg, project);
3691
- } else if (Node11.isIdentifier(firstDecoratorArg)) {
3644
+ } else if (Node10.isIdentifier(firstDecoratorArg)) {
3692
3645
  const identName = firstDecoratorArg.getText();
3693
3646
  const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
3694
3647
  if (!resolvedVar) {
@@ -3706,144 +3659,437 @@ function extractContractRoute(args) {
3706
3659
  if (contractDef.body !== null) {
3707
3660
  bodyZodRef = { name: `${identName}.body`, filePath };
3708
3661
  }
3709
- if (contractDef.query !== null) {
3710
- queryZodRef = { name: `${identName}.query`, filePath };
3662
+ if (contractDef.query !== null) {
3663
+ queryZodRef = { name: `${identName}.query`, filePath };
3664
+ }
3665
+ }
3666
+ } else {
3667
+ console.warn(
3668
+ `[nestjs-codegen/fast] @ApplyContract arg is not an identifier or call expression in ${sourceFile.getFilePath()} \u2014 skipping`
3669
+ );
3670
+ return null;
3671
+ }
3672
+ if (!contractDef) return null;
3673
+ if (!verb) return null;
3674
+ const resolvedPath = joinPaths(prefix, verb.handlerPath);
3675
+ const methodName = method.getName();
3676
+ const classAs = readAsDecorator(cls, `class ${className}`);
3677
+ const methodAs = readAsDecorator(method, `${className}.${methodName}`);
3678
+ return buildRoute({
3679
+ className,
3680
+ methodName,
3681
+ resolvedMethod: verb.httpMethod,
3682
+ combinedPath: resolvedPath,
3683
+ classAs,
3684
+ methodAs,
3685
+ sourceFile,
3686
+ seenNames,
3687
+ mixin,
3688
+ contractSource: {
3689
+ query: contractDef.query,
3690
+ body: contractDef.body,
3691
+ response: contractDef.response,
3692
+ error: contractDef.error,
3693
+ // Path A: capture both the importable ref and the raw text. The emitter
3694
+ // prefers inlining the text (client-safe — re-exporting from a controller
3695
+ // would drag server-only deps into the client bundle).
3696
+ bodyZodRef,
3697
+ bodyZodText: contractDef.bodyZodText,
3698
+ queryZodRef,
3699
+ queryZodText: contractDef.queryZodText
3700
+ }
3701
+ });
3702
+ }
3703
+ function extractDtoRoute(args) {
3704
+ const { cls, method, verb, prefix, className, sourceFile, project, seenNames, mixin } = args;
3705
+ if (!verb) return null;
3706
+ const combined = joinPaths(prefix, verb.handlerPath);
3707
+ const methodName = method.getName();
3708
+ const classAs = readAsDecorator(cls, `class ${className}`);
3709
+ const methodAs = readAsDecorator(method, `${className}.${methodName}`);
3710
+ const dtoContract = extractDtoContract(method, method.getSourceFile(), project, mixin, cls);
3711
+ const mixinResponse = mixin ? resolveInstantiatedReturnType(cls, methodName) : void 0;
3712
+ return buildRoute({
3713
+ className,
3714
+ methodName,
3715
+ resolvedMethod: verb.httpMethod,
3716
+ combinedPath: combined,
3717
+ classAs,
3718
+ methodAs,
3719
+ sourceFile,
3720
+ seenNames,
3721
+ mixin,
3722
+ contractSource: {
3723
+ query: dtoContract?.query ?? null,
3724
+ body: dtoContract?.body ?? null,
3725
+ response: mixinResponse ?? dtoContract?.response ?? "unknown",
3726
+ error: dtoContract?.error ?? null,
3727
+ paramTypes: dtoContract?.paramTypes ?? null,
3728
+ queryRef: dtoContract?.queryRef ?? null,
3729
+ bodyRef: dtoContract?.bodyRef ?? null,
3730
+ responseRef: dtoContract?.responseRef ?? null,
3731
+ errorRef: dtoContract?.errorRef ?? null,
3732
+ filterFields: dtoContract?.filterFields ?? null,
3733
+ filterFieldTypes: dtoContract?.filterFieldTypes ?? null,
3734
+ filterSource: dtoContract?.filterSource ?? null,
3735
+ formWarnings: dtoContract?.formWarnings ?? [],
3736
+ bodySchema: dtoContract?.bodySchema ?? null,
3737
+ querySchema: dtoContract?.querySchema ?? null,
3738
+ stream: dtoContract?.stream ?? false,
3739
+ multipart: dtoContract?.multipart ?? false,
3740
+ multipartBody: dtoContract?.multipartBody ?? null,
3741
+ binaryResponse: dtoContract?.binaryResponse ?? false,
3742
+ asQuery: dtoContract?.asQuery ?? false
3743
+ }
3744
+ });
3745
+ }
3746
+ function extractFromSourceFile(sourceFile, project) {
3747
+ const routes = [];
3748
+ const seenNames = /* @__PURE__ */ new Map();
3749
+ for (const cls of sourceFile.getClasses()) {
3750
+ const controllerDecorator = cls.getDecorator("Controller");
3751
+ if (!controllerDecorator) continue;
3752
+ const firstArg2 = controllerDecorator.getArguments()[0];
3753
+ const prefix = decoratorStringArg(firstArg2) ?? "";
3754
+ const className = cls.getName() ?? "Unknown";
3755
+ const inherited = resolveInheritedMethods(cls);
3756
+ const mixinBinding = inherited ? {
3757
+ factoryName: inherited.factoryName,
3758
+ factoryFilePath: inherited.factoryFilePath,
3759
+ classArgs: inherited.classArgs,
3760
+ namedClassArgs: inherited.namedClassArgs
3761
+ } : void 0;
3762
+ const ownMethods = cls.getMethods();
3763
+ const ownNames = new Set(ownMethods.map((m) => m.getName()));
3764
+ const methods = [
3765
+ ...ownMethods.map((method) => ({ method, mixin: mixinBinding })),
3766
+ ...(inherited?.methods ?? []).filter((method) => !ownNames.has(method.getName())).map((method) => ({ method, mixin: mixinBinding }))
3767
+ ];
3768
+ for (const { method, mixin } of methods) {
3769
+ const verb = resolveVerb(method);
3770
+ const applyContractDecorator = method.getDecorator("ApplyContract");
3771
+ const route = applyContractDecorator ? extractContractRoute({
3772
+ cls,
3773
+ method,
3774
+ applyContractDecorator,
3775
+ verb,
3776
+ prefix,
3777
+ className,
3778
+ sourceFile,
3779
+ project,
3780
+ seenNames,
3781
+ mixin
3782
+ }) : extractDtoRoute({
3783
+ cls,
3784
+ method,
3785
+ verb,
3786
+ prefix,
3787
+ className,
3788
+ sourceFile,
3789
+ project,
3790
+ seenNames,
3791
+ mixin
3792
+ });
3793
+ if (route) routes.push(route);
3794
+ }
3795
+ }
3796
+ return routes;
3797
+ }
3798
+
3799
+ // src/discovery/shared-props.ts
3800
+ function discoverSharedProps(project, moduleEntry) {
3801
+ try {
3802
+ let sourceFile = project.getSourceFile(moduleEntry);
3803
+ if (!sourceFile) {
3804
+ try {
3805
+ sourceFile = project.addSourceFileAtPath(moduleEntry);
3806
+ } catch {
3807
+ return null;
3808
+ }
3809
+ }
3810
+ const forRootCall = findForRootCall(sourceFile);
3811
+ if (!forRootCall) return null;
3812
+ const initializer = findShareInitializer(forRootCall);
3813
+ if (!initializer) return null;
3814
+ return extractShareType(initializer, sourceFile, project);
3815
+ } catch {
3816
+ return null;
3817
+ }
3818
+ }
3819
+ function discoverSharedPropsFromConfig(config) {
3820
+ if (!config.app?.moduleEntry) return null;
3821
+ try {
3822
+ const tsconfigPath = config.app.tsconfig ?? join4(config.codegen.cwd, "tsconfig.json");
3823
+ let project;
3824
+ try {
3825
+ project = sharedPropsProject(tsconfigPath);
3826
+ } catch {
3827
+ project = new Project4({
3828
+ skipAddingFilesFromTsConfig: true,
3829
+ skipLoadingLibFiles: true,
3830
+ skipFileDependencyResolution: true,
3831
+ compilerOptions: { allowJs: true, strict: false }
3832
+ });
3833
+ }
3834
+ return discoverSharedProps(project, config.app.moduleEntry);
3835
+ } catch {
3836
+ return null;
3837
+ }
3838
+ }
3839
+ var parsedTsconfigs = /* @__PURE__ */ new Map();
3840
+ function readText(file) {
3841
+ try {
3842
+ return readFileSync2(file, "utf8");
3843
+ } catch {
3844
+ return "\0missing";
3845
+ }
3846
+ }
3847
+ function sharedPropsProject(tsconfigPath) {
3848
+ const text = loadDiscoveryTsconfig(tsconfigPath).files.map((file) => `${file}\0${readText(file)}`).join("\0");
3849
+ const flags = {
3850
+ skipAddingFilesFromTsConfig: true,
3851
+ skipLoadingLibFiles: true,
3852
+ skipFileDependencyResolution: true
3853
+ };
3854
+ const parsed = parsedTsconfigs.get(tsconfigPath);
3855
+ if (parsed?.text === text) return new Project4({ ...flags, compilerOptions: parsed.options });
3856
+ const project = new Project4({ ...flags, tsConfigFilePath: tsconfigPath });
3857
+ parsedTsconfigs.set(tsconfigPath, { text, options: project.getCompilerOptions() });
3858
+ return project;
3859
+ }
3860
+ function findForRootCall(sourceFile) {
3861
+ const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind4.CallExpression);
3862
+ for (const call of callExpressions) {
3863
+ const expr = call.getExpression();
3864
+ if (!Node11.isPropertyAccessExpression(expr)) continue;
3865
+ const methodName = expr.getName();
3866
+ const objectExpr = expr.getExpression();
3867
+ if (methodName === "forRoot" && Node11.isIdentifier(objectExpr)) {
3868
+ const name = objectExpr.getText();
3869
+ if (name === "InertiaModule") {
3870
+ return call;
3871
+ }
3872
+ }
3873
+ }
3874
+ return null;
3875
+ }
3876
+ function findShareInitializer(forRootCall) {
3877
+ if (!Node11.isCallExpression(forRootCall)) return null;
3878
+ const args = forRootCall.getArguments();
3879
+ const firstArg2 = args[0];
3880
+ if (!firstArg2 || !Node11.isObjectLiteralExpression(firstArg2)) return null;
3881
+ for (const prop of firstArg2.getProperties()) {
3882
+ if (Node11.isPropertyAssignment(prop) && prop.getName() === "share") {
3883
+ return prop.getInitializer() ?? null;
3884
+ }
3885
+ }
3886
+ return null;
3887
+ }
3888
+ function extractShareType(node, sourceFile, project) {
3889
+ if (Node11.isIdentifier(node)) {
3890
+ const ref = resolveIdentifierToImportRef(node, sourceFile, project);
3891
+ if (ref) {
3892
+ return {
3893
+ typeString: `Awaited<ReturnType<typeof import('${ref.importPath}').${ref.exportName}>>`,
3894
+ properties: null,
3895
+ isImportRef: true
3896
+ };
3897
+ }
3898
+ }
3899
+ if (Node11.isArrowFunction(node)) {
3900
+ const result = extractFromFunctionLike(node, sourceFile);
3901
+ return result ? { ...result, isImportRef: false } : null;
3902
+ }
3903
+ if (Node11.isFunctionExpression(node)) {
3904
+ const result = extractFromFunctionLike(node, sourceFile);
3905
+ return result ? { ...result, isImportRef: false } : null;
3906
+ }
3907
+ if (Node11.isObjectLiteralExpression(node)) {
3908
+ const result = extractFromObjectLiteral(node);
3909
+ return result ? { ...result, isImportRef: false } : null;
3910
+ }
3911
+ return null;
3912
+ }
3913
+ function resolveIdentifierToImportRef(id, sourceFile, project) {
3914
+ if (!Node11.isIdentifier(id)) return null;
3915
+ const name = id.getText();
3916
+ const localFunc = sourceFile.getFunction(name);
3917
+ if (localFunc?.isExported()) {
3918
+ const filePath = sourceFile.getFilePath().replace(/\.ts$/, "");
3919
+ return { importPath: filePath, exportName: name };
3920
+ }
3921
+ const localVar = sourceFile.getVariableDeclaration(name);
3922
+ if (localVar?.isExported()) {
3923
+ const filePath = sourceFile.getFilePath().replace(/\.ts$/, "");
3924
+ return { importPath: filePath, exportName: name };
3925
+ }
3926
+ for (const imp of sourceFile.getImportDeclarations()) {
3927
+ for (const named of imp.getNamedImports()) {
3928
+ const importedName = named.getAliasNode()?.getText() ?? named.getName();
3929
+ if (importedName !== name) continue;
3930
+ const resolvedSource = imp.getModuleSpecifierSourceFile();
3931
+ if (!resolvedSource) continue;
3932
+ const originalName = named.getName();
3933
+ const fn = resolvedSource.getFunction(originalName);
3934
+ if (fn?.isExported()) {
3935
+ const filePath = resolvedSource.getFilePath().replace(/\.ts$/, "");
3936
+ return { importPath: filePath, exportName: originalName };
3937
+ }
3938
+ const v = resolvedSource.getVariableDeclaration(originalName);
3939
+ if (v?.isExported()) {
3940
+ const filePath = resolvedSource.getFilePath().replace(/\.ts$/, "");
3941
+ return { importPath: filePath, exportName: originalName };
3942
+ }
3943
+ }
3944
+ }
3945
+ return null;
3946
+ }
3947
+ function extractFromFunctionLike(node, _sourceFile) {
3948
+ const returnTypeNode = Node11.isArrowFunction(node) || Node11.isFunctionExpression(node) ? node.getReturnTypeNode() : null;
3949
+ if (returnTypeNode) {
3950
+ return extractFromReturnTypeAnnotation(returnTypeNode);
3951
+ }
3952
+ if (Node11.isArrowFunction(node)) {
3953
+ const body = node.getBody();
3954
+ if (Node11.isParenthesizedExpression(body)) {
3955
+ const inner = body.getExpression();
3956
+ if (Node11.isObjectLiteralExpression(inner)) {
3957
+ return extractFromObjectLiteral(inner);
3958
+ }
3959
+ }
3960
+ if (Node11.isObjectLiteralExpression(body)) {
3961
+ return extractFromObjectLiteral(body);
3962
+ }
3963
+ if (Node11.isBlock(body)) {
3964
+ return extractFromBlockReturn(body);
3965
+ }
3966
+ }
3967
+ if (Node11.isFunctionExpression(node)) {
3968
+ const body = node.getBody();
3969
+ if (Node11.isBlock(body)) {
3970
+ return extractFromBlockReturn(body);
3971
+ }
3972
+ }
3973
+ return null;
3974
+ }
3975
+ function extractFromReturnTypeAnnotation(typeNode) {
3976
+ if (Node11.isTypeReference(typeNode)) {
3977
+ const typeName = typeNode.getTypeName();
3978
+ if (Node11.isIdentifier(typeName) && typeName.getText() === "Promise") {
3979
+ const typeArgs = typeNode.getTypeArguments();
3980
+ const firstArg2 = typeArgs[0];
3981
+ if (firstArg2) {
3982
+ return extractFromReturnTypeAnnotation(firstArg2);
3711
3983
  }
3984
+ return null;
3712
3985
  }
3713
- } else {
3714
- console.warn(
3715
- `[nestjs-codegen/fast] @ApplyContract arg is not an identifier or call expression in ${sourceFile.getFilePath()} \u2014 skipping`
3716
- );
3717
- return null;
3718
3986
  }
3719
- if (!contractDef) return null;
3720
- if (!verb) return null;
3721
- const resolvedPath = joinPaths(prefix, verb.handlerPath);
3722
- const methodName = method.getName();
3723
- const classAs = readAsDecorator(cls, `class ${className}`);
3724
- const methodAs = readAsDecorator(method, `${className}.${methodName}`);
3725
- return buildRoute({
3726
- className,
3727
- methodName,
3728
- resolvedMethod: verb.httpMethod,
3729
- combinedPath: resolvedPath,
3730
- classAs,
3731
- methodAs,
3732
- sourceFile,
3733
- seenNames,
3734
- mixin,
3735
- contractSource: {
3736
- query: contractDef.query,
3737
- body: contractDef.body,
3738
- response: contractDef.response,
3739
- error: contractDef.error,
3740
- // Path A: capture both the importable ref and the raw text. The emitter
3741
- // prefers inlining the text (client-safe — re-exporting from a controller
3742
- // would drag server-only deps into the client bundle).
3743
- bodyZodRef,
3744
- bodyZodText: contractDef.bodyZodText,
3745
- queryZodRef,
3746
- queryZodText: contractDef.queryZodText
3987
+ if (Node11.isTypeLiteral(typeNode)) {
3988
+ const properties = [];
3989
+ for (const member of typeNode.getMembers()) {
3990
+ if (Node11.isPropertySignature(member)) {
3991
+ const name = member.getName();
3992
+ const memberTypeNode = member.getTypeNode();
3993
+ const type = memberTypeNode ? memberTypeNode.getText() : "unknown";
3994
+ properties.push({ name, type });
3995
+ }
3747
3996
  }
3748
- });
3997
+ if (properties.length === 0) return null;
3998
+ const typeString = `{ ${properties.map((p) => `${p.name}: ${p.type}`).join("; ")} }`;
3999
+ return { typeString, properties, isImportRef: false };
4000
+ }
4001
+ return null;
3749
4002
  }
3750
- function extractDtoRoute(args) {
3751
- const { cls, method, verb, prefix, className, sourceFile, project, seenNames, mixin } = args;
3752
- if (!verb) return null;
3753
- const combined = joinPaths(prefix, verb.handlerPath);
3754
- const methodName = method.getName();
3755
- const classAs = readAsDecorator(cls, `class ${className}`);
3756
- const methodAs = readAsDecorator(method, `${className}.${methodName}`);
3757
- const dtoContract = extractDtoContract(method, method.getSourceFile(), project, mixin, cls);
3758
- const mixinResponse = mixin ? resolveInstantiatedReturnType(cls, methodName) : void 0;
3759
- return buildRoute({
3760
- className,
3761
- methodName,
3762
- resolvedMethod: verb.httpMethod,
3763
- combinedPath: combined,
3764
- classAs,
3765
- methodAs,
3766
- sourceFile,
3767
- seenNames,
3768
- mixin,
3769
- contractSource: {
3770
- query: dtoContract?.query ?? null,
3771
- body: dtoContract?.body ?? null,
3772
- response: mixinResponse ?? dtoContract?.response ?? "unknown",
3773
- error: dtoContract?.error ?? null,
3774
- paramTypes: dtoContract?.paramTypes ?? null,
3775
- queryRef: dtoContract?.queryRef ?? null,
3776
- bodyRef: dtoContract?.bodyRef ?? null,
3777
- responseRef: dtoContract?.responseRef ?? null,
3778
- errorRef: dtoContract?.errorRef ?? null,
3779
- filterFields: dtoContract?.filterFields ?? null,
3780
- filterFieldTypes: dtoContract?.filterFieldTypes ?? null,
3781
- filterSource: dtoContract?.filterSource ?? null,
3782
- formWarnings: dtoContract?.formWarnings ?? [],
3783
- bodySchema: dtoContract?.bodySchema ?? null,
3784
- querySchema: dtoContract?.querySchema ?? null,
3785
- stream: dtoContract?.stream ?? false,
3786
- multipart: dtoContract?.multipart ?? false,
3787
- multipartBody: dtoContract?.multipartBody ?? null,
3788
- binaryResponse: dtoContract?.binaryResponse ?? false,
3789
- asQuery: dtoContract?.asQuery ?? false
4003
+ function extractFromBlockReturn(block) {
4004
+ if (!Node11.isBlock(block)) return null;
4005
+ const statements = block.getStatements();
4006
+ for (let i = statements.length - 1; i >= 0; i--) {
4007
+ const stmt = statements[i];
4008
+ if (!Node11.isReturnStatement(stmt)) continue;
4009
+ const expr = stmt.getExpression();
4010
+ if (!expr) continue;
4011
+ if (Node11.isObjectLiteralExpression(expr)) {
4012
+ return extractFromObjectLiteral(expr);
3790
4013
  }
3791
- });
4014
+ if (Node11.isParenthesizedExpression(expr)) {
4015
+ const inner = expr.getExpression();
4016
+ if (Node11.isObjectLiteralExpression(inner)) {
4017
+ return extractFromObjectLiteral(inner);
4018
+ }
4019
+ }
4020
+ break;
4021
+ }
4022
+ return null;
3792
4023
  }
3793
- function extractFromSourceFile(sourceFile, project) {
3794
- const routes = [];
3795
- const seenNames = /* @__PURE__ */ new Map();
3796
- for (const cls of sourceFile.getClasses()) {
3797
- const controllerDecorator = cls.getDecorator("Controller");
3798
- if (!controllerDecorator) continue;
3799
- const firstArg2 = controllerDecorator.getArguments()[0];
3800
- const prefix = decoratorStringArg(firstArg2) ?? "";
3801
- const className = cls.getName() ?? "Unknown";
3802
- const inherited = resolveInheritedMethods(cls);
3803
- const mixinBinding = inherited ? {
3804
- factoryName: inherited.factoryName,
3805
- factoryFilePath: inherited.factoryFilePath,
3806
- classArgs: inherited.classArgs,
3807
- namedClassArgs: inherited.namedClassArgs
3808
- } : void 0;
3809
- const ownMethods = cls.getMethods();
3810
- const ownNames = new Set(ownMethods.map((m) => m.getName()));
3811
- const methods = [
3812
- ...ownMethods.map((method) => ({ method, mixin: mixinBinding })),
3813
- ...(inherited?.methods ?? []).filter((method) => !ownNames.has(method.getName())).map((method) => ({ method, mixin: mixinBinding }))
3814
- ];
3815
- for (const { method, mixin } of methods) {
3816
- const verb = resolveVerb(method);
3817
- const applyContractDecorator = method.getDecorator("ApplyContract");
3818
- const route = applyContractDecorator ? extractContractRoute({
3819
- cls,
3820
- method,
3821
- applyContractDecorator,
3822
- verb,
3823
- prefix,
3824
- className,
3825
- sourceFile,
3826
- project,
3827
- seenNames,
3828
- mixin
3829
- }) : extractDtoRoute({
3830
- cls,
3831
- method,
3832
- verb,
3833
- prefix,
3834
- className,
3835
- sourceFile,
3836
- project,
3837
- seenNames,
3838
- mixin
3839
- });
3840
- if (route) routes.push(route);
4024
+ function extractFromObjectLiteral(objLiteral) {
4025
+ if (!Node11.isObjectLiteralExpression(objLiteral)) return null;
4026
+ const properties = [];
4027
+ for (const prop of objLiteral.getProperties()) {
4028
+ if (!Node11.isPropertyAssignment(prop)) continue;
4029
+ const name = prop.getName();
4030
+ const initializer = prop.getInitializer();
4031
+ if (!initializer) continue;
4032
+ const type = inferExpressionType(initializer);
4033
+ properties.push({ name, type });
4034
+ }
4035
+ if (properties.length === 0) return null;
4036
+ const typeString = `{ ${properties.map((p) => `${p.name}: ${p.type}`).join("; ")} }`;
4037
+ return { typeString, properties, isImportRef: false };
4038
+ }
4039
+ function inferExpressionType(node) {
4040
+ if (Node11.isStringLiteral(node)) return "string";
4041
+ if (Node11.isTemplateExpression(node) || Node11.isNoSubstitutionTemplateLiteral(node))
4042
+ return "string";
4043
+ if (Node11.isNumericLiteral(node)) return "number";
4044
+ if (node.getKind() === SyntaxKind4.TrueKeyword || node.getKind() === SyntaxKind4.FalseKeyword) {
4045
+ return "boolean";
4046
+ }
4047
+ if (node.getKind() === SyntaxKind4.NullKeyword) return "null";
4048
+ if (Node11.isIdentifier(node) && node.getText() === "undefined") return "undefined";
4049
+ if (Node11.isObjectLiteralExpression(node)) {
4050
+ const props = node.getProperties();
4051
+ if (props.length === 0) return "Record<string, unknown>";
4052
+ const entries = [];
4053
+ for (const prop of props) {
4054
+ if (!Node11.isPropertyAssignment(prop)) continue;
4055
+ const key = prop.getName();
4056
+ const init = prop.getInitializer();
4057
+ if (!init) continue;
4058
+ entries.push(`${key}: ${inferExpressionType(init)}`);
3841
4059
  }
4060
+ if (entries.length === 0) return "Record<string, unknown>";
4061
+ return `{ ${entries.join("; ")} }`;
3842
4062
  }
3843
- return routes;
4063
+ if (Node11.isArrayLiteralExpression(node)) {
4064
+ const elements = node.getElements();
4065
+ if (elements.length === 0) return "Array<unknown>";
4066
+ const first = elements[0];
4067
+ if (first) return `Array<${inferExpressionType(first)}>`;
4068
+ return "Array<unknown>";
4069
+ }
4070
+ if (Node11.isConditionalExpression(node)) {
4071
+ const whenTrue = inferExpressionType(node.getWhenTrue());
4072
+ const whenFalse = inferExpressionType(node.getWhenFalse());
4073
+ if (whenTrue === whenFalse) return whenTrue;
4074
+ return `${whenTrue} | ${whenFalse}`;
4075
+ }
4076
+ if (Node11.isParenthesizedExpression(node)) {
4077
+ return inferExpressionType(node.getExpression());
4078
+ }
4079
+ if (Node11.isAsExpression(node)) {
4080
+ const typeNode = node.getTypeNode();
4081
+ if (typeNode) return typeNode.getText();
4082
+ }
4083
+ return "unknown";
3844
4084
  }
3845
4085
 
4086
+ // src/emit/emit-api.ts
4087
+ import { mkdir, writeFile } from "fs/promises";
4088
+ import { isAbsolute as isAbsolute2, join as join5, relative as relative4 } from "path";
4089
+
3846
4090
  // src/extension/registry.ts
4091
+ import { relative as relative3, resolve as resolve4 } from "path";
4092
+ import { Project as Project5 } from "ts-morph";
3847
4093
  function resolveApiSlots(extensions) {
3848
4094
  let layer;
3849
4095
  let layerOwner;
@@ -5628,36 +5874,64 @@ async function discoverInputFiles(config) {
5628
5874
  const matched = await fg3(globs, { cwd, absolute: true, onlyFiles: true });
5629
5875
  return [...new Set(matched)].sort();
5630
5876
  }
5631
- async function computeInputsHash(config, extraInputs = []) {
5877
+ var READ_CONCURRENCY = 64;
5878
+ async function readAll(paths, read) {
5879
+ const out = /* @__PURE__ */ new Map();
5880
+ for (let i = 0; i < paths.length; i += READ_CONCURRENCY) {
5881
+ const batch = paths.slice(i, i + READ_CONCURRENCY);
5882
+ const texts = await Promise.all(batch.map(read));
5883
+ batch.forEach((path, j) => out.set(path, texts[j]));
5884
+ }
5885
+ return out;
5886
+ }
5887
+ async function readInputs(config) {
5888
+ const cwd = config.codegen.cwd;
5889
+ const tsconfigPath = resolveTsconfigPath(cwd, config.app?.tsconfig ?? void 0);
5890
+ const extended = loadDiscoveryTsconfig(tsconfigPath).files;
5891
+ const tsconfigFiles = extended.length > 0 ? extended : [tsconfigPath];
5892
+ const files = await discoverInputFiles(config);
5893
+ const contents = /* @__PURE__ */ new Map();
5894
+ const tsconfigs = await readAll(
5895
+ tsconfigFiles,
5896
+ (file) => readFile2(file, "utf8").catch(() => null)
5897
+ );
5898
+ for (const [file, text] of tsconfigs) contents.set(file, text);
5899
+ for (const [file, text] of await readAll(files, (file2) => readFile2(file2, "utf8"))) {
5900
+ contents.set(file, text);
5901
+ }
5902
+ return { tsconfigFiles, files, contents, tracked: /* @__PURE__ */ new Map() };
5903
+ }
5904
+ async function readInput(snapshot, path) {
5905
+ return snapshot.contents.get(path) ?? readFile2(path, "utf8");
5906
+ }
5907
+ async function computeInputsHash(config, extraInputs = [], snapshot) {
5908
+ const inputs = snapshot ?? await readInputs(config);
5632
5909
  const hash = createHash("sha256");
5633
5910
  hash.update(`version:${VERSION}
5634
5911
  `);
5635
5912
  hash.update(`config:${serializeConfig(config)}
5636
5913
  `);
5637
5914
  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);
5915
+ for (const file of inputs.tsconfigFiles) {
5642
5916
  hash.update(`tsconfig:${relative7(cwd, file)}
5643
5917
  `);
5644
- hash.update(contents ?? " missing");
5918
+ hash.update(inputs.contents.get(file) ?? " missing");
5645
5919
  hash.update("\n");
5646
5920
  }
5647
- const globbed = await discoverInputFiles(config);
5648
- const globbedRelative = new Set(globbed.map((file) => relative7(cwd, file)));
5921
+ const globbedRelative = new Set(inputs.files.map((file) => relative7(cwd, file)));
5649
5922
  const extra = [...new Set(extraInputs)].filter((file) => !globbedRelative.has(file)).sort();
5650
- for (const file of globbed) {
5651
- const contents = await readFile2(file, "utf8");
5923
+ for (const file of inputs.files) {
5652
5924
  hash.update(`file:${relative7(cwd, file)}
5653
5925
  `);
5654
- hash.update(contents);
5926
+ hash.update(inputs.contents.get(file));
5655
5927
  hash.update("\n");
5656
5928
  }
5929
+ const unread = extra.map((file) => join13(cwd, file)).filter((abs) => !inputs.tracked.has(abs));
5930
+ for (const [abs, text] of await readAll(unread, readTrackedInput)) inputs.tracked.set(abs, text);
5657
5931
  for (const file of extra) {
5658
5932
  hash.update(`extra:${file}
5659
5933
  `);
5660
- hash.update(await readTrackedInput(join13(cwd, file)));
5934
+ hash.update(inputs.tracked.get(join13(cwd, file)));
5661
5935
  hash.update("\n");
5662
5936
  }
5663
5937
  return hash.digest("hex");
@@ -5738,7 +6012,8 @@ function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint, differ
5738
6012
  async function generate(config, inputRoutes = [], entryPoint = "cli") {
5739
6013
  setCodegenDebug(config.debug);
5740
6014
  const manifest = await readManifest(config.codegen.outDir);
5741
- const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? []);
6015
+ const inputs = await readInputs(config);
6016
+ const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? [], inputs);
5742
6017
  if (await isManifestFresh(config.codegen.outDir, manifest, inputsHash)) {
5743
6018
  console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
5744
6019
  return;
@@ -5766,7 +6041,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
5766
6041
  }
5767
6042
  if (config.pages) {
5768
6043
  const pagesConfig = config.pages;
5769
- const pages = await discoverPageScopes(config);
6044
+ const pages = await discoverPageScopes(config, (path) => readInput(inputs, path));
5770
6045
  const sharedProps = discoverSharedPropsFromConfig(config);
5771
6046
  await emitPages(pages, config.codegen.outDir, {
5772
6047
  propsExport: pagesConfig.propsExport,
@@ -5816,7 +6091,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
5816
6091
  }
5817
6092
  const outputFiles = await listOutputFiles(config.codegen.outDir);
5818
6093
  const extraInputs = [...trackedInputs].sort();
5819
- const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs) : inputsHash;
6094
+ const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs, inputs) : inputsHash;
5820
6095
  await writeManifest(config.codegen.outDir, {
5821
6096
  version: VERSION,
5822
6097
  hash: recordedHash,
@@ -5999,13 +6274,15 @@ async function watch(config, onChange, options = {}) {
5999
6274
  pagesWatcher.on("unlink", schedulePagesRegenerate);
6000
6275
  let contractsDebounceTimer;
6001
6276
  const pendingChangedPaths = /* @__PURE__ */ new Set();
6277
+ let pendingAddedOrRemoved = false;
6002
6278
  const contractsWatcher = chokidar.watch(join16(config.codegen.cwd, config.contracts.glob), {
6003
6279
  ignoreInitial: true,
6004
6280
  persistent: true,
6005
6281
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
6006
6282
  });
6007
- function scheduleContractsRegenerate(changedPath) {
6283
+ function scheduleContractsRegenerate(changedPath, addedOrRemoved = false) {
6008
6284
  if (typeof changedPath === "string") pendingChangedPaths.add(changedPath);
6285
+ if (addedOrRemoved) pendingAddedOrRemoved = true;
6009
6286
  if (contractsDebounceTimer !== void 0) {
6010
6287
  clearTimeout(contractsDebounceTimer);
6011
6288
  }
@@ -6013,8 +6290,12 @@ async function watch(config, onChange, options = {}) {
6013
6290
  contractsDebounceTimer = void 0;
6014
6291
  const changed = [...pendingChangedPaths];
6015
6292
  pendingChangedPaths.clear();
6293
+ const filesAddedOrRemoved = pendingAddedOrRemoved;
6294
+ pendingAddedOrRemoved = false;
6016
6295
  try {
6017
- const routes = await (await getDiscovery()).rediscover(changed);
6296
+ const routes = await (await getDiscovery()).rediscover(changed, {
6297
+ filesAddedOrRemoved
6298
+ });
6018
6299
  lastRoutes = routes;
6019
6300
  await generateAndTrack(routes);
6020
6301
  } catch (err) {
@@ -6026,20 +6307,20 @@ async function watch(config, onChange, options = {}) {
6026
6307
  onChange?.();
6027
6308
  }, config.contracts.debounceMs);
6028
6309
  }
6029
- contractsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
6310
+ contractsWatcher.on("add", (p) => scheduleContractsRegenerate(p, true));
6030
6311
  contractsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
6031
- contractsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
6312
+ contractsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p, true));
6032
6313
  const formsWatcher = chokidar.watch(join16(config.codegen.cwd, config.forms.watch), {
6033
6314
  ignoreInitial: true,
6034
6315
  persistent: true,
6035
6316
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
6036
6317
  });
6037
- formsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
6318
+ formsWatcher.on("add", (p) => scheduleContractsRegenerate(p, true));
6038
6319
  formsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
6039
- formsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
6040
- trackedWatcher.on("add", (p) => scheduleContractsRegenerate(p));
6320
+ formsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p, true));
6321
+ trackedWatcher.on("add", (p) => scheduleContractsRegenerate(p, true));
6041
6322
  trackedWatcher.on("change", (p) => scheduleContractsRegenerate(p));
6042
- trackedWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
6323
+ trackedWatcher.on("unlink", (p) => scheduleContractsRegenerate(p, true));
6043
6324
  return {
6044
6325
  close: async () => {
6045
6326
  if (pagesDebounceTimer !== void 0) {
@@ -6143,7 +6424,7 @@ function createChainModuleRenderer(opts) {
6143
6424
  }
6144
6425
 
6145
6426
  // src/extension/genui-sandbox-kit.ts
6146
- import { existsSync as existsSync2 } from "fs";
6427
+ import { existsSync as existsSync2, statSync as statSync2 } from "fs";
6147
6428
  import { dirname as dirname5, isAbsolute as isAbsolute3, resolve as resolve6 } from "path";
6148
6429
  import fg4 from "fast-glob";
6149
6430
  var GENUI_KIT_MODULE = "@dudousxd/nestjs-agent-core/genui/kit";
@@ -6155,6 +6436,12 @@ var DEFAULT_KIT_DIRS = [
6155
6436
  "app/components/ui",
6156
6437
  "components/ui"
6157
6438
  ];
6439
+ var LEGACY_OPTIONS = {
6440
+ entry: "`entry: X` is now `components: X`",
6441
+ include: "`include: G` is now `components: G` (globs, files, directories or packages)",
6442
+ css: "`css: G` is now `styles: G` (the app's style entry files)"
6443
+ };
6444
+ var MIN_CORE = "0.53.0";
6158
6445
  async function loadKitModule(specifier = GENUI_KIT_MODULE) {
6159
6446
  let mod;
6160
6447
  try {
@@ -6163,19 +6450,24 @@ async function loadKitModule(specifier = GENUI_KIT_MODULE) {
6163
6450
  const code = err.code;
6164
6451
  if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
6165
6452
  throw new CodegenError(
6166
- `genuiSandboxKit() needs "${GENUI_KIT_MODULE}" \u2014 install @dudousxd/nestjs-agent-core@>=0.52.0, or pass \`generate\` to genuiSandboxKit().`
6453
+ `genuiSandboxKit() needs "${GENUI_KIT_MODULE}" \u2014 install @dudousxd/nestjs-agent-core@>=${MIN_CORE}, or pass \`generate\` to genuiSandboxKit().`
6167
6454
  );
6168
6455
  }
6169
6456
  if (code === "ERR_PACKAGE_PATH_NOT_EXPORTED") {
6170
6457
  throw new CodegenError(
6171
- `genuiSandboxKit() needs "${GENUI_KIT_MODULE}", which the installed @dudousxd/nestjs-agent-core does not export \u2014 upgrade it to >=0.52.0.`
6458
+ `genuiSandboxKit() needs "${GENUI_KIT_MODULE}", which the installed @dudousxd/nestjs-agent-core does not export \u2014 upgrade it to >=${MIN_CORE}.`
6172
6459
  );
6173
6460
  }
6174
6461
  throw err;
6175
6462
  }
6176
6463
  if (typeof mod.writeSandboxKitDocs !== "function") {
6177
6464
  throw new CodegenError(
6178
- `"${GENUI_KIT_MODULE}" has no writeSandboxKitDocs export \u2014 upgrade @dudousxd/nestjs-agent-core to >=0.52.0.`
6465
+ `"${GENUI_KIT_MODULE}" has no writeSandboxKitDocs export \u2014 upgrade @dudousxd/nestjs-agent-core to >=${MIN_CORE}.`
6466
+ );
6467
+ }
6468
+ if (typeof mod.resolveStyleEntries !== "function") {
6469
+ throw new CodegenError(
6470
+ `The installed @dudousxd/nestjs-agent-core predates genuiSandboxKit({ components, styles, wrapper }) \u2014 upgrade it to >=${MIN_CORE}.`
6179
6471
  );
6180
6472
  }
6181
6473
  return mod;
@@ -6189,52 +6481,92 @@ function globBase(glob) {
6189
6481
  return base.join("/");
6190
6482
  }
6191
6483
  function resolveKitFilesLocally(root, options) {
6192
- if (options.entry !== void 0) {
6193
- const entry = isAbsolute3(options.entry) ? options.entry : resolve6(root, options.entry);
6194
- return { entry, files: [entry], dirs: [dirname5(entry)] };
6195
- }
6196
- const include = options.include ?? DEFAULT_KIT_DIRS.filter((dir) => existsSync2(resolve6(root, dir))).map(
6197
- (dir) => `${dir}/*.{tsx,jsx,ts,js}`
6198
- )[0];
6199
- if (include === void 0) return null;
6200
- const globs = typeof include === "string" ? [include] : [...include];
6201
- const files = fg4.sync(globs, { cwd: root, absolute: true, onlyFiles: true }).sort();
6202
- return { files, dirs: [...new Set(globs.map((glob) => resolve6(root, globBase(glob))))] };
6484
+ const specs = options.components === void 0 ? DEFAULT_KIT_DIRS.filter((dir) => existsSync2(resolve6(root, dir))).slice(0, 1) : typeof options.components === "string" ? [options.components] : [...options.components];
6485
+ const files = [];
6486
+ const dirs = /* @__PURE__ */ new Set();
6487
+ for (const spec of specs) {
6488
+ if (/[*?{]/.test(spec)) {
6489
+ files.push(...fg4.sync(spec, { cwd: root, absolute: true, onlyFiles: true }));
6490
+ dirs.add(resolve6(root, globBase(spec)));
6491
+ continue;
6492
+ }
6493
+ const path = isAbsolute3(spec) ? spec : resolve6(root, spec);
6494
+ if (existsSync2(path) && statSync2(path).isDirectory()) {
6495
+ files.push(...fg4.sync("*.{tsx,jsx,ts,js}", { cwd: path, absolute: true, onlyFiles: true }));
6496
+ dirs.add(path);
6497
+ } else if (existsSync2(path)) {
6498
+ files.push(path);
6499
+ dirs.add(dirname5(path));
6500
+ }
6501
+ }
6502
+ if (options.wrapper !== void 0) {
6503
+ const wrapper = isAbsolute3(options.wrapper) ? options.wrapper : resolve6(root, options.wrapper);
6504
+ if (existsSync2(wrapper)) files.push(wrapper);
6505
+ }
6506
+ if (files.length === 0 && dirs.size === 0) return null;
6507
+ return { files: [...new Set(files)].sort(), dirs: [...dirs] };
6203
6508
  }
6204
6509
  function genuiSandboxKit(options = {}) {
6510
+ const legacy = Object.keys(LEGACY_OPTIONS).filter(
6511
+ (key) => options[key] !== void 0
6512
+ );
6513
+ if (legacy.length > 0) {
6514
+ throw new CodegenError(
6515
+ `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.`
6516
+ );
6517
+ }
6205
6518
  const output = options.output ?? DEFAULT_SANDBOX_KIT_OUTPUT;
6206
6519
  const source = {
6207
- ...options.entry !== void 0 ? { entry: options.entry } : {},
6208
- ...options.include !== void 0 ? { include: options.include } : {}
6520
+ ...options.components !== void 0 ? { components: options.components } : {},
6521
+ ...options.wrapper !== void 0 ? { wrapper: options.wrapper } : {}
6209
6522
  };
6210
6523
  const extension = {
6211
6524
  name: "genui-sandbox-kit",
6212
6525
  // Not read by the host: serialized into the config hash, so changing an option
6213
6526
  // invalidates skip-when-unchanged.
6214
- options: { ...source, css: options.css, tsconfig: options.tsconfig, output },
6527
+ options: {
6528
+ ...source,
6529
+ styles: options.styles,
6530
+ stylesDocs: options.stylesDocs,
6531
+ tsconfig: options.tsconfig,
6532
+ output
6533
+ },
6215
6534
  async emitFiles(ctx) {
6216
6535
  const root = ctx.cwd;
6217
6536
  let generate2 = options.generate;
6218
- let resolveFiles = resolveKitFilesLocally;
6537
+ let mod;
6219
6538
  if (!generate2) {
6220
- const mod = await loadKitModule(options.kitModule);
6539
+ mod = await loadKitModule(options.kitModule);
6221
6540
  generate2 = mod.writeSandboxKitDocs;
6222
- if (typeof mod.resolveSandboxKitFiles === "function") {
6223
- resolveFiles = mod.resolveSandboxKitFiles;
6224
- }
6225
6541
  }
6226
- const kit = resolveFiles(root, source);
6542
+ const resolveFiles = mod?.resolveSandboxKitFiles ?? resolveKitFilesLocally;
6543
+ let kit = null;
6544
+ try {
6545
+ kit = resolveFiles(root, source);
6546
+ } catch {
6547
+ kit = null;
6548
+ }
6227
6549
  if (kit) ctx.trackInput(...kit.files, ...kit.dirs);
6228
- if (options.css !== void 0) {
6229
- const css = typeof options.css === "string" ? [options.css] : [...options.css];
6230
- ctx.trackInput(...fg4.sync(css, { cwd: root, absolute: true, onlyFiles: true }));
6550
+ if (options.styles !== void 0) {
6551
+ const entries = mod?.resolveStyleEntries ? mod.resolveStyleEntries(root, options.styles).files : fg4.sync(typeof options.styles === "string" ? [options.styles] : [...options.styles], {
6552
+ cwd: root,
6553
+ absolute: true,
6554
+ onlyFiles: true
6555
+ });
6556
+ const files = mod?.collectStyleSources ? mod.collectStyleSources(entries).map((source2) => source2.file) : entries;
6557
+ ctx.trackInput(.../* @__PURE__ */ new Set([...entries, ...files]));
6558
+ }
6559
+ if (options.stylesDocs !== void 0 && !options.stylesDocs.includes("\n")) {
6560
+ const guide = isAbsolute3(options.stylesDocs) ? options.stylesDocs : resolve6(root, options.stylesDocs);
6561
+ if (existsSync2(guide)) ctx.trackInput(guide);
6231
6562
  }
6232
6563
  ctx.trackInput(isAbsolute3(output) ? output : resolve6(root, output));
6233
6564
  await generate2({
6234
6565
  root,
6235
6566
  output,
6236
6567
  ...source,
6237
- ...options.css !== void 0 ? { css: options.css } : {},
6568
+ ...options.styles !== void 0 ? { styles: options.styles } : {},
6569
+ ...options.stylesDocs !== void 0 ? { stylesDocs: options.stylesDocs } : {},
6238
6570
  ...options.tsconfig !== void 0 ? { tsconfig: options.tsconfig } : {}
6239
6571
  });
6240
6572
  return [];
@@ -6244,7 +6576,7 @@ function genuiSandboxKit(options = {}) {
6244
6576
  }
6245
6577
 
6246
6578
  // src/index.ts
6247
- var VERSION = "0.28.0";
6579
+ var VERSION = "0.29.1";
6248
6580
  export {
6249
6581
  CodegenError,
6250
6582
  ConfigError,