@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.cjs CHANGED
@@ -243,7 +243,7 @@ async function emitApi(routes, outDir) {
243
243
  await (0, import_promises3.mkdir)(outDir, {
244
244
  recursive: true
245
245
  });
246
- const content = buildApiFile(routes);
246
+ const content = buildApiFile(routes, outDir);
247
247
  await (0, import_promises3.writeFile)((0, import_node_path3.join)(outDir, "api.ts"), content, "utf8");
248
248
  }
249
249
  __name(emitApi, "emitApi");
@@ -316,9 +316,12 @@ function emitRouterTypeBlock(tree, indent) {
316
316
  if (node.kind === "leaf") {
317
317
  const c = node;
318
318
  const method = c.method.toUpperCase();
319
- const query = c.contractSource.query ?? "never";
320
- const body = method === "GET" ? "never" : c.contractSource.body ?? "never";
321
- const response = c.contractSource.response;
319
+ const queryRef = c.contractSource.queryRef;
320
+ const query = queryRef ? queryRef.isArray ? `Array<${queryRef.name}>` : queryRef.name : c.contractSource.query ?? "never";
321
+ const bodyRef = c.contractSource.bodyRef;
322
+ 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;
322
325
  const safeMethod = JSON.stringify(method);
323
326
  const safeUrl = JSON.stringify(c.path);
324
327
  lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; query: ${query}; body: ${body}; response: ${response} };`);
@@ -345,15 +348,16 @@ function emitApiObjectBlock(tree, indent) {
345
348
  if (method === "GET") {
346
349
  const typeAccess = buildRouterTypeAccess(c.name);
347
350
  lines.push(`${pad}${objKey}: {`);
348
- lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) =>`);
349
- lines.push(`${pad} queryOptions({`);
350
- lines.push(`${pad} queryKey: [${flatName}, query],`);
351
- lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
352
- lines.push(`${pad} }),`);
351
+ 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} }),`);
353
356
  lines.push(`${pad}},`);
354
357
  } else {
355
358
  const typeAccess = buildRouterTypeAccess(c.name);
356
359
  lines.push(`${pad}${objKey}: {`);
360
+ lines.push(`${pad} queryKey: () => [${flatName}] as const,`);
357
361
  lines.push(`${pad} mutationOptions: () => ({`);
358
362
  lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
359
363
  lines.push(`${pad} }),`);
@@ -373,18 +377,43 @@ function buildRouterTypeAccess(name) {
373
377
  return `ApiRouter${segments.map((s) => `[${JSON.stringify(s)}]`).join("")}`;
374
378
  }
375
379
  __name(buildRouterTypeAccess, "buildRouterTypeAccess");
376
- function buildApiFile(routes) {
380
+ function buildApiFile(routes, outDir) {
377
381
  const contracted = routes.filter((r) => r.contract);
378
- const hasGetRoutes = contracted.some((r) => r.method === "GET");
382
+ const importsByFile = /* @__PURE__ */ new Map();
383
+ for (const r of contracted) {
384
+ const cs = r.contract?.contractSource;
385
+ if (!cs) continue;
386
+ for (const ref of [
387
+ cs.queryRef,
388
+ cs.bodyRef,
389
+ cs.responseRef
390
+ ]) {
391
+ if (!ref) continue;
392
+ let names = importsByFile.get(ref.filePath);
393
+ if (!names) {
394
+ names = /* @__PURE__ */ new Set();
395
+ importsByFile.set(ref.filePath, names);
396
+ }
397
+ names.add(ref.name);
398
+ }
399
+ }
379
400
  const lines = [
380
401
  "// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.",
381
402
  ""
382
403
  ];
383
- if (hasGetRoutes) {
384
- lines.push("import { queryOptions } from '@tanstack/query-core';");
385
- }
386
404
  lines.push("import { route } from './routes.js';");
387
405
  lines.push("import { createFetcher } from '@dudousxd/nestjs-inertia-client';");
406
+ if (importsByFile.size > 0 && outDir) {
407
+ lines.push("");
408
+ for (const [filePath, names] of importsByFile) {
409
+ let relPath = (0, import_node_path3.relative)(outDir, filePath).replace(/\.ts$/, "");
410
+ if (!relPath.startsWith(".")) relPath = `./${relPath}`;
411
+ const sortedNames = [
412
+ ...names
413
+ ].sort();
414
+ lines.push(`import type { ${sortedNames.join(", ")} } from '${relPath}';`);
415
+ }
416
+ }
388
417
  lines.push("");
389
418
  lines.push("export const fetcher = createFetcher();");
390
419
  lines.push("");
@@ -981,7 +1010,8 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
981
1010
  const name = import_ts_morph.Node.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
982
1011
  if (name === "string" || name === "number" || name === "boolean") return name;
983
1012
  if (name === "Date") return "string";
984
- if (name === "unknown" || name === "any") return "unknown";
1013
+ if (name === "unknown" || name === "any" || name === "void") return "unknown";
1014
+ if (name === "StreamableFile" || name === "Observable" || name === "ReadableStream") return "unknown";
985
1015
  if (name === "Array") {
986
1016
  const typeArgs = typeNode.getTypeArguments();
987
1017
  const firstTypeArg = typeArgs[0];
@@ -1129,6 +1159,68 @@ function resolveIdentifierToClassType(node, sourceFile, project, depth) {
1129
1159
  return name;
1130
1160
  }
1131
1161
  __name(resolveIdentifierToClassType, "resolveIdentifierToClassType");
1162
+ function tryResolveTypeRef(typeNode, sourceFile, project) {
1163
+ if (import_ts_morph.Node.isTypeReference(typeNode)) {
1164
+ const typeName = typeNode.getTypeName();
1165
+ const name = import_ts_morph.Node.isIdentifier(typeName) ? typeName.getText() : null;
1166
+ if (!name) return null;
1167
+ if (name === "Promise") {
1168
+ const typeArgs = typeNode.getTypeArguments();
1169
+ const first = typeArgs[0];
1170
+ if (first) return tryResolveTypeRef(first, sourceFile, project);
1171
+ return null;
1172
+ }
1173
+ if (name === "Array") {
1174
+ const typeArgs = typeNode.getTypeArguments();
1175
+ const first = typeArgs[0];
1176
+ if (first) {
1177
+ const inner = tryResolveTypeRef(first, sourceFile, project);
1178
+ if (inner) return {
1179
+ ...inner,
1180
+ isArray: true
1181
+ };
1182
+ }
1183
+ return null;
1184
+ }
1185
+ if ([
1186
+ "string",
1187
+ "number",
1188
+ "boolean",
1189
+ "void",
1190
+ "unknown",
1191
+ "any",
1192
+ "Date"
1193
+ ].includes(name)) {
1194
+ return null;
1195
+ }
1196
+ const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1197
+ if (localDecl && localDecl.isExported()) {
1198
+ return {
1199
+ name,
1200
+ filePath: sourceFile.getFilePath()
1201
+ };
1202
+ }
1203
+ const resolved = resolveImportedType(name, sourceFile, project);
1204
+ if (resolved && (resolved.kind === "class" || resolved.kind === "interface")) {
1205
+ const decl = resolved.decl;
1206
+ if (decl.isExported()) {
1207
+ return {
1208
+ name,
1209
+ filePath: resolved.file.getFilePath()
1210
+ };
1211
+ }
1212
+ }
1213
+ }
1214
+ if (import_ts_morph.Node.isArrayTypeNode(typeNode)) {
1215
+ const inner = tryResolveTypeRef(typeNode.getElementTypeNode(), sourceFile, project);
1216
+ if (inner) return {
1217
+ ...inner,
1218
+ isArray: true
1219
+ };
1220
+ }
1221
+ return null;
1222
+ }
1223
+ __name(tryResolveTypeRef, "tryResolveTypeRef");
1132
1224
  function extractDtoContract(method, sourceFile, project) {
1133
1225
  const body = extractBodyType(method, sourceFile, project);
1134
1226
  const query = extractQueryType(method, sourceFile, project);
@@ -1137,11 +1229,61 @@ function extractDtoContract(method, sourceFile, project) {
1137
1229
  if (body === null && query === null && paramsType === null && response === "unknown") {
1138
1230
  return null;
1139
1231
  }
1232
+ let bodyRef = null;
1233
+ let queryRef = null;
1234
+ let responseRef = null;
1235
+ for (const param of method.getParameters()) {
1236
+ if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode()) {
1237
+ bodyRef = tryResolveTypeRef(param.getTypeNode(), sourceFile, project);
1238
+ }
1239
+ if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode()) {
1240
+ queryRef = tryResolveTypeRef(param.getTypeNode(), sourceFile, project);
1241
+ }
1242
+ }
1243
+ const returnTypeNode = method.getReturnTypeNode();
1244
+ if (returnTypeNode) {
1245
+ responseRef = tryResolveTypeRef(returnTypeNode, sourceFile, project);
1246
+ }
1247
+ if (!responseRef) {
1248
+ const apiResp = method.getDecorator("ApiResponse");
1249
+ if (apiResp) {
1250
+ const args = apiResp.getArguments();
1251
+ const optsArg = args[0];
1252
+ if (optsArg && import_ts_morph.Node.isObjectLiteralExpression(optsArg)) {
1253
+ for (const prop of optsArg.getProperties()) {
1254
+ if (import_ts_morph.Node.isPropertyAssignment(prop) && prop.getName() === "type") {
1255
+ const val = prop.getInitializer();
1256
+ if (val && import_ts_morph.Node.isIdentifier(val)) {
1257
+ const name = val.getText();
1258
+ const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1259
+ if (localDecl && localDecl.isExported()) {
1260
+ responseRef = {
1261
+ name,
1262
+ filePath: sourceFile.getFilePath()
1263
+ };
1264
+ } else {
1265
+ const resolved = resolveImportedType(name, sourceFile, project);
1266
+ if (resolved && (resolved.kind === "class" || resolved.kind === "interface") && resolved.decl.isExported()) {
1267
+ responseRef = {
1268
+ name,
1269
+ filePath: resolved.file.getFilePath()
1270
+ };
1271
+ }
1272
+ }
1273
+ }
1274
+ }
1275
+ }
1276
+ }
1277
+ }
1278
+ }
1140
1279
  return {
1141
1280
  query,
1142
1281
  body,
1143
1282
  response,
1144
- params: paramsType
1283
+ params: paramsType,
1284
+ queryRef,
1285
+ bodyRef,
1286
+ responseRef
1145
1287
  };
1146
1288
  }
1147
1289
  __name(extractDtoContract, "extractDtoContract");
@@ -1280,7 +1422,10 @@ function extractFromSourceFile(sourceFile, project) {
1280
1422
  contractSource: {
1281
1423
  query: dtoContract.query,
1282
1424
  body: dtoContract.body,
1283
- response: dtoContract.response
1425
+ response: dtoContract.response,
1426
+ queryRef: dtoContract.queryRef,
1427
+ bodyRef: dtoContract.bodyRef,
1428
+ responseRef: dtoContract.responseRef
1284
1429
  }
1285
1430
  }
1286
1431
  } : {}
@@ -1453,7 +1598,7 @@ async function watch(config, onChange) {
1453
1598
  __name(watch, "watch");
1454
1599
 
1455
1600
  // src/index.ts
1456
- var VERSION = "1.0.6";
1601
+ var VERSION = "1.2.0";
1457
1602
  // Annotate the CommonJS export names for ESM import in node:
1458
1603
  0 && (module.exports = {
1459
1604
  CodegenError,