@dudousxd/nestjs-inertia-codegen 1.0.6 → 1.2.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
@@ -62,10 +62,18 @@ declare class CodegenError extends Error {
62
62
  constructor(message: string, options?: ErrorOptions);
63
63
  }
64
64
 
65
+ interface TypeRef {
66
+ name: string;
67
+ filePath: string;
68
+ isArray?: boolean;
69
+ }
65
70
  interface ContractSource {
66
71
  query: string | null;
67
72
  body: string | null;
68
73
  response: string;
74
+ queryRef?: TypeRef | null;
75
+ bodyRef?: TypeRef | null;
76
+ responseRef?: TypeRef | null;
69
77
  }
70
78
  interface ContractDescriptor {
71
79
  contractSource: ContractSource;
@@ -120,6 +128,6 @@ declare function acquireLock(outDir: string): Promise<{
120
128
  release: () => Promise<void>;
121
129
  } | null>;
122
130
 
123
- declare const VERSION = "1.0.6";
131
+ declare const VERSION = "1.2.0";
124
132
 
125
133
  export { CodegenError, ConfigError, type ResolvedConfig, type ScopeConfig, type UserConfig, VERSION, type Watcher, acquireLock, defineConfig, generate, loadConfig, watch };
package/dist/index.d.ts CHANGED
@@ -62,10 +62,18 @@ declare class CodegenError extends Error {
62
62
  constructor(message: string, options?: ErrorOptions);
63
63
  }
64
64
 
65
+ interface TypeRef {
66
+ name: string;
67
+ filePath: string;
68
+ isArray?: boolean;
69
+ }
65
70
  interface ContractSource {
66
71
  query: string | null;
67
72
  body: string | null;
68
73
  response: string;
74
+ queryRef?: TypeRef | null;
75
+ bodyRef?: TypeRef | null;
76
+ responseRef?: TypeRef | null;
69
77
  }
70
78
  interface ContractDescriptor {
71
79
  contractSource: ContractSource;
@@ -120,6 +128,6 @@ declare function acquireLock(outDir: string): Promise<{
120
128
  release: () => Promise<void>;
121
129
  } | null>;
122
130
 
123
- declare const VERSION = "1.0.6";
131
+ declare const VERSION = "1.2.0";
124
132
 
125
133
  export { CodegenError, ConfigError, type ResolvedConfig, type ScopeConfig, type UserConfig, VERSION, type Watcher, acquireLock, defineConfig, generate, loadConfig, watch };
package/dist/index.js CHANGED
@@ -197,12 +197,12 @@ __name(extractPropsSource, "extractPropsSource");
197
197
 
198
198
  // src/emit/emit-api.ts
199
199
  import { mkdir, writeFile } from "fs/promises";
200
- import { join as join2 } from "path";
200
+ import { join as join2, relative as relative3 } from "path";
201
201
  async function emitApi(routes, outDir) {
202
202
  await mkdir(outDir, {
203
203
  recursive: true
204
204
  });
205
- const content = buildApiFile(routes);
205
+ const content = buildApiFile(routes, outDir);
206
206
  await writeFile(join2(outDir, "api.ts"), content, "utf8");
207
207
  }
208
208
  __name(emitApi, "emitApi");
@@ -275,9 +275,12 @@ function emitRouterTypeBlock(tree, indent) {
275
275
  if (node.kind === "leaf") {
276
276
  const c = node;
277
277
  const method = c.method.toUpperCase();
278
- const query = c.contractSource.query ?? "never";
279
- const body = method === "GET" ? "never" : c.contractSource.body ?? "never";
280
- const response = c.contractSource.response;
278
+ const queryRef = c.contractSource.queryRef;
279
+ const query = queryRef ? queryRef.isArray ? `Array<${queryRef.name}>` : queryRef.name : c.contractSource.query ?? "never";
280
+ const bodyRef = c.contractSource.bodyRef;
281
+ 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;
281
284
  const safeMethod = JSON.stringify(method);
282
285
  const safeUrl = JSON.stringify(c.path);
283
286
  lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; query: ${query}; body: ${body}; response: ${response} };`);
@@ -304,15 +307,16 @@ function emitApiObjectBlock(tree, indent) {
304
307
  if (method === "GET") {
305
308
  const typeAccess = buildRouterTypeAccess(c.name);
306
309
  lines.push(`${pad}${objKey}: {`);
307
- lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) =>`);
308
- lines.push(`${pad} queryOptions({`);
309
- lines.push(`${pad} queryKey: [${flatName}, query],`);
310
- lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
311
- lines.push(`${pad} }),`);
310
+ 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} }),`);
312
315
  lines.push(`${pad}},`);
313
316
  } else {
314
317
  const typeAccess = buildRouterTypeAccess(c.name);
315
318
  lines.push(`${pad}${objKey}: {`);
319
+ lines.push(`${pad} queryKey: () => [${flatName}] as const,`);
316
320
  lines.push(`${pad} mutationOptions: () => ({`);
317
321
  lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
318
322
  lines.push(`${pad} }),`);
@@ -332,18 +336,43 @@ function buildRouterTypeAccess(name) {
332
336
  return `ApiRouter${segments.map((s) => `[${JSON.stringify(s)}]`).join("")}`;
333
337
  }
334
338
  __name(buildRouterTypeAccess, "buildRouterTypeAccess");
335
- function buildApiFile(routes) {
339
+ function buildApiFile(routes, outDir) {
336
340
  const contracted = routes.filter((r) => r.contract);
337
- const hasGetRoutes = contracted.some((r) => r.method === "GET");
341
+ const importsByFile = /* @__PURE__ */ new Map();
342
+ for (const r of contracted) {
343
+ const cs = r.contract?.contractSource;
344
+ if (!cs) continue;
345
+ for (const ref of [
346
+ cs.queryRef,
347
+ cs.bodyRef,
348
+ cs.responseRef
349
+ ]) {
350
+ if (!ref) continue;
351
+ let names = importsByFile.get(ref.filePath);
352
+ if (!names) {
353
+ names = /* @__PURE__ */ new Set();
354
+ importsByFile.set(ref.filePath, names);
355
+ }
356
+ names.add(ref.name);
357
+ }
358
+ }
338
359
  const lines = [
339
360
  "// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.",
340
361
  ""
341
362
  ];
342
- if (hasGetRoutes) {
343
- lines.push("import { queryOptions } from '@tanstack/query-core';");
344
- }
345
363
  lines.push("import { route } from './routes.js';");
346
364
  lines.push("import { createFetcher } from '@dudousxd/nestjs-inertia-client';");
365
+ if (importsByFile.size > 0 && outDir) {
366
+ lines.push("");
367
+ for (const [filePath, names] of importsByFile) {
368
+ let relPath = relative3(outDir, filePath).replace(/\.ts$/, "");
369
+ if (!relPath.startsWith(".")) relPath = `./${relPath}`;
370
+ const sortedNames = [
371
+ ...names
372
+ ].sort();
373
+ lines.push(`import type { ${sortedNames.join(", ")} } from '${relPath}';`);
374
+ }
375
+ }
347
376
  lines.push("");
348
377
  lines.push("export const fetcher = createFetcher();");
349
378
  lines.push("");
@@ -940,7 +969,8 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
940
969
  const name = Node.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
941
970
  if (name === "string" || name === "number" || name === "boolean") return name;
942
971
  if (name === "Date") return "string";
943
- if (name === "unknown" || name === "any") return "unknown";
972
+ if (name === "unknown" || name === "any" || name === "void") return "unknown";
973
+ if (name === "StreamableFile" || name === "Observable" || name === "ReadableStream") return "unknown";
944
974
  if (name === "Array") {
945
975
  const typeArgs = typeNode.getTypeArguments();
946
976
  const firstTypeArg = typeArgs[0];
@@ -1088,6 +1118,68 @@ function resolveIdentifierToClassType(node, sourceFile, project, depth) {
1088
1118
  return name;
1089
1119
  }
1090
1120
  __name(resolveIdentifierToClassType, "resolveIdentifierToClassType");
1121
+ function tryResolveTypeRef(typeNode, sourceFile, project) {
1122
+ if (Node.isTypeReference(typeNode)) {
1123
+ const typeName = typeNode.getTypeName();
1124
+ const name = Node.isIdentifier(typeName) ? typeName.getText() : null;
1125
+ if (!name) return null;
1126
+ if (name === "Promise") {
1127
+ const typeArgs = typeNode.getTypeArguments();
1128
+ const first = typeArgs[0];
1129
+ if (first) return tryResolveTypeRef(first, sourceFile, project);
1130
+ return null;
1131
+ }
1132
+ if (name === "Array") {
1133
+ const typeArgs = typeNode.getTypeArguments();
1134
+ const first = typeArgs[0];
1135
+ if (first) {
1136
+ const inner = tryResolveTypeRef(first, sourceFile, project);
1137
+ if (inner) return {
1138
+ ...inner,
1139
+ isArray: true
1140
+ };
1141
+ }
1142
+ return null;
1143
+ }
1144
+ if ([
1145
+ "string",
1146
+ "number",
1147
+ "boolean",
1148
+ "void",
1149
+ "unknown",
1150
+ "any",
1151
+ "Date"
1152
+ ].includes(name)) {
1153
+ return null;
1154
+ }
1155
+ const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1156
+ if (localDecl && localDecl.isExported()) {
1157
+ return {
1158
+ name,
1159
+ filePath: sourceFile.getFilePath()
1160
+ };
1161
+ }
1162
+ const resolved = resolveImportedType(name, sourceFile, project);
1163
+ if (resolved && (resolved.kind === "class" || resolved.kind === "interface")) {
1164
+ const decl = resolved.decl;
1165
+ if (decl.isExported()) {
1166
+ return {
1167
+ name,
1168
+ filePath: resolved.file.getFilePath()
1169
+ };
1170
+ }
1171
+ }
1172
+ }
1173
+ if (Node.isArrayTypeNode(typeNode)) {
1174
+ const inner = tryResolveTypeRef(typeNode.getElementTypeNode(), sourceFile, project);
1175
+ if (inner) return {
1176
+ ...inner,
1177
+ isArray: true
1178
+ };
1179
+ }
1180
+ return null;
1181
+ }
1182
+ __name(tryResolveTypeRef, "tryResolveTypeRef");
1091
1183
  function extractDtoContract(method, sourceFile, project) {
1092
1184
  const body = extractBodyType(method, sourceFile, project);
1093
1185
  const query = extractQueryType(method, sourceFile, project);
@@ -1096,11 +1188,61 @@ function extractDtoContract(method, sourceFile, project) {
1096
1188
  if (body === null && query === null && paramsType === null && response === "unknown") {
1097
1189
  return null;
1098
1190
  }
1191
+ let bodyRef = null;
1192
+ let queryRef = null;
1193
+ let responseRef = null;
1194
+ for (const param of method.getParameters()) {
1195
+ if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode()) {
1196
+ bodyRef = tryResolveTypeRef(param.getTypeNode(), sourceFile, project);
1197
+ }
1198
+ if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode()) {
1199
+ queryRef = tryResolveTypeRef(param.getTypeNode(), sourceFile, project);
1200
+ }
1201
+ }
1202
+ const returnTypeNode = method.getReturnTypeNode();
1203
+ if (returnTypeNode) {
1204
+ responseRef = tryResolveTypeRef(returnTypeNode, sourceFile, project);
1205
+ }
1206
+ if (!responseRef) {
1207
+ const apiResp = method.getDecorator("ApiResponse");
1208
+ if (apiResp) {
1209
+ const args = apiResp.getArguments();
1210
+ const optsArg = args[0];
1211
+ if (optsArg && Node.isObjectLiteralExpression(optsArg)) {
1212
+ for (const prop of optsArg.getProperties()) {
1213
+ if (Node.isPropertyAssignment(prop) && prop.getName() === "type") {
1214
+ const val = prop.getInitializer();
1215
+ if (val && Node.isIdentifier(val)) {
1216
+ const name = val.getText();
1217
+ const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1218
+ if (localDecl && localDecl.isExported()) {
1219
+ responseRef = {
1220
+ name,
1221
+ filePath: sourceFile.getFilePath()
1222
+ };
1223
+ } else {
1224
+ const resolved = resolveImportedType(name, sourceFile, project);
1225
+ if (resolved && (resolved.kind === "class" || resolved.kind === "interface") && resolved.decl.isExported()) {
1226
+ responseRef = {
1227
+ name,
1228
+ filePath: resolved.file.getFilePath()
1229
+ };
1230
+ }
1231
+ }
1232
+ }
1233
+ }
1234
+ }
1235
+ }
1236
+ }
1237
+ }
1099
1238
  return {
1100
1239
  query,
1101
1240
  body,
1102
1241
  response,
1103
- params: paramsType
1242
+ params: paramsType,
1243
+ queryRef,
1244
+ bodyRef,
1245
+ responseRef
1104
1246
  };
1105
1247
  }
1106
1248
  __name(extractDtoContract, "extractDtoContract");
@@ -1239,7 +1381,10 @@ function extractFromSourceFile(sourceFile, project) {
1239
1381
  contractSource: {
1240
1382
  query: dtoContract.query,
1241
1383
  body: dtoContract.body,
1242
- response: dtoContract.response
1384
+ response: dtoContract.response,
1385
+ queryRef: dtoContract.queryRef,
1386
+ bodyRef: dtoContract.bodyRef,
1387
+ responseRef: dtoContract.responseRef
1243
1388
  }
1244
1389
  }
1245
1390
  } : {}
@@ -1412,7 +1557,7 @@ async function watch(config, onChange) {
1412
1557
  __name(watch, "watch");
1413
1558
 
1414
1559
  // src/index.ts
1415
- var VERSION = "1.0.6";
1560
+ var VERSION = "1.2.0";
1416
1561
  export {
1417
1562
  CodegenError,
1418
1563
  ConfigError,