@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.cjs CHANGED
@@ -308,7 +308,20 @@ function insertIntoTree(tree, segments, leaf, fullName) {
308
308
  }
309
309
  }
310
310
  __name(insertIntoTree, "insertIntoTree");
311
- function emitRouterTypeBlock(tree, indent) {
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,13 @@ 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);
325
337
  const safeMethod = JSON.stringify(method);
326
338
  const safeUrl = JSON.stringify(c.path);
327
339
  lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; query: ${query}; body: ${body}; response: ${response} };`);
328
340
  } else {
329
341
  lines.push(`${pad}${objKey}: {`);
330
- lines.push(...emitRouterTypeBlock(node.children, indent + 2));
342
+ lines.push(...emitRouterTypeBlock(node.children, indent + 2, outDir));
331
343
  lines.push(`${pad}};`);
332
344
  }
333
345
  }
@@ -349,18 +361,20 @@ function emitApiObjectBlock(tree, indent) {
349
361
  const typeAccess = buildRouterTypeAccess(c.name);
350
362
  lines.push(`${pad}${objKey}: {`);
351
363
  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} }),`);
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} }),`);
356
369
  lines.push(`${pad}},`);
357
370
  } else {
358
371
  const typeAccess = buildRouterTypeAccess(c.name);
359
372
  lines.push(`${pad}${objKey}: {`);
360
373
  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} }),`);
374
+ lines.push(`${pad} mutationOptions: () =>`);
375
+ lines.push(`${pad} _mutationOptions({`);
376
+ lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
377
+ lines.push(`${pad} }),`);
364
378
  lines.push(`${pad}},`);
365
379
  }
