@dudousxd/nestjs-inertia-codegen 2.0.0 → 3.0.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,9 @@ 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 query = c.contractSource.queryRef ? c.contractSource.queryRef.name : c.contractSource.query ?? "never";
267
+ const body = method === "GET" ? "never" : c.contractSource.bodyRef ? c.contractSource.bodyRef.name : c.contractSource.body ?? "never";
268
+ const response = c.contractSource.responseRef ? c.contractSource.responseRef.name : c.contractSource.response;
269
269
  const safeMethod = JSON.stringify(method);
270
270
  const safeUrl = JSON.stringify(c.path);
271
271
  lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; query: ${query}; body: ${body}; response: ${response} };`);
@@ -293,11 +293,10 @@ function emitApiObjectBlock(tree, indent) {
293
293
  const typeAccess = buildRouterTypeAccess(c.name);
294
294
  lines.push(`${pad}${objKey}: {`);
295
295
  lines.push(`${pad} queryKey: (query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
296
- lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) =>`);
297
- lines.push(`${pad} queryOptions({`);
298
- lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] : [${flatName}],`);
299
- lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
300
- lines.push(`${pad} }),`);
296
+ lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) => ({`);
297
+ lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
298
+ lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
299
+ lines.push(`${pad} }),`);
301
300
  lines.push(`${pad}},`);
302
301
  } else {
303
302
  const typeAccess = buildRouterTypeAccess(c.name);
@@ -322,18 +321,43 @@ function buildRouterTypeAccess(name) {
322
321
  return `ApiRouter${segments.map((s) => `[${JSON.stringify(s)}]`).join("")}`;
323
322
  }
324
323
  __name(buildRouterTypeAccess, "buildRouterTypeAccess");
325
- function buildApiFile(routes) {
324
+ function buildApiFile(routes, outDir) {
326
325
  const contracted = routes.filter((r) => r.contract);
327
- const hasGetRoutes = contracted.some((r) => r.method === "GET");
326
+ const importsByFile = /* @__PURE__ */ new Map();
327
+ for (const r of contracted) {
328
+ const cs = r.contract?.contractSource;
329
+ if (!cs) continue;
330
+ for (const ref of [
331
+ cs.queryRef,
332
+ cs.bodyRef,
333
+ cs.responseRef
334
+ ]) {
335
+ if (!ref) continue;
336
+ let names = importsByFile.get(ref.filePath);
337
+ if (!names) {
338
+ names = /* @__PURE__ */ new Set();
339
+ importsByFile.set(ref.filePath, names);
340
+ }
341
+ names.add(ref.name);
342
+ }
343
+ }
328
344
  const lines = [
329
345
  "// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.",
330
346
  ""
331
347
  ];
332
- if (hasGetRoutes) {
333
- lines.push("import { queryOptions } from '@tanstack/query-core';");
334
- }
335
348
  lines.push("import { route } from './routes.js';");
336
349
  lines.push("import { createFetcher } from '@dudousxd/nestjs-inertia-client';");
350
+ if (importsByFile.size > 0 && outDir) {
351
+ lines.push("");
352
+ for (const [filePath, names] of importsByFile) {
353
+ let relPath = relative3(outDir, filePath).replace(/\.ts$/, "");
354
+ if (!relPath.startsWith(".")) relPath = `./${relPath}`;
355
+ const sortedNames = [
356
+ ...names
357
+ ].sort();
358
+ lines.push(`import type { ${sortedNames.join(", ")} } from '${relPath}';`);
359
+ }
360
+ }
337
361
  lines.push("");
338
362
  lines.push("export const fetcher = createFetcher();");
339
363
  lines.push("");
@@ -1078,6 +1102,53 @@ function resolveIdentifierToClassType(node, sourceFile, project, depth) {
1078
1102
  return name;
1079
1103
  }
1080
1104
  __name(resolveIdentifierToClassType, "resolveIdentifierToClassType");
1105
+ function tryResolveTypeRef(typeNode, sourceFile, project) {
1106
+ if (Node.isTypeReference(typeNode)) {
1107
+ const typeName = typeNode.getTypeName();
1108
+ const name = Node.isIdentifier(typeName) ? typeName.getText() : null;
1109
+ if (!name) return null;
1110
+ if (name === "Promise") {
1111
+ const typeArgs = typeNode.getTypeArguments();
1112
+ const first = typeArgs[0];
1113
+ if (first) return tryResolveTypeRef(first, sourceFile, project);
1114
+ return null;
1115
+ }
1116
+ if ([
1117
+ "string",
1118
+ "number",
1119
+ "boolean",
1120
+ "void",
1121
+ "unknown",
1122
+ "any",
1123
+ "Date",
1124
+ "Array"
1125
+ ].includes(name)) {
1126
+ return null;
1127
+ }
1128
+ const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1129
+ if (localDecl && localDecl.isExported()) {
1130
+ return {
1131
+ name,
1132
+ filePath: sourceFile.getFilePath()
1133
+ };
1134
+ }
1135
+ const resolved = resolveImportedType(name, sourceFile, project);
1136
+ if (resolved && (resolved.kind === "class" || resolved.kind === "interface")) {
1137
+ const decl = resolved.decl;
1138
+ if (decl.isExported()) {
1139
+ return {
1140
+ name,
1141
+ filePath: resolved.file.getFilePath()
1142
+ };
1143
+ }
1144
+ }
1145
+ }
1146
+ if (Node.isArrayTypeNode(typeNode)) {
1147
+ return tryResolveTypeRef(typeNode.getElementTypeNode(), sourceFile, project);
1148
+ }
1149
+ return null;
1150
+ }
1151
+ __name(tryResolveTypeRef, "tryResolveTypeRef");
1081
1152
  function extractDtoContract(method, sourceFile, project) {
1082
1153
  const body = extractBodyType(method, sourceFile, project);
1083
1154
  const query = extractQueryType(method, sourceFile, project);
@@ -1086,11 +1157,61 @@ function extractDtoContract(method, sourceFile, project) {
1086
1157
  if (body === null && query === null && paramsType === null && response === "unknown") {
1087
1158
  return null;
1088
1159
  }
1160
+ let bodyRef = null;
1161
+ let queryRef = null;
1162
+ let responseRef = null;
1163
+ for (const param of method.getParameters()) {
1164
+ if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode()) {
1165
+ bodyRef = tryResolveTypeRef(param.getTypeNode(), sourceFile, project);
1166
+ }
1167
+ if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode()) {
1168
+ queryRef = tryResolveTypeRef(param.getTypeNode(), sourceFile, project);
1169
+ }
1170
+ }
1171
+ const returnTypeNode = method.getReturnTypeNode();
1172
+ if (returnTypeNode) {
1173
+ responseRef = tryResolveTypeRef(returnTypeNode, sourceFile, project);
1174
+ }
1175
+ if (!responseRef) {
1176
+ const apiResp = method.getDecorator("ApiResponse");
1177
+ if (apiResp) {
1178
+ const args = apiResp.getArguments();
1179
+ const optsArg = args[0];
1180
+ if (optsArg && Node.isObjectLiteralExpression(optsArg)) {
1181
+ for (const prop of optsArg.getProperties()) {
1182
+ if (Node.isPropertyAssignment(prop) && prop.getName() === "type") {
1183
+ const val = prop.getInitializer();
1184
+ if (val && Node.isIdentifier(val)) {
1185
+ const name = val.getText();
1186
+ const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1187
+ if (localDecl && localDecl.isExported()) {
1188
+ responseRef = {
1189
+ name,
1190
+ filePath: sourceFile.getFilePath()
1191
+ };
1192
+ } else {
1193
+ const resolved = resolveImportedType(name, sourceFile, project);
1194
+ if (resolved && (resolved.kind === "class" || resolved.kind === "interface") && resolved.decl.isExported()) {
1195
+ responseRef = {
1196
+ name,
1197
+ filePath: resolved.file.getFilePath()
1198
+ };
1199
+ }
1200
+ }
1201
+ }
1202
+ }
1203
+ }
1204
+ }
1205
+ }
1206
+ }
1089
1207
  return {
1090
1208
  query,
1091
1209
  body,
1092
1210
  response,
1093
- params: paramsType
1211
+ params: paramsType,
1212
+ queryRef,
1213
+ bodyRef,
1214
+ responseRef
1094
1215
  };
1095
1216
  }
1096
1217
  __name(extractDtoContract, "extractDtoContract");
@@ -1229,7 +1350,10 @@ function extractFromSourceFile(sourceFile, project) {
1229
1350
  contractSource: {
1230
1351
  query: dtoContract.query,
1231
1352
  body: dtoContract.body,
1232
- response: dtoContract.response
1353
+ response: dtoContract.response,
1354
+ queryRef: dtoContract.queryRef,
1355
+ bodyRef: dtoContract.bodyRef,
1356
+ responseRef: dtoContract.responseRef
1233
1357
  }
1234
1358
  }
1235
1359
  } : {}
@@ -1402,7 +1526,7 @@ async function watch(config, onChange) {
1402
1526
  __name(watch, "watch");
1403
1527
 
1404
1528
  // src/index.ts
1405
- var VERSION = "2.0.0";
1529
+ var VERSION = "3.0.0";
1406
1530
 
1407
1531
  // src/cli/codegen.ts
1408
1532
  async function runCodegen(opts = {}) {