@dudousxd/nestjs-inertia-codegen 1.3.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,6 +298,16 @@ function insertIntoTree(tree, segments, leaf, fullName) {
308
298
  }
309
299
  }
310
300
  __name(insertIntoTree, "insertIntoTree");
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
311
  function buildResponseType(c, outDir) {
312
312
  if (c.controllerRef) {
313
313
  let relPath = (0, import_node_path3.relative)(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
@@ -334,9 +334,10 @@ function emitRouterTypeBlock(tree, indent, outDir) {
334
334
  const bodyRef = c.contractSource.bodyRef;
335
335
  const body = method === "GET" ? "never" : bodyRef ? bodyRef.isArray ? `Array<${bodyRef.name}>` : bodyRef.name : c.contractSource.body ?? "never";
336
336
  const response = buildResponseType(c, outDir);
337
+ const params = buildParamsType(c.params);
337
338
  const safeMethod = JSON.stringify(method);
338
339
  const safeUrl = JSON.stringify(c.path);
339
- 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} };`);
340
341
  } else {
341
342
  lines.push(`${pad}${objKey}: {`);
342
343
  lines.push(...emitRouterTypeBlock(node.children, indent + 2, outDir));
@@ -359,21 +360,60 @@ function emitApiObjectBlock(tree, indent) {
359
360
  const fetcherMethod = method.toLowerCase();
360
361
  if (method === "GET") {
361
362
  const typeAccess = buildRouterTypeAccess(c.name);
363
+ const withParams = hasPathParams(c.params);
362
364
  lines.push(`${pad}${objKey}: {`);
363
- lines.push(`${pad} queryKey: (query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
364
- lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) =>`);
365
- lines.push(`${pad} _queryOptions({`);
366
- lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
367
- lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
368
- 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
+ }
369
404
  lines.push(`${pad}},`);
370
405
  } else {
371
406
  const typeAccess = buildRouterTypeAccess(c.name);
407
+ const withParams = hasPathParams(c.params);
372
408
  lines.push(`${pad}${objKey}: {`);
373
409
  lines.push(`${pad} queryKey: () => [${flatName}] as const,`);
374
410
  lines.push(`${pad} mutationOptions: () =>`);
375
411
  lines.push(`${pad} _mutationOptions({`);
376
- lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
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
+ }
377
417
  lines.push(`${pad} }),`);
378
418
  lines.push(`${pad}},`);
379
419
  }
@@ -480,12 +520,12 @@ function buildApiFile(routes, outDir) {
480
520
  method: r.method,
481
521
  name,
482
522
  path: r.path,
523
+ params: r.params,
483
524
  controllerRef: r.controllerRef,
484
525
  contractSource: c.contractSource
485
526
  };
486
527
  insertIntoTree(tree, segments, leaf, name);
487
528
  }
488
- void detectCollisions;
489
529
  lines.push("export type ApiRouter = {");
490
530
  lines.push(...emitRouterTypeBlock(tree, 2, outDir ?? ""));
491
531
  lines.push("};");
@@ -584,7 +624,8 @@ __name(emitIndex, "emitIndex");
584
624
  // src/emit/emit-pages.ts
585
625
  var import_promises6 = require("fs/promises");
586
626
  var import_node_path6 = require("path");
587
- async function emitPages(pages, outDir) {
627
+ async function emitPages(pages, outDir, options = {}) {
628
+ const propsExport = options.propsExport ?? "ComponentProps";
588
629
  await (0, import_promises6.mkdir)(outDir, {
589
630
  recursive: true
590
631
  });
@@ -593,14 +634,40 @@ async function emitPages(pages, outDir) {
593
634
  const key = needsQuotes(p.name) ? JSON.stringify(p.name) : p.name;
594
635
  return ` ${key}: ${propType};`;
595
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";
596
644
  const content = `// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.
597
645
  export interface InertiaPages {
598
646
  ${body}
599
647
  }
648
+
649
+ export type InertiaPageName = ${pageNameUnion};
650
+ ` + propsHelper + `
651
+ declare module '@dudousxd/nestjs-inertia' {
652
+ interface InertiaPages {
653
+ ${augBody}
654
+ }
655
+ }
600
656
  `;
601
657
  await (0, import_promises6.writeFile)((0, import_node_path6.join)(outDir, "pages.d.ts"), content, "utf8");
602
658
  }
603
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");
604
671
  function needsQuotes(name) {
605
672
  return !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
606
673
  }
@@ -737,7 +804,9 @@ async function generate(config, routes = []) {
737
804
  propsExport: config.pages.propsExport,
738
805
  componentNameStrategy: config.pages.componentNameStrategy
739
806
  });
740
- await emitPages(pages, config.codegen.outDir);
807
+ await emitPages(pages, config.codegen.outDir, {
808
+ propsExport: config.pages.propsExport
809
+ });
741
810
  await emitCache(pages, config.codegen.outDir);
742
811
  const hasRoutes = routes.length > 0;
743
812
  const hasContracts = routes.some((r) => r.contract);
@@ -752,7 +821,7 @@ async function generate(config, routes = []) {
752
821
  __name(generate, "generate");
753
822
 
754
823
  // src/watch/watcher.ts
755
- var import_promises9 = require("fs/promises");
824
+ var import_promises10 = require("fs/promises");
756
825
  var import_node_path10 = require("path");
757
826
  var import_chokidar = __toESM(require("chokidar"), 1);
758
827
 
@@ -761,8 +830,18 @@ var import_node_fs = require("fs");
761
830
  var import_node_path8 = require("path");
762
831
  var import_fast_glob2 = __toESM(require("fast-glob"), 1);
763
832
  var import_ts_morph = require("ts-morph");
764
- var _projectRoot = "";
765
- var _tsconfigPaths = null;
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");
766
845
  var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
767
846
  function dbg(...args) {
768
847
  if (_debug) console.log("[codegen:debug]", ...args);
@@ -811,10 +890,17 @@ async function discoverContractsFast(opts) {
811
890
  project.addSourceFileAtPath(f);
812
891
  }
813
892
  const routes = [];
814
- _projectRoot = cwd;
815
- _tsconfigPaths = loadTsconfigPaths(tsconfigPath);
816
- for (const sourceFile of project.getSourceFiles()) {
817
- 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;
818
904
  }
819
905
  return routes;
820
906
  }
@@ -1022,10 +1108,11 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
1022
1108
  (0, import_node_path8.resolve)(dir, moduleSpecifier, "index.ts")
1023
1109
  ];
1024
1110
  }
1025
- const baseUrl = _projectRoot;
1026
- dbg("resolveModuleSpecifier", moduleSpecifier, "paths:", JSON.stringify(_tsconfigPaths), "baseUrl:", baseUrl);
1027
- if (_tsconfigPaths) {
1028
- for (const [pattern, mappings] of Object.entries(_tsconfigPaths)) {
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)) {
1029
1116
  const prefix = pattern.replace("*", "");
1030
1117
  if (moduleSpecifier.startsWith(prefix)) {
1031
1118
  const rest = moduleSpecifier.slice(prefix.length);
@@ -1511,18 +1598,16 @@ function extractFromSourceFile(sourceFile, project) {
1511
1598
  methodName,
1512
1599
  filePath: sourceFile.getFilePath()
1513
1600
  },
1514
- ...dtoContract ? {
1515
- contract: {
1516
- contractSource: {
1517
- query: dtoContract.query,
1518
- body: dtoContract.body,
1519
- response: dtoContract.response,
1520
- queryRef: dtoContract.queryRef,
1521
- bodyRef: dtoContract.bodyRef,
1522
- responseRef: dtoContract.responseRef
1523
- }
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
1524
1609
  }
1525
- } : {}
1610
+ }
1526
1611
  });
1527
1612
  }
1528
1613
  }
@@ -1533,6 +1618,7 @@ __name(extractFromSourceFile, "extractFromSourceFile");
1533
1618
 
1534
1619
  // src/watch/lock-file.ts
1535
1620
  var import_promises8 = require("fs/promises");
1621
+ var import_promises9 = require("fs/promises");
1536
1622
  var import_node_path9 = require("path");
1537
1623
  var LOCK_FILE = ".watcher.lock";
1538
1624
  function isProcessAlive(pid) {
@@ -1545,28 +1631,37 @@ function isProcessAlive(pid) {
1545
1631
  }
1546
1632
  __name(isProcessAlive, "isProcessAlive");
1547
1633
  async function acquireLock(outDir) {
1548
- await (0, import_promises8.mkdir)(outDir, {
1634
+ await (0, import_promises9.mkdir)(outDir, {
1549
1635
  recursive: true
1550
1636
  });
1551
1637
  const lockPath = (0, import_node_path9.join)(outDir, LOCK_FILE);
1552
- try {
1553
- const raw = await (0, import_promises8.readFile)(lockPath, "utf8");
1554
- const existing = JSON.parse(raw);
1555
- if (isProcessAlive(existing.pid)) {
1556
- return null;
1557
- }
1558
- } catch {
1559
- }
1560
1638
  const lockData = {
1561
1639
  pid: process.pid,
1562
1640
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
1563
1641
  };
1564
- 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)}
1565
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
+ }
1566
1661
  return {
1567
1662
  release: /* @__PURE__ */ __name(async () => {
1568
1663
  try {
1569
- await (0, import_promises8.unlink)(lockPath);
1664
+ await (0, import_promises9.unlink)(lockPath);
1570
1665
  } catch {
1571
1666
  }
1572
1667
  }, "release")
@@ -1585,7 +1680,7 @@ async function watch(config, onChange) {
1585
1680
  if (lock === null) {
1586
1681
  let holderPid = "unknown";
1587
1682
  try {
1588
- 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");
1589
1684
  const data = JSON.parse(raw);
1590
1685
  if (data.pid !== void 0) holderPid = String(data.pid);
1591
1686
  } catch {
@@ -1626,7 +1721,8 @@ async function watch(config, onChange) {
1626
1721
  pagesDebounceTimer = void 0;
1627
1722
  try {
1628
1723
  await generate(config);
1629
- } catch {
1724
+ } catch (err) {
1725
+ console.error("[nestjs-inertia-codegen] Pages generation failed:", err instanceof Error ? err.message : err);
1630
1726
  }
1631
1727
  onChange?.();
1632
1728
  }, PAGES_DEBOUNCE_MS);
@@ -1664,7 +1760,8 @@ async function watch(config, onChange) {
1664
1760
  if (hasContracts) {
1665
1761
  await emitApi(routes, config.codegen.outDir);
1666
1762
  }
1667
- } catch {
1763
+ } catch (err) {
1764
+ console.error("[nestjs-inertia-codegen] Contracts generation failed:", err instanceof Error ? err.message : err);
1668
1765
  }
1669
1766
  onChange?.();
1670
1767
  }, config.contracts.debounceMs);
@@ -1692,7 +1789,7 @@ async function watch(config, onChange) {
1692
1789
  __name(watch, "watch");
1693
1790
 
1694
1791
  // src/index.ts
1695
- var VERSION = "1.3.0";
1792
+ var VERSION = "1.4.0";
1696
1793
  // Annotate the CommonJS export names for ESM import in node:
1697
1794
  0 && (module.exports = {
1698
1795
  CodegenError,