366
380
  } else {
@@ -383,11 +397,15 @@ function buildApiFile(routes, outDir) {
383
397
  for (const r of contracted) {
384
398
  const cs = r.contract?.contractSource;
385
399
  if (!cs) continue;
386
- for (const ref of [
400
+ const refs = r.controllerRef ? [
401
+ cs.queryRef,
402
+ cs.bodyRef
403
+ ] : [
387
404
  cs.queryRef,
388
405
  cs.bodyRef,
389
406
  cs.responseRef
390
- ]) {
407
+ ];
408
+ for (const ref of refs) {
391
409
  if (!ref) continue;
392
410
  let names = importsByFile.get(ref.filePath);
393
411
  if (!names) {
@@ -397,10 +415,18 @@ function buildApiFile(routes, outDir) {
397
415
  names.add(ref.name);
398
416
  }
399
417
  }
418
+ const hasGetRoutes = contracted.some((r) => r.method === "GET");
419
+ const hasMutationRoutes = contracted.some((r) => r.method !== "GET");
400
420
  const lines = [
401
421
  "// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.",
402
422
  ""
403
423
  ];
424
+ const tqImports = [];
425
+ if (hasGetRoutes) tqImports.push("queryOptions as _queryOptions");
426
+ if (hasMutationRoutes) tqImports.push("mutationOptions as _mutationOptions");
427
+ if (tqImports.length > 0) {
428
+ lines.push(`import { ${tqImports.join(", ")} } from '@tanstack/react-query';`);
429
+ }
404
430
  lines.push("import { route } from './routes.js';");
405
431
  lines.push("import { createFetcher } from '@dudousxd/nestjs-inertia-client';");
406
432
  if (importsByFile.size > 0 && outDir) {
@@ -454,13 +480,14 @@ function buildApiFile(routes, outDir) {
454
480
  method: r.method,
455
481
  name,
456
482
  path: r.path,
483
+ controllerRef: r.controllerRef,
457
484
  contractSource: c.contractSource
458
485
  };
459
486
  insertIntoTree(tree, segments, leaf, name);
460
487
  }
461
488
  void detectCollisions;
462
489
  lines.push("export type ApiRouter = {");
463
- lines.push(...emitRouterTypeBlock(tree, 2));
490
+ lines.push(...emitRouterTypeBlock(tree, 2, outDir ?? ""));
464
491
  lines.push("};");
465
492
  lines.push("");
466
493
  lines.push("export const api = {");
@@ -730,9 +757,28 @@ var import_node_path10 = require("path");
730
757
  var import_chokidar = __toESM(require("chokidar"), 1);
731
758
 
732
759
  // src/discovery/contracts-fast.ts
760
+ var import_node_fs = require("fs");
733
761
  var import_node_path8 = require("path");
734
762
  var import_fast_glob2 = __toESM(require("fast-glob"), 1);
735
763
  var import_ts_morph = require("ts-morph");
764
+ var _projectRoot = "";
765
+ var _tsconfigPaths = null;
766
+ var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
767
+ function dbg(...args) {
768
+ if (_debug) console.log("[codegen:debug]", ...args);
769
+ }
770
+ __name(dbg, "dbg");
771
+ function loadTsconfigPaths(tsconfigPath) {
772
+ try {
773
+ const raw = (0, import_node_fs.readFileSync)(tsconfigPath, "utf8");
774
+ const stripped = raw.replace(/\/\/.*$/gm, "");
775
+ const parsed = JSON.parse(stripped);
776
+ return parsed.compilerOptions?.paths ?? null;
777
+ } catch {
778
+ return null;
779
+ }
780
+ }
781
+ __name(loadTsconfigPaths, "loadTsconfigPaths");
736
782
  async function discoverContractsFast(opts) {
737
783
  const { cwd, glob, tsconfig } = opts;
738
784
  const tsconfigPath = tsconfig ? (0, import_node_path8.resolve)(tsconfig) : (0, import_node_path8.join)(cwd, "tsconfig.json");
@@ -765,6 +811,8 @@ async function discoverContractsFast(opts) {
765
811
  project.addSourceFileAtPath(f);
766
812
  }
767
813
  const routes = [];
814
+ _projectRoot = cwd;
815
+ _tsconfigPaths = loadTsconfigPaths(tsconfigPath);
768
816
  for (const sourceFile of project.getSourceFiles()) {
769
817
  routes.push(...extractFromSourceFile(sourceFile, project));
770
818
  }
@@ -966,17 +1014,41 @@ function findTypeInFile(name, file) {
966
1014
  return null;
967
1015
  }
968
1016
  __name(findTypeInFile, "findTypeInFile");
1017
+ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
1018
+ if (moduleSpecifier.startsWith(".")) {
1019
+ const dir = (0, import_node_path8.dirname)(sourceFile.getFilePath());
1020
+ return [
1021
+ (0, import_node_path8.resolve)(dir, `${moduleSpecifier}.ts`),
1022
+ (0, import_node_path8.resolve)(dir, moduleSpecifier, "index.ts")
1023
+ ];
1024
+ }
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)) {
1029
+ const prefix = pattern.replace("*", "");
1030
+ if (moduleSpecifier.startsWith(prefix)) {
1031
+ const rest = moduleSpecifier.slice(prefix.length);
1032
+ const candidates = [];
1033
+ for (const mapping of mappings) {
1034
+ const resolved = (0, import_node_path8.resolve)(baseUrl, mapping.replace("*", rest));
1035
+ candidates.push(`${resolved}.ts`, (0, import_node_path8.resolve)(resolved, "index.ts"));
1036
+ }
1037
+ dbg(" resolved candidates:", candidates);
1038
+ return candidates;
1039
+ }
1040
+ }
1041
+ }
1042
+ return [];
1043
+ }
1044
+ __name(resolveModuleSpecifier, "resolveModuleSpecifier");
969
1045
  function resolveImportedType(name, sourceFile, project) {
970
1046
  for (const importDecl of sourceFile.getImportDeclarations()) {
971
1047
  const namedImport = importDecl.getNamedImports().find((n) => n.getName() === name);
972
1048
  if (!namedImport) continue;
973
1049
  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
- ];
1050
+ const candidates = resolveModuleSpecifier(moduleSpecifier, sourceFile, project);
1051
+ if (candidates.length === 0) continue;
980
1052
  for (const candidate of candidates) {
981
1053
  let importedFile = project.getSourceFile(candidate);
982
1054
  if (!importedFile) {
@@ -1020,6 +1092,18 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
1020
1092
  }
1021
1093
  return "Array<unknown>";
1022
1094
  }
1095
+ if ([
1096
+ "Record",
1097
+ "Omit",
1098
+ "Pick",
1099
+ "Partial",
1100
+ "Required",
1101
+ "Readonly",
1102
+ "Map",
1103
+ "Set"
1104
+ ].includes(name)) {
1105
+ return typeNode.getText();
1106
+ }
1023
1107
  if (name === "Promise") {
1024
1108
  const typeArgs = typeNode.getTypeArguments();
1025
1109
  const firstTypeArg = typeArgs[0];
@@ -1032,7 +1116,8 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
1032
1116
  if (resolved) {
1033
1117
  return expandTypeDecl(resolved, project, depth - 1);
1034
1118
  }
1035
- return name;
1119
+ dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
1120
+ return "unknown";
1036
1121
  }
1037
1122
  const kind = typeNode.getKind();
1038
1123
  if (kind === import_ts_morph.SyntaxKind.StringKeyword) return "string";
@@ -1194,7 +1279,7 @@ function tryResolveTypeRef(typeNode, sourceFile, project) {
1194
1279
  return null;
1195
1280
  }
1196
1281
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1197
- if (localDecl && localDecl.isExported()) {
1282
+ if (localDecl?.isExported()) {
1198
1283
  return {
1199
1284
  name,
1200
1285
  filePath: sourceFile.getFilePath()
@@ -1256,7 +1341,7 @@ function extractDtoContract(method, sourceFile, project) {
1256
1341
  if (val && import_ts_morph.Node.isIdentifier(val)) {
1257
1342
  const name = val.getText();
1258
1343
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1259
- if (localDecl && localDecl.isExported()) {
1344
+ if (localDecl?.isExported()) {
1260
1345
  responseRef = {
1261
1346
  name,
1262
1347
  filePath: sourceFile.getFilePath()
@@ -1382,6 +1467,11 @@ function extractFromSourceFile(sourceFile, project) {
1382
1467
  path: combined,
1383
1468
  name: routeName,
1384
1469
  params,
1470
+ controllerRef: {
1471
+ className,
1472
+ methodName,
1473
+ filePath: sourceFile.getFilePath()
1474
+ },
1385
1475
  contract: {
1386
1476
  contractSource: {
1387
1477
  query: contractDef.query,
@@ -1416,7 +1506,11 @@ function extractFromSourceFile(sourceFile, project) {
1416
1506
  path: combined,
1417
1507
  name: routeName,
1418
1508
  params,
1419
- // Attach contract if DTO extraction produced useful type info
1509
+ controllerRef: {
1510
+ className,
1511
+ methodName,
1512
+ filePath: sourceFile.getFilePath()
1513
+ },
1420
1514
  ...dtoContract ? {
1421
1515
  contract: {
1422
1516
  contractSource: {
@@ -1598,7 +1692,7 @@ async function watch(config, onChange) {
1598
1692
  __name(watch, "watch");
1599
1693
 
1600
1694
  // src/index.ts
1601
- var VERSION = "1.2.0";
1695
+ var VERSION = "1.3.0";
1602
1696
  // Annotate the CommonJS export names for ESM import in node:
1603
1697
  0 && (module.exports = {
1604
1698
  CodegenError,