@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/dist/index.d.cts CHANGED
@@ -78,6 +78,11 @@ interface ContractSource {
78
78
  interface ContractDescriptor {
79
79
  contractSource: ContractSource;
80
80
  }
81
+ interface ControllerRef {
82
+ className: string;
83
+ methodName: string;
84
+ filePath: string;
85
+ }
81
86
  interface RouteDescriptor {
82
87
  method: string;
83
88
  path: string;
@@ -87,6 +92,7 @@ interface RouteDescriptor {
87
92
  source: 'path' | 'query' | 'body' | 'header';
88
93
  }>;
89
94
  contract?: ContractDescriptor;
95
+ controllerRef?: ControllerRef;
90
96
  }
91
97
 
92
98
  /**
@@ -128,6 +134,6 @@ declare function acquireLock(outDir: string): Promise<{
128
134
  release: () => Promise<void>;
129
135
  } | null>;
130
136
 
131
- declare const VERSION = "1.2.0";
137
+ declare const VERSION = "1.3.0";
132
138
 
133
139
  export { CodegenError, ConfigError, type ResolvedConfig, type ScopeConfig, type UserConfig, VERSION, type Watcher, acquireLock, defineConfig, generate, loadConfig, watch };
package/dist/index.d.ts CHANGED
@@ -78,6 +78,11 @@ interface ContractSource {
78
78
  interface ContractDescriptor {
79
79
  contractSource: ContractSource;
80
80
  }
81
+ interface ControllerRef {
82
+ className: string;
83
+ methodName: string;
84
+ filePath: string;
85
+ }
81
86
  interface RouteDescriptor {
82
87
  method: string;
83
88
  path: string;
@@ -87,6 +92,7 @@ interface RouteDescriptor {
87
92
  source: 'path' | 'query' | 'body' | 'header';
88
93
  }>;
89
94
  contract?: ContractDescriptor;
95
+ controllerRef?: ControllerRef;
90
96
  }
91
97
 
92
98
  /**
@@ -128,6 +134,6 @@ declare function acquireLock(outDir: string): Promise<{
128
134
  release: () => Promise<void>;
129
135
  } | null>;
130
136
 
131
- declare const VERSION = "1.2.0";
137
+ declare const VERSION = "1.3.0";
132
138
 
133
139
  export { CodegenError, ConfigError, type ResolvedConfig, type ScopeConfig, type UserConfig, VERSION, type Watcher, acquireLock, defineConfig, generate, loadConfig, watch };
package/dist/index.js CHANGED
@@ -267,7 +267,20 @@ function insertIntoTree(tree, segments, leaf, fullName) {
267
267
  }
268
268
  }
269
269
  __name(insertIntoTree, "insertIntoTree");
270
- function emitRouterTypeBlock(tree, indent) {
270
+ function buildResponseType(c, outDir) {
271
+ if (c.controllerRef) {
272
+ let relPath = relative3(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
273
+ if (!relPath.startsWith(".")) relPath = `./${relPath}`;
274
+ return `Awaited<ReturnType<import('${relPath}').${c.controllerRef.className}['${c.controllerRef.methodName}']>>`;
275
+ }
276
+ const respRef = c.contractSource.responseRef;
277
+ if (respRef) {
278
+ return respRef.isArray ? `Array<${respRef.name}>` : respRef.name;
279
+ }
280
+ return c.contractSource.response;
281
+ }
282
+ __name(buildResponseType, "buildResponseType");
283
+ function emitRouterTypeBlock(tree, indent, outDir) {
271
284
  const pad = " ".repeat(indent);
272
285
  const lines = [];
273
286
  for (const [key, node] of tree) {
@@ -279,14 +292,13 @@ function emitRouterTypeBlock(tree, indent) {
279
292
  const query = queryRef ? queryRef.isArray ? `Array<${queryRef.name}>` : queryRef.name : c.contractSource.query ?? "never";
280
293
  const bodyRef = c.contractSource.bodyRef;
281
294
  const body = method === "GET" ? "never" : bodyRef ? bodyRef.isArray ? `Array<${bodyRef.name}>` : bodyRef.name : c.contractSource.body ?? "never";
282
- const respRef = c.contractSource.responseRef;
283
- const response = respRef ? respRef.isArray ? `Array<${respRef.name}>` : respRef.name : c.contractSource.response;
295
+ const response = buildResponseType(c, outDir);
284
296
  const safeMethod = JSON.stringify(method);
285
297
  const safeUrl = JSON.stringify(c.path);
286
298
  lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; query: ${query}; body: ${body}; response: ${response} };`);
287
299
  } else {
288
300
  lines.push(`${pad}${objKey}: {`);
289
- lines.push(...emitRouterTypeBlock(node.children, indent + 2));
301
+ lines.push(...emitRouterTypeBlock(node.children, indent + 2, outDir));
290
302
  lines.push(`${pad}};`);
291
303
  }
292
304
  }
@@ -308,18 +320,20 @@ function emitApiObjectBlock(tree, indent) {
308
320
  const typeAccess = buildRouterTypeAccess(c.name);
309
321
  lines.push(`${pad}${objKey}: {`);
310
322
  lines.push(`${pad} queryKey: (query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
311
- lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) => ({`);
312
- lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
313
- lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
314
- lines.push(`${pad} }),`);
323
+ lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) =>`);
324
+ lines.push(`${pad} _queryOptions({`);
325
+ lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
326
+ lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
327
+ lines.push(`${pad} }),`);
315
328
  lines.push(`${pad}},`);
316
329
  } else {
317
330
  const typeAccess = buildRouterTypeAccess(c.name);
318
331
  lines.push(`${pad}${objKey}: {`);
319
332
  lines.push(`${pad} queryKey: () => [${flatName}] as const,`);
320
- lines.push(`${pad} mutationOptions: () => ({`);
321
- lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
322
- lines.push(`${pad} }),`);
333
+ lines.push(`${pad} mutationOptions: () =>`);
334
+ lines.push(`${pad} _mutationOptions({`);
335
+ lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
336
+ lines.push(`${pad} }),`);
323
337
  lines.push(`${pad}},`);
324
338
  }
325
339
  } else {
@@ -342,11 +356,15 @@ function buildApiFile(routes, outDir) {
342
356
  for (const r of contracted) {
343
357
  const cs = r.contract?.contractSource;
344
358
  if (!cs) continue;
345
- for (const ref of [
359
+ const refs = r.controllerRef ? [
360
+ cs.queryRef,
361
+ cs.bodyRef
362
+ ] : [
346
363
  cs.queryRef,
347
364
  cs.bodyRef,
348
365
  cs.responseRef
349
- ]) {
366
+ ];
367
+ for (const ref of refs) {
350
368
  if (!ref) continue;
351
369
  let names = importsByFile.get(ref.filePath);
352
370
  if (!names) {
@@ -356,10 +374,18 @@ function buildApiFile(routes, outDir) {
356
374
  names.add(ref.name);
357
375
  }
358
376
  }
377
+ const hasGetRoutes = contracted.some((r) => r.method === "GET");
378
+ const hasMutationRoutes = contracted.some((r) => r.method !== "GET");
359
379
  const lines = [
360
380
  "// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.",
361
381
  ""
362
382
  ];
383
+ const tqImports = [];
384
+ if (hasGetRoutes) tqImports.push("queryOptions as _queryOptions");
385
+ if (hasMutationRoutes) tqImports.push("mutationOptions as _mutationOptions");
386
+ if (tqImports.length > 0) {
387
+ lines.push(`import { ${tqImports.join(", ")} } from '@tanstack/react-query';`);
388
+ }
363
389
  lines.push("import { route } from './routes.js';");
364
390
  lines.push("import { createFetcher } from '@dudousxd/nestjs-inertia-client';");
365
391
  if (importsByFile.size > 0 && outDir) {
@@ -413,13 +439,14 @@ function buildApiFile(routes, outDir) {
413
439
  method: r.method,
414
440
  name,
415
441
  path: r.path,
442
+ controllerRef: r.controllerRef,
416
443
  contractSource: c.contractSource
417
444
  };
418
445
  insertIntoTree(tree, segments, leaf, name);
419
446
  }
420
447
  void detectCollisions;
421
448
  lines.push("export type ApiRouter = {");
422
- lines.push(...emitRouterTypeBlock(tree, 2));
449
+ lines.push(...emitRouterTypeBlock(tree, 2, outDir ?? ""));
423
450
  lines.push("};");
424
451
  lines.push("");
425
452
  lines.push("export const api = {");
@@ -689,9 +716,28 @@ import { join as join9 } from "path";
689
716
  import chokidar from "chokidar";
690
717
 
691
718
  // src/discovery/contracts-fast.ts
719
+ import { readFileSync } from "fs";
692
720
  import { dirname, join as join7, resolve as resolve2 } from "path";
693
721
  import fg2 from "fast-glob";
694
722
  import { Node, Project, SyntaxKind } from "ts-morph";
723
+ var _projectRoot = "";
724
+ var _tsconfigPaths = null;
725
+ var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
726
+ function dbg(...args) {
727
+ if (_debug) console.log("[codegen:debug]", ...args);
728
+ }
729
+ __name(dbg, "dbg");
730
+ function loadTsconfigPaths(tsconfigPath) {
731
+ try {
732
+ const raw = readFileSync(tsconfigPath, "utf8");
733
+ const stripped = raw.replace(/\/\/.*$/gm, "");
734
+ const parsed = JSON.parse(stripped);
735
+ return parsed.compilerOptions?.paths ?? null;
736
+ } catch {
737
+ return null;
738
+ }
739
+ }
740
+ __name(loadTsconfigPaths, "loadTsconfigPaths");
695
741
  async function discoverContractsFast(opts) {
696
742
  const { cwd, glob, tsconfig } = opts;
697
743
  const tsconfigPath = tsconfig ? resolve2(tsconfig) : join7(cwd, "tsconfig.json");
@@ -724,6 +770,8 @@ async function discoverContractsFast(opts) {
724
770
  project.addSourceFileAtPath(f);
725
771
  }
726
772
  const routes = [];
773
+ _projectRoot = cwd;
774
+ _tsconfigPaths = loadTsconfigPaths(tsconfigPath);
727
775
  for (const sourceFile of project.getSourceFiles()) {
728
776
  routes.push(...extractFromSourceFile(sourceFile, project));
729
777
  }
@@ -925,17 +973,41 @@ function findTypeInFile(name, file) {
925
973
  return null;
926
974
  }
927
975
  __name(findTypeInFile, "findTypeInFile");
976
+ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
977
+ if (moduleSpecifier.startsWith(".")) {
978
+ const dir = dirname(sourceFile.getFilePath());
979
+ return [
980
+ resolve2(dir, `${moduleSpecifier}.ts`),
981
+ resolve2(dir, moduleSpecifier, "index.ts")
982
+ ];
983
+ }
984
+ const baseUrl = _projectRoot;
985
+ dbg("resolveModuleSpecifier", moduleSpecifier, "paths:", JSON.stringify(_tsconfigPaths), "baseUrl:", baseUrl);
986
+ if (_tsconfigPaths) {
987
+ for (const [pattern, mappings] of Object.entries(_tsconfigPaths)) {
988
+ const prefix = pattern.replace("*", "");
989
+ if (moduleSpecifier.startsWith(prefix)) {
990
+ const rest = moduleSpecifier.slice(prefix.length);
991
+ const candidates = [];
992
+ for (const mapping of mappings) {
993
+ const resolved = resolve2(baseUrl, mapping.replace("*", rest));
994
+ candidates.push(`${resolved}.ts`, resolve2(resolved, "index.ts"));
995
+ }
996
+ dbg(" resolved candidates:", candidates);
997
+ return candidates;
998
+ }
999
+ }
1000
+ }
1001
+ return [];
1002
+ }
1003
+ __name(resolveModuleSpecifier, "resolveModuleSpecifier");
928
1004
  function resolveImportedType(name, sourceFile, project) {
929
1005
  for (const importDecl of sourceFile.getImportDeclarations()) {
930
1006
  const namedImport = importDecl.getNamedImports().find((n) => n.getName() === name);
931
1007
  if (!namedImport) continue;
932
1008
  const moduleSpecifier = importDecl.getModuleSpecifierValue();
933
- if (!moduleSpecifier.startsWith(".")) return null;
934
- const dir = dirname(sourceFile.getFilePath());
935
- const candidates = [
936
- resolve2(dir, `${moduleSpecifier}.ts`),
937
- resolve2(dir, moduleSpecifier, "index.ts")
938
- ];
1009
+ const candidates = resolveModuleSpecifier(moduleSpecifier, sourceFile, project);
1010
+ if (candidates.length === 0) continue;
939
1011
  for (const candidate of candidates) {
940
1012
  let importedFile = project.getSourceFile(candidate);
941
1013
  if (!importedFile) {
@@ -979,6 +1051,18 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
979
1051
  }
980
1052
  return "Array<unknown>";
981
1053
  }
1054
+ if ([
1055
+ "Record",
1056
+ "Omit",
1057
+ "Pick",
1058
+ "Partial",
1059
+ "Required",
1060
+ "Readonly",
1061
+ "Map",
1062
+ "Set"
1063
+ ].includes(name)) {
1064
+ return typeNode.getText();
1065
+ }
982
1066
  if (name === "Promise") {
983
1067
  const typeArgs = typeNode.getTypeArguments();
984
1068
  const firstTypeArg = typeArgs[0];
@@ -991,7 +1075,8 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
991
1075
  if (resolved) {
992
1076
  return expandTypeDecl(resolved, project, depth - 1);
993
1077
  }
994
- return name;
1078
+ dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
1079
+ return "unknown";
995
1080
  }
996
1081
  const kind = typeNode.getKind();
997
1082
  if (kind === SyntaxKind.StringKeyword) return "string";
@@ -1153,7 +1238,7 @@ function tryResolveTypeRef(typeNode, sourceFile, project) {
1153
1238
  return null;
1154
1239
  }
1155
1240
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1156
- if (localDecl && localDecl.isExported()) {
1241
+ if (localDecl?.isExported()) {
1157
1242
  return {
1158
1243
  name,
1159
1244
  filePath: sourceFile.getFilePath()
@@ -1215,7 +1300,7 @@ function extractDtoContract(method, sourceFile, project) {
1215
1300
  if (val && Node.isIdentifier(val)) {
1216
1301
  const name = val.getText();
1217
1302
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1218
- if (localDecl && localDecl.isExported()) {
1303
+ if (localDecl?.isExported()) {
1219
1304
  responseRef = {
1220
1305
  name,
1221
1306
  filePath: sourceFile.getFilePath()
@@ -1341,6 +1426,11 @@ function extractFromSourceFile(sourceFile, project) {
1341
1426
  path: combined,
1342
1427
  name: routeName,
1343
1428
  params,
1429
+ controllerRef: {
1430
+ className,
1431
+ methodName,
1432
+ filePath: sourceFile.getFilePath()
1433
+ },
1344
1434
  contract: {
1345
1435
  contractSource: {
1346
1436
  query: contractDef.query,
@@ -1375,7 +1465,11 @@ function extractFromSourceFile(sourceFile, project) {
1375
1465
  path: combined,
1376
1466
  name: routeName,
1377
1467
  params,
1378
- // Attach contract if DTO extraction produced useful type info
1468
+ controllerRef: {
1469
+ className,
1470
+ methodName,
1471
+ filePath: sourceFile.getFilePath()
1472
+ },
1379
1473
  ...dtoContract ? {
1380
1474
  contract: {
1381
1475
  contractSource: {
@@ -1557,7 +1651,7 @@ async function watch(config, onChange) {
1557
1651
  __name(watch, "watch");
1558
1652
 
1559
1653
  // src/index.ts
1560
- var VERSION = "1.2.0";
1654
+ var VERSION = "1.3.0";
1561
1655
  export {
1562
1656
  CodegenError,
1563
1657
  ConfigError,