@dudousxd/nestjs-inertia-codegen 1.2.0 → 1.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog — @dudousxd/nestjs-inertia-codegen
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - feat(codegen): ReturnType<import(...)> for response types, queryKey helper, TanStack helpers, type ref imports, path alias resolution, debug mode
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies []:
12
+ - @dudousxd/nestjs-inertia@2.0.0
13
+
3
14
  ## 3.0.0
4
15
 
5
16
  ### Minor Changes
package/dist/cli/main.cjs CHANGED
@@ -287,7 +287,20 @@ function insertIntoTree(tree, segments, leaf, fullName) {
287
287
  }
288
288
  }
289
289
  __name(insertIntoTree, "insertIntoTree");
290
- function emitRouterTypeBlock(tree, indent) {
290
+ function buildResponseType(c, outDir) {
291
+ if (c.controllerRef) {
292
+ let relPath = (0, import_node_path3.relative)(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
293
+ if (!relPath.startsWith(".")) relPath = `./${relPath}`;
294
+ return `Awaited<ReturnType<import('${relPath}').${c.controllerRef.className}['${c.controllerRef.methodName}']>>`;
295
+ }
296
+ const respRef = c.contractSource.responseRef;
297
+ if (respRef) {
298
+ return respRef.isArray ? `Array<${respRef.name}>` : respRef.name;
299
+ }
300
+ return c.contractSource.response;
301
+ }
302
+ __name(buildResponseType, "buildResponseType");
303
+ function emitRouterTypeBlock(tree, indent, outDir) {
291
304
  const pad = " ".repeat(indent);
292
305
  const lines = [];
293
306
  for (const [key, node] of tree) {
@@ -299,14 +312,13 @@ function emitRouterTypeBlock(tree, indent) {
299
312
  const query = queryRef ? queryRef.isArray ? `Array<${queryRef.name}>` : queryRef.name : c.contractSource.query ?? "never";
300
313
  const bodyRef = c.contractSource.bodyRef;
301
314
  const body = method === "GET" ? "never" : bodyRef ? bodyRef.isArray ? `Array<${bodyRef.name}>` : bodyRef.name : c.contractSource.body ?? "never";
302
- const respRef = c.contractSource.responseRef;
303
- const response = respRef ? respRef.isArray ? `Array<${respRef.name}>` : respRef.name : c.contractSource.response;
315
+ const response = buildResponseType(c, outDir);
304
316
  const safeMethod = JSON.stringify(method);
305
317
  const safeUrl = JSON.stringify(c.path);
306
318
  lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; query: ${query}; body: ${body}; response: ${response} };`);
307
319
  } else {
308
320
  lines.push(`${pad}${objKey}: {`);
309
- lines.push(...emitRouterTypeBlock(node.children, indent + 2));
321
+ lines.push(...emitRouterTypeBlock(node.children, indent + 2, outDir));
310
322
  lines.push(`${pad}};`);
311
323
  }
312
324
  }
@@ -328,18 +340,20 @@ function emitApiObjectBlock(tree, indent) {
328
340
  const typeAccess = buildRouterTypeAccess(c.name);
329
341
  lines.push(`${pad}${objKey}: {`);
330
342
  lines.push(`${pad} queryKey: (query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
331
- lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) => ({`);
332
- lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
333
- lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
334
- lines.push(`${pad} }),`);
343
+ lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) =>`);
344
+ lines.push(`${pad} _queryOptions({`);
345
+ lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
346
+ lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
347
+ lines.push(`${pad} }),`);
335
348
  lines.push(`${pad}},`);
336
349
  } else {
337
350
  const typeAccess = buildRouterTypeAccess(c.name);
338
351
  lines.push(`${pad}${objKey}: {`);
339
352
  lines.push(`${pad} queryKey: () => [${flatName}] as const,`);
340
- lines.push(`${pad} mutationOptions: () => ({`);
341
- lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
342
- lines.push(`${pad} }),`);
353
+ lines.push(`${pad} mutationOptions: () =>`);
354
+ lines.push(`${pad} _mutationOptions({`);
355
+ lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
356
+ lines.push(`${pad} }),`);
343
357
  lines.push(`${pad}},`);
344
358
  }
345
359
  } else {
@@ -362,11 +376,15 @@ function buildApiFile(routes, outDir) {
362
376
  for (const r of contracted) {
363
377
  const cs = r.contract?.contractSource;
364
378
  if (!cs) continue;
365
- for (const ref of [
379
+ const refs = r.controllerRef ? [
380
+ cs.queryRef,
381
+ cs.bodyRef
382
+ ] : [
366
383
  cs.queryRef,
367
384
  cs.bodyRef,
368
385
  cs.responseRef
369
- ]) {
386
+ ];
387
+ for (const ref of refs) {
370
388
  if (!ref) continue;
371
389
  let names = importsByFile.get(ref.filePath);
372
390
  if (!names) {
@@ -376,10 +394,18 @@ function buildApiFile(routes, outDir) {
376
394
  names.add(ref.name);
377
395
  }
378
396
  }
397
+ const hasGetRoutes = contracted.some((r) => r.method === "GET");
398
+ const hasMutationRoutes = contracted.some((r) => r.method !== "GET");
379
399
  const lines = [
380
400
  "// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.",
381
401
  ""
382
402
  ];
403
+ const tqImports = [];
404
+ if (hasGetRoutes) tqImports.push("queryOptions as _queryOptions");
405
+ if (hasMutationRoutes) tqImports.push("mutationOptions as _mutationOptions");
406
+ if (tqImports.length > 0) {
407
+ lines.push(`import { ${tqImports.join(", ")} } from '@tanstack/react-query';`);
408
+ }
383
409
  lines.push("import { route } from './routes.js';");
384
410
  lines.push("import { createFetcher } from '@dudousxd/nestjs-inertia-client';");
385
411
  if (importsByFile.size > 0 && outDir) {
@@ -433,13 +459,14 @@ function buildApiFile(routes, outDir) {
433
459
  method: r.method,
434
460
  name,
435
461
  path: r.path,
462
+ controllerRef: r.controllerRef,
436
463
  contractSource: c.contractSource
437
464
  };
438
465
  insertIntoTree(tree, segments, leaf, name);
439
466
  }
440
467
  void detectCollisions;
441
468
  lines.push("export type ApiRouter = {");
442
- lines.push(...emitRouterTypeBlock(tree, 2));
469
+ lines.push(...emitRouterTypeBlock(tree, 2, outDir ?? ""));
443
470
  lines.push("};");
444
471
  lines.push("");
445
472
  lines.push("export const api = {");
@@ -709,9 +736,28 @@ var import_node_path10 = require("path");
709
736
  var import_chokidar = __toESM(require("chokidar"), 1);
710
737
 
711
738
  // src/discovery/contracts-fast.ts
739
+ var import_node_fs = require("fs");
712
740
  var import_node_path8 = require("path");
713
741
  var import_fast_glob2 = __toESM(require("fast-glob"), 1);
714
742
  var import_ts_morph = require("ts-morph");
743
+ var _projectRoot = "";
744
+ var _tsconfigPaths = null;
745
+ var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
746
+ function dbg(...args) {
747
+ if (_debug) console.log("[codegen:debug]", ...args);
748
+ }
749
+ __name(dbg, "dbg");
750
+ function loadTsconfigPaths(tsconfigPath) {
751
+ try {
752
+ const raw = (0, import_node_fs.readFileSync)(tsconfigPath, "utf8");
753
+ const stripped = raw.replace(/\/\/.*$/gm, "");
754
+ const parsed = JSON.parse(stripped);
755
+ return parsed.compilerOptions?.paths ?? null;
756
+ } catch {
757
+ return null;
758
+ }
759
+ }
760
+ __name(loadTsconfigPaths, "loadTsconfigPaths");
715
761
  async function discoverContractsFast(opts) {
716
762
  const { cwd, glob, tsconfig } = opts;
717
763
  const tsconfigPath = tsconfig ? (0, import_node_path8.resolve)(tsconfig) : (0, import_node_path8.join)(cwd, "tsconfig.json");
@@ -744,6 +790,8 @@ async function discoverContractsFast(opts) {
744
790
  project.addSourceFileAtPath(f);
745
791
  }
746
792
  const routes = [];
793
+ _projectRoot = cwd;
794
+ _tsconfigPaths = loadTsconfigPaths(tsconfigPath);
747
795
  for (const sourceFile of project.getSourceFiles()) {
748
796
  routes.push(...extractFromSourceFile(sourceFile, project));
749
797
  }
@@ -945,17 +993,41 @@ function findTypeInFile(name, file) {
945
993
  return null;
946
994
  }
947
995
  __name(findTypeInFile, "findTypeInFile");
996
+ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
997
+ if (moduleSpecifier.startsWith(".")) {
998
+ const dir = (0, import_node_path8.dirname)(sourceFile.getFilePath());
999
+ return [
1000
+ (0, import_node_path8.resolve)(dir, `${moduleSpecifier}.ts`),
1001
+ (0, import_node_path8.resolve)(dir, moduleSpecifier, "index.ts")
1002
+ ];
1003
+ }
1004
+ const baseUrl = _projectRoot;
1005
+ dbg("resolveModuleSpecifier", moduleSpecifier, "paths:", JSON.stringify(_tsconfigPaths), "baseUrl:", baseUrl);
1006
+ if (_tsconfigPaths) {
1007
+ for (const [pattern, mappings] of Object.entries(_tsconfigPaths)) {
1008
+ const prefix = pattern.replace("*", "");
1009
+ if (moduleSpecifier.startsWith(prefix)) {
1010
+ const rest = moduleSpecifier.slice(prefix.length);
1011
+ const candidates = [];
1012
+ for (const mapping of mappings) {
1013
+ const resolved = (0, import_node_path8.resolve)(baseUrl, mapping.replace("*", rest));
1014
+ candidates.push(`${resolved}.ts`, (0, import_node_path8.resolve)(resolved, "index.ts"));
1015
+ }
1016
+ dbg(" resolved candidates:", candidates);
1017
+ return candidates;
1018
+ }
1019
+ }
1020
+ }
1021
+ return [];
1022
+ }
1023
+ __name(resolveModuleSpecifier, "resolveModuleSpecifier");
948
1024
  function resolveImportedType(name, sourceFile, project) {
949
1025
  for (const importDecl of sourceFile.getImportDeclarations()) {
950
1026
  const namedImport = importDecl.getNamedImports().find((n) => n.getName() === name);
951
1027
  if (!namedImport) continue;
952
1028
  const moduleSpecifier = importDecl.getModuleSpecifierValue();
953
- if (!moduleSpecifier.startsWith(".")) return null;
954
- const dir = (0, import_node_path8.dirname)(sourceFile.getFilePath());
955
- const candidates = [
956
- (0, import_node_path8.resolve)(dir, `${moduleSpecifier}.ts`),
957
- (0, import_node_path8.resolve)(dir, moduleSpecifier, "index.ts")
958
- ];
1029
+ const candidates = resolveModuleSpecifier(moduleSpecifier, sourceFile, project);
1030
+ if (candidates.length === 0) continue;
959
1031
  for (const candidate of candidates) {
960
1032
  let importedFile = project.getSourceFile(candidate);
961
1033
  if (!importedFile) {
@@ -999,6 +1071,18 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
999
1071
  }
1000
1072
  return "Array<unknown>";
1001
1073
  }
1074
+ if ([
1075
+ "Record",
1076
+ "Omit",
1077
+ "Pick",
1078
+ "Partial",
1079
+ "Required",
1080
+ "Readonly",
1081
+ "Map",
1082
+ "Set"
1083
+ ].includes(name)) {
1084
+ return typeNode.getText();
1085
+ }
1002
1086
  if (name === "Promise") {
1003
1087
  const typeArgs = typeNode.getTypeArguments();
1004
1088
  const firstTypeArg = typeArgs[0];
@@ -1011,7 +1095,8 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
1011
1095
  if (resolved) {
1012
1096
  return expandTypeDecl(resolved, project, depth - 1);
1013
1097
  }
1014
- return name;
1098
+ dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
1099
+ return "unknown";
1015
1100
  }
1016
1101
  const kind = typeNode.getKind();
1017
1102
  if (kind === import_ts_morph.SyntaxKind.StringKeyword) return "string";
@@ -1173,7 +1258,7 @@ function tryResolveTypeRef(typeNode, sourceFile, project) {
1173
1258
  return null;
1174
1259
  }
1175
1260
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1176
- if (localDecl && localDecl.isExported()) {
1261
+ if (localDecl?.isExported()) {
1177
1262
  return {
1178
1263
  name,
1179
1264
  filePath: sourceFile.getFilePath()
@@ -1235,7 +1320,7 @@ function extractDtoContract(method, sourceFile, project) {
1235
1320
  if (val && import_ts_morph.Node.isIdentifier(val)) {
1236
1321
  const name = val.getText();
1237
1322
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1238
- if (localDecl && localDecl.isExported()) {
1323
+ if (localDecl?.isExported()) {
1239
1324
  responseRef = {
1240
1325
  name,
1241
1326
  filePath: sourceFile.getFilePath()
@@ -1361,6 +1446,11 @@ function extractFromSourceFile(sourceFile, project) {
1361
1446
  path: combined,
1362
1447
  name: routeName,
1363
1448
  params,
1449
+ controllerRef: {
1450
+ className,
1451
+ methodName,
1452
+ filePath: sourceFile.getFilePath()
1453
+ },
1364
1454
  contract: {
1365
1455
  contractSource: {
1366
1456
  query: contractDef.query,
@@ -1395,7 +1485,11 @@ function extractFromSourceFile(sourceFile, project) {
1395
1485
  path: combined,
1396
1486
  name: routeName,
1397
1487
  params,
1398
- // Attach contract if DTO extraction produced useful type info
1488
+ controllerRef: {
1489
+ className,
1490
+ methodName,
1491
+ filePath: sourceFile.getFilePath()
1492
+ },
1399
1493
  ...dtoContract ? {
1400
1494
  contract: {
1401
1495
  contractSource: {
@@ -1577,7 +1671,7 @@ async function watch(config, onChange) {
1577
1671
  __name(watch, "watch");
1578
1672
 
1579
1673
  // src/index.ts
1580
- var VERSION = "1.2.0";
1674
+ var VERSION = "1.3.0";
1581
1675
 
1582
1676
  // src/cli/codegen.ts
1583
1677
  async function runCodegen(opts = {}) {
@@ -1609,7 +1703,7 @@ __name(runCodegen, "runCodegen");
1609
1703
 
1610
1704
  // src/cli/init.ts
1611
1705
  var import_node_child_process = require("child_process");
1612
- var import_node_fs = require("fs");
1706
+ var import_node_fs2 = require("fs");
1613
1707
  var import_promises10 = require("fs/promises");
1614
1708
  var import_node_path11 = require("path");
1615
1709
  var import_node_readline = require("readline");
@@ -1833,7 +1927,7 @@ __name(findAfterLastImport, "findAfterLastImport");
1833
1927
  function patchAppModule(filePath, rootView) {
1834
1928
  let content;
1835
1929
  try {
1836
- content = (0, import_node_fs.readFileSync)(filePath, "utf8");
1930
+ content = (0, import_node_fs2.readFileSync)(filePath, "utf8");
1837
1931
  } catch {
1838
1932
  return "skipped";
1839
1933
  }
@@ -1871,14 +1965,14 @@ ${indent}HomeController,${content.slice(bracketPos)}`;
1871
1965
  }
1872
1966
  }
1873
1967
  if (!changed) return "already";
1874
- (0, import_node_fs.writeFileSync)(filePath, content, "utf8");
1968
+ (0, import_node_fs2.writeFileSync)(filePath, content, "utf8");
1875
1969
  return "patched";
1876
1970
  }
1877
1971
  __name(patchAppModule, "patchAppModule");
1878
1972
  function patchMainTs(filePath) {
1879
1973
  let content;
1880
1974
  try {
1881
- content = (0, import_node_fs.readFileSync)(filePath, "utf8");
1975
+ content = (0, import_node_fs2.readFileSync)(filePath, "utf8");
1882
1976
  } catch {
1883
1977
  return "skipped";
1884
1978
  }
@@ -1902,7 +1996,7 @@ ${content.slice(insertAt)}`;
1902
1996
  });`;
1903
1997
  content = `${content.slice(0, insertAfterPos)}
1904
1998
  ${viteSetup}${content.slice(insertAfterPos)}`;
1905
- (0, import_node_fs.writeFileSync)(filePath, content, "utf8");
1999
+ (0, import_node_fs2.writeFileSync)(filePath, content, "utf8");
1906
2000
  return "patched";
1907
2001
  }
1908
2002
  __name(patchMainTs, "patchMainTs");
@@ -1951,11 +2045,19 @@ function htmlShellTemplate(framework, _engine) {
1951
2045
  __name(htmlShellTemplate, "htmlShellTemplate");
1952
2046
  function viteConfigTemplate(framework) {
1953
2047
  const pluginOption = `{ ${framework}: true }`;
1954
- return `import { defineConfig } from 'vite';
2048
+ return `import { resolve } from 'node:path';
2049
+ import { defineConfig } from 'vite';
1955
2050
  import nestInertia from '@dudousxd/nestjs-inertia-vite/plugin';
1956
2051
 
1957
2052
  export default defineConfig({
1958
2053
  plugins: [nestInertia(${pluginOption})],
2054
+ resolve: {
2055
+ alias: {
2056
+ '@': resolve(__dirname, 'src'),
2057
+ '~': resolve(__dirname, 'inertia'),
2058
+ '~codegen': resolve(__dirname, '.nestjs-inertia'),
2059
+ },
2060
+ },
1959
2061
  });
1960
2062
  `;
1961
2063
  }