@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/cli/main.js CHANGED
@@ -185,12 +185,12 @@ __name(extractPropsSource, "extractPropsSource");
185
185
 
186
186
  // src/emit/emit-api.ts
187
187
  import { mkdir, writeFile } from "fs/promises";
188
- import { join as join2 } from "path";
188
+ import { join as join2, relative as relative3 } from "path";
189
189
  async function emitApi(routes, outDir) {
190
190
  await mkdir(outDir, {
191
191
  recursive: true
192
192
  });
193
- const content = buildApiFile(routes);
193
+ const content = buildApiFile(routes, outDir);
194
194
  await writeFile(join2(outDir, "api.ts"), content, "utf8");
195
195
  }
196
196
  __name(emitApi, "emitApi");
@@ -263,9 +263,12 @@ function emitRouterTypeBlock(tree, indent) {
263
263
  if (node.kind === "leaf") {
264
264
  const c = node;
265
265
  const method = c.method.toUpperCase();
266
- const query = c.contractSource.query ?? "never";
267
- const body = method === "GET" ? "never" : c.contractSource.body ?? "never";
268
- const response = c.contractSource.response;
266
+ const queryRef = c.contractSource.queryRef;
267
+ const query = queryRef ? queryRef.isArray ? `Array<${queryRef.name}>` : queryRef.name : c.contractSource.query ?? "never";
268
+ const bodyRef = c.contractSource.bodyRef;
269
+ const body = method === "GET" ? "never" : bodyRef ? bodyRef.isArray ? `Array<${bodyRef.name}>` : bodyRef.name : c.contractSource.body ?? "never";
270
+ const respRef = c.contractSource.responseRef;
271
+ const response = respRef ? respRef.isArray ? `Array<${respRef.name}>` : respRef.name : c.contractSource.response;
269
272
  const safeMethod = JSON.stringify(method);
270
273
  const safeUrl = JSON.stringify(c.path);
271
274
  lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; query: ${query}; body: ${body}; response: ${response} };`);
@@ -292,15 +295,16 @@ function emitApiObjectBlock(tree, indent) {
292
295
  if (method === "GET") {
293
296
  const typeAccess = buildRouterTypeAccess(c.name);
294
297
  lines.push(`${pad}${objKey}: {`);
295
- lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) =>`);
296
- lines.push(`${pad} queryOptions({`);
297
- lines.push(`${pad} queryKey: [${flatName}, query],`);
298
- lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
299
- lines.push(`${pad} }),`);
298
+ lines.push(`${pad} queryKey: (query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
299
+ lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) => ({`);
300
+ lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
301
+ lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
302
+ lines.push(`${pad} }),`);
300
303
  lines.push(`${pad}},`);
301
304
  } else {
302
305
  const typeAccess = buildRouterTypeAccess(c.name);
303
306
  lines.push(`${pad}${objKey}: {`);
307
+ lines.push(`${pad} queryKey: () => [${flatName}] as const,`);
304
308
  lines.push(`${pad} mutationOptions: () => ({`);
305
309
  lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
306
310
  lines.push(`${pad} }),`);
@@ -320,18 +324,43 @@ function buildRouterTypeAccess(name) {
320
324
  return `ApiRouter${segments.map((s) => `[${JSON.stringify(s)}]`).join("")}`;
321
325
  }
322
326
  __name(buildRouterTypeAccess, "buildRouterTypeAccess");
323
- function buildApiFile(routes) {
327
+ function buildApiFile(routes, outDir) {
324
328
  const contracted = routes.filter((r) => r.contract);
325
- const hasGetRoutes = contracted.some((r) => r.method === "GET");
329
+ const importsByFile = /* @__PURE__ */ new Map();
330
+ for (const r of contracted) {
331
+ const cs = r.contract?.contractSource;
332
+ if (!cs) continue;
333
+ for (const ref of [
334
+ cs.queryRef,
335
+ cs.bodyRef,
336
+ cs.responseRef
337
+ ]) {
338
+ if (!ref) continue;
339
+ let names = importsByFile.get(ref.filePath);
340
+ if (!names) {
341
+ names = /* @__PURE__ */ new Set();
342
+ importsByFile.set(ref.filePath, names);
343
+ }
344
+ names.add(ref.name);
345
+ }
346
+ }
326
347
  const lines = [
327
348
  "// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.",
328
349
  ""
329
350
  ];
330
- if (hasGetRoutes) {
331
- lines.push("import { queryOptions } from '@tanstack/query-core';");
332
- }
333
351
  lines.push("import { route } from './routes.js';");
334
352
  lines.push("import { createFetcher } from '@dudousxd/nestjs-inertia-client';");
353
+ if (importsByFile.size > 0 && outDir) {
354
+ lines.push("");
355
+ for (const [filePath, names] of importsByFile) {
356
+ let relPath = relative3(outDir, filePath).replace(/\.ts$/, "");
357
+ if (!relPath.startsWith(".")) relPath = `./${relPath}`;
358
+ const sortedNames = [
359
+ ...names
360
+ ].sort();
361
+ lines.push(`import type { ${sortedNames.join(", ")} } from '${relPath}';`);
362
+ }
363
+ }
335
364
  lines.push("");
336
365
  lines.push("export const fetcher = createFetcher();");
337
366
  lines.push("");
@@ -928,7 +957,8 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
928
957
  const name = Node.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
929
958
  if (name === "string" || name === "number" || name === "boolean") return name;
930
959
  if (name === "Date") return "string";
931
- if (name === "unknown" || name === "any") return "unknown";
960
+ if (name === "unknown" || name === "any" || name === "void") return "unknown";
961
+ if (name === "StreamableFile" || name === "Observable" || name === "ReadableStream") return "unknown";
932
962
  if (name === "Array") {
933
963
  const typeArgs = typeNode.getTypeArguments();
934
964
  const firstTypeArg = typeArgs[0];
@@ -1076,6 +1106,68 @@ function resolveIdentifierToClassType(node, sourceFile, project, depth) {
1076
1106
  return name;
1077
1107
  }
1078
1108
  __name(resolveIdentifierToClassType, "resolveIdentifierToClassType");
1109
+ function tryResolveTypeRef(typeNode, sourceFile, project) {
1110
+ if (Node.isTypeReference(typeNode)) {
1111
+ const typeName = typeNode.getTypeName();
1112
+ const name = Node.isIdentifier(typeName) ? typeName.getText() : null;
1113
+ if (!name) return null;
1114
+ if (name === "Promise") {
1115
+ const typeArgs = typeNode.getTypeArguments();
1116
+ const first = typeArgs[0];
1117
+ if (first) return tryResolveTypeRef(first, sourceFile, project);
1118
+ return null;
1119
+ }
1120
+ if (name === "Array") {
1121
+ const typeArgs = typeNode.getTypeArguments();
1122
+ const first = typeArgs[0];
1123
+ if (first) {
1124
+ const inner = tryResolveTypeRef(first, sourceFile, project);
1125
+ if (inner) return {
1126
+ ...inner,
1127
+ isArray: true
1128
+ };
1129
+ }
1130
+ return null;
1131
+ }
1132
+ if ([
1133
+ "string",
1134
+ "number",
1135
+ "boolean",
1136
+ "void",
1137
+ "unknown",
1138
+ "any",
1139
+ "Date"
1140
+ ].includes(name)) {
1141
+ return null;
1142
+ }
1143
+ const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1144
+ if (localDecl && localDecl.isExported()) {
1145
+ return {
1146
+ name,
1147
+ filePath: sourceFile.getFilePath()
1148
+ };
1149
+ }
1150
+ const resolved = resolveImportedType(name, sourceFile, project);
1151
+ if (resolved && (resolved.kind === "class" || resolved.kind === "interface")) {
1152
+ const decl = resolved.decl;
1153
+ if (decl.isExported()) {
1154
+ return {
1155
+ name,
1156
+ filePath: resolved.file.getFilePath()
1157
+ };
1158
+ }
1159
+ }
1160
+ }
1161
+ if (Node.isArrayTypeNode(typeNode)) {
1162
+ const inner = tryResolveTypeRef(typeNode.getElementTypeNode(), sourceFile, project);
1163
+ if (inner) return {
1164
+ ...inner,
1165
+ isArray: true
1166
+ };
1167
+ }
1168
+ return null;
1169
+ }
1170
+ __name(tryResolveTypeRef, "tryResolveTypeRef");
1079
1171
  function extractDtoContract(method, sourceFile, project) {
1080
1172
  const body = extractBodyType(method, sourceFile, project);
1081
1173
  const query = extractQueryType(method, sourceFile, project);
@@ -1084,11 +1176,61 @@ function extractDtoContract(method, sourceFile, project) {
1084
1176
  if (body === null && query === null && paramsType === null && response === "unknown") {
1085
1177
  return null;
1086
1178
  }
1179
+ let bodyRef = null;
1180
+ let queryRef = null;
1181
+ let responseRef = null;
1182
+ for (const param of method.getParameters()) {
1183
+ if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode()) {
1184
+ bodyRef = tryResolveTypeRef(param.getTypeNode(), sourceFile, project);
1185
+ }
1186
+ if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode()) {
1187
+ queryRef = tryResolveTypeRef(param.getTypeNode(), sourceFile, project);
1188
+ }
1189
+ }
1190
+ const returnTypeNode = method.getReturnTypeNode();
1191
+ if (returnTypeNode) {
1192
+ responseRef = tryResolveTypeRef(returnTypeNode, sourceFile, project);
1193
+ }
1194
+ if (!responseRef) {
1195
+ const apiResp = method.getDecorator("ApiResponse");
1196
+ if (apiResp) {
1197
+ const args = apiResp.getArguments();
1198
+ const optsArg = args[0];
1199
+ if (optsArg && Node.isObjectLiteralExpression(optsArg)) {
1200
+ for (const prop of optsArg.getProperties()) {
1201
+ if (Node.isPropertyAssignment(prop) && prop.getName() === "type") {
1202
+ const val = prop.getInitializer();
1203
+ if (val && Node.isIdentifier(val)) {
1204
+ const name = val.getText();
1205
+ const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1206
+ if (localDecl && localDecl.isExported()) {
1207
+ responseRef = {
1208
+ name,
1209
+ filePath: sourceFile.getFilePath()
1210
+ };
1211
+ } else {
1212
+ const resolved = resolveImportedType(name, sourceFile, project);
1213
+ if (resolved && (resolved.kind === "class" || resolved.kind === "interface") && resolved.decl.isExported()) {
1214
+ responseRef = {
1215
+ name,
1216
+ filePath: resolved.file.getFilePath()
1217
+ };
1218
+ }
1219
+ }
1220
+ }
1221
+ }
1222
+ }
1223
+ }
1224
+ }
1225
+ }
1087
1226
  return {
1088
1227
  query,
1089
1228
  body,
1090
1229
  response,
1091
- params: paramsType
1230
+ params: paramsType,
1231
+ queryRef,
1232
+ bodyRef,
1233
+ responseRef
1092
1234
  };
1093
1235
  }
1094
1236
  __name(extractDtoContract, "extractDtoContract");
@@ -1227,7 +1369,10 @@ function extractFromSourceFile(sourceFile, project) {
1227
1369
  contractSource: {
1228
1370
  query: dtoContract.query,
1229
1371
  body: dtoContract.body,
1230
- response: dtoContract.response
1372
+ response: dtoContract.response,
1373
+ queryRef: dtoContract.queryRef,
1374
+ bodyRef: dtoContract.bodyRef,
1375
+ responseRef: dtoContract.responseRef
1231
1376
  }
1232
1377
  }
1233
1378
  } : {}
@@ -1400,7 +1545,7 @@ async function watch(config, onChange) {
1400
1545
  __name(watch, "watch");
1401
1546
 
1402
1547
  // src/index.ts
1403
- var VERSION = "1.0.6";
1548
+ var VERSION = "1.2.0";
1404
1549
 
1405
1550
  // src/cli/codegen.ts
1406
1551
  async function runCodegen(opts = {}) {