@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/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # Changelog — @dudousxd/nestjs-inertia-codegen
2
2
 
3
+ ## 3.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - feat(codegen): import type references from source instead of inline expansion — eliminates unknown fields from depth limits
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies []:
12
+ - @dudousxd/nestjs-inertia@3.0.0
13
+
14
+ ## 2.0.1
15
+
16
+ ### Patch Changes
17
+
18
+ - fix(codegen): remove @tanstack/query-core dependency — generated api.ts uses plain object literals
19
+
20
+ - Updated dependencies []:
21
+ - @dudousxd/nestjs-inertia@2.0.1
22
+
23
+ ## 2.0.0
24
+
25
+ ### Minor Changes
26
+
27
+ - feat(codegen): add queryKey() helper for typed cache invalidation — api.crew.getCrew.queryKey()
28
+
29
+ ### Patch Changes
30
+
31
+ - Updated dependencies []:
32
+ - @dudousxd/nestjs-inertia@2.0.0
33
+
34
+ ## 1.0.7
35
+
36
+ ### Patch Changes
37
+
38
+ - fix(core): use data-page="app" attribute on script tag for Inertia v3 protocol compatibility
39
+
40
+ - Updated dependencies []:
41
+ - @dudousxd/nestjs-inertia@1.0.7
42
+
3
43
  ## 1.0.6
4
44
 
5
45
  ### Patch Changes
package/dist/cli/main.cjs CHANGED
@@ -222,7 +222,7 @@ async function emitApi(routes, outDir) {
222
222
  await (0, import_promises3.mkdir)(outDir, {
223
223
  recursive: true
224
224
  });
225
- const content = buildApiFile(routes);
225
+ const content = buildApiFile(routes, outDir);
226
226
  await (0, import_promises3.writeFile)((0, import_node_path3.join)(outDir, "api.ts"), content, "utf8");
227
227
  }
228
228
  __name(emitApi, "emitApi");
