@dudousxd/nestjs-inertia-codegen 1.2.0 → 1.4.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
@@ -269,16 +269,6 @@ function validateNameSegment(seg, fullName) {
269
269
  }
270
270
  }
271
271
  __name(validateNameSegment, "validateNameSegment");
272
- function detectCollisions(tree, name) {
273
- for (const [key, node] of tree) {
274
- if (node.kind === "leaf") {
275
- } else {
276
- void key;
277
- }
278
- }
279
- void name;
280
- }
281
- __name(detectCollisions, "detectCollisions");
282
272
  function insertIntoTree(tree, segments, leaf, fullName) {
283
273
  const head = segments[0];
284
274
  const rest = segments.slice(1);
@@ -308,7 +298,30 @@ function insertIntoTree(tree, segments, leaf, fullName) {
308
298
  }
309
299
  }
310
300
  __name(insertIntoTree, "insertIntoTree");
311
- function emitRouterTypeBlock(tree, indent) {
301
+ function buildParamsType(params) {
302
+ const pathParams = params.filter((p) => p.source === "path");
303
+ if (pathParams.length === 0) return "never";
304
+ return `{ ${pathParams.map((p) => `${p.name}: string`).join("; ")} }`;
305
+ }
306
+ __name(buildParamsType, "buildParamsType");
307
+ function hasPathParams(params) {
308
+ return params.some((p) => p.source === "path");
309
+ }
310
+ __name(hasPathParams, "hasPathParams");
311
+ function buildResponseType(c, outDir) {
312
+ if (c.controllerRef) {
313
+ let relPath = (0, import_node_path3.relative)(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
314
+ if (!relPath.startsWith(".")) relPath = `./${relPath}`;
315
+ return `Awaited<ReturnType<import('${relPath}').${c.controllerRef.className}['${c.controllerRef.methodName}']>>`;
316
+ }
317
+ const respRef = c.contractSource.responseRef;
318
+ if (respRef) {
319
+ return respRef.isArray ? `Array<${respRef.name}>` : respRef.name;
320
+ }
321
+ return c.contractSource.response;
322
+ }
323
+ __name(buildResponseType, "buildResponseType");
324
+ function emitRouterTypeBlock(tree, indent, outDir) {
312
325
  const pad = " ".repeat(indent);
313
326
  const lines = [];
314
327
  for (const [key, node] of tree) {
@@ -320,14 +333,14 @@ function emitRouterTypeBlock(tree, indent) {
320
333
  const query = queryRef ? queryRef.isArray ? `Array<${queryRef.name}>` : queryRef.name : c.contractSource.query ?? "never";
321
334
  const bodyRef = c.contractSource.bodyRef;
322
335
  const body = method === "GET" ? "never" : bodyRef ? bodyRef.isArray ? `Array<${bodyRef.name}>` : bodyRef.name : c.contractSource.body ?? "never";
323
- const respRef = c.contractSource.responseRef;
324
- const response = respRef ? respRef.isArray ? `Array<${respRef.name}>` : respRef.name : c.contractSource.response;
336
+ const response = buildResponseType(c, outDir);
337
+ const params = buildParamsType(c.params);
325
338
  const safeMethod = JSON.stringify(method);
326
339
  const safeUrl = JSON.stringify(c.path);
327
- lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; query: ${query}; body: ${body}; response: ${response} };`);
340
+ lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; params: ${params}; query: ${query}; body: ${body}; response: ${response} };`);
328
341
  } else {
329
342
  lines.push(`${pad}${objKey}: {`);
330
- lines.push(...emitRouterTypeBlock(node.children, indent + 2));
343
+ lines.push(...emitRouterTypeBlock(node.children, indent + 2, outDir));
331
344
  lines.push(`${pad}};`);
332
345
  }
333
346
  }
@@ -347,20 +360,61 @@ function emitApiObjectBlock(tree, indent) {
347
360
  const fetcherMethod = method.toLowerCase();
348
361
  if (method === "GET") {
349
362
  const typeAccess = buildRouterTypeAccess(c.name);
363
+ const withParams = hasPathParams(c.params);
350
364
  lines.push(`${pad}${objKey}: {`);
351
- lines.push(`${pad} queryKey: (query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
352
- lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) => ({`);
353
- lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
354
- lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
355
- lines.push(`${pad} }),`);
365
+ if (withParams) {
366
+ lines.push(`${pad} queryKey: (params: ${typeAccess}['params'], query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, params, query] as const : [${flatName}, params] as const,`);
367
+ lines.push(`${pad} queryOptions: (params: ${typeAccess}['params'], query?: ${typeAccess}['query']) =>`);
368
+ lines.push(`${pad} _queryOptions({`);
369
+ lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, params, query] as const : [${flatName}, params] as const,`);
370
+ lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never, params as never) || ${safePath}, { query }),`);
371
+ lines.push(`${pad} }),`);
372
+ lines.push(`${pad} infiniteQueryOptions: (params: ${typeAccess}['params'], query?: ${typeAccess}['query']) => ({`);
373
+ lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, params, query] as const : [${flatName}, params] as const,`);
374
+ lines.push(`${pad} queryFn: ({ pageParam }: { pageParam: number }) => fetcher.get<${typeAccess}['response']>(route(${flatName} as never, params as never) || ${safePath}, { query: { ...query, page: pageParam } }),`);
375
+ lines.push(`${pad} initialPageParam: 1,`);
376
+ lines.push(`${pad} getNextPageParam: (lastPage: ${typeAccess}['response']) => {`);
377
+ lines.push(`${pad} const meta = (lastPage as any)?.meta;`);
378
+ lines.push(`${pad} if (meta?.page != null && meta?.lastPage != null) {`);
379
+ lines.push(`${pad} return meta.page < meta.lastPage ? meta.page + 1 : undefined;`);
380
+ lines.push(`${pad} }`);
381
+ lines.push(`${pad} return undefined;`);
382
+ lines.push(`${pad} },`);
383
+ lines.push(`${pad} }),`);
384
+ } else {
385
+ lines.push(`${pad} queryKey: (query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
386
+ lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) =>`);
387
+ lines.push(`${pad} _queryOptions({`);
388
+ lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
389
+ lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
390
+ lines.push(`${pad} }),`);
391
+ lines.push(`${pad} infiniteQueryOptions: (query?: ${typeAccess}['query']) => ({`);
392
+ lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
393
+ lines.push(`${pad} queryFn: ({ pageParam }: { pageParam: number }) => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query: { ...query, page: pageParam } }),`);
394
+ lines.push(`${pad} initialPageParam: 1,`);
395
+ lines.push(`${pad} getNextPageParam: (lastPage: ${typeAccess}['response']) => {`);
396
+ lines.push(`${pad} const meta = (lastPage as any)?.meta;`);
397
+ lines.push(`${pad} if (meta?.page != null && meta?.lastPage != null) {`);
398
+ lines.push(`${pad} return meta.page < meta.lastPage ? meta.page + 1 : undefined;`);
399
+ lines.push(`${pad} }`);
400
+ lines.push(`${pad} return undefined;`);
401
+ lines.push(`${pad} },`);
402
+ lines.push(`${pad} }),`);
403
+ }
356
404
  lines.push(`${pad}},`);
357
405
  } else {
358
406
  const typeAccess = buildRouterTypeAccess(c.name);
407
+ const withParams = hasPathParams(c.params);
359
408
  lines.push(`${pad}${objKey}: {`);
360
409
  lines.push(`${pad} queryKey: () => [${flatName}] as const,`);
361
- lines.push(`${pad} mutationOptions: () => ({`);
362
- lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
363
- lines.push(`${pad} }),`);
410
+ lines.push(`${pad} mutationOptions: () =>`);
411
+ lines.push(`${pad} _mutationOptions({`);
412
+ if (withParams) {
413
+ lines.push(`${pad} mutationFn: (input: { params: ${typeAccess}['params']; body: ${typeAccess}['body'] }) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never, input.params as never) || ${safePath}, { body: input.body }),`);
414
+ } else {
415
+ lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
416
+ }
417
+ lines.push(`${pad} }),`);
364
418
  lines.push(`${pad}},`);
365
419
  }
366
420
  } else {
@@ -383,11 +437,15 @@ function buildApiFile(routes, outDir) {
383
437
  for (const r of contracted) {
384
438
  const cs = r.contract?.contractSource;
385
439
  if (!cs) continue;
386
- for (const ref of [
440
+ const refs = r.controllerRef ? [
441
+ cs.queryRef,
442
+ cs.bodyRef
443
+ ] : [
387
444
  cs.queryRef,
388
445
  cs.bodyRef,
389
446
  cs.responseRef
390
- ]) {
447
+ ];
448
+ for (const ref of refs) {
391
449
  if (!ref) continue;
392
450
  let names = importsByFile.get(ref.filePath);
393
451
  if (!names) {
@@ -397,10 +455,18 @@ function buildApiFile(routes, outDir) {
397
455
  names.add(ref.name);
398
456
  }
399
457
  }
458
+ const hasGetRoutes = contracted.some((r) => r.method === "GET");
459
+ const hasMutationRoutes = contracted.some((r) => r.method !== "GET");
400
460
  const lines = [
401
461
  "// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.",
402
462
  ""
403
463
  ];
464
+ const tqImports = [];
465
+ if (hasGetRoutes) tqImports.push("queryOptions as _queryOptions");
466
+ if (hasMutationRoutes) tqImports.push("mutationOptions as _mutationOptions");
467
+ if (tqImports.length > 0) {
468
+ lines.push(`import { ${tqImports.join(", ")} } from '@tanstack/react-query';`);
469
+ }
404
470
  lines.push("import { route } from './routes.js';");
405
471
  lines.push("import { createFetcher } from '@dudousxd/nestjs-inertia-client';");
406
472
  if (importsByFile.size > 0 && outDir) {
@@ -454,13 +520,14 @@ function buildApiFile(routes, outDir) {
454
520
  method: r.method,
455
521
  name,
456
522
  path: r.path,
523
+ params: r.params,
524
+ controllerRef: r.controllerRef,
457
525
  contractSource: c.contractSource
458
526
  };
459
527
  insertIntoTree(tree, segments, leaf, name);
460
528
  }
461
- void detectCollisions;
462
529
  lines.push("export type ApiRouter = {");
463
- lines.push(...emitRouterTypeBlock(tree, 2));
530
+ lines.push(...emitRouterTypeBlock(tree, 2, outDir ?? ""));
464
531
  lines.push("};");
465
532
  lines.push("");
466
533
  lines.push("export const api = {");
@@ -557,7 +624,8 @@ __name(emitIndex, "emitIndex");
557
624
  // src/emit/emit-pages.ts
558
625
  var import_promises6 = require("fs/promises");
559
626
  var import_node_path6 = require("path");
560
- async function emitPages(pages, outDir) {
627
+ async function emitPages(pages, outDir, options = {}) {
628
+ const propsExport = options.propsExport ?? "ComponentProps";
561
629
  await (0, import_promises6.mkdir)(outDir, {
562
630
  recursive: true
563
631
  });
@@ -566,14 +634,40 @@ async function emitPages(pages, outDir) {
566
634
  const key = needsQuotes(p.name) ? JSON.stringify(p.name) : p.name;
567
635
  return ` ${key}: ${propType};`;
568
636
  }).join("\n");
637
+ const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
638
+ const augBody = pages.map((p) => {
639
+ const key = needsQuotes(p.name) ? JSON.stringify(p.name) : p.name;
640
+ const valueType = buildAugmentationType(p, outDir, propsExport);
641
+ return ` ${key}: ${valueType};`;
642
+ }).join("\n");
643
+ const propsHelper = "\nexport type InertiaProps<K extends InertiaPageName> = import('@dudousxd/nestjs-inertia').InertiaPages[K];\n";
569
644
  const content = `// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.
