@dudousxd/nestjs-codegen 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -251,7 +251,7 @@ Run \`nestjs-codegen init\` to create a starter config.`
251
251
 
252
252
  // src/generate.ts
253
253
  var import_promises12 = require("fs/promises");
254
- var import_node_path13 = require("path");
254
+ var import_node_path14 = require("path");
255
255
 
256
256
  // src/discovery/pages.ts
257
257
  var import_promises2 = require("fs/promises");
@@ -580,9 +580,10 @@ function inferExpressionType(node) {
580
580
 
581
581
  // src/emit/emit-api.ts
582
582
  var import_promises3 = require("fs/promises");
583
- var import_node_path4 = require("path");
583
+ var import_node_path5 = require("path");
584
584
 
585
585
  // src/extension/registry.ts
586
+ var import_node_path4 = require("path");
586
587
  var import_ts_morph2 = require("ts-morph");
587
588
  function resolveApiSlots(extensions) {
588
589
  let layer;
@@ -615,7 +616,7 @@ function mergeExclusive(target, incoming, {
615
616
  target.set(key, { value, owner });
616
617
  }
617
618
  }
618
- function createExtensionContext(config, getRoutes) {
619
+ function createExtensionContext(config, getRoutes, trackedInputs) {
619
620
  let project;
620
621
  return {
621
622
  cwd: config.codegen.cwd,
@@ -624,6 +625,13 @@ function createExtensionContext(config, getRoutes) {
624
625
  get routes() {
625
626
  return getRoutes();
626
627
  },
628
+ trackInput(...paths) {
629
+ if (!trackedInputs) return;
630
+ for (const path of paths) {
631
+ if (!path) continue;
632
+ trackedInputs.add((0, import_node_path4.relative)(config.codegen.cwd, (0, import_node_path4.resolve)(config.codegen.cwd, path)));
633
+ }
634
+ },
627
635
  project() {
628
636
  if (!project) {
629
637
  project = new import_ts_morph2.Project({
@@ -683,7 +691,7 @@ function requestShape(route) {
683
691
  async function emitApi(routes, outDir, opts = {}) {
684
692
  await (0, import_promises3.mkdir)(outDir, { recursive: true });
685
693
  const content = buildApiFile(routes, outDir, opts);
686
- await (0, import_promises3.writeFile)((0, import_node_path4.join)(outDir, "api.ts"), content, "utf8");
694
+ await (0, import_promises3.writeFile)((0, import_node_path5.join)(outDir, "api.ts"), content, "utf8");
687
695
  }
688
696
  function splitName(name) {
689
697
  return name.split(".");
@@ -791,7 +799,7 @@ function rawResponseType(c, outDir) {
791
799
  return c.contractSource.response;
792
800
  }
793
801
  if (c.controllerRef) {
794
- let relPath = (0, import_node_path4.relative)(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
802
+ let relPath = (0, import_node_path5.relative)(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
795
803
  if (!relPath.startsWith(".")) relPath = `./${relPath}`;
796
804
  return `Awaited<ReturnType<import('${relPath}').${c.controllerRef.className}['${c.controllerRef.methodName}']>>`;
797
805
  }
@@ -1117,6 +1125,11 @@ function buildApiFile(routes, outDir, opts = {}) {
1117
1125
  config: {},
1118
1126
  project: () => {
1119
1127
  throw new Error("ExtensionContext.project() is unavailable in standalone emitApi.");
1128
+ },
1129
+ // No manifest is written on the standalone path, so there is nothing for
1130
+ // tracked dependencies to be recorded into — accept and discard them
1131
+ // rather than making every caller supply a sink.
1132
+ trackInput: () => {
1120
1133
  }
1121
1134
  };
1122
1135
  const importsByFile = /* @__PURE__ */ new Map();
@@ -1172,8 +1185,8 @@ function buildApiFile(routes, outDir, opts = {}) {
1172
1185
  const emittedNames = /* @__PURE__ */ new Set();
1173
1186
  for (const [filePath, names] of importsByFile) {
1174
1187
  let relPath;
1175
- if ((0, import_node_path4.isAbsolute)(filePath)) {
1176
- relPath = (0, import_node_path4.relative)(outDir, filePath).replace(/\.ts$/, "");
1188
+ if ((0, import_node_path5.isAbsolute)(filePath)) {
1189
+ relPath = (0, import_node_path5.relative)(outDir, filePath).replace(/\.ts$/, "");
1177
1190
  if (!relPath.startsWith(".")) relPath = `./${relPath}`;
1178
1191
  } else {
1179
1192
  relPath = filePath;
@@ -1267,7 +1280,7 @@ function buildApiFile(routes, outDir, opts = {}) {
1267
1280
 
1268
1281
  // src/emit/emit-cache.ts
1269
1282
  var import_promises4 = require("fs/promises");
1270
- var import_node_path5 = require("path");
1283
+ var import_node_path6 = require("path");
1271
1284
  async function emitCache(pages, outDir) {
1272
1285
  await (0, import_promises4.mkdir)(outDir, { recursive: true });
1273
1286
  const entries = await Promise.all(
@@ -1281,19 +1294,19 @@ async function emitCache(pages, outDir) {
1281
1294
  })
1282
1295
  );
1283
1296
  const cache = { pages: entries };
1284
- await (0, import_promises4.writeFile)((0, import_node_path5.join)(outDir, "components.json"), `${JSON.stringify(cache, null, 2)}
1297
+ await (0, import_promises4.writeFile)((0, import_node_path6.join)(outDir, "components.json"), `${JSON.stringify(cache, null, 2)}
1285
1298
  `, "utf8");
1286
1299
  }
1287
1300
 
1288
1301
  // src/emit/emit-forms.ts
1289
1302
  var import_promises5 = require("fs/promises");
1290
- var import_node_path6 = require("path");
1303
+ var import_node_path7 = require("path");
1291
1304
  async function emitForms(routes, outDir, config, adapter) {
1292
1305
  if (config && config.enabled === false) return false;
1293
1306
  const content = buildFormsFileWithAdapter(routes, outDir, adapter, config);
1294
1307
  if (content === null) return false;
1295
1308
  await (0, import_promises5.mkdir)(outDir, { recursive: true });
1296
- await (0, import_promises5.writeFile)((0, import_node_path6.join)(outDir, "forms.ts"), content, "utf8");
1309
+ await (0, import_promises5.writeFile)((0, import_node_path7.join)(outDir, "forms.ts"), content, "utf8");
1297
1310
  return true;
1298
1311
  }
1299
1312
  function pascal(segment) {
@@ -1306,7 +1319,7 @@ function deriveBaseName(routeName) {
1306
1319
  return { method, full };
1307
1320
  }
1308
1321
  function relImport(outDir, filePath) {
1309
- let relPath = (0, import_node_path6.relative)(outDir, filePath).replace(/\.ts$/, "");
1322
+ let relPath = (0, import_node_path7.relative)(outDir, filePath).replace(/\.ts$/, "");
1310
1323
  if (!relPath.startsWith(".")) relPath = `./${relPath}`;
1311
1324
  return relPath;
1312
1325
  }
@@ -1580,7 +1593,7 @@ function buildFormsFileWithAdapter(routes, outDir, adapter, config) {
1580
1593
 
1581
1594
  // src/emit/emit-index.ts
1582
1595
  var import_promises6 = require("fs/promises");
1583
- var import_node_path7 = require("path");
1596
+ var import_node_path8 = require("path");
1584
1597
  async function emitIndex(outDir, hasContracts = false, hasForms = false) {
1585
1598
  await (0, import_promises6.mkdir)(outDir, { recursive: true });
1586
1599
  const exports2 = ["export * from './pages.js';", "export * from './routes.js';"];
@@ -1593,12 +1606,12 @@ async function emitIndex(outDir, hasContracts = false, hasForms = false) {
1593
1606
  const content = ["// Generated by @dudousxd/nestjs-codegen. Do not edit.", ...exports2, ""].join(
1594
1607
  "\n"
1595
1608
  );
1596
- await (0, import_promises6.writeFile)((0, import_node_path7.join)(outDir, "index.d.ts"), content, "utf8");
1609
+ await (0, import_promises6.writeFile)((0, import_node_path8.join)(outDir, "index.d.ts"), content, "utf8");
1597
1610
  }
1598
1611
 
1599
1612
  // src/emit/emit-mocks.ts
1600
1613
  var import_promises7 = require("fs/promises");
1601
- var import_node_path8 = require("path");
1614
+ var import_node_path9 = require("path");
1602
1615
 
1603
1616
  // src/ir/schema-node-to-json-schema.ts
1604
1617
  var DEFAULT_CTX = { refPrefix: "#/components/schemas/" };
@@ -1925,12 +1938,12 @@ async function emitMocks(routes, outDir, opts = {}) {
1925
1938
  await (0, import_promises7.mkdir)(outDir, { recursive: true });
1926
1939
  const content = buildMocksFile(routes, opts);
1927
1940
  const fileName = opts.fileName ?? "mocks.ts";
1928
- await (0, import_promises7.writeFile)((0, import_node_path8.join)(outDir, fileName), content, "utf8");
1941
+ await (0, import_promises7.writeFile)((0, import_node_path9.join)(outDir, fileName), content, "utf8");
1929
1942
  }
1930
1943
 
1931
1944
  // src/emit/emit-openapi.ts
1932
1945
  var import_promises8 = require("fs/promises");
1933
- var import_node_path9 = require("path");
1946
+ var import_node_path10 = require("path");
1934
1947
  var REF_PREFIX2 = "#/components/schemas/";
1935
1948
  function toOpenApiPath(path) {
1936
1949
  return path.replace(/:([^/]+)/g, "{$1}");
@@ -2051,13 +2064,13 @@ async function emitOpenApi(routes, outDir, opts = {}) {
2051
2064
  await (0, import_promises8.mkdir)(outDir, { recursive: true });
2052
2065
  const doc = buildOpenApiSpec(routes, opts);
2053
2066
  const fileName = opts.fileName ?? "openapi.json";
2054
- await (0, import_promises8.writeFile)((0, import_node_path9.join)(outDir, fileName), `${JSON.stringify(doc, null, 2)}
2067
+ await (0, import_promises8.writeFile)((0, import_node_path10.join)(outDir, fileName), `${JSON.stringify(doc, null, 2)}
2055
2068
  `, "utf8");
2056
2069
  }
2057
2070
 
2058
2071
  // src/emit/emit-pages.ts
2059
2072
  var import_promises9 = require("fs/promises");
2060
- var import_node_path10 = require("path");
2073
+ var import_node_path11 = require("path");
2061
2074
  async function emitPages(pages, outDir, _options = {}) {
2062
2075
  await (0, import_promises9.mkdir)(outDir, { recursive: true });
2063
2076
  const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
@@ -2078,7 +2091,7 @@ ${augBody}
2078
2091
  }
2079
2092
  ${sharedPropsBlock}}
2080
2093
  `;
2081
- await (0, import_promises9.writeFile)((0, import_node_path10.join)(outDir, "pages.d.ts"), content, "utf8");
2094
+ await (0, import_promises9.writeFile)((0, import_node_path11.join)(outDir, "pages.d.ts"), content, "utf8");
2082
2095
  }
2083
2096
  function buildSharedPropsBlock(sharedProps) {
2084
2097
  if (!sharedProps) return "";
@@ -2097,7 +2110,7 @@ ${propsBody}
2097
2110
  `;
2098
2111
  }
2099
2112
  function buildAugmentationType(page, outDir) {
2100
- let importPath = (0, import_node_path10.relative)(outDir, page.absolutePath).replace(/\.(tsx?|vue|svelte)$/, "");
2113
+ let importPath = (0, import_node_path11.relative)(outDir, page.absolutePath).replace(/\.(tsx?|vue|svelte)$/, "");
2101
2114
  if (!importPath.startsWith(".")) {
2102
2115
  importPath = `./${importPath}`;
2103
2116
  }
@@ -2109,11 +2122,11 @@ function needsQuotes(name) {
2109
2122
 
2110
2123
  // src/emit/emit-routes.ts
2111
2124
  var import_promises10 = require("fs/promises");
2112
- var import_node_path11 = require("path");
2125
+ var import_node_path12 = require("path");
2113
2126
  async function emitRoutes(routes, outDir) {
2114
2127
  await (0, import_promises10.mkdir)(outDir, { recursive: true });
2115
2128
  const content = buildRoutesFile(routes);
2116
- await (0, import_promises10.writeFile)((0, import_node_path11.join)(outDir, "routes.ts"), content, "utf8");
2129
+ await (0, import_promises10.writeFile)((0, import_node_path12.join)(outDir, "routes.ts"), content, "utf8");
2117
2130
  }
2118
2131
  function buildRoutesFile(routes) {
2119
2132
  if (routes.length === 0) {
@@ -2228,7 +2241,7 @@ function buildEmpty() {
2228
2241
  // src/generate-manifest.ts
2229
2242
  var import_node_crypto = require("crypto");
2230
2243
  var import_promises11 = require("fs/promises");
2231
- var import_node_path12 = require("path");
2244
+ var import_node_path13 = require("path");
2232
2245
  var import_fast_glob2 = __toESM(require("fast-glob"), 1);
2233
2246
  var MANIFEST_FILE = ".codegen-manifest.json";
2234
2247
  var LOCK_FILE = ".watcher.lock";
@@ -2251,6 +2264,9 @@ function isManifestShape(value) {
2251
2264
  if (candidate.configKeyHashes !== void 0 && !isStringRecord(candidate.configKeyHashes)) {
2252
2265
  return false;
2253
2266
  }
2267
+ if (candidate.extraInputs !== void 0 && (!Array.isArray(candidate.extraInputs) || !candidate.extraInputs.every((entry) => typeof entry === "string"))) {
2268
+ return false;
2269
+ }
2254
2270
  if (!Array.isArray(candidate.files)) return false;
2255
2271
  return candidate.files.every((entry) => typeof entry === "string");
2256
2272
  }
@@ -2290,26 +2306,35 @@ async function discoverInputFiles(config) {
2290
2306
  const matched = await (0, import_fast_glob2.default)(globs, { cwd, absolute: true, onlyFiles: true });
2291
2307
  return [...new Set(matched)].sort();
2292
2308
  }
2293
- async function computeInputsHash(config) {
2309
+ async function computeInputsHash(config, extraInputs = []) {
2294
2310
  const hash = (0, import_node_crypto.createHash)("sha256");
2295
2311
  hash.update(`version:${VERSION}
2296
2312
  `);
2297
2313
  hash.update(`config:${serializeConfig(config)}
2298
2314
  `);
2299
- const inputFiles = await discoverInputFiles(config);
2300
2315
  const cwd = config.codegen.cwd;
2301
- for (const file of inputFiles) {
2316
+ const globbed = await discoverInputFiles(config);
2317
+ const globbedRelative = new Set(globbed.map((file) => (0, import_node_path13.relative)(cwd, file)));
2318
+ const extra = [...new Set(extraInputs)].filter((file) => !globbedRelative.has(file)).sort();
2319
+ for (const file of globbed) {
2302
2320
  const contents = await (0, import_promises11.readFile)(file, "utf8");
2303
- hash.update(`file:${(0, import_node_path12.relative)(cwd, file)}
2321
+ hash.update(`file:${(0, import_node_path13.relative)(cwd, file)}
2304
2322
  `);
2305
2323
  hash.update(contents);
2306
2324
  hash.update("\n");
2307
2325
  }
2326
+ for (const file of extra) {
2327
+ const contents = await (0, import_promises11.readFile)((0, import_node_path13.join)(cwd, file), "utf8").catch(() => null);
2328
+ hash.update(`extra:${file}
2329
+ `);
2330
+ hash.update(contents ?? "\0missing");
2331
+ hash.update("\n");
2332
+ }
2308
2333
  return hash.digest("hex");
2309
2334
  }
2310
2335
  async function readManifest(outDir) {
2311
2336
  try {
2312
- const raw = await (0, import_promises11.readFile)((0, import_node_path12.join)(outDir, MANIFEST_FILE), "utf8");
2337
+ const raw = await (0, import_promises11.readFile)((0, import_node_path13.join)(outDir, MANIFEST_FILE), "utf8");
2313
2338
  const parsed = JSON.parse(raw);
2314
2339
  if (!isManifestShape(parsed)) return null;
2315
2340
  return {
@@ -2318,6 +2343,7 @@ async function readManifest(outDir) {
2318
2343
  ...parsed.entryPoint ? { entryPoint: parsed.entryPoint } : {},
2319
2344
  ...parsed.configHash ? { configHash: parsed.configHash } : {},
2320
2345
  ...parsed.configKeyHashes ? { configKeyHashes: parsed.configKeyHashes } : {},
2346
+ ...parsed.extraInputs ? { extraInputs: parsed.extraInputs } : {},
2321
2347
  files: parsed.files
2322
2348
  };
2323
2349
  } catch {
@@ -2328,7 +2354,7 @@ function computeConfigHash(config) {
2328
2354
  return (0, import_node_crypto.createHash)("sha256").update(serializeConfig(config)).digest("hex");
2329
2355
  }
2330
2356
  async function writeManifest(outDir, manifest) {
2331
- await (0, import_promises11.writeFile)((0, import_node_path12.join)(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
2357
+ await (0, import_promises11.writeFile)((0, import_node_path13.join)(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
2332
2358
  `, "utf8");
2333
2359
  }
2334
2360
  async function listOutputFiles(outDir) {
@@ -2336,11 +2362,11 @@ async function listOutputFiles(outDir) {
2336
2362
  async function walk(dir) {
2337
2363
  const entries = await (0, import_promises11.readdir)(dir, { withFileTypes: true }).catch(() => []);
2338
2364
  for (const entry of entries) {
2339
- const abs = (0, import_node_path12.join)(dir, entry.name);
2365
+ const abs = (0, import_node_path13.join)(dir, entry.name);
2340
2366
  if (entry.isDirectory()) {
2341
2367
  await walk(abs);
2342
2368
  } else if (entry.isFile()) {
2343
- const rel = (0, import_node_path12.relative)(outDir, abs);
2369
+ const rel = (0, import_node_path13.relative)(outDir, abs);
2344
2370
  if (rel === MANIFEST_FILE || rel === LOCK_FILE) continue;
2345
2371
  found.push(rel);
2346
2372
  }
@@ -2377,8 +2403,8 @@ function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint, differ
2377
2403
  }
2378
2404
  async function generate(config, inputRoutes = [], entryPoint = "cli") {
2379
2405
  setCodegenDebug(config.debug);
2380
- const inputsHash = await computeInputsHash(config);
2381
2406
  const manifest = await readManifest(config.codegen.outDir);
2407
+ const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? []);
2382
2408
  if (await isManifestFresh(config.codegen.outDir, manifest, inputsHash)) {
2383
2409
  console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
2384
2410
  return;
@@ -2399,7 +2425,8 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
2399
2425
  }
2400
2426
  const extensions = config.extensions ?? [];
2401
2427
  let routes = inputRoutes;
2402
- const ctx = createExtensionContext(config, () => routes);
2428
+ const trackedInputs = /* @__PURE__ */ new Set();
2429
+ const ctx = createExtensionContext(config, () => routes, trackedInputs);
2403
2430
  if (extensions.length > 0) {
2404
2431
  routes = await applyTransformRoutes(routes, extensions, ctx);
2405
2432
  }
@@ -2453,31 +2480,34 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
2453
2480
  if (extensions.length > 0) {
2454
2481
  const extraFiles = await collectEmittedFiles(extensions, ctx);
2455
2482
  for (const file of extraFiles) {
2456
- const dest = (0, import_node_path13.join)(config.codegen.outDir, file.path);
2457
- await (0, import_promises12.mkdir)((0, import_node_path13.dirname)(dest), { recursive: true });
2483
+ const dest = (0, import_node_path14.join)(config.codegen.outDir, file.path);
2484
+ await (0, import_promises12.mkdir)((0, import_node_path14.dirname)(dest), { recursive: true });
2458
2485
  await (0, import_promises12.writeFile)(dest, file.contents, "utf8");
2459
2486
  }
2460
2487
  }
2461
2488
  const outputFiles = await listOutputFiles(config.codegen.outDir);
2489
+ const extraInputs = [...trackedInputs].sort();
2490
+ const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs) : inputsHash;
2462
2491
  await writeManifest(config.codegen.outDir, {
2463
2492
  version: VERSION,
2464
- hash: inputsHash,
2493
+ hash: recordedHash,
2465
2494
  entryPoint,
2466
2495
  configHash,
2467
2496
  configKeyHashes,
2468
- files: outputFiles
2497
+ files: outputFiles,
2498
+ ...extraInputs.length > 0 ? { extraInputs } : {}
2469
2499
  });
2470
2500
  }
2471
2501
 
2472
2502
  // src/watch/watcher.ts
2473
2503
  var import_promises15 = require("fs/promises");
2474
- var import_node_path17 = require("path");
2504
+ var import_node_path18 = require("path");
2475
2505
  var import_chokidar = __toESM(require("chokidar"), 1);
2476
2506
 
2477
2507
  // src/discovery/contracts-fast.ts
2478
- var import_node_path15 = require("path");
2508
+ var import_node_path16 = require("path");
2479
2509
  var import_fast_glob3 = __toESM(require("fast-glob"), 1);
2480
- var import_ts_morph9 = require("ts-morph");
2510
+ var import_ts_morph10 = require("ts-morph");
2481
2511
 
2482
2512
  // src/discovery/dto-type-resolver.ts
2483
2513
  var import_ts_morph7 = require("ts-morph");
@@ -2487,7 +2517,7 @@ var import_ts_morph4 = require("ts-morph");
2487
2517
 
2488
2518
  // src/discovery/type-ref-resolution.ts
2489
2519
  var import_node_fs = require("fs");
2490
- var import_node_path14 = require("path");
2520
+ var import_node_path15 = require("path");
2491
2521
  var import_ts_morph3 = require("ts-morph");
2492
2522
  var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
2493
2523
  var _ctxByProject = /* @__PURE__ */ new WeakMap();
@@ -2539,12 +2569,12 @@ function findTypeInFile(name, file) {
2539
2569
  }
2540
2570
  function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
2541
2571
  if (moduleSpecifier.startsWith(".")) {
2542
- const dir = (0, import_node_path14.dirname)(sourceFile.getFilePath());
2572
+ const dir = (0, import_node_path15.dirname)(sourceFile.getFilePath());
2543
2573
  const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
2544
2574
  return [
2545
- (0, import_node_path14.resolve)(dir, `${noExt}.ts`),
2546
- (0, import_node_path14.resolve)(dir, `${moduleSpecifier}.ts`),
2547
- (0, import_node_path14.resolve)(dir, moduleSpecifier, "index.ts")
2575
+ (0, import_node_path15.resolve)(dir, `${noExt}.ts`),
2576
+ (0, import_node_path15.resolve)(dir, `${moduleSpecifier}.ts`),
2577
+ (0, import_node_path15.resolve)(dir, moduleSpecifier, "index.ts")
2548
2578
  ];
2549
2579
  }
2550
2580
  const ctx = _ctxFor(project);
@@ -2565,8 +2595,8 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
2565
2595
  const rest = moduleSpecifier.slice(prefix.length);
2566
2596
  const candidates = [];
2567
2597
  for (const mapping of mappings) {
2568
- const resolved = (0, import_node_path14.resolve)(baseUrl, mapping.replace("*", rest));
2569
- candidates.push(`${resolved}.ts`, (0, import_node_path14.resolve)(resolved, "index.ts"));
2598
+ const resolved = (0, import_node_path15.resolve)(baseUrl, mapping.replace("*", rest));
2599
+ candidates.push(`${resolved}.ts`, (0, import_node_path15.resolve)(resolved, "index.ts"));
2570
2600
  }
2571
2601
  dbg(" resolved candidates:", candidates);
2572
2602
  return candidates;
@@ -3456,6 +3486,12 @@ function toFilterFieldType(name, r) {
3456
3486
  }
3457
3487
 
3458
3488
  // src/discovery/filter-for.ts
3489
+ function resolveMixinEntityClass(mixin, project) {
3490
+ const entityArg = mixin?.classArgs[0];
3491
+ if (!entityArg) return void 0;
3492
+ const file = project.getSourceFile(entityArg.filePath) ?? project.addSourceFileAtPathIfExists(entityArg.filePath);
3493
+ return file?.getClass(entityArg.name);
3494
+ }
3459
3495
  function classifyFilterForHint(typeInit) {
3460
3496
  if (import_ts_morph6.Node.isStringLiteral(typeInit)) {
3461
3497
  switch (typeInit.getLiteralValue()) {
@@ -3529,7 +3565,9 @@ function extractFilterForHints(classDecl, project) {
3529
3565
  }
3530
3566
  return hints;
3531
3567
  }
3532
- function extractApplyFilterInfo(method, sourceFile, project) {
3568
+ function extractApplyFilterInfo(method, sourceFile, project, mixin) {
3569
+ const declFile = method.getSourceFile();
3570
+ const mixinEntity = resolveMixinEntityClass(mixin, project);
3533
3571
  for (const param of method.getParameters()) {
3534
3572
  const filterDecorator = param.getDecorators().find((d) => d.getName() === "ApplyFilter");
3535
3573
  if (!filterDecorator) continue;
@@ -3549,12 +3587,12 @@ function extractApplyFilterInfo(method, sourceFile, project) {
3549
3587
  }
3550
3588
  }
3551
3589
  const filterClassName = filterClassArg.getText();
3552
- const resolved = findType(filterClassName, sourceFile, project);
3553
- if (resolved && resolved.kind === "class") {
3554
- const classDecl = resolved.decl;
3590
+ const resolved = findType(filterClassName, declFile, project);
3591
+ const classDecl = resolved?.kind === "class" ? resolved.decl : resolveLocalClassDeclaration(filterClassArg);
3592
+ if (classDecl) {
3555
3593
  let fieldTypes = extractClassPropertyTypes(classDecl, project);
3556
3594
  if (fieldTypes.length === 0) {
3557
- fieldTypes = extractFilterableEntityFields(classDecl, project);
3595
+ fieldTypes = extractFilterableEntityFields(classDecl, project, mixinEntity);
3558
3596
  }
3559
3597
  const filterForHints = extractFilterForHints(classDecl, project);
3560
3598
  if (filterForHints.size > 0) {
@@ -3639,22 +3677,29 @@ function extractClassPropertyTypes(classDecl, project) {
3639
3677
  }
3640
3678
  return fields;
3641
3679
  }
3642
- function extractFilterableEntityFields(filterClass, project) {
3680
+ function resolveLocalClassDeclaration(identifier) {
3681
+ if (!import_ts_morph6.Node.isIdentifier(identifier)) return void 0;
3682
+ return identifier.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && import_ts_morph6.Node.isClassDeclaration(n));
3683
+ }
3684
+ function resolveDeclaredEntity(optionsArg, filterClass, project) {
3685
+ if (!import_ts_morph6.Node.isObjectLiteralExpression(optionsArg)) return void 0;
3686
+ const entityProp = optionsArg.getProperty("entity");
3687
+ if (!entityProp || !import_ts_morph6.Node.isPropertyAssignment(entityProp)) return void 0;
3688
+ const entityInit = entityProp.getInitializer();
3689
+ if (!entityInit || !import_ts_morph6.Node.isIdentifier(entityInit)) return void 0;
3690
+ const resolved = findType(entityInit.getText(), filterClass.getSourceFile(), project);
3691
+ if (!resolved || resolved.kind !== "class") return void 0;
3692
+ return resolved.decl;
3693
+ }
3694
+ function extractFilterableEntityFields(filterClass, project, entityOverride) {
3643
3695
  const filterableDecorator = filterClass.getDecorators().find((d) => d.getName() === "Filterable");
3644
3696
  if (!filterableDecorator) return [];
3645
3697
  const args = filterableDecorator.getArguments();
3646
3698
  if (args.length === 0) return [];
3647
3699
  const optionsArg = args[0];
3648
3700
  if (!import_ts_morph6.Node.isObjectLiteralExpression(optionsArg)) return [];
3649
- const entityProp = optionsArg.getProperty("entity");
3650
- if (!entityProp || !import_ts_morph6.Node.isPropertyAssignment(entityProp)) return [];
3651
- const entityInit = entityProp.getInitializer();
3652
- if (!entityInit || !import_ts_morph6.Node.isIdentifier(entityInit)) return [];
3653
- const entityName = entityInit.getText();
3654
- const filterSourceFile = filterClass.getSourceFile();
3655
- const resolvedEntity = findType(entityName, filterSourceFile, project);
3656
- if (!resolvedEntity || resolvedEntity.kind !== "class") return [];
3657
- const entityDecl = resolvedEntity.decl;
3701
+ const entityDecl = entityOverride ?? resolveDeclaredEntity(optionsArg, filterClass, project);
3702
+ if (!entityDecl) return [];
3658
3703
  const fields = collectEntityFields(
3659
3704
  entityDecl,
3660
3705
  entityDecl.getSourceFile(),
@@ -4079,9 +4124,9 @@ function unwrapNamedContainer(node, names) {
4079
4124
  }
4080
4125
  return node;
4081
4126
  }
4082
- function extractDtoContract(method, sourceFile, project) {
4127
+ function extractDtoContract(method, sourceFile, project, mixin) {
4083
4128
  let body = extractBodyType(method, sourceFile, project);
4084
- const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
4129
+ const filterInfo = extractApplyFilterInfo(method, sourceFile, project, mixin);
4085
4130
  const query = extractQueryType(method, sourceFile, project);
4086
4131
  const uploads = extractUploadedFiles(method);
4087
4132
  const multipartBody = uploads.fields ? `{ ${uploads.fields} }` : null;
@@ -4191,12 +4236,94 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
4191
4236
  return null;
4192
4237
  }
4193
4238
 
4194
- // src/discovery/zod-ast-to-ts.ts
4239
+ // src/discovery/heritage.ts
4195
4240
  var import_ts_morph8 = require("ts-morph");
4241
+ function unwrapExpression(node) {
4242
+ let current = node;
4243
+ while (import_ts_morph8.Node.isAsExpression(current) || import_ts_morph8.Node.isSatisfiesExpression(current) || import_ts_morph8.Node.isParenthesizedExpression(current) || import_ts_morph8.Node.isTypeAssertion(current)) {
4244
+ current = current.getExpression();
4245
+ }
4246
+ return current;
4247
+ }
4248
+ function resolveReturnedClass(factoryDecl) {
4249
+ const body = factoryDecl.getBody();
4250
+ if (!body) return void 0;
4251
+ const returns = body.getDescendants().filter((n) => import_ts_morph8.Node.isReturnStatement(n));
4252
+ for (const ret of returns) {
4253
+ const expr = ret.getExpression();
4254
+ if (!expr) continue;
4255
+ const inner = unwrapExpression(expr);
4256
+ if (import_ts_morph8.Node.isClassExpression(inner)) {
4257
+ return inner;
4258
+ }
4259
+ if (import_ts_morph8.Node.isIdentifier(inner)) {
4260
+ const name = inner.getText();
4261
+ const decl = body.getDescendants().find((n) => import_ts_morph8.Node.isClassDeclaration(n) && n.getName() === name);
4262
+ if (decl) return decl;
4263
+ }
4264
+ }
4265
+ return void 0;
4266
+ }
4267
+ function resolveInheritedMethods(cls) {
4268
+ const expr = cls.getExtends()?.getExpression();
4269
+ if (!expr || !import_ts_morph8.Node.isCallExpression(expr)) return void 0;
4270
+ const callee = expr.getExpression();
4271
+ if (!import_ts_morph8.Node.isIdentifier(callee)) return void 0;
4272
+ const factoryDecl = callee.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => import_ts_morph8.Node.isFunctionDeclaration(n));
4273
+ if (!factoryDecl) return void 0;
4274
+ const returnedClass = resolveReturnedClass(factoryDecl);
4275
+ if (!returnedClass) return void 0;
4276
+ const classArgs = [];
4277
+ for (const arg of expr.getArguments()) {
4278
+ if (!import_ts_morph8.Node.isIdentifier(arg)) continue;
4279
+ const decl = arg.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => import_ts_morph8.Node.isClassDeclaration(n));
4280
+ if (decl) {
4281
+ classArgs.push({
4282
+ name: decl.getName() ?? arg.getText(),
4283
+ filePath: decl.getSourceFile().getFilePath()
4284
+ });
4285
+ }
4286
+ }
4287
+ return {
4288
+ methods: returnedClass.getMethods(),
4289
+ factoryName: callee.getText(),
4290
+ factoryFilePath: factoryDecl.getSourceFile().getFilePath(),
4291
+ classArgs
4292
+ };
4293
+ }
4294
+ var mixinTypeProject;
4295
+ function getMixinTypeProject() {
4296
+ mixinTypeProject ??= new import_ts_morph8.Project({
4297
+ skipAddingFilesFromTsConfig: true,
4298
+ compilerOptions: { strict: true }
4299
+ });
4300
+ return mixinTypeProject;
4301
+ }
4302
+ function clearMixinTypeProject() {
4303
+ mixinTypeProject = void 0;
4304
+ }
4305
+ function resolveInstantiatedReturnType(cls, methodName) {
4306
+ const filePath = cls.getSourceFile().getFilePath();
4307
+ const className = cls.getName();
4308
+ if (!className) return void 0;
4309
+ const project = getMixinTypeProject();
4310
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPathIfExists(filePath);
4311
+ const typedCls = sourceFile?.getClass(className);
4312
+ if (!typedCls) return void 0;
4313
+ const prop = typedCls.getType().getProperty(methodName);
4314
+ if (!prop) return void 0;
4315
+ const returnType = prop.getTypeAtLocation(typedCls).getCallSignatures()[0]?.getReturnType();
4316
+ if (!returnType) return void 0;
4317
+ const unwrapped = returnType.getSymbol()?.getName() === "Promise" ? returnType.getTypeArguments()[0] ?? returnType : returnType;
4318
+ return unwrapped.getText(typedCls);
4319
+ }
4320
+
4321
+ // src/discovery/zod-ast-to-ts.ts
4322
+ var import_ts_morph9 = require("ts-morph");
4196
4323
  function zodAstToTs(node) {
4197
- if (!import_ts_morph8.Node.isCallExpression(node)) return "unknown";
4324
+ if (!import_ts_morph9.Node.isCallExpression(node)) return "unknown";
4198
4325
  const expr = node.getExpression();
4199
- if (import_ts_morph8.Node.isPropertyAccessExpression(expr)) {
4326
+ if (import_ts_morph9.Node.isPropertyAccessExpression(expr)) {
4200
4327
  const methodName = expr.getName();
4201
4328
  const receiver = expr.getExpression();
4202
4329
  if (methodName === "optional") {
@@ -4220,17 +4347,17 @@ function zodAstToTs(node) {
4220
4347
  case "literal": {
4221
4348
  const lit = args[0];
4222
4349
  if (!lit) return "unknown";
4223
- if (import_ts_morph8.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
4224
- if (import_ts_morph8.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
4225
- if (lit.getKind() === import_ts_morph8.SyntaxKind.TrueKeyword) return "true";
4226
- if (lit.getKind() === import_ts_morph8.SyntaxKind.FalseKeyword) return "false";
4350
+ if (import_ts_morph9.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
4351
+ if (import_ts_morph9.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
4352
+ if (lit.getKind() === import_ts_morph9.SyntaxKind.TrueKeyword) return "true";
4353
+ if (lit.getKind() === import_ts_morph9.SyntaxKind.FalseKeyword) return "false";
4227
4354
  return "unknown";
4228
4355
  }
4229
4356
  case "enum": {
4230
4357
  const arrArg = args[0];
4231
- if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
4358
+ if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
4232
4359
  const members = arrArg.getElements().map(
4233
- (el) => import_ts_morph8.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
4360
+ (el) => import_ts_morph9.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
4234
4361
  );
4235
4362
  return members.join(" | ");
4236
4363
  }
@@ -4241,10 +4368,10 @@ function zodAstToTs(node) {
4241
4368
  }
4242
4369
  case "object": {
4243
4370
  const objArg = args[0];
4244
- if (!objArg || !import_ts_morph8.Node.isObjectLiteralExpression(objArg)) return "unknown";
4371
+ if (!objArg || !import_ts_morph9.Node.isObjectLiteralExpression(objArg)) return "unknown";
4245
4372
  const lines = [];
4246
4373
  for (const prop of objArg.getProperties()) {
4247
- if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
4374
+ if (!import_ts_morph9.Node.isPropertyAssignment(prop)) continue;
4248
4375
  const key = prop.getName();
4249
4376
  const valNode = prop.getInitializer();
4250
4377
  if (!valNode) continue;
@@ -4256,7 +4383,7 @@ function zodAstToTs(node) {
4256
4383
  }
4257
4384
  case "union": {
4258
4385
  const arrArg = args[0];
4259
- if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
4386
+ if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
4260
4387
  return arrArg.getElements().map(zodAstToTs).join(" | ");
4261
4388
  }
4262
4389
  case "record": {
@@ -4266,7 +4393,7 @@ function zodAstToTs(node) {
4266
4393
  }
4267
4394
  case "tuple": {
4268
4395
  const arrArg = args[0];
4269
- if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
4396
+ if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
4270
4397
  return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
4271
4398
  }
4272
4399
  default:
@@ -4276,18 +4403,18 @@ function zodAstToTs(node) {
4276
4403
  return "unknown";
4277
4404
  }
4278
4405
  function isOptionalChain(node) {
4279
- if (!import_ts_morph8.Node.isCallExpression(node)) return false;
4406
+ if (!import_ts_morph9.Node.isCallExpression(node)) return false;
4280
4407
  const expr = node.getExpression();
4281
- return import_ts_morph8.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
4408
+ return import_ts_morph9.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
4282
4409
  }
4283
4410
  function parseDefineContractCall(callExpr) {
4284
- if (!import_ts_morph8.Node.isCallExpression(callExpr)) return null;
4411
+ if (!import_ts_morph9.Node.isCallExpression(callExpr)) return null;
4285
4412
  const callee = callExpr.getExpression();
4286
- const calleeName = import_ts_morph8.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph8.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
4413
+ const calleeName = import_ts_morph9.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph9.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
4287
4414
  if (calleeName !== "defineContract") return null;
4288
4415
  const args = callExpr.getArguments();
4289
4416
  const optsArg = args[0];
4290
- if (!optsArg || !import_ts_morph8.Node.isObjectLiteralExpression(optsArg)) return null;
4417
+ if (!optsArg || !import_ts_morph9.Node.isObjectLiteralExpression(optsArg)) return null;
4291
4418
  let query = null;
4292
4419
  let body = null;
4293
4420
  let response = "unknown";
@@ -4295,7 +4422,7 @@ function parseDefineContractCall(callExpr) {
4295
4422
  let bodyZodText = null;
4296
4423
  let queryZodText = null;
4297
4424
  for (const prop of optsArg.getProperties()) {
4298
- if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
4425
+ if (!import_ts_morph9.Node.isPropertyAssignment(prop)) continue;
4299
4426
  const propName = prop.getName();
4300
4427
  const val = prop.getInitializer();
4301
4428
  if (!val) continue;
@@ -4324,21 +4451,22 @@ async function discoverContractsFast(opts) {
4324
4451
  project.addSourceFileAtPath(f);
4325
4452
  }
4326
4453
  bindDiscoveryContext(project, cwd, tsconfigPath);
4454
+ clearMixinTypeProject();
4327
4455
  return extractAllRoutes(project);
4328
4456
  }
4329
4457
  function resolveTsconfigPath(cwd, tsconfig) {
4330
- return tsconfig ? (0, import_node_path15.resolve)(tsconfig) : (0, import_node_path15.join)(cwd, "tsconfig.json");
4458
+ return tsconfig ? (0, import_node_path16.resolve)(tsconfig) : (0, import_node_path16.join)(cwd, "tsconfig.json");
4331
4459
  }
4332
4460
  function createDiscoveryProject(tsconfigPath) {
4333
4461
  try {
4334
- return new import_ts_morph9.Project({
4462
+ return new import_ts_morph10.Project({
4335
4463
  tsConfigFilePath: tsconfigPath,
4336
4464
  skipAddingFilesFromTsConfig: true,
4337
4465
  skipLoadingLibFiles: true,
4338
4466
  skipFileDependencyResolution: true
4339
4467
  });
4340
4468
  } catch {
4341
- return new import_ts_morph9.Project({
4469
+ return new import_ts_morph10.Project({
4342
4470
  skipAddingFilesFromTsConfig: true,
4343
4471
  skipLoadingLibFiles: true,
4344
4472
  skipFileDependencyResolution: true,
@@ -4413,7 +4541,7 @@ var PersistentDiscovery = class _PersistentDiscovery {
4413
4541
  async rediscover(changedPaths) {
4414
4542
  if (changedPaths) {
4415
4543
  for (const p of changedPaths) {
4416
- const abs = (0, import_node_path15.resolve)(p);
4544
+ const abs = (0, import_node_path16.resolve)(p);
4417
4545
  const sf = this.project.getSourceFile(abs);
4418
4546
  if (sf) {
4419
4547
  await sf.refreshFromFileSystem();
@@ -4445,15 +4573,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
4445
4573
  runExtraction() {
4446
4574
  clearTypeResolutionCaches(this.project);
4447
4575
  clearEnumCache(this.project);
4576
+ clearMixinTypeProject();
4448
4577
  return extractRoutesFrom(this.project, this.controllerPaths);
4449
4578
  }
4450
4579
  };
4451
4580
  function decoratorStringArg(decoratorExpr) {
4452
4581
  if (!decoratorExpr) return void 0;
4453
- if (import_ts_morph9.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
4454
- if (import_ts_morph9.Node.isArrayLiteralExpression(decoratorExpr)) {
4582
+ if (import_ts_morph10.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
4583
+ if (import_ts_morph10.Node.isArrayLiteralExpression(decoratorExpr)) {
4455
4584
  const first = decoratorExpr.getElements()[0];
4456
- if (first && import_ts_morph9.Node.isStringLiteral(first)) return first.getLiteralValue();
4585
+ if (first && import_ts_morph10.Node.isStringLiteral(first)) return first.getLiteralValue();
4457
4586
  }
4458
4587
  return void 0;
4459
4588
  }
@@ -4475,7 +4604,8 @@ function joinPaths(prefix, suffix) {
4475
4604
  if (!prefix && !suffix) return "/";
4476
4605
  if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
4477
4606
  if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
4478
- const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
4607
+ const withLeadingSlash = prefix.startsWith("/") ? prefix : `/${prefix}`;
4608
+ const p = withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
4479
4609
  const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
4480
4610
  const combined = p + s;
4481
4611
  return combined === "" ? "/" : combined;
@@ -4529,7 +4659,8 @@ function buildRoute(args) {
4529
4659
  methodAs,
4530
4660
  sourceFile,
4531
4661
  seenNames,
4532
- contractSource
4662
+ contractSource,
4663
+ mixin
4533
4664
  } = args;
4534
4665
  const routeName = resolveRouteName(className, methodName, classAs, methodAs);
4535
4666
  const qualifiedRef = `${className}.${methodName}`;
@@ -4545,7 +4676,12 @@ function buildRoute(args) {
4545
4676
  path: combinedPath,
4546
4677
  name: routeName,
4547
4678
  params: extractParams(combinedPath),
4548
- controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
4679
+ controllerRef: {
4680
+ className,
4681
+ methodName,
4682
+ filePath: sourceFile.getFilePath(),
4683
+ ...mixin ? { mixin } : {}
4684
+ },
4549
4685
  contract: { contractSource }
4550
4686
  };
4551
4687
  }
@@ -4559,16 +4695,17 @@ function extractContractRoute(args) {
4559
4695
  className,
4560
4696
  sourceFile,
4561
4697
  project,
4562
- seenNames
4698
+ seenNames,
4699
+ mixin
4563
4700
  } = args;
4564
4701
  const firstDecoratorArg = applyContractDecorator.getArguments()[0];
4565
4702
  if (!firstDecoratorArg) return null;
4566
4703
  let contractDef = null;
4567
4704
  let bodyZodRef = null;
4568
4705
  let queryZodRef = null;
4569
- if (import_ts_morph9.Node.isCallExpression(firstDecoratorArg)) {
4706
+ if (import_ts_morph10.Node.isCallExpression(firstDecoratorArg)) {
4570
4707
  contractDef = parseDefineContractCall(firstDecoratorArg);
4571
- } else if (import_ts_morph9.Node.isIdentifier(firstDecoratorArg)) {
4708
+ } else if (import_ts_morph10.Node.isIdentifier(firstDecoratorArg)) {
4572
4709
  const identName = firstDecoratorArg.getText();
4573
4710
  const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
4574
4711
  if (!resolvedVar) {
@@ -4611,6 +4748,7 @@ function extractContractRoute(args) {
4611
4748
  methodAs,
4612
4749
  sourceFile,
4613
4750
  seenNames,
4751
+ mixin,
4614
4752
  contractSource: {
4615
4753
  query: contractDef.query,
4616
4754
  body: contractDef.body,
@@ -4627,13 +4765,14 @@ function extractContractRoute(args) {
4627
4765
  });
4628
4766
  }
4629
4767
  function extractDtoRoute(args) {
4630
- const { cls, method, verb, prefix, className, sourceFile, project, seenNames } = args;
4768
+ const { cls, method, verb, prefix, className, sourceFile, project, seenNames, mixin } = args;
4631
4769
  if (!verb) return null;
4632
4770
  const combined = joinPaths(prefix, verb.handlerPath);
4633
4771
  const methodName = method.getName();
4634
4772
  const classAs = readAsDecorator(cls, `class ${className}`);
4635
4773
  const methodAs = readAsDecorator(method, `${className}.${methodName}`);
4636
- const dtoContract = extractDtoContract(method, sourceFile, project);
4774
+ const dtoContract = extractDtoContract(method, sourceFile, project, mixin);
4775
+ const mixinResponse = mixin ? resolveInstantiatedReturnType(cls, methodName) : void 0;
4637
4776
  return buildRoute({
4638
4777
  className,
4639
4778
  methodName,
@@ -4643,10 +4782,11 @@ function extractDtoRoute(args) {
4643
4782
  methodAs,
4644
4783
  sourceFile,
4645
4784
  seenNames,
4785
+ mixin,
4646
4786
  contractSource: {
4647
4787
  query: dtoContract?.query ?? null,
4648
4788
  body: dtoContract?.body ?? null,
4649
- response: dtoContract?.response ?? "unknown",
4789
+ response: mixinResponse ?? dtoContract?.response ?? "unknown",
4650
4790
  error: dtoContract?.error ?? null,
4651
4791
  queryRef: dtoContract?.queryRef ?? null,
4652
4792
  bodyRef: dtoContract?.bodyRef ?? null,
@@ -4675,7 +4815,17 @@ function extractFromSourceFile(sourceFile, project) {
4675
4815
  const firstArg2 = controllerDecorator.getArguments()[0];
4676
4816
  const prefix = decoratorStringArg(firstArg2) ?? "";
4677
4817
  const className = cls.getName() ?? "Unknown";
4678
- for (const method of cls.getMethods()) {
4818
+ const inherited = resolveInheritedMethods(cls);
4819
+ const mixinBinding = inherited ? {
4820
+ factoryName: inherited.factoryName,
4821
+ factoryFilePath: inherited.factoryFilePath,
4822
+ classArgs: inherited.classArgs
4823
+ } : void 0;
4824
+ const methods = [
4825
+ ...cls.getMethods().map((method) => ({ method })),
4826
+ ...(inherited?.methods ?? []).map((method) => ({ method, mixin: mixinBinding }))
4827
+ ];
4828
+ for (const { method, mixin } of methods) {
4679
4829
  const verb = resolveVerb(method);
4680
4830
  const applyContractDecorator = method.getDecorator("ApplyContract");
4681
4831
  const route = applyContractDecorator ? extractContractRoute({
@@ -4687,7 +4837,8 @@ function extractFromSourceFile(sourceFile, project) {
4687
4837
  className,
4688
4838
  sourceFile,
4689
4839
  project,
4690
- seenNames
4840
+ seenNames,
4841
+ mixin
4691
4842
  }) : extractDtoRoute({
4692
4843
  cls,
4693
4844
  method,
@@ -4696,7 +4847,8 @@ function extractFromSourceFile(sourceFile, project) {
4696
4847
  className,
4697
4848
  sourceFile,
4698
4849
  project,
4699
- seenNames
4850
+ seenNames,
4851
+ mixin
4700
4852
  });
4701
4853
  if (route) routes.push(route);
4702
4854
  }
@@ -4707,7 +4859,7 @@ function extractFromSourceFile(sourceFile, project) {
4707
4859
  // src/watch/lock-file.ts
4708
4860
  var import_promises13 = require("fs/promises");
4709
4861
  var import_promises14 = require("fs/promises");
4710
- var import_node_path16 = require("path");
4862
+ var import_node_path17 = require("path");
4711
4863
  var LOCK_FILE2 = ".watcher.lock";
4712
4864
  function isProcessAlive(pid) {
4713
4865
  try {
@@ -4719,7 +4871,7 @@ function isProcessAlive(pid) {
4719
4871
  }
4720
4872
  async function acquireLock(outDir) {
4721
4873
  await (0, import_promises14.mkdir)(outDir, { recursive: true });
4722
- const lockPath = (0, import_node_path16.join)(outDir, LOCK_FILE2);
4874
+ const lockPath = (0, import_node_path17.join)(outDir, LOCK_FILE2);
4723
4875
  const lockData = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
4724
4876
  try {
4725
4877
  const fd = await (0, import_promises13.open)(lockPath, "wx");
@@ -4760,7 +4912,7 @@ async function watch(config, onChange, options = {}) {
4760
4912
  if (lock === null) {
4761
4913
  let holderPid = "unknown";
4762
4914
  try {
4763
- const raw = await (0, import_promises15.readFile)((0, import_node_path17.join)(config.codegen.outDir, ".watcher.lock"), "utf8");
4915
+ const raw = await (0, import_promises15.readFile)((0, import_node_path18.join)(config.codegen.outDir, ".watcher.lock"), "utf8");
4764
4916
  const data = JSON.parse(raw);
4765
4917
  if (data.pid !== void 0) holderPid = String(data.pid);
4766
4918
  } catch {
@@ -4813,7 +4965,7 @@ async function watch(config, onChange, options = {}) {
4813
4965
  }
4814
4966
  let pagesDebounceTimer;
4815
4967
  const pagesGlob = config.pages?.glob ?? ".nestjs-codegen-no-pages";
4816
- const pagesWatcher = import_chokidar.default.watch((0, import_node_path17.join)(config.codegen.cwd, pagesGlob), {
4968
+ const pagesWatcher = import_chokidar.default.watch((0, import_node_path18.join)(config.codegen.cwd, pagesGlob), {
4817
4969
  ignoreInitial: true,
4818
4970
  persistent: true,
4819
4971
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
@@ -4840,7 +4992,7 @@ async function watch(config, onChange, options = {}) {
4840
4992
  pagesWatcher.on("unlink", schedulePagesRegenerate);
4841
4993
  let contractsDebounceTimer;
4842
4994
  const pendingChangedPaths = /* @__PURE__ */ new Set();
4843
- const contractsWatcher = import_chokidar.default.watch((0, import_node_path17.join)(config.codegen.cwd, config.contracts.glob), {
4995
+ const contractsWatcher = import_chokidar.default.watch((0, import_node_path18.join)(config.codegen.cwd, config.contracts.glob), {
4844
4996
  ignoreInitial: true,
4845
4997
  persistent: true,
4846
4998
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
@@ -4870,7 +5022,7 @@ async function watch(config, onChange, options = {}) {
4870
5022
  contractsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
4871
5023
  contractsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
4872
5024
  contractsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
4873
- const formsWatcher = import_chokidar.default.watch((0, import_node_path17.join)(config.codegen.cwd, config.forms.watch), {
5025
+ const formsWatcher = import_chokidar.default.watch((0, import_node_path18.join)(config.codegen.cwd, config.forms.watch), {
4874
5026
  ignoreInitial: true,
4875
5027
  persistent: true,
4876
5028
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
@@ -4980,7 +5132,7 @@ function createChainModuleRenderer(opts) {
4980
5132
  }
4981
5133
 
4982
5134
  // src/index.ts
4983
- var VERSION = "0.15.0";
5135
+ var VERSION = "0.17.0";
4984
5136
  // Annotate the CommonJS export names for ESM import in node:
4985
5137
  0 && (module.exports = {
4986
5138
  CodegenError,