@@ -295,9 +295,12 @@ function emitRouterTypeBlock(tree, indent) {
295
295
  if (node.kind === "leaf") {
296
296
  const c = node;
297
297
  const method = c.method.toUpperCase();
298
- const query = c.contractSource.query ?? "never";
299
- const body = method === "GET" ? "never" : c.contractSource.body ?? "never";
300
- const response = c.contractSource.response;
298
+ const queryRef = c.contractSource.queryRef;
299
+ const query = queryRef ? queryRef.isArray ? `Array<${queryRef.name}>` : queryRef.name : c.contractSource.query ?? "never";
300
+ const bodyRef = c.contractSource.bodyRef;
301
+ const body = method === "GET" ? "never" : bodyRef ? bodyRef.isArray ? `Array<${bodyRef.name}>` : bodyRef.name : c.contractSource.body ?? "never";
302
+ const respRef = c.contractSource.responseRef;
303
+ const response = respRef ? respRef.isArray ? `Array<${respRef.name}>` : respRef.name : c.contractSource.response;
301
304
  const safeMethod = JSON.stringify(method);
302
305
  const safeUrl = JSON.stringify(c.path);
303
306
  lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; query: ${query}; body: ${body}; response: ${response} };`);
@@ -324,15 +327,16 @@ function emitApiObjectBlock(tree, indent) {
324
327
  if (method === "GET") {
325
328
  const typeAccess = buildRouterTypeAccess(c.name);
326
329
  lines.push(`${pad}${objKey}: {`);
327
- lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) =>`);
328
- lines.push(`${pad} queryOptions({`);
329
- lines.push(`${pad} queryKey: [${flatName}, query],`);
330
- lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
331
- lines.push(`${pad} }),`);
330
+ lines.push(`${pad} queryKey: (query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
331
+ lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) => ({`);
332
+ lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
333
+ lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
334
+ lines.push(`${pad} }),`);
332
335
  lines.push(`${pad}},`);
333
336
  } else {
334
337
  const typeAccess = buildRouterTypeAccess(c.name);
335
338
  lines.push(`${pad}${objKey}: {`);
339
+ lines.push(`${pad} queryKey: () => [${flatName}] as const,`);
336
340
  lines.push(`${pad} mutationOptions: () => ({`);
337
341
  lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
338
342
  lines.push(`${pad} }),`);
@@ -352,18 +356,43 @@ function buildRouterTypeAccess(name) {
352
356
  return `ApiRouter${segments.map((s) => `[${JSON.stringify(s)}]`).join("")}`;
353
357
  }
354
358
  __name(buildRouterTypeAccess, "buildRouterTypeAccess");
355
- function buildApiFile(routes) {
359
+ function buildApiFile(routes, outDir) {
356
360
  const contracted = routes.filter((r) => r.contract);
357
- const hasGetRoutes = contracted.some((r) => r.method === "GET");
361
+ const importsByFile = /* @__PURE__ */ new Map();
362
+ for (const r of contracted) {
363
+ const cs = r.contract?.contractSource;
364
+ if (!cs) continue;
365
+ for (const ref of [
366
+ cs.queryRef,
367
+ cs.bodyRef,
368
+ cs.responseRef
369
+ ]) {
370
+ if (!ref) continue;
371
+ let names = importsByFile.get(ref.filePath);
372
+ if (!names) {
373
+ names = /* @__PURE__ */ new Set();
374
+ importsByFile.set(ref.filePath, names);
375
+ }
376
+ names.add(ref.name);
377
+ }
378
+ }
358
379
  const lines = [
359
380
  "// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.",
360
381
  ""
361
382
  ];
362
- if (hasGetRoutes) {
363
- lines.push("import { queryOptions } from '@tanstack/query-core';");
364
- }
365
383
  lines.push("import { route } from './routes.js';");
366
384
  lines.push("import { createFetcher } from '@dudousxd/nestjs-inertia-client';");
385
+ if (importsByFile.size > 0 && outDir) {
386
+ lines.push("");
387
+ for (const [filePath, names] of importsByFile) {
388
+ let relPath = (0, import_node_path3.relative)(outDir, filePath).replace(/\.ts$/, "");
389
+ if (!relPath.startsWith(".")) relPath = `./${relPath}`;
390
+ const sortedNames = [
391
+ ...names
392
+ ].sort();
393
+ lines.push(`import type { ${sortedNames.join(", ")} } from '${relPath}';`);
394
+ }
395
+ }
367
396
  lines.push("");
368
397
  lines.push("export const fetcher = createFetcher();");
369
398
  lines.push("");
@@ -960,7 +989,8 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
960
989
  const name = import_ts_morph.Node.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
961
990
  if (name === "string" || name === "number" || name === "boolean") return name;
962
991
  if (name === "Date") return "string";
963
- if (name === "unknown" || name === "any") return "unknown";
992
+ if (name === "unknown" || name === "any" || name === "void") return "unknown";
993
+ if (name === "StreamableFile" || name === "Observable" || name === "ReadableStream") return "unknown";
964
994
  if (name === "Array") {
965
995
  const typeArgs = typeNode.getTypeArguments();
966
996
  const firstTypeArg = typeArgs[0];
@@ -1108,6 +1138,68 @@ function resolveIdentifierToClassType(node, sourceFile, project, depth) {
1108
1138
  return name;
1109
1139
  }
1110
1140
  __name(resolveIdentifierToClassType, "resolveIdentifierToClassType");
1141
+ function tryResolveTypeRef(typeNode, sourceFile, project) {
1142
+ if (import_ts_morph.Node.isTypeReference(typeNode)) {
1143
+ const typeName = typeNode.getTypeName();
1144
+ const name = import_ts_morph.Node.isIdentifier(typeName) ? typeName.getText() : null;
1145
+ if (!name) return null;
1146
+ if (name === "Promise") {
1147
+ const typeArgs = typeNode.getTypeArguments();
1148
+ const first = typeArgs[0];
1149
+ if (first) return tryResolveTypeRef(first, sourceFile, project);
1150
+ return null;
1151
+ }
1152
+ if (name === "Array") {
1153
+ const typeArgs = typeNode.getTypeArguments();
1154
+ const first = typeArgs[0];
1155
+ if (first) {
1156
+ const inner = tryResolveTypeRef(first, sourceFile, project);
1157
+ if (inner) return {
1158
+ ...inner,
1159
+ isArray: true
1160
+ };
1161
+ }
1162
+ return null;
1163
+ }
1164
+ if ([
1165
+ "string",
1166
+ "number",
1167
+ "boolean",
1168
+ "void",
1169
+ "unknown",
1170
+ "any",
1171
+ "Date"
1172
+ ].includes(name)) {
1173
+ return null;
1174
+ }
1175
+ const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1176
+ if (localDecl && localDecl.isExported()) {
1177
+ return {
1178
+ name,
1179
+ filePath: sourceFile.getFilePath()
1180
+ };
1181
+ }
1182
+ const resolved = resolveImportedType(name, sourceFile, project);
1183
+ if (resolved && (resolved.kind === "class" || resolved.kind === "interface")) {
1184
+ const decl = resolved.decl;
1185
+ if (decl.isExported()) {
1186
+ return {
1187
+ name,
1188
+ filePath: resolved.file.getFilePath()
1189
+ };
1190
+ }
1191
+ }
1192
+ }
1193
+ if (import_ts_morph.Node.isArrayTypeNode(typeNode)) {
1194
+ const inner = tryResolveTypeRef(typeNode.getElementTypeNode(), sourceFile, project);
1195
+ if (inner) return {
1196
+ ...inner,
1197
+ isArray: true
1198
+ };
1199
+ }
1200
+ return null;
1201
+ }
1202
+ __name(tryResolveTypeRef, "tryResolveTypeRef");
1111
1203
  function extractDtoContract(method, sourceFile, project) {
1112
1204
  const body = extractBodyType(method, sourceFile, project);
1113
1205
  const query = extractQueryType(method, sourceFile, project);
@@ -1116,11 +1208,61 @@ function extractDtoContract(method, sourceFile, project) {
1116
1208
  if (body === null && query === null && paramsType === null && response === "unknown") {
1117
1209
  return null;
1118
1210
  }
1211
+ let bodyRef = null;
1212
+ let queryRef = null;
1213
+ let responseRef = null;
1214
+ for (const param of method.getParameters()) {
1215
+ if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode()) {
1216
+ bodyRef = tryResolveTypeRef(param.getTypeNode(), sourceFile, project);
1217
+ }
1218
+ if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode()) {
1219
+ queryRef = tryResolveTypeRef(param.getTypeNode(), sourceFile, project);
1220
+ }
1221
+ }
1222
+ const returnTypeNode = method.getReturnTypeNode();
1223
+ if (returnTypeNode) {
1224
+ responseRef = tryResolveTypeRef(returnTypeNode, sourceFile, project);
1225
+ }
1226
+ if (!responseRef) {
1227
+ const apiResp = method.getDecorator("ApiResponse");
1228
+ if (apiResp) {
1229
+ const args = apiResp.getArguments();
1230
+ const optsArg = args[0];
1231
+ if (optsArg && import_ts_morph.Node.isObjectLiteralExpression(optsArg)) {
1232
+ for (const prop of optsArg.getProperties()) {
1233
+ if (import_ts_morph.Node.isPropertyAssignment(prop) && prop.getName() === "type") {
1234
+ const val = prop.getInitializer();
1235
+ if (val && import_ts_morph.Node.isIdentifier(val)) {
1236
+ const name = val.getText();
1237
+ const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1238
+ if (localDecl && localDecl.isExported()) {
1239
+ responseRef = {
1240
+ name,
1241
+ filePath: sourceFile.getFilePath()
1242
+ };
1243
+ } else {
1244
+ const resolved = resolveImportedType(name, sourceFile, project);
1245
+ if (resolved && (resolved.kind === "class" || resolved.kind === "interface") && resolved.decl.isExported()) {
1246
+ responseRef = {
1247
+ name,
1248
+ filePath: resolved.file.getFilePath()
1249
+ };
1250
+ }
1251
+ }
1252
+ }
1253
+ }
1254
+ }
1255
+ }
1256
+ }
1257
+ }
1119
1258
  return {
1120
1259
  query,
1121
1260
  body,
1122
1261
  response,
1123
- params: paramsType
1262
+ params: paramsType,
1263
+ queryRef,
1264
+ bodyRef,
1265
+ responseRef
1124
1266
  };
1125
1267
  }
1126
1268
  __name(extractDtoContract, "extractDtoContract");
@@ -1259,7 +1401,10 @@ function extractFromSourceFile(sourceFile, project) {
1259
1401
  contractSource: {
1260
1402
  query: dtoContract.query,
1261
1403
  body: dtoContract.body,
1262
- response: dtoContract.response
1404
+ response: dtoContract.response,
1405
+ queryRef: dtoContract.queryRef,
1406
+ bodyRef: dtoContract.bodyRef,
1407
+ responseRef: dtoContract.responseRef
1263
1408
  }
1264
1409
  }
1265
1410
  } : {}
@@ -1432,7 +1577,7 @@ async function watch(config, onChange) {
1432
1577
  __name(watch, "watch");
1433
1578
 
1434
1579
  // src/index.ts
1435
- var VERSION = "1.0.6";
1580
+ var VERSION = "1.2.0";
1436
1581
 
1437
1582
  // src/cli/codegen.ts
1438
1583
  async function runCodegen(opts = {}) {