@dudousxd/nestjs-inertia-codegen 1.2.0 → 1.3.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 +11 -0
- package/dist/cli/main.cjs +133 -31
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +131 -29
- package/dist/cli/main.js.map +1 -1
- package/dist/index.cjs +119 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +119 -25
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog — @dudousxd/nestjs-inertia-codegen
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- feat(codegen): ReturnType<import(...)> for response types, queryKey helper, TanStack helpers, type ref imports, path alias resolution, debug mode
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies []:
|
|
12
|
+
- @dudousxd/nestjs-inertia@2.0.0
|
|
13
|
+
|
|
3
14
|
## 3.0.0
|
|
4
15
|
|
|
5
16
|
### Minor Changes
|
package/dist/cli/main.cjs
CHANGED
|
@@ -287,7 +287,20 @@ function insertIntoTree(tree, segments, leaf, fullName) {
|
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
289
|
__name(insertIntoTree, "insertIntoTree");
|
|
290
|
-
function
|
|
290
|
+
function buildResponseType(c, outDir) {
|
|
291
|
+
if (c.controllerRef) {
|
|
292
|
+
let relPath = (0, import_node_path3.relative)(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
|
|
293
|
+
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
294
|
+
return `Awaited<ReturnType<import('${relPath}').${c.controllerRef.className}['${c.controllerRef.methodName}']>>`;
|
|
295
|
+
}
|
|
296
|
+
const respRef = c.contractSource.responseRef;
|
|
297
|
+
if (respRef) {
|
|
298
|
+
return respRef.isArray ? `Array<${respRef.name}>` : respRef.name;
|
|
299
|
+
}
|
|
300
|
+
return c.contractSource.response;
|
|
301
|
+
}
|
|
302
|
+
__name(buildResponseType, "buildResponseType");
|
|
303
|
+
function emitRouterTypeBlock(tree, indent, outDir) {
|
|
291
304
|
const pad = " ".repeat(indent);
|
|
292
305
|
const lines = [];
|
|
293
306
|
for (const [key, node] of tree) {
|
|
@@ -299,14 +312,13 @@ function emitRouterTypeBlock(tree, indent) {
|
|
|
299
312
|
const query = queryRef ? queryRef.isArray ? `Array<${queryRef.name}>` : queryRef.name : c.contractSource.query ?? "never";
|
|
300
313
|
const bodyRef = c.contractSource.bodyRef;
|
|
301
314
|
const body = method === "GET" ? "never" : bodyRef ? bodyRef.isArray ? `Array<${bodyRef.name}>` : bodyRef.name : c.contractSource.body ?? "never";
|
|
302
|
-
const
|
|
303
|
-
const response = respRef ? respRef.isArray ? `Array<${respRef.name}>` : respRef.name : c.contractSource.response;
|
|
315
|
+
const response = buildResponseType(c, outDir);
|
|
304
316
|
const safeMethod = JSON.stringify(method);
|
|
305
317
|
const safeUrl = JSON.stringify(c.path);
|
|
306
318
|
lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; query: ${query}; body: ${body}; response: ${response} };`);
|
|
307
319
|
} else {
|
|
308
320
|
lines.push(`${pad}${objKey}: {`);
|
|
309
|
-
lines.push(...emitRouterTypeBlock(node.children, indent + 2));
|
|
321
|
+
lines.push(...emitRouterTypeBlock(node.children, indent + 2, outDir));
|
|
310
322
|
lines.push(`${pad}};`);
|
|
311
323
|
}
|
|
312
324
|
}
|
|
@@ -328,18 +340,20 @@ function emitApiObjectBlock(tree, indent) {
|
|
|
328
340
|
const typeAccess = buildRouterTypeAccess(c.name);
|
|
329
341
|
lines.push(`${pad}${objKey}: {`);
|
|
330
342
|
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}
|
|
333
|
-
lines.push(`${pad}
|
|
334
|
-
lines.push(`${pad}
|
|
343
|
+
lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) =>`);
|
|
344
|
+
lines.push(`${pad} _queryOptions({`);
|
|
345
|
+
lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
|
|
346
|
+
lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
|
|
347
|
+
lines.push(`${pad} }),`);
|
|
335
348
|
lines.push(`${pad}},`);
|
|
336
349
|
} else {
|
|
337
350
|
const typeAccess = buildRouterTypeAccess(c.name);
|
|
338
351
|
lines.push(`${pad}${objKey}: {`);
|
|
339
352
|
lines.push(`${pad} queryKey: () => [${flatName}] as const,`);
|
|
340
|
-
lines.push(`${pad} mutationOptions: ()
|
|
341
|
-
lines.push(`${pad}
|
|
342
|
-
lines.push(`${pad}
|
|
353
|
+
lines.push(`${pad} mutationOptions: () =>`);
|
|
354
|
+
lines.push(`${pad} _mutationOptions({`);
|
|
355
|
+
lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
|
|
356
|
+
lines.push(`${pad} }),`);
|
|
343
357
|
lines.push(`${pad}},`);
|
|
344
358
|
}
|
|
345
359
|
} else {
|
|
@@ -362,11 +376,15 @@ function buildApiFile(routes, outDir) {
|
|
|
362
376
|
for (const r of contracted) {
|
|
363
377
|
const cs = r.contract?.contractSource;
|
|
364
378
|
if (!cs) continue;
|
|
365
|
-
|
|
379
|
+
const refs = r.controllerRef ? [
|
|
380
|
+
cs.queryRef,
|
|
381
|
+
cs.bodyRef
|
|
382
|
+
] : [
|
|
366
383
|
cs.queryRef,
|
|
367
384
|
cs.bodyRef,
|
|
368
385
|
cs.responseRef
|
|
369
|
-
]
|
|
386
|
+
];
|
|
387
|
+
for (const ref of refs) {
|
|
370
388
|
if (!ref) continue;
|
|
371
389
|
let names = importsByFile.get(ref.filePath);
|
|
372
390
|
if (!names) {
|
|
@@ -376,10 +394,18 @@ function buildApiFile(routes, outDir) {
|
|
|
376
394
|
names.add(ref.name);
|
|
377
395
|
}
|
|
378
396
|
}
|
|
397
|
+
const hasGetRoutes = contracted.some((r) => r.method === "GET");
|
|
398
|
+
const hasMutationRoutes = contracted.some((r) => r.method !== "GET");
|
|
379
399
|
const lines = [
|
|
380
400
|
"// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.",
|
|
381
401
|
""
|
|
382
402
|
];
|
|
403
|
+
const tqImports = [];
|
|
404
|
+
if (hasGetRoutes) tqImports.push("queryOptions as _queryOptions");
|
|
405
|
+
if (hasMutationRoutes) tqImports.push("mutationOptions as _mutationOptions");
|
|
406
|
+
if (tqImports.length > 0) {
|
|
407
|
+
lines.push(`import { ${tqImports.join(", ")} } from '@tanstack/react-query';`);
|
|
408
|
+
}
|
|
383
409
|
lines.push("import { route } from './routes.js';");
|
|
384
410
|
lines.push("import { createFetcher } from '@dudousxd/nestjs-inertia-client';");
|
|
385
411
|
if (importsByFile.size > 0 && outDir) {
|
|
@@ -433,13 +459,14 @@ function buildApiFile(routes, outDir) {
|
|
|
433
459
|
method: r.method,
|
|
434
460
|
name,
|
|
435
461
|
path: r.path,
|
|
462
|
+
controllerRef: r.controllerRef,
|
|
436
463
|
contractSource: c.contractSource
|
|
437
464
|
};
|
|
438
465
|
insertIntoTree(tree, segments, leaf, name);
|
|
439
466
|
}
|
|
440
467
|
void detectCollisions;
|
|
441
468
|
lines.push("export type ApiRouter = {");
|
|
442
|
-
lines.push(...emitRouterTypeBlock(tree, 2));
|
|
469
|
+
lines.push(...emitRouterTypeBlock(tree, 2, outDir ?? ""));
|
|
443
470
|
lines.push("};");
|
|
444
471
|
lines.push("");
|
|
445
472
|
lines.push("export const api = {");
|
|
@@ -709,9 +736,28 @@ var import_node_path10 = require("path");
|
|
|
709
736
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
710
737
|
|
|
711
738
|
// src/discovery/contracts-fast.ts
|
|
739
|
+
var import_node_fs = require("fs");
|
|
712
740
|
var import_node_path8 = require("path");
|
|
713
741
|
var import_fast_glob2 = __toESM(require("fast-glob"), 1);
|
|
714
742
|
var import_ts_morph = require("ts-morph");
|
|
743
|
+
var _projectRoot = "";
|
|
744
|
+
var _tsconfigPaths = null;
|
|
745
|
+
var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
|
|
746
|
+
function dbg(...args) {
|
|
747
|
+
if (_debug) console.log("[codegen:debug]", ...args);
|
|
748
|
+
}
|
|
749
|
+
__name(dbg, "dbg");
|
|
750
|
+
function loadTsconfigPaths(tsconfigPath) {
|
|
751
|
+
try {
|
|
752
|
+
const raw = (0, import_node_fs.readFileSync)(tsconfigPath, "utf8");
|
|
753
|
+
const stripped = raw.replace(/\/\/.*$/gm, "");
|
|
754
|
+
const parsed = JSON.parse(stripped);
|
|
755
|
+
return parsed.compilerOptions?.paths ?? null;
|
|
756
|
+
} catch {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
__name(loadTsconfigPaths, "loadTsconfigPaths");
|
|
715
761
|
async function discoverContractsFast(opts) {
|
|
716
762
|
const { cwd, glob, tsconfig } = opts;
|
|
717
763
|
const tsconfigPath = tsconfig ? (0, import_node_path8.resolve)(tsconfig) : (0, import_node_path8.join)(cwd, "tsconfig.json");
|
|
@@ -744,6 +790,8 @@ async function discoverContractsFast(opts) {
|
|
|
744
790
|
project.addSourceFileAtPath(f);
|
|
745
791
|
}
|
|
746
792
|
const routes = [];
|
|
793
|
+
_projectRoot = cwd;
|
|
794
|
+
_tsconfigPaths = loadTsconfigPaths(tsconfigPath);
|
|
747
795
|
for (const sourceFile of project.getSourceFiles()) {
|
|
748
796
|
routes.push(...extractFromSourceFile(sourceFile, project));
|
|
749
797
|
}
|
|
@@ -945,17 +993,41 @@ function findTypeInFile(name, file) {
|
|
|
945
993
|
return null;
|
|
946
994
|
}
|
|
947
995
|
__name(findTypeInFile, "findTypeInFile");
|
|
996
|
+
function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
997
|
+
if (moduleSpecifier.startsWith(".")) {
|
|
998
|
+
const dir = (0, import_node_path8.dirname)(sourceFile.getFilePath());
|
|
999
|
+
return [
|
|
1000
|
+
(0, import_node_path8.resolve)(dir, `${moduleSpecifier}.ts`),
|
|
1001
|
+
(0, import_node_path8.resolve)(dir, moduleSpecifier, "index.ts")
|
|
1002
|
+
];
|
|
1003
|
+
}
|
|
1004
|
+
const baseUrl = _projectRoot;
|
|
1005
|
+
dbg("resolveModuleSpecifier", moduleSpecifier, "paths:", JSON.stringify(_tsconfigPaths), "baseUrl:", baseUrl);
|
|
1006
|
+
if (_tsconfigPaths) {
|
|
1007
|
+
for (const [pattern, mappings] of Object.entries(_tsconfigPaths)) {
|
|
1008
|
+
const prefix = pattern.replace("*", "");
|
|
1009
|
+
if (moduleSpecifier.startsWith(prefix)) {
|
|
1010
|
+
const rest = moduleSpecifier.slice(prefix.length);
|
|
1011
|
+
const candidates = [];
|
|
1012
|
+
for (const mapping of mappings) {
|
|
1013
|
+
const resolved = (0, import_node_path8.resolve)(baseUrl, mapping.replace("*", rest));
|
|
1014
|
+
candidates.push(`${resolved}.ts`, (0, import_node_path8.resolve)(resolved, "index.ts"));
|
|
1015
|
+
}
|
|
1016
|
+
dbg(" resolved candidates:", candidates);
|
|
1017
|
+
return candidates;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
return [];
|
|
1022
|
+
}
|
|
1023
|
+
__name(resolveModuleSpecifier, "resolveModuleSpecifier");
|
|
948
1024
|
function resolveImportedType(name, sourceFile, project) {
|
|
949
1025
|
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
950
1026
|
const namedImport = importDecl.getNamedImports().find((n) => n.getName() === name);
|
|
951
1027
|
if (!namedImport) continue;
|
|
952
1028
|
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
const candidates = [
|
|
956
|
-
(0, import_node_path8.resolve)(dir, `${moduleSpecifier}.ts`),
|
|
957
|
-
(0, import_node_path8.resolve)(dir, moduleSpecifier, "index.ts")
|
|
958
|
-
];
|
|
1029
|
+
const candidates = resolveModuleSpecifier(moduleSpecifier, sourceFile, project);
|
|
1030
|
+
if (candidates.length === 0) continue;
|
|
959
1031
|
for (const candidate of candidates) {
|
|
960
1032
|
let importedFile = project.getSourceFile(candidate);
|
|
961
1033
|
if (!importedFile) {
|
|
@@ -999,6 +1071,18 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
|
999
1071
|
}
|
|
1000
1072
|
return "Array<unknown>";
|
|
1001
1073
|
}
|
|
1074
|
+
if ([
|
|
1075
|
+
"Record",
|
|
1076
|
+
"Omit",
|
|
1077
|
+
"Pick",
|
|
1078
|
+
"Partial",
|
|
1079
|
+
"Required",
|
|
1080
|
+
"Readonly",
|
|
1081
|
+
"Map",
|
|
1082
|
+
"Set"
|
|
1083
|
+
].includes(name)) {
|
|
1084
|
+
return typeNode.getText();
|
|
1085
|
+
}
|
|
1002
1086
|
if (name === "Promise") {
|
|
1003
1087
|
const typeArgs = typeNode.getTypeArguments();
|
|
1004
1088
|
const firstTypeArg = typeArgs[0];
|
|
@@ -1011,7 +1095,8 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
|
1011
1095
|
if (resolved) {
|
|
1012
1096
|
return expandTypeDecl(resolved, project, depth - 1);
|
|
1013
1097
|
}
|
|
1014
|
-
|
|
1098
|
+
dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
|
|
1099
|
+
return "unknown";
|
|
1015
1100
|
}
|
|
1016
1101
|
const kind = typeNode.getKind();
|
|
1017
1102
|
if (kind === import_ts_morph.SyntaxKind.StringKeyword) return "string";
|
|
@@ -1173,7 +1258,7 @@ function tryResolveTypeRef(typeNode, sourceFile, project) {
|
|
|
1173
1258
|
return null;
|
|
1174
1259
|
}
|
|
1175
1260
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
1176
|
-
if (localDecl
|
|
1261
|
+
if (localDecl?.isExported()) {
|
|
1177
1262
|
return {
|
|
1178
1263
|
name,
|
|
1179
1264
|
filePath: sourceFile.getFilePath()
|
|
@@ -1235,7 +1320,7 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
1235
1320
|
if (val && import_ts_morph.Node.isIdentifier(val)) {
|
|
1236
1321
|
const name = val.getText();
|
|
1237
1322
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
1238
|
-
if (localDecl
|
|
1323
|
+
if (localDecl?.isExported()) {
|
|
1239
1324
|
responseRef = {
|
|
1240
1325
|
name,
|
|
1241
1326
|
filePath: sourceFile.getFilePath()
|
|
@@ -1361,6 +1446,11 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
1361
1446
|
path: combined,
|
|
1362
1447
|
name: routeName,
|
|
1363
1448
|
params,
|
|
1449
|
+
controllerRef: {
|
|
1450
|
+
className,
|
|
1451
|
+
methodName,
|
|
1452
|
+
filePath: sourceFile.getFilePath()
|
|
1453
|
+
},
|
|
1364
1454
|
contract: {
|
|
1365
1455
|
contractSource: {
|
|
1366
1456
|
query: contractDef.query,
|
|
@@ -1395,7 +1485,11 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
1395
1485
|
path: combined,
|
|
1396
1486
|
name: routeName,
|
|
1397
1487
|
params,
|
|
1398
|
-
|
|
1488
|
+
controllerRef: {
|
|
1489
|
+
className,
|
|
1490
|
+
methodName,
|
|
1491
|
+
filePath: sourceFile.getFilePath()
|
|
1492
|
+
},
|
|
1399
1493
|
...dtoContract ? {
|
|
1400
1494
|
contract: {
|
|
1401
1495
|
contractSource: {
|
|
@@ -1577,7 +1671,7 @@ async function watch(config, onChange) {
|
|
|
1577
1671
|
__name(watch, "watch");
|
|
1578
1672
|
|
|
1579
1673
|
// src/index.ts
|
|
1580
|
-
var VERSION = "1.
|
|
1674
|
+
var VERSION = "1.3.0";
|
|
1581
1675
|
|
|
1582
1676
|
// src/cli/codegen.ts
|
|
1583
1677
|
async function runCodegen(opts = {}) {
|
|
@@ -1609,7 +1703,7 @@ __name(runCodegen, "runCodegen");
|
|
|
1609
1703
|
|
|
1610
1704
|
// src/cli/init.ts
|
|
1611
1705
|
var import_node_child_process = require("child_process");
|
|
1612
|
-
var
|
|
1706
|
+
var import_node_fs2 = require("fs");
|
|
1613
1707
|
var import_promises10 = require("fs/promises");
|
|
1614
1708
|
var import_node_path11 = require("path");
|
|
1615
1709
|
var import_node_readline = require("readline");
|
|
@@ -1833,7 +1927,7 @@ __name(findAfterLastImport, "findAfterLastImport");
|
|
|
1833
1927
|
function patchAppModule(filePath, rootView) {
|
|
1834
1928
|
let content;
|
|
1835
1929
|
try {
|
|
1836
|
-
content = (0,
|
|
1930
|
+
content = (0, import_node_fs2.readFileSync)(filePath, "utf8");
|
|
1837
1931
|
} catch {
|
|
1838
1932
|
return "skipped";
|
|
1839
1933
|
}
|
|
@@ -1871,14 +1965,14 @@ ${indent}HomeController,${content.slice(bracketPos)}`;
|
|
|
1871
1965
|
}
|
|
1872
1966
|
}
|
|
1873
1967
|
if (!changed) return "already";
|
|
1874
|
-
(0,
|
|
1968
|
+
(0, import_node_fs2.writeFileSync)(filePath, content, "utf8");
|
|
1875
1969
|
return "patched";
|
|
1876
1970
|
}
|
|
1877
1971
|
__name(patchAppModule, "patchAppModule");
|
|
1878
1972
|
function patchMainTs(filePath) {
|
|
1879
1973
|
let content;
|
|
1880
1974
|
try {
|
|
1881
|
-
content = (0,
|
|
1975
|
+
content = (0, import_node_fs2.readFileSync)(filePath, "utf8");
|
|
1882
1976
|
} catch {
|
|
1883
1977
|
return "skipped";
|
|
1884
1978
|
}
|
|
@@ -1902,7 +1996,7 @@ ${content.slice(insertAt)}`;
|
|
|
1902
1996
|
});`;
|
|
1903
1997
|
content = `${content.slice(0, insertAfterPos)}
|
|
1904
1998
|
${viteSetup}${content.slice(insertAfterPos)}`;
|
|
1905
|
-
(0,
|
|
1999
|
+
(0, import_node_fs2.writeFileSync)(filePath, content, "utf8");
|
|
1906
2000
|
return "patched";
|
|
1907
2001
|
}
|
|
1908
2002
|
__name(patchMainTs, "patchMainTs");
|
|
@@ -1951,11 +2045,19 @@ function htmlShellTemplate(framework, _engine) {
|
|
|
1951
2045
|
__name(htmlShellTemplate, "htmlShellTemplate");
|
|
1952
2046
|
function viteConfigTemplate(framework) {
|
|
1953
2047
|
const pluginOption = `{ ${framework}: true }`;
|
|
1954
|
-
return `import {
|
|
2048
|
+
return `import { resolve } from 'node:path';
|
|
2049
|
+
import { defineConfig } from 'vite';
|
|
1955
2050
|
import nestInertia from '@dudousxd/nestjs-inertia-vite/plugin';
|
|
1956
2051
|
|
|
1957
2052
|
export default defineConfig({
|
|
1958
2053
|
plugins: [nestInertia(${pluginOption})],
|
|
2054
|
+
resolve: {
|
|
2055
|
+
alias: {
|
|
2056
|
+
'@': resolve(__dirname, 'src'),
|
|
2057
|
+
'~': resolve(__dirname, 'inertia'),
|
|
2058
|
+
'~codegen': resolve(__dirname, '.nestjs-inertia'),
|
|
2059
|
+
},
|
|
2060
|
+
},
|
|
1959
2061
|
});
|
|
1960
2062
|
`;
|
|
1961
2063
|
}
|