570
645
  export interface InertiaPages {
571
646
  ${body}
572
647
  }
648
+
649
+ export type InertiaPageName = ${pageNameUnion};
650
+ ` + propsHelper + `
651
+ declare module '@dudousxd/nestjs-inertia' {
652
+ interface InertiaPages {
653
+ ${augBody}
654
+ }
655
+ }
573
656
  `;
574
657
  await (0, import_promises6.writeFile)((0, import_node_path6.join)(outDir, "pages.d.ts"), content, "utf8");
575
658
  }
576
659
  __name(emitPages, "emitPages");
660
+ function buildAugmentationType(page, outDir, propsExport) {
661
+ if (!page.propsSource) {
662
+ return "Record<string, unknown>";
663
+ }
664
+ let importPath = (0, import_node_path6.relative)(outDir, page.absolutePath).replace(/\.(tsx?|vue|svelte)$/, "");
665
+ if (!importPath.startsWith(".")) {
666
+ importPath = `./${importPath}`;
667
+ }
668
+ return `import('${importPath}').${propsExport}`;
669
+ }
670
+ __name(buildAugmentationType, "buildAugmentationType");
577
671
  function needsQuotes(name) {
578
672
  return !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
579
673
  }
@@ -710,7 +804,9 @@ async function generate(config, routes = []) {
710
804
  propsExport: config.pages.propsExport,
711
805
  componentNameStrategy: config.pages.componentNameStrategy
712
806
  });
713
- await emitPages(pages, config.codegen.outDir);
807
+ await emitPages(pages, config.codegen.outDir, {
808
+ propsExport: config.pages.propsExport
809
+ });
714
810
  await emitCache(pages, config.codegen.outDir);
715
811
  const hasRoutes = routes.length > 0;
716
812
  const hasContracts = routes.some((r) => r.contract);
@@ -725,14 +821,43 @@ async function generate(config, routes = []) {
725
821
  __name(generate, "generate");
726
822
 
727
823
  // src/watch/watcher.ts
728
- var import_promises9 = require("fs/promises");
824
+ var import_promises10 = require("fs/promises");
729
825
  var import_node_path10 = require("path");
730
826
  var import_chokidar = __toESM(require("chokidar"), 1);
731
827
 
732
828
  // src/discovery/contracts-fast.ts
829
+ var import_node_fs = require("fs");
733
830
  var import_node_path8 = require("path");
734
831
  var import_fast_glob2 = __toESM(require("fast-glob"), 1);
735
832
  var import_ts_morph = require("ts-morph");
833
+ var _ctx = {
834
+ projectRoot: "",
835
+ tsconfigPaths: null
836
+ };
837
+ function _projectRoot() {
838
+ return _ctx.projectRoot;
839
+ }
840
+ __name(_projectRoot, "_projectRoot");
841
+ function _tsconfigPaths() {
842
+ return _ctx.tsconfigPaths;
843
+ }
844
+ __name(_tsconfigPaths, "_tsconfigPaths");
845
+ var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
846
+ function dbg(...args) {
847
+ if (_debug) console.log("[codegen:debug]", ...args);
848
+ }
849
+ __name(dbg, "dbg");
850
+ function loadTsconfigPaths(tsconfigPath) {
851
+ try {
852
+ const raw = (0, import_node_fs.readFileSync)(tsconfigPath, "utf8");
853
+ const stripped = raw.replace(/\/\/.*$/gm, "");
854
+ const parsed = JSON.parse(stripped);
855
+ return parsed.compilerOptions?.paths ?? null;
856
+ } catch {
857
+ return null;
858
+ }
859
+ }
860
+ __name(loadTsconfigPaths, "loadTsconfigPaths");
736
861
  async function discoverContractsFast(opts) {
737
862
  const { cwd, glob, tsconfig } = opts;
738
863
  const tsconfigPath = tsconfig ? (0, import_node_path8.resolve)(tsconfig) : (0, import_node_path8.join)(cwd, "tsconfig.json");
@@ -765,8 +890,17 @@ async function discoverContractsFast(opts) {
765
890
  project.addSourceFileAtPath(f);
766
891
  }
767
892
  const routes = [];
768
- for (const sourceFile of project.getSourceFiles()) {
769
- routes.push(...extractFromSourceFile(sourceFile, project));
893
+ const prevCtx = _ctx;
894
+ _ctx = {
895
+ projectRoot: cwd,
896
+ tsconfigPaths: loadTsconfigPaths(tsconfigPath)
897
+ };
898
+ try {
899
+ for (const sourceFile of project.getSourceFiles()) {
900
+ routes.push(...extractFromSourceFile(sourceFile, project));
901
+ }
902
+ } finally {
903
+ _ctx = prevCtx;
770
904
  }
771
905
  return routes;
772
906
  }
@@ -966,17 +1100,42 @@ function findTypeInFile(name, file) {
966
1100
  return null;
967
1101
  }
968
1102
  __name(findTypeInFile, "findTypeInFile");
1103
+ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
1104
+ if (moduleSpecifier.startsWith(".")) {
1105
+ const dir = (0, import_node_path8.dirname)(sourceFile.getFilePath());
1106
+ return [
1107
+ (0, import_node_path8.resolve)(dir, `${moduleSpecifier}.ts`),
1108
+ (0, import_node_path8.resolve)(dir, moduleSpecifier, "index.ts")
1109
+ ];
1110
+ }
1111
+ const baseUrl = _projectRoot();
1112
+ const tsconfigPaths = _tsconfigPaths();
1113
+ dbg("resolveModuleSpecifier", moduleSpecifier, "paths:", JSON.stringify(tsconfigPaths), "baseUrl:", baseUrl);
1114
+ if (tsconfigPaths) {
1115
+ for (const [pattern, mappings] of Object.entries(tsconfigPaths)) {
1116
+ const prefix = pattern.replace("*", "");
1117
+ if (moduleSpecifier.startsWith(prefix)) {
1118
+ const rest = moduleSpecifier.slice(prefix.length);
1119
+ const candidates = [];
1120
+ for (const mapping of mappings) {
1121
+ const resolved = (0, import_node_path8.resolve)(baseUrl, mapping.replace("*", rest));
1122
+ candidates.push(`${resolved}.ts`, (0, import_node_path8.resolve)(resolved, "index.ts"));
1123
+ }
1124
+ dbg(" resolved candidates:", candidates);
1125
+ return candidates;
1126
+ }
1127
+ }
1128
+ }
1129
+ return [];
1130
+ }
1131
+ __name(resolveModuleSpecifier, "resolveModuleSpecifier");
969
1132
  function resolveImportedType(name, sourceFile, project) {
970
1133
  for (const importDecl of sourceFile.getImportDeclarations()) {
971
1134
  const namedImport = importDecl.getNamedImports().find((n) => n.getName() === name);
972
1135
  if (!namedImport) continue;
973
1136
  const moduleSpecifier = importDecl.getModuleSpecifierValue();
974
- if (!moduleSpecifier.startsWith(".")) return null;
975
- const dir = (0, import_node_path8.dirname)(sourceFile.getFilePath());
976
- const candidates = [
977
- (0, import_node_path8.resolve)(dir, `${moduleSpecifier}.ts`),
978
- (0, import_node_path8.resolve)(dir, moduleSpecifier, "index.ts")
979
- ];
1137
+ const candidates = resolveModuleSpecifier(moduleSpecifier, sourceFile, project);
1138
+ if (candidates.length === 0) continue;
980
1139
  for (const candidate of candidates) {
981
1140
  let importedFile = project.getSourceFile(candidate);
982
1141
  if (!importedFile) {
@@ -1020,6 +1179,18 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
1020
1179
  }
1021
1180
  return "Array<unknown>";
1022
1181
  }
1182
+ if ([
1183
+ "Record",
1184
+ "Omit",
1185
+ "Pick",
1186
+ "Partial",
1187
+ "Required",
1188
+ "Readonly",
1189
+ "Map",
1190
+ "Set"
1191
+ ].includes(name)) {
1192
+ return typeNode.getText();
1193
+ }
1023
1194
  if (name === "Promise") {
1024
1195
  const typeArgs = typeNode.getTypeArguments();
1025
1196
  const firstTypeArg = typeArgs[0];
@@ -1032,7 +1203,8 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
1032
1203
  if (resolved) {
1033
1204
  return expandTypeDecl(resolved, project, depth - 1);
1034
1205
  }
1035
- return name;
1206
+ dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
1207
+ return "unknown";
1036
1208
  }
1037
1209
  const kind = typeNode.getKind();
1038
1210
  if (kind === import_ts_morph.SyntaxKind.StringKeyword) return "string";
@@ -1194,7 +1366,7 @@ function tryResolveTypeRef(typeNode, sourceFile, project) {
1194
1366
  return null;
1195
1367
  }
1196
1368
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1197
- if (localDecl && localDecl.isExported()) {
1369
+ if (localDecl?.isExported()) {
1198
1370
  return {
1199
1371
  name,
1200
1372
  filePath: sourceFile.getFilePath()
@@ -1256,7 +1428,7 @@ function extractDtoContract(method, sourceFile, project) {
1256
1428
  if (val && import_ts_morph.Node.isIdentifier(val)) {
1257
1429
  const name = val.getText();
1258
1430
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1259
- if (localDecl && localDecl.isExported()) {
1431
+ if (localDecl?.isExported()) {
1260
1432
  responseRef = {
1261
1433
  name,
1262
1434
  filePath: sourceFile.getFilePath()
@@ -1382,6 +1554,11 @@ function extractFromSourceFile(sourceFile, project) {
1382
1554
  path: combined,
1383
1555
  name: routeName,
1384
1556
  params,
1557
+ controllerRef: {
1558
+ className,
1559
+ methodName,
1560
+ filePath: sourceFile.getFilePath()
1561
+ },
1385
1562
  contract: {
1386
1563
  contractSource: {
1387
1564
  query: contractDef.query,
@@ -1416,19 +1593,21 @@ function extractFromSourceFile(sourceFile, project) {
1416
1593
  path: combined,
1417
1594
  name: routeName,
1418
1595
  params,
1419
- // Attach contract if DTO extraction produced useful type info
1420
- ...dtoContract ? {
1421
- contract: {
1422
- contractSource: {
1423
- query: dtoContract.query,
1424
- body: dtoContract.body,
1425
- response: dtoContract.response,
1426
- queryRef: dtoContract.queryRef,
1427
- bodyRef: dtoContract.bodyRef,
1428
- responseRef: dtoContract.responseRef
1429
- }
1596
+ controllerRef: {
1597
+ className,
1598
+ methodName,
1599
+ filePath: sourceFile.getFilePath()
1600
+ },
1601
+ contract: {
1602
+ contractSource: {
1603
+ query: dtoContract?.query ?? null,
1604
+ body: dtoContract?.body ?? null,
1605
+ response: dtoContract?.response ?? "unknown",
1606
+ queryRef: dtoContract?.queryRef,
1607
+ bodyRef: dtoContract?.bodyRef,
1608
+ responseRef: dtoContract?.responseRef
1430
1609
  }
1431
- } : {}
1610
+ }
1432
1611
  });
1433
1612
  }
1434
1613
  }
@@ -1439,6 +1618,7 @@ __name(extractFromSourceFile, "extractFromSourceFile");
1439
1618
 
1440
1619
  // src/watch/lock-file.ts
1441
1620
  var import_promises8 = require("fs/promises");
1621
+ var import_promises9 = require("fs/promises");
1442
1622
  var import_node_path9 = require("path");
1443
1623
  var LOCK_FILE = ".watcher.lock";
1444
1624
  function isProcessAlive(pid) {
@@ -1451,28 +1631,37 @@ function isProcessAlive(pid) {
1451
1631
  }
1452
1632
  __name(isProcessAlive, "isProcessAlive");
1453
1633
  async function acquireLock(outDir) {
1454
- await (0, import_promises8.mkdir)(outDir, {
1634
+ await (0, import_promises9.mkdir)(outDir, {
1455
1635
  recursive: true
1456
1636
  });
1457
1637
  const lockPath = (0, import_node_path9.join)(outDir, LOCK_FILE);
1458
- try {
1459
- const raw = await (0, import_promises8.readFile)(lockPath, "utf8");
1460
- const existing = JSON.parse(raw);
1461
- if (isProcessAlive(existing.pid)) {
1462
- return null;
1463
- }
1464
- } catch {
1465
- }
1466
1638
  const lockData = {
1467
1639
  pid: process.pid,
1468
1640
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
1469
1641
  };
1470
- await (0, import_promises8.writeFile)(lockPath, `${JSON.stringify(lockData, null, 2)}
1642
+ try {
1643
+ const fd = await (0, import_promises8.open)(lockPath, "wx");
1644
+ await fd.writeFile(`${JSON.stringify(lockData, null, 2)}
1471
1645
  `, "utf8");
1646
+ await fd.close();
1647
+ } catch (err) {
1648
+ if (err.code === "EEXIST") {
1649
+ try {
1650
+ const raw = await (0, import_promises9.readFile)(lockPath, "utf8");
1651
+ const existing = JSON.parse(raw);
1652
+ if (isProcessAlive(existing.pid)) return null;
1653
+ await (0, import_promises9.unlink)(lockPath);
1654
+ return acquireLock(outDir);
1655
+ } catch {
1656
+ return null;
1657
+ }
1658
+ }
1659
+ return null;
1660
+ }
1472
1661
  return {
1473
1662
  release: /* @__PURE__ */ __name(async () => {
1474
1663
  try {
1475
- await (0, import_promises8.unlink)(lockPath);
1664
+ await (0, import_promises9.unlink)(lockPath);
1476
1665
  } catch {
1477
1666
  }
1478
1667
  }, "release")
@@ -1491,7 +1680,7 @@ async function watch(config, onChange) {
1491
1680
  if (lock === null) {
1492
1681
  let holderPid = "unknown";
1493
1682
  try {
1494
- const raw = await (0, import_promises9.readFile)((0, import_node_path10.join)(config.codegen.outDir, ".watcher.lock"), "utf8");
1683
+ const raw = await (0, import_promises10.readFile)((0, import_node_path10.join)(config.codegen.outDir, ".watcher.lock"), "utf8");
1495
1684
  const data = JSON.parse(raw);
1496
1685
  if (data.pid !== void 0) holderPid = String(data.pid);
1497
1686
  } catch {
@@ -1532,7 +1721,8 @@ async function watch(config, onChange) {
1532
1721
  pagesDebounceTimer = void 0;
1533
1722
  try {
1534
1723
  await generate(config);
1535
- } catch {
1724
+ } catch (err) {
1725
+ console.error("[nestjs-inertia-codegen] Pages generation failed:", err instanceof Error ? err.message : err);
1536
1726
  }
1537
1727
  onChange?.();
1538
1728
  }, PAGES_DEBOUNCE_MS);
@@ -1570,7 +1760,8 @@ async function watch(config, onChange) {
1570
1760
  if (hasContracts) {
1571
1761
  await emitApi(routes, config.codegen.outDir);
1572
1762
  }
1573
- } catch {
1763
+ } catch (err) {
1764
+ console.error("[nestjs-inertia-codegen] Contracts generation failed:", err instanceof Error ? err.message : err);
1574
1765
  }
1575
1766
  onChange?.();
1576
1767
  }, config.contracts.debounceMs);
@@ -1598,7 +1789,7 @@ async function watch(config, onChange) {
1598
1789
  __name(watch, "watch");
1599
1790
 
1600
1791
  // src/index.ts
1601
- var VERSION = "1.2.0";
1792
+ var VERSION = "1.4.0";
1602
1793
  // Annotate the CommonJS export names for ESM import in node:
1603
1794
  0 && (module.exports = {
1604
1795
  CodegenError,