@dudousxd/nestjs-codegen 0.2.1 → 0.4.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 +57 -0
- package/dist/cli/main.cjs +1229 -1612
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +1211 -1594
- package/dist/cli/main.js.map +1 -1
- package/dist/extension/index.cjs +12 -2
- package/dist/extension/index.cjs.map +1 -1
- package/dist/extension/index.d.cts +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/extension/index.js +10 -1
- package/dist/extension/index.js.map +1 -1
- package/dist/{index-BwIRjOQA.d.cts → index-DA4uySjo.d.cts} +84 -41
- package/dist/{index-BwIRjOQA.d.ts → index-DA4uySjo.d.ts} +84 -41
- package/dist/index.cjs +1073 -1460
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -15
- package/dist/index.d.ts +56 -15
- package/dist/index.js +1043 -1430
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +934 -1365
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.d.cts +9 -2
- package/dist/nest/index.d.ts +9 -2
- package/dist/nest/index.js +921 -1352
- package/dist/nest/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -42,10 +42,10 @@ __export(src_exports, {
|
|
|
42
42
|
extractSchemaFromDto: () => extractSchemaFromDto,
|
|
43
43
|
generate: () => generate,
|
|
44
44
|
loadConfig: () => loadConfig,
|
|
45
|
+
renderTsType: () => renderTsType,
|
|
45
46
|
resolveAdapter: () => resolveAdapter,
|
|
46
47
|
resolveConfig: () => resolveConfig,
|
|
47
|
-
watch: () => watch
|
|
48
|
-
zodAdapter: () => zodAdapter
|
|
48
|
+
watch: () => watch
|
|
49
49
|
});
|
|
50
50
|
module.exports = __toCommonJS(src_exports);
|
|
51
51
|
|
|
@@ -73,112 +73,9 @@ var CodegenError = class extends Error {
|
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
-
// src/adapters/zod.ts
|
|
77
|
-
function toObjectKey(name) {
|
|
78
|
-
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
79
|
-
}
|
|
80
|
-
function messageSuffix(messageRaw2) {
|
|
81
|
-
return messageRaw2 ? `{ message: ${messageRaw2} }` : "";
|
|
82
|
-
}
|
|
83
|
-
function renderStringChecks(checks) {
|
|
84
|
-
let out = "";
|
|
85
|
-
for (const c of checks) {
|
|
86
|
-
switch (c.check) {
|
|
87
|
-
case "email":
|
|
88
|
-
out += `.email(${messageSuffix(c.messageRaw)})`;
|
|
89
|
-
break;
|
|
90
|
-
case "url":
|
|
91
|
-
out += `.url(${messageSuffix(c.messageRaw)})`;
|
|
92
|
-
break;
|
|
93
|
-
case "uuid":
|
|
94
|
-
out += `.uuid(${messageSuffix(c.messageRaw)})`;
|
|
95
|
-
break;
|
|
96
|
-
case "regex":
|
|
97
|
-
out += `.regex(${c.pattern})`;
|
|
98
|
-
break;
|
|
99
|
-
case "min":
|
|
100
|
-
out += `.min(${c.value})`;
|
|
101
|
-
break;
|
|
102
|
-
case "max":
|
|
103
|
-
out += `.max(${c.value})`;
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return out;
|
|
108
|
-
}
|
|
109
|
-
function render(node, ctx) {
|
|
110
|
-
switch (node.kind) {
|
|
111
|
-
case "string":
|
|
112
|
-
return `z.string()${renderStringChecks(node.checks)}`;
|
|
113
|
-
case "number": {
|
|
114
|
-
let out = "z.number()";
|
|
115
|
-
for (const c of node.checks) {
|
|
116
|
-
if (c.check === "int") out += ".int()";
|
|
117
|
-
else if (c.check === "positive") out += ".positive()";
|
|
118
|
-
else if (c.check === "negative") out += ".negative()";
|
|
119
|
-
else if (c.check === "min") out += `.min(${c.value})`;
|
|
120
|
-
else if (c.check === "max") out += `.max(${c.value})`;
|
|
121
|
-
}
|
|
122
|
-
return out;
|
|
123
|
-
}
|
|
124
|
-
case "boolean":
|
|
125
|
-
return "z.boolean()";
|
|
126
|
-
case "date":
|
|
127
|
-
return "z.coerce.date()";
|
|
128
|
-
case "unknown":
|
|
129
|
-
return node.note ? `z.unknown() /* ${node.note} */` : "z.unknown()";
|
|
130
|
-
case "instanceof":
|
|
131
|
-
return `z.instanceof(${node.ctor})`;
|
|
132
|
-
case "enum":
|
|
133
|
-
return `z.enum([${node.literals.join(", ")}])`;
|
|
134
|
-
case "literal":
|
|
135
|
-
return `z.literal(${node.raw})`;
|
|
136
|
-
case "union":
|
|
137
|
-
return `z.union([${node.options.map((o) => render(o, ctx)).join(", ")}])`;
|
|
138
|
-
case "object": {
|
|
139
|
-
if (node.fields.length === 0) {
|
|
140
|
-
return node.passthrough ? "z.object({}).passthrough()" : "z.object({})";
|
|
141
|
-
}
|
|
142
|
-
const inner = node.fields.map((f) => `${toObjectKey(f.key)}: ${render(f.value, ctx)}`).join(", ");
|
|
143
|
-
return `z.object({ ${inner} })${node.passthrough ? ".passthrough()" : ""}`;
|
|
144
|
-
}
|
|
145
|
-
case "array":
|
|
146
|
-
return `z.array(${render(node.element, ctx)})`;
|
|
147
|
-
case "optional":
|
|
148
|
-
return `${render(node.inner, ctx)}.optional()`;
|
|
149
|
-
case "ref":
|
|
150
|
-
return node.name;
|
|
151
|
-
case "lazyRef":
|
|
152
|
-
return `z.lazy(() => ${node.name})`;
|
|
153
|
-
case "annotated": {
|
|
154
|
-
const comments = node.unmappable.map((n) => `/* @${n}: not translatable to zod (server-only) */`).join(" ");
|
|
155
|
-
return `${render(node.inner, ctx)} ${comments}`;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
var zodAdapter = {
|
|
160
|
-
name: "zod",
|
|
161
|
-
importStatements(usage) {
|
|
162
|
-
return usage.used ? ["import { z } from 'zod';"] : [];
|
|
163
|
-
},
|
|
164
|
-
render,
|
|
165
|
-
inferType(schemaConst) {
|
|
166
|
-
return `z.infer<typeof ${schemaConst}>`;
|
|
167
|
-
},
|
|
168
|
-
renderModule(mod) {
|
|
169
|
-
const ctx = { named: mod.named };
|
|
170
|
-
const namedNestedSchemas = /* @__PURE__ */ new Map();
|
|
171
|
-
for (const [name, node] of mod.named) {
|
|
172
|
-
namedNestedSchemas.set(name, render(node, ctx));
|
|
173
|
-
}
|
|
174
|
-
return { schemaText: render(mod.root, ctx), namedNestedSchemas, warnings: mod.warnings };
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
|
|
178
76
|
// src/adapters/registry.ts
|
|
179
77
|
function resolveAdapter(option) {
|
|
180
78
|
if (typeof option !== "string") return option;
|
|
181
|
-
if (option === "zod") return zodAdapter;
|
|
182
79
|
const pkg = `@dudousxd/nestjs-codegen-${option}`;
|
|
183
80
|
const named = `${option}Adapter`;
|
|
184
81
|
throw new ConfigError(
|
|
@@ -231,8 +128,21 @@ If this is intentional, move the file inside your project directory.`
|
|
|
231
128
|
function resolveConfig(userConfig, cwd) {
|
|
232
129
|
return applyDefaults(userConfig, cwd ?? process.cwd());
|
|
233
130
|
}
|
|
131
|
+
function validateUserConfig(userConfig) {
|
|
132
|
+
if (userConfig.validation == null) {
|
|
133
|
+
throw new ConfigError(
|
|
134
|
+
"validation adapter is required \u2014 install @dudousxd/nestjs-codegen-zod and pass zodAdapter, or use @dudousxd/nestjs-codegen-valibot / -arktype"
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (userConfig.pages && typeof userConfig.pages.glob !== "string") {
|
|
138
|
+
throw new ConfigError(
|
|
139
|
+
"Config validation failed: `pages.glob` must be a string when `pages` is set"
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
234
143
|
function applyDefaults(userConfig, cwd) {
|
|
235
|
-
|
|
144
|
+
validateUserConfig(userConfig);
|
|
145
|
+
const outDir = userConfig.codegen?.outDir ? resolveAbsolute(cwd, userConfig.codegen.outDir) : (0, import_node_path.join)(cwd, ".nestjs-codegen");
|
|
236
146
|
const resolvedCwd = userConfig.codegen?.cwd ? resolveAbsolute(cwd, userConfig.codegen.cwd) : cwd;
|
|
237
147
|
let app = null;
|
|
238
148
|
if (userConfig.app) {
|
|
@@ -250,7 +160,8 @@ function applyDefaults(userConfig, cwd) {
|
|
|
250
160
|
}
|
|
251
161
|
return {
|
|
252
162
|
extensions: userConfig.extensions ?? [],
|
|
253
|
-
|
|
163
|
+
// Non-null: validateUserConfig() above throws when `validation` is absent.
|
|
164
|
+
validation: resolveAdapter(userConfig.validation),
|
|
254
165
|
pages: userConfig.pages ? {
|
|
255
166
|
glob: userConfig.pages.glob,
|
|
256
167
|
propsExport: userConfig.pages.propsExport ?? "ComponentProps",
|
|
@@ -304,18 +215,12 @@ Run \`nestjs-codegen init\` to create a starter config.`
|
|
|
304
215
|
`Config file must have a default export. Did you forget \`export default defineConfig({...})\`? (${configPath})`
|
|
305
216
|
);
|
|
306
217
|
}
|
|
307
|
-
if (userConfig.pages && typeof userConfig.pages.glob !== "string") {
|
|
308
|
-
throw new ConfigError(
|
|
309
|
-
`Config validation failed: \`pages.glob\` must be a string when \`pages\` is set (${configPath})`
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
218
|
return applyDefaults(userConfig, resolvedCwd);
|
|
313
219
|
}
|
|
314
220
|
|
|
315
221
|
// src/generate.ts
|
|
316
222
|
var import_promises9 = require("fs/promises");
|
|
317
|
-
var
|
|
318
|
-
var import_ts_morph3 = require("ts-morph");
|
|
223
|
+
var import_node_path10 = require("path");
|
|
319
224
|
|
|
320
225
|
// src/discovery/pages.ts
|
|
321
226
|
var import_promises2 = require("fs/promises");
|
|
@@ -370,6 +275,7 @@ function extractPropsSource(source, exportName) {
|
|
|
370
275
|
}
|
|
371
276
|
|
|
372
277
|
// src/discovery/shared-props.ts
|
|
278
|
+
var import_node_path3 = require("path");
|
|
373
279
|
var import_ts_morph = require("ts-morph");
|
|
374
280
|
function discoverSharedProps(project, moduleEntry) {
|
|
375
281
|
try {
|
|
@@ -390,6 +296,31 @@ function discoverSharedProps(project, moduleEntry) {
|
|
|
390
296
|
return null;
|
|
391
297
|
}
|
|
392
298
|
}
|
|
299
|
+
function discoverSharedPropsFromConfig(config) {
|
|
300
|
+
if (!config.app?.moduleEntry) return null;
|
|
301
|
+
try {
|
|
302
|
+
const tsconfigPath = config.app.tsconfig ?? (0, import_node_path3.join)(config.codegen.cwd, "tsconfig.json");
|
|
303
|
+
let project;
|
|
304
|
+
try {
|
|
305
|
+
project = new import_ts_morph.Project({
|
|
306
|
+
tsConfigFilePath: tsconfigPath,
|
|
307
|
+
skipAddingFilesFromTsConfig: true,
|
|
308
|
+
skipLoadingLibFiles: true,
|
|
309
|
+
skipFileDependencyResolution: true
|
|
310
|
+
});
|
|
311
|
+
} catch {
|
|
312
|
+
project = new import_ts_morph.Project({
|
|
313
|
+
skipAddingFilesFromTsConfig: true,
|
|
314
|
+
skipLoadingLibFiles: true,
|
|
315
|
+
skipFileDependencyResolution: true,
|
|
316
|
+
compilerOptions: { allowJs: true, strict: false }
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
return discoverSharedProps(project, config.app.moduleEntry);
|
|
320
|
+
} catch {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
393
324
|
function findForRootCall(sourceFile) {
|
|
394
325
|
const callExpressions = sourceFile.getDescendantsOfKind(import_ts_morph.SyntaxKind.CallExpression);
|
|
395
326
|
for (const call of callExpressions) {
|
|
@@ -409,9 +340,9 @@ function findForRootCall(sourceFile) {
|
|
|
409
340
|
function findShareInitializer(forRootCall) {
|
|
410
341
|
if (!import_ts_morph.Node.isCallExpression(forRootCall)) return null;
|
|
411
342
|
const args = forRootCall.getArguments();
|
|
412
|
-
const
|
|
413
|
-
if (!
|
|
414
|
-
for (const prop of
|
|
343
|
+
const firstArg2 = args[0];
|
|
344
|
+
if (!firstArg2 || !import_ts_morph.Node.isObjectLiteralExpression(firstArg2)) return null;
|
|
345
|
+
for (const prop of firstArg2.getProperties()) {
|
|
415
346
|
if (import_ts_morph.Node.isPropertyAssignment(prop) && prop.getName() === "share") {
|
|
416
347
|
return prop.getInitializer() ?? null;
|
|
417
348
|
}
|
|
@@ -510,9 +441,9 @@ function extractFromReturnTypeAnnotation(typeNode) {
|
|
|
510
441
|
const typeName = typeNode.getTypeName();
|
|
511
442
|
if (import_ts_morph.Node.isIdentifier(typeName) && typeName.getText() === "Promise") {
|
|
512
443
|
const typeArgs = typeNode.getTypeArguments();
|
|
513
|
-
const
|
|
514
|
-
if (
|
|
515
|
-
return extractFromReturnTypeAnnotation(
|
|
444
|
+
const firstArg2 = typeArgs[0];
|
|
445
|
+
if (firstArg2) {
|
|
446
|
+
return extractFromReturnTypeAnnotation(firstArg2);
|
|
516
447
|
}
|
|
517
448
|
return null;
|
|
518
449
|
}
|
|
@@ -618,25 +549,14 @@ function inferExpressionType(node) {
|
|
|
618
549
|
|
|
619
550
|
// src/emit/emit-api.ts
|
|
620
551
|
var import_promises3 = require("fs/promises");
|
|
621
|
-
var
|
|
552
|
+
var import_node_path4 = require("path");
|
|
622
553
|
|
|
623
554
|
// src/extension/registry.ts
|
|
624
555
|
var import_ts_morph2 = require("ts-morph");
|
|
625
556
|
function resolveApiSlots(extensions) {
|
|
626
|
-
let transport;
|
|
627
|
-
let transportOwner;
|
|
628
557
|
let layer;
|
|
629
558
|
let layerOwner;
|
|
630
559
|
for (const ext of extensions) {
|
|
631
|
-
if (ext.apiTransport) {
|
|
632
|
-
if (transport) {
|
|
633
|
-
throw new CodegenError(
|
|
634
|
-
`api transport claimed by both "${transportOwner}" and "${ext.name}" \u2014 only one extension may set apiTransport.`
|
|
635
|
-
);
|
|
636
|
-
}
|
|
637
|
-
transport = ext.apiTransport;
|
|
638
|
-
transportOwner = ext.name;
|
|
639
|
-
}
|
|
640
560
|
if (ext.apiClientLayer) {
|
|
641
561
|
if (layer) {
|
|
642
562
|
throw new CodegenError(
|
|
@@ -648,11 +568,22 @@ function resolveApiSlots(extensions) {
|
|
|
648
568
|
}
|
|
649
569
|
}
|
|
650
570
|
return {
|
|
651
|
-
...transport ? { transport } : {},
|
|
652
571
|
...layer ? { layer } : {}
|
|
653
572
|
};
|
|
654
573
|
}
|
|
655
574
|
var CORE_FILES = /* @__PURE__ */ new Set(["routes.ts", "api.ts", "forms.ts", "index.d.ts", "pages.d.ts"]);
|
|
575
|
+
function mergeExclusive(target, incoming, {
|
|
576
|
+
owner,
|
|
577
|
+
describe
|
|
578
|
+
}) {
|
|
579
|
+
for (const [key, value] of incoming) {
|
|
580
|
+
const prev = target.get(key);
|
|
581
|
+
if (prev !== void 0) {
|
|
582
|
+
throw new CodegenError(describe(key, prev.owner, owner));
|
|
583
|
+
}
|
|
584
|
+
target.set(key, { value, owner });
|
|
585
|
+
}
|
|
586
|
+
}
|
|
656
587
|
function createExtensionContext(config, getRoutes) {
|
|
657
588
|
let project;
|
|
658
589
|
return {
|
|
@@ -697,29 +628,36 @@ async function collectEmittedFiles(extensions, ctx) {
|
|
|
697
628
|
`Extension "${ext.name}" tried to emit the core-owned file "${file.path}". Core files (${[...CORE_FILES].join(", ")}) cannot be produced by extensions.`
|
|
698
629
|
);
|
|
699
630
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
);
|
|
705
|
-
}
|
|
706
|
-
owners.set(key, ext.name);
|
|
631
|
+
mergeExclusive(owners, [[key, file]], {
|
|
632
|
+
owner: ext.name,
|
|
633
|
+
describe: (_key, prevOwner, owner) => `Output file "${file.path}" is emitted by both "${prevOwner}" and "${owner}". Two extensions cannot write the same file.`
|
|
634
|
+
});
|
|
707
635
|
files.push(file);
|
|
708
636
|
}
|
|
709
637
|
}
|
|
710
638
|
return files;
|
|
711
639
|
}
|
|
712
640
|
|
|
641
|
+
// src/extension/types.ts
|
|
642
|
+
function requestShape(route) {
|
|
643
|
+
const cs = route.contract?.contractSource;
|
|
644
|
+
const isGet = route.method.toUpperCase() === "GET";
|
|
645
|
+
const isQuery = isGet || !!cs?.filterFields?.length;
|
|
646
|
+
const hasBody = !!cs?.bodyRef || cs?.body != null && cs.body !== "never";
|
|
647
|
+
const hasQuery = isGet || !!cs?.queryRef || cs?.query != null && cs.query !== "never";
|
|
648
|
+
return { isGet, isQuery, hasBody, hasQuery };
|
|
649
|
+
}
|
|
650
|
+
|
|
713
651
|
// src/emit/emit-api.ts
|
|
714
652
|
async function emitApi(routes, outDir, opts = {}) {
|
|
715
653
|
await (0, import_promises3.mkdir)(outDir, { recursive: true });
|
|
716
654
|
const content = buildApiFile(routes, outDir, opts);
|
|
717
|
-
await (0, import_promises3.writeFile)((0,
|
|
655
|
+
await (0, import_promises3.writeFile)((0, import_node_path4.join)(outDir, "api.ts"), content, "utf8");
|
|
718
656
|
}
|
|
719
657
|
function splitName(name) {
|
|
720
658
|
return name.split(".");
|
|
721
659
|
}
|
|
722
|
-
function
|
|
660
|
+
function toObjectKey(segment) {
|
|
723
661
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(segment)) {
|
|
724
662
|
return segment;
|
|
725
663
|
}
|
|
@@ -812,7 +750,7 @@ function emitFilterQueryType(c) {
|
|
|
812
750
|
}
|
|
813
751
|
function buildResponseType(c, outDir) {
|
|
814
752
|
if (c.controllerRef) {
|
|
815
|
-
let relPath = (0,
|
|
753
|
+
let relPath = (0, import_node_path4.relative)(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
|
|
816
754
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
817
755
|
return `Awaited<ReturnType<import('${relPath}').${c.controllerRef.className}['${c.controllerRef.methodName}']>>`;
|
|
818
756
|
}
|
|
@@ -826,7 +764,7 @@ function emitRouterTypeBlock(tree, indent, outDir) {
|
|
|
826
764
|
const pad = " ".repeat(indent);
|
|
827
765
|
const lines = [];
|
|
828
766
|
for (const [key, node] of tree) {
|
|
829
|
-
const objKey =
|
|
767
|
+
const objKey = toObjectKey(key);
|
|
830
768
|
if (node.kind === "leaf") {
|
|
831
769
|
const c = node;
|
|
832
770
|
const method = c.method.toUpperCase();
|
|
@@ -852,15 +790,12 @@ function emitRouterTypeBlock(tree, indent, outDir) {
|
|
|
852
790
|
return lines;
|
|
853
791
|
}
|
|
854
792
|
function buildRequestModel(c) {
|
|
855
|
-
const isGet = c.method.toUpperCase() === "GET";
|
|
856
793
|
const m = c.method.toLowerCase();
|
|
857
794
|
const flat = JSON.stringify(c.name);
|
|
858
795
|
const path = JSON.stringify(c.path);
|
|
859
796
|
const TA = buildRouterTypeAccess(c.name);
|
|
860
797
|
const withParams = hasPathParams(c.params);
|
|
861
|
-
const
|
|
862
|
-
const isQuery = isGet || !!c.contractSource.filterFields?.length;
|
|
863
|
-
const hasQuery = isGet || !!c.contractSource.queryRef || c.contractSource.query != null && c.contractSource.query !== "never";
|
|
798
|
+
const { isGet, isQuery, hasBody, hasQuery } = requestShape(c.route);
|
|
864
799
|
const fields = [];
|
|
865
800
|
if (withParams) fields.push(`params: ${TA}['params']`);
|
|
866
801
|
if (hasQuery) fields.push(`query?: ${TA}['query']`);
|
|
@@ -882,7 +817,6 @@ function buildRequestModel(c) {
|
|
|
882
817
|
urlExpr,
|
|
883
818
|
optsExpr,
|
|
884
819
|
responseType: `${TA}['response']`,
|
|
885
|
-
bodyType: `${TA}['body']`,
|
|
886
820
|
queryKeyExpr: `[${flat}, input] as const`
|
|
887
821
|
};
|
|
888
822
|
}
|
|
@@ -930,7 +864,7 @@ function emitApiObjectBlock(tree, indent, p) {
|
|
|
930
864
|
const pad = " ".repeat(indent);
|
|
931
865
|
const lines = [];
|
|
932
866
|
for (const [key, node] of tree) {
|
|
933
|
-
const objKey =
|
|
867
|
+
const objKey = toObjectKey(key);
|
|
934
868
|
if (node.kind === "branch") {
|
|
935
869
|
lines.push(`${pad}${objKey}: {`);
|
|
936
870
|
lines.push(...emitApiObjectBlock(node.children, indent + 2, p));
|
|
@@ -938,30 +872,28 @@ function emitApiObjectBlock(tree, indent, p) {
|
|
|
938
872
|
continue;
|
|
939
873
|
}
|
|
940
874
|
const req = buildRequestModel(node);
|
|
941
|
-
const
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
params: node.params,
|
|
946
|
-
contract: { contractSource: node.contractSource },
|
|
947
|
-
...node.controllerRef ? { controllerRef: node.controllerRef } : {}
|
|
875
|
+
const leaf = {
|
|
876
|
+
route: node.route,
|
|
877
|
+
request: req,
|
|
878
|
+
requestExpr: renderFetcherRequest(req)
|
|
948
879
|
};
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
880
|
+
const owned = /* @__PURE__ */ new Map();
|
|
881
|
+
if (p.layer) {
|
|
882
|
+
mergeExclusive(owned, Object.entries(p.layer.buildMembers(leaf.requestExpr, leaf, p.ctx)), {
|
|
883
|
+
owner: p.layer.name,
|
|
884
|
+
describe: (name, prevOwner, owner) => `api member "${name}" on route "${req.routeName}" is contributed by more than one extension (conflict between "${prevOwner}" and "${owner}").`
|
|
885
|
+
});
|
|
886
|
+
}
|
|
953
887
|
for (const ext of p.memberExts) {
|
|
954
888
|
const extra = ext.apiMembers?.(leaf, p.ctx);
|
|
955
889
|
if (!extra) continue;
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
);
|
|
961
|
-
}
|
|
962
|
-
members[name] = value;
|
|
963
|
-
}
|
|
890
|
+
mergeExclusive(owned, Object.entries(extra), {
|
|
891
|
+
owner: ext.name,
|
|
892
|
+
describe: (name, prevOwner, owner) => `api member "${name}" on route "${req.routeName}" is contributed by more than one extension (conflict between "${prevOwner}" and "${owner}").`
|
|
893
|
+
});
|
|
964
894
|
}
|
|
895
|
+
const members = {};
|
|
896
|
+
for (const [name, { value }] of owned) members[name] = value;
|
|
965
897
|
lines.push(...renderLeaf(pad, objKey, req, leaf.requestExpr, members));
|
|
966
898
|
}
|
|
967
899
|
return lines;
|
|
@@ -970,10 +902,82 @@ function buildRouterTypeAccess(name) {
|
|
|
970
902
|
const segments = splitName(name);
|
|
971
903
|
return `ApiRouter${segments.map((s) => `[${JSON.stringify(s)}]`).join("")}`;
|
|
972
904
|
}
|
|
905
|
+
var RESOLVER_HELPERS = [
|
|
906
|
+
// --- Recursive helper type _RouterAt: walks nested ApiRouter by dot-path ---
|
|
907
|
+
"type _RouterAt<R, P extends string> = P extends `${infer Head}.${infer Tail}`",
|
|
908
|
+
" ? Head extends keyof R ? _RouterAt<R[Head], Tail> : never",
|
|
909
|
+
" : P extends keyof R ? R[P] : never;",
|
|
910
|
+
"",
|
|
911
|
+
// --- ResolveByName: resolve a field from a dot-path name ---
|
|
912
|
+
"type ResolveByName<K extends string, Field extends string> = _RouterAt<ApiRouter, K> extends infer R ? Field extends keyof R ? R[Field] : never : never;",
|
|
913
|
+
"",
|
|
914
|
+
// --- ResolveByPath: scan all leaves for matching method + url ---
|
|
915
|
+
// Flattens ApiRouter recursively and finds the entry whose method === M and url === U.
|
|
916
|
+
"type _LeafValues<T> = T extends { method: string; url: string }",
|
|
917
|
+
" ? T",
|
|
918
|
+
" : T extends object ? _LeafValues<T[keyof T]> : never;",
|
|
919
|
+
"",
|
|
920
|
+
"type ResolveByPath<M extends string, U extends string, Field extends string> = _LeafValues<ApiRouter> extends infer L",
|
|
921
|
+
" ? L extends { method: M; url: U }",
|
|
922
|
+
" ? Field extends keyof L ? L[Field] : never",
|
|
923
|
+
" : never",
|
|
924
|
+
" : never;",
|
|
925
|
+
""
|
|
926
|
+
];
|
|
927
|
+
var ROUTE_NAMESPACE = [
|
|
928
|
+
"export namespace Route {",
|
|
929
|
+
' export type Response<K extends string> = ResolveByName<K, "response">;',
|
|
930
|
+
' export type Body<K extends string> = ResolveByName<K, "body">;',
|
|
931
|
+
' export type Query<K extends string> = ResolveByName<K, "query">;',
|
|
932
|
+
' export type Params<K extends string> = ResolveByName<K, "params">;',
|
|
933
|
+
' export type Error<K extends string> = ResolveByName<K, "error">;',
|
|
934
|
+
' export type FilterFields<K extends string> = ResolveByName<K, "filterFields">;',
|
|
935
|
+
" export type Request<K extends string> = {",
|
|
936
|
+
" body: Body<K>;",
|
|
937
|
+
" query: Query<K>;",
|
|
938
|
+
" params: Params<K>;",
|
|
939
|
+
" };",
|
|
940
|
+
"}",
|
|
941
|
+
""
|
|
942
|
+
];
|
|
943
|
+
var PATH_NAMESPACE = [
|
|
944
|
+
"export namespace Path {",
|
|
945
|
+
' export type Response<M extends string, U extends string> = ResolveByPath<M, U, "response">;',
|
|
946
|
+
' export type Body<M extends string, U extends string> = ResolveByPath<M, U, "body">;',
|
|
947
|
+
' export type Query<M extends string, U extends string> = ResolveByPath<M, U, "query">;',
|
|
948
|
+
' export type Params<M extends string, U extends string> = ResolveByPath<M, U, "params">;',
|
|
949
|
+
' export type Error<M extends string, U extends string> = ResolveByPath<M, U, "error">;',
|
|
950
|
+
' export type FilterFields<M extends string, U extends string> = ResolveByPath<M, U, "filterFields">;',
|
|
951
|
+
"}",
|
|
952
|
+
""
|
|
953
|
+
];
|
|
954
|
+
var EMPTY_ROUTE_NAMESPACE = [
|
|
955
|
+
"export namespace Route {",
|
|
956
|
+
" export type Response<K extends string> = never;",
|
|
957
|
+
" export type Body<K extends string> = never;",
|
|
958
|
+
" export type Query<K extends string> = never;",
|
|
959
|
+
" export type Params<K extends string> = never;",
|
|
960
|
+
" export type Error<K extends string> = never;",
|
|
961
|
+
" export type FilterFields<K extends string> = never;",
|
|
962
|
+
" export type Request<K extends string> = { body: never; query: never; params: never };",
|
|
963
|
+
"}",
|
|
964
|
+
""
|
|
965
|
+
];
|
|
966
|
+
var EMPTY_PATH_NAMESPACE = [
|
|
967
|
+
"export namespace Path {",
|
|
968
|
+
" export type Response<M extends string, U extends string> = never;",
|
|
969
|
+
" export type Body<M extends string, U extends string> = never;",
|
|
970
|
+
" export type Query<M extends string, U extends string> = never;",
|
|
971
|
+
" export type Params<M extends string, U extends string> = never;",
|
|
972
|
+
" export type Error<M extends string, U extends string> = never;",
|
|
973
|
+
" export type FilterFields<M extends string, U extends string> = never;",
|
|
974
|
+
"}",
|
|
975
|
+
""
|
|
976
|
+
];
|
|
973
977
|
function buildApiFile(routes, outDir, opts = {}) {
|
|
974
978
|
const fetcherImportPath = opts.fetcherImportPath;
|
|
975
979
|
const extensions = opts.extensions ?? [];
|
|
976
|
-
const {
|
|
980
|
+
const { layer } = resolveApiSlots(extensions);
|
|
977
981
|
const memberExts = extensions.filter((e) => e.apiMembers);
|
|
978
982
|
const headerExts = extensions.filter((e) => e.apiHeader);
|
|
979
983
|
const contracted = routes.filter((r) => r.contract);
|
|
@@ -1018,7 +1022,6 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1018
1022
|
seenImports.add(imp);
|
|
1019
1023
|
extImports.push(imp);
|
|
1020
1024
|
};
|
|
1021
|
-
for (const imp of transport?.imports?.(ctx) ?? []) pushImport(imp);
|
|
1022
1025
|
for (const imp of layer?.imports?.(ctx) ?? []) pushImport(imp);
|
|
1023
1026
|
for (const ext of headerExts) {
|
|
1024
1027
|
for (const imp of ext.apiHeader?.(ctx)?.imports ?? []) pushImport(imp);
|
|
@@ -1034,8 +1037,8 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1034
1037
|
const emittedNames = /* @__PURE__ */ new Set();
|
|
1035
1038
|
for (const [filePath, names] of importsByFile) {
|
|
1036
1039
|
let relPath;
|
|
1037
|
-
if ((0,
|
|
1038
|
-
relPath = (0,
|
|
1040
|
+
if ((0, import_node_path4.isAbsolute)(filePath)) {
|
|
1041
|
+
relPath = (0, import_node_path4.relative)(outDir, filePath).replace(/\.ts$/, "");
|
|
1039
1042
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
1040
1043
|
} else {
|
|
1041
1044
|
relPath = filePath;
|
|
@@ -1063,27 +1066,8 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1063
1066
|
lines.push("}");
|
|
1064
1067
|
lines.push("export type Api = ReturnType<typeof createApi>;");
|
|
1065
1068
|
lines.push("");
|
|
1066
|
-
lines.push(
|
|
1067
|
-
lines.push(
|
|
1068
|
-
lines.push(" export type Body<K extends string> = never;");
|
|
1069
|
-
lines.push(" export type Query<K extends string> = never;");
|
|
1070
|
-
lines.push(" export type Params<K extends string> = never;");
|
|
1071
|
-
lines.push(" export type Error<K extends string> = never;");
|
|
1072
|
-
lines.push(" export type FilterFields<K extends string> = never;");
|
|
1073
|
-
lines.push(
|
|
1074
|
-
" export type Request<K extends string> = { body: never; query: never; params: never };"
|
|
1075
|
-
);
|
|
1076
|
-
lines.push("}");
|
|
1077
|
-
lines.push("");
|
|
1078
|
-
lines.push("export namespace Path {");
|
|
1079
|
-
lines.push(" export type Response<M extends string, U extends string> = never;");
|
|
1080
|
-
lines.push(" export type Body<M extends string, U extends string> = never;");
|
|
1081
|
-
lines.push(" export type Query<M extends string, U extends string> = never;");
|
|
1082
|
-
lines.push(" export type Params<M extends string, U extends string> = never;");
|
|
1083
|
-
lines.push(" export type Error<M extends string, U extends string> = never;");
|
|
1084
|
-
lines.push(" export type FilterFields<M extends string, U extends string> = never;");
|
|
1085
|
-
lines.push("}");
|
|
1086
|
-
lines.push("");
|
|
1069
|
+
lines.push(...EMPTY_ROUTE_NAMESPACE);
|
|
1070
|
+
lines.push(...EMPTY_PATH_NAMESPACE);
|
|
1087
1071
|
return lines.join("\n");
|
|
1088
1072
|
}
|
|
1089
1073
|
const tree = /* @__PURE__ */ new Map();
|
|
@@ -1101,7 +1085,8 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1101
1085
|
path: r.path,
|
|
1102
1086
|
params: r.params,
|
|
1103
1087
|
controllerRef: r.controllerRef,
|
|
1104
|
-
contractSource: c.contractSource
|
|
1088
|
+
contractSource: c.contractSource,
|
|
1089
|
+
route: r
|
|
1105
1090
|
};
|
|
1106
1091
|
insertIntoTree(tree, segments, leaf, name);
|
|
1107
1092
|
}
|
|
@@ -1114,7 +1099,6 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1114
1099
|
lines.push(" return {");
|
|
1115
1100
|
lines.push(
|
|
1116
1101
|
...emitApiObjectBlock(tree, 4, {
|
|
1117
|
-
...transport ? { transport } : {},
|
|
1118
1102
|
...layer ? { layer } : {},
|
|
1119
1103
|
memberExts,
|
|
1120
1104
|
ctx
|
|
@@ -1125,61 +1109,9 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1125
1109
|
lines.push("");
|
|
1126
1110
|
lines.push("export type Api = ReturnType<typeof createApi>;");
|
|
1127
1111
|
lines.push("");
|
|
1128
|
-
lines.push(
|
|
1129
|
-
lines.push(
|
|
1130
|
-
lines.push(
|
|
1131
|
-
lines.push("");
|
|
1132
|
-
lines.push(
|
|
1133
|
-
"type ResolveByName<K extends string, Field extends string> = _RouterAt<ApiRouter, K> extends infer R ? Field extends keyof R ? R[Field] : never : never;"
|
|
1134
|
-
);
|
|
1135
|
-
lines.push("");
|
|
1136
|
-
lines.push("type _LeafValues<T> = T extends { method: string; url: string }");
|
|
1137
|
-
lines.push(" ? T");
|
|
1138
|
-
lines.push(" : T extends object ? _LeafValues<T[keyof T]> : never;");
|
|
1139
|
-
lines.push("");
|
|
1140
|
-
lines.push(
|
|
1141
|
-
"type ResolveByPath<M extends string, U extends string, Field extends string> = _LeafValues<ApiRouter> extends infer L"
|
|
1142
|
-
);
|
|
1143
|
-
lines.push(" ? L extends { method: M; url: U }");
|
|
1144
|
-
lines.push(" ? Field extends keyof L ? L[Field] : never");
|
|
1145
|
-
lines.push(" : never");
|
|
1146
|
-
lines.push(" : never;");
|
|
1147
|
-
lines.push("");
|
|
1148
|
-
lines.push("export namespace Route {");
|
|
1149
|
-
lines.push(' export type Response<K extends string> = ResolveByName<K, "response">;');
|
|
1150
|
-
lines.push(' export type Body<K extends string> = ResolveByName<K, "body">;');
|
|
1151
|
-
lines.push(' export type Query<K extends string> = ResolveByName<K, "query">;');
|
|
1152
|
-
lines.push(' export type Params<K extends string> = ResolveByName<K, "params">;');
|
|
1153
|
-
lines.push(' export type Error<K extends string> = ResolveByName<K, "error">;');
|
|
1154
|
-
lines.push(' export type FilterFields<K extends string> = ResolveByName<K, "filterFields">;');
|
|
1155
|
-
lines.push(" export type Request<K extends string> = {");
|
|
1156
|
-
lines.push(" body: Body<K>;");
|
|
1157
|
-
lines.push(" query: Query<K>;");
|
|
1158
|
-
lines.push(" params: Params<K>;");
|
|
1159
|
-
lines.push(" };");
|
|
1160
|
-
lines.push("}");
|
|
1161
|
-
lines.push("");
|
|
1162
|
-
lines.push("export namespace Path {");
|
|
1163
|
-
lines.push(
|
|
1164
|
-
' export type Response<M extends string, U extends string> = ResolveByPath<M, U, "response">;'
|
|
1165
|
-
);
|
|
1166
|
-
lines.push(
|
|
1167
|
-
' export type Body<M extends string, U extends string> = ResolveByPath<M, U, "body">;'
|
|
1168
|
-
);
|
|
1169
|
-
lines.push(
|
|
1170
|
-
' export type Query<M extends string, U extends string> = ResolveByPath<M, U, "query">;'
|
|
1171
|
-
);
|
|
1172
|
-
lines.push(
|
|
1173
|
-
' export type Params<M extends string, U extends string> = ResolveByPath<M, U, "params">;'
|
|
1174
|
-
);
|
|
1175
|
-
lines.push(
|
|
1176
|
-
' export type Error<M extends string, U extends string> = ResolveByPath<M, U, "error">;'
|
|
1177
|
-
);
|
|
1178
|
-
lines.push(
|
|
1179
|
-
' export type FilterFields<M extends string, U extends string> = ResolveByPath<M, U, "filterFields">;'
|
|
1180
|
-
);
|
|
1181
|
-
lines.push("}");
|
|
1182
|
-
lines.push("");
|
|
1112
|
+
lines.push(...RESOLVER_HELPERS);
|
|
1113
|
+
lines.push(...ROUTE_NAMESPACE);
|
|
1114
|
+
lines.push(...PATH_NAMESPACE);
|
|
1183
1115
|
for (const ext of headerExts) {
|
|
1184
1116
|
const statements = ext.apiHeader?.(ctx)?.statements;
|
|
1185
1117
|
if (statements?.length) {
|
|
@@ -1191,7 +1123,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1191
1123
|
|
|
1192
1124
|
// src/emit/emit-cache.ts
|
|
1193
1125
|
var import_promises4 = require("fs/promises");
|
|
1194
|
-
var
|
|
1126
|
+
var import_node_path5 = require("path");
|
|
1195
1127
|
async function emitCache(pages, outDir) {
|
|
1196
1128
|
await (0, import_promises4.mkdir)(outDir, { recursive: true });
|
|
1197
1129
|
const entries = await Promise.all(
|
|
@@ -1205,95 +1137,21 @@ async function emitCache(pages, outDir) {
|
|
|
1205
1137
|
})
|
|
1206
1138
|
);
|
|
1207
1139
|
const cache = { pages: entries };
|
|
1208
|
-
await (0, import_promises4.writeFile)((0,
|
|
1140
|
+
await (0, import_promises4.writeFile)((0, import_node_path5.join)(outDir, "components.json"), `${JSON.stringify(cache, null, 2)}
|
|
1209
1141
|
`, "utf8");
|
|
1210
1142
|
}
|
|
1211
1143
|
|
|
1212
1144
|
// src/emit/emit-forms.ts
|
|
1213
1145
|
var import_promises5 = require("fs/promises");
|
|
1214
|
-
var
|
|
1146
|
+
var import_node_path6 = require("path");
|
|
1215
1147
|
async function emitForms(routes, outDir, config, adapter) {
|
|
1216
1148
|
if (config && config.enabled === false) return false;
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
if (content2 === null) return false;
|
|
1220
|
-
await (0, import_promises5.mkdir)(outDir, { recursive: true });
|
|
1221
|
-
await (0, import_promises5.writeFile)((0, import_node_path5.join)(outDir, "forms.ts"), content2, "utf8");
|
|
1222
|
-
return true;
|
|
1223
|
-
}
|
|
1224
|
-
const entries = collectFormEntries(routes);
|
|
1225
|
-
if (entries.length === 0) return false;
|
|
1149
|
+
const content = buildFormsFileWithAdapter(routes, outDir, adapter, config);
|
|
1150
|
+
if (content === null) return false;
|
|
1226
1151
|
await (0, import_promises5.mkdir)(outDir, { recursive: true });
|
|
1227
|
-
|
|
1228
|
-
await (0, import_promises5.writeFile)((0, import_node_path5.join)(outDir, "forms.ts"), content, "utf8");
|
|
1152
|
+
await (0, import_promises5.writeFile)((0, import_node_path6.join)(outDir, "forms.ts"), content, "utf8");
|
|
1229
1153
|
return true;
|
|
1230
1154
|
}
|
|
1231
|
-
function buildFormsFileWithAdapter(routes, adapter) {
|
|
1232
|
-
const sorted = [...routes].filter((r) => r.contract).sort((a, b) => a.name.localeCompare(b.name));
|
|
1233
|
-
const methodNameCounts = /* @__PURE__ */ new Map();
|
|
1234
|
-
for (const route of sorted) {
|
|
1235
|
-
const cs = route.contract.contractSource;
|
|
1236
|
-
if (!cs.bodySchema && !cs.querySchema) continue;
|
|
1237
|
-
methodNameCounts.set(
|
|
1238
|
-
deriveBaseName(route.name).method,
|
|
1239
|
-
(methodNameCounts.get(deriveBaseName(route.name).method) ?? 0) + 1
|
|
1240
|
-
);
|
|
1241
|
-
}
|
|
1242
|
-
const named = /* @__PURE__ */ new Map();
|
|
1243
|
-
const decls = [];
|
|
1244
|
-
const mapEntries = [];
|
|
1245
|
-
let used = false;
|
|
1246
|
-
for (const route of sorted) {
|
|
1247
|
-
const cs = route.contract.contractSource;
|
|
1248
|
-
const { method, full } = deriveBaseName(route.name);
|
|
1249
|
-
const base = (methodNameCounts.get(method) ?? 0) > 1 ? full : method;
|
|
1250
|
-
const block = [];
|
|
1251
|
-
if (cs.bodyZodText && !cs.bodySchema) {
|
|
1252
|
-
block.push(
|
|
1253
|
-
`// warning: ${route.name} body is a defineContract (zod) schema; not translated to ${adapter.name} \u2014 use the zod adapter.`
|
|
1254
|
-
);
|
|
1255
|
-
}
|
|
1256
|
-
let bodyConst;
|
|
1257
|
-
if (cs.bodySchema) {
|
|
1258
|
-
used = true;
|
|
1259
|
-
const r = adapter.renderModule(cs.bodySchema);
|
|
1260
|
-
for (const [n, t] of r.namedNestedSchemas) named.set(n, t);
|
|
1261
|
-
bodyConst = `${base}BodySchema`;
|
|
1262
|
-
block.push(`export const ${bodyConst} = ${r.schemaText};`);
|
|
1263
|
-
block.push(`export type ${base}Body = ${adapter.inferType(bodyConst)};`);
|
|
1264
|
-
}
|
|
1265
|
-
if (cs.querySchema) {
|
|
1266
|
-
used = true;
|
|
1267
|
-
const r = adapter.renderModule(cs.querySchema);
|
|
1268
|
-
for (const [n, t] of r.namedNestedSchemas) named.set(n, t);
|
|
1269
|
-
const queryConst = `${base}QuerySchema`;
|
|
1270
|
-
block.push(`export const ${queryConst} = ${r.schemaText};`);
|
|
1271
|
-
block.push(`export type ${base}Query = ${adapter.inferType(queryConst)};`);
|
|
1272
|
-
}
|
|
1273
|
-
if (block.length === 0) continue;
|
|
1274
|
-
decls.push(`// ${route.name}`, ...block, "");
|
|
1275
|
-
if (bodyConst) mapEntries.push(` ${JSON.stringify(route.name)}: ${bodyConst},`);
|
|
1276
|
-
}
|
|
1277
|
-
if (!used) return null;
|
|
1278
|
-
const lines = ["// Generated by @dudousxd/nestjs-codegen. Do not edit."];
|
|
1279
|
-
for (const imp of adapter.importStatements({ used: true })) lines.push(imp);
|
|
1280
|
-
lines.push("");
|
|
1281
|
-
if (named.size > 0) {
|
|
1282
|
-
lines.push("// Hoisted nested schemas (shared across endpoints).");
|
|
1283
|
-
for (const [n, t] of named) lines.push(`const ${n} = ${t};`);
|
|
1284
|
-
lines.push("");
|
|
1285
|
-
}
|
|
1286
|
-
lines.push(...decls);
|
|
1287
|
-
lines.push("/** Route name \u2192 body schema map. */");
|
|
1288
|
-
lines.push("export const formSchemas = {");
|
|
1289
|
-
lines.push(...mapEntries);
|
|
1290
|
-
lines.push("} as const;");
|
|
1291
|
-
lines.push("");
|
|
1292
|
-
return lines.join("\n");
|
|
1293
|
-
}
|
|
1294
|
-
function hasSchema(src) {
|
|
1295
|
-
return !!src && (src.ref !== null || src.text !== null);
|
|
1296
|
-
}
|
|
1297
1155
|
function pascal(segment) {
|
|
1298
1156
|
return segment.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
1299
1157
|
}
|
|
@@ -1303,124 +1161,16 @@ function deriveBaseName(routeName) {
|
|
|
1303
1161
|
const full = segments.map(pascal).join("");
|
|
1304
1162
|
return { method, full };
|
|
1305
1163
|
}
|
|
1306
|
-
function collectFormEntries(routes) {
|
|
1307
|
-
const sorted = [...routes].filter((r) => r.contract).sort((a, b) => a.name.localeCompare(b.name));
|
|
1308
|
-
const methodNameCounts = /* @__PURE__ */ new Map();
|
|
1309
|
-
const candidates = [];
|
|
1310
|
-
for (const route of sorted) {
|
|
1311
|
-
const cs = route.contract.contractSource;
|
|
1312
|
-
const body = { ref: cs.bodyZodRef ?? null, text: cs.bodyZodText ?? null };
|
|
1313
|
-
const query = { ref: cs.queryZodRef ?? null, text: cs.queryZodText ?? null };
|
|
1314
|
-
if (!hasSchema(body) && !hasSchema(query)) continue;
|
|
1315
|
-
const { method, full } = deriveBaseName(route.name);
|
|
1316
|
-
methodNameCounts.set(method, (methodNameCounts.get(method) ?? 0) + 1);
|
|
1317
|
-
candidates.push({ route, method, full });
|
|
1318
|
-
}
|
|
1319
|
-
const entries = [];
|
|
1320
|
-
for (const { route, method, full } of candidates) {
|
|
1321
|
-
const cs = route.contract.contractSource;
|
|
1322
|
-
const collision = (methodNameCounts.get(method) ?? 0) > 1;
|
|
1323
|
-
const baseName = collision ? full : method;
|
|
1324
|
-
const body = { ref: cs.bodyZodRef ?? null, text: cs.bodyZodText ?? null };
|
|
1325
|
-
const query = { ref: cs.queryZodRef ?? null, text: cs.queryZodText ?? null };
|
|
1326
|
-
entries.push({
|
|
1327
|
-
routeName: route.name,
|
|
1328
|
-
baseName,
|
|
1329
|
-
body: hasSchema(body) ? body : void 0,
|
|
1330
|
-
query: hasSchema(query) ? query : void 0,
|
|
1331
|
-
nestedSchemas: cs.formNestedSchemas ?? null,
|
|
1332
|
-
warnings: cs.formWarnings ?? []
|
|
1333
|
-
});
|
|
1334
|
-
}
|
|
1335
|
-
return entries;
|
|
1336
|
-
}
|
|
1337
1164
|
function relImport(outDir, filePath) {
|
|
1338
|
-
let relPath = (0,
|
|
1165
|
+
let relPath = (0, import_node_path6.relative)(outDir, filePath).replace(/\.ts$/, "");
|
|
1339
1166
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
1340
1167
|
return relPath;
|
|
1341
1168
|
}
|
|
1342
1169
|
function refRootIdentifier(refName) {
|
|
1343
1170
|
return refName.split(".")[0] ?? refName;
|
|
1344
1171
|
}
|
|
1345
|
-
function
|
|
1346
|
-
|
|
1347
|
-
const lines = [
|
|
1348
|
-
"// Generated by @dudousxd/nestjs-codegen. Do not edit.",
|
|
1349
|
-
`import { z } from '${zodImport}';`
|
|
1350
|
-
];
|
|
1351
|
-
const importsByFile = /* @__PURE__ */ new Map();
|
|
1352
|
-
const refAlias = /* @__PURE__ */ new Map();
|
|
1353
|
-
for (const entry of entries) {
|
|
1354
|
-
for (const src of [entry.body, entry.query]) {
|
|
1355
|
-
if (src?.ref && !src.text) {
|
|
1356
|
-
const root = refRootIdentifier(src.ref.name);
|
|
1357
|
-
const set = importsByFile.get(src.ref.filePath) ?? /* @__PURE__ */ new Set();
|
|
1358
|
-
set.add(root);
|
|
1359
|
-
importsByFile.set(src.ref.filePath, set);
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
if (importsByFile.size > 0) {
|
|
1364
|
-
const emitted = /* @__PURE__ */ new Set();
|
|
1365
|
-
for (const [filePath, roots] of [...importsByFile.entries()].sort()) {
|
|
1366
|
-
const relPath = relImport(outDir, filePath);
|
|
1367
|
-
const specifiers = [];
|
|
1368
|
-
for (const root of [...roots].sort()) {
|
|
1369
|
-
if (emitted.has(root)) {
|
|
1370
|
-
const alias = `${root}_${emitted.size}`;
|
|
1371
|
-
specifiers.push(`${root} as ${alias}`);
|
|
1372
|
-
emitted.add(alias);
|
|
1373
|
-
refAlias.set(`${filePath}\0${root}`, alias);
|
|
1374
|
-
} else {
|
|
1375
|
-
specifiers.push(root);
|
|
1376
|
-
emitted.add(root);
|
|
1377
|
-
refAlias.set(`${filePath}\0${root}`, root);
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
lines.push(`import { ${specifiers.join(", ")} } from '${relPath}';`);
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
lines.push("");
|
|
1384
|
-
const { globalSchemas, renamesByEntry } = planNestedSchemas(entries);
|
|
1385
|
-
if (globalSchemas.size > 0) {
|
|
1386
|
-
lines.push("// Hoisted nested schemas (shared across endpoints).");
|
|
1387
|
-
for (const [name, text] of globalSchemas) {
|
|
1388
|
-
lines.push(`const ${name} = ${text};`);
|
|
1389
|
-
}
|
|
1390
|
-
lines.push("");
|
|
1391
|
-
}
|
|
1392
|
-
const mapEntries = [];
|
|
1393
|
-
for (const entry of entries) {
|
|
1394
|
-
lines.push(`// ${entry.routeName}`);
|
|
1395
|
-
if (entry.warnings && entry.warnings.length > 0) {
|
|
1396
|
-
for (const w of entry.warnings) {
|
|
1397
|
-
lines.push(`// warning: ${w}`);
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
const rename = renamesByEntry.get(entry) ?? null;
|
|
1401
|
-
if (entry.body) {
|
|
1402
|
-
const schemaName = `${entry.baseName}BodySchema`;
|
|
1403
|
-
const typeName = `${entry.baseName}Body`;
|
|
1404
|
-
const text = applyRenames(renderSchema(entry.body, outDir, refAlias), rename);
|
|
1405
|
-
lines.push(`export const ${schemaName} = ${text};`);
|
|
1406
|
-
lines.push(`export type ${typeName} = z.infer<typeof ${schemaName}>;`);
|
|
1407
|
-
mapEntries.push(` ${JSON.stringify(entry.routeName)}: ${schemaName},`);
|
|
1408
|
-
}
|
|
1409
|
-
if (entry.query) {
|
|
1410
|
-
const schemaName = `${entry.baseName}QuerySchema`;
|
|
1411
|
-
const typeName = `${entry.baseName}Query`;
|
|
1412
|
-
const text = applyRenames(renderSchema(entry.query, outDir, refAlias), rename);
|
|
1413
|
-
lines.push(`export const ${schemaName} = ${text};`);
|
|
1414
|
-
lines.push(`export type ${typeName} = z.infer<typeof ${schemaName}>;`);
|
|
1415
|
-
}
|
|
1416
|
-
lines.push("");
|
|
1417
|
-
}
|
|
1418
|
-
lines.push("/** Route name \u2192 body schema map. */");
|
|
1419
|
-
lines.push("export const formSchemas = {");
|
|
1420
|
-
lines.push(...mapEntries);
|
|
1421
|
-
lines.push("} as const;");
|
|
1422
|
-
lines.push("");
|
|
1423
|
-
return lines.join("\n");
|
|
1172
|
+
function hasSource(src) {
|
|
1173
|
+
return !!(src.schema || src.zodText || src.zodRef);
|
|
1424
1174
|
}
|
|
1425
1175
|
function applyRenames(text, renames) {
|
|
1426
1176
|
if (!renames || renames.size === 0) return text;
|
|
@@ -1486,38 +1236,200 @@ function planNestedSchemas(entries) {
|
|
|
1486
1236
|
}
|
|
1487
1237
|
return { globalSchemas, renamesByEntry };
|
|
1488
1238
|
}
|
|
1489
|
-
function
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1239
|
+
function buildFormsFileWithAdapter(routes, outDir, adapter, config) {
|
|
1240
|
+
const acceptsRawZod = adapter.acceptsRawZodSource === true;
|
|
1241
|
+
const sorted = [...routes].filter((r) => r.contract).sort((a, b) => a.name.localeCompare(b.name));
|
|
1242
|
+
const methodNameCounts = /* @__PURE__ */ new Map();
|
|
1243
|
+
const candidates = [];
|
|
1244
|
+
for (const route of sorted) {
|
|
1245
|
+
const cs = route.contract.contractSource;
|
|
1246
|
+
const body = {
|
|
1247
|
+
schema: cs.bodySchema ?? null,
|
|
1248
|
+
zodText: cs.bodyZodText ?? null,
|
|
1249
|
+
zodRef: cs.bodyZodRef ?? null
|
|
1250
|
+
};
|
|
1251
|
+
const query = {
|
|
1252
|
+
schema: cs.querySchema ?? null,
|
|
1253
|
+
zodText: cs.queryZodText ?? null,
|
|
1254
|
+
zodRef: cs.queryZodRef ?? null
|
|
1255
|
+
};
|
|
1256
|
+
if (!hasSource(body) && !hasSource(query)) continue;
|
|
1257
|
+
const { method, full } = deriveBaseName(route.name);
|
|
1258
|
+
methodNameCounts.set(method, (methodNameCounts.get(method) ?? 0) + 1);
|
|
1259
|
+
candidates.push({
|
|
1260
|
+
routeName: route.name,
|
|
1261
|
+
baseName: full,
|
|
1262
|
+
// resolved below
|
|
1263
|
+
body: hasSource(body) ? body : void 0,
|
|
1264
|
+
query: hasSource(query) ? query : void 0,
|
|
1265
|
+
nestedSchemas: cs.formNestedSchemas ?? null,
|
|
1266
|
+
warnings: cs.formWarnings ?? []
|
|
1267
|
+
});
|
|
1508
1268
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1269
|
+
const entries = candidates.map((c) => {
|
|
1270
|
+
const { method, full } = deriveBaseName(c.routeName);
|
|
1271
|
+
const collision = (methodNameCounts.get(method) ?? 0) > 1;
|
|
1272
|
+
return { ...c, baseName: collision ? full : method };
|
|
1273
|
+
});
|
|
1274
|
+
if (entries.length === 0) return null;
|
|
1275
|
+
const importsByFile = /* @__PURE__ */ new Map();
|
|
1276
|
+
const refAlias = /* @__PURE__ */ new Map();
|
|
1277
|
+
for (const entry of entries) {
|
|
1278
|
+
for (const src of [entry.body, entry.query]) {
|
|
1279
|
+
if (src?.zodRef && !src.zodText && !src.schema) {
|
|
1280
|
+
const root = refRootIdentifier(src.zodRef.name);
|
|
1281
|
+
const set = importsByFile.get(src.zodRef.filePath) ?? /* @__PURE__ */ new Set();
|
|
1282
|
+
set.add(root);
|
|
1283
|
+
importsByFile.set(src.zodRef.filePath, set);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1511
1286
|
}
|
|
1512
|
-
const
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1287
|
+
const importLines = [];
|
|
1288
|
+
if (importsByFile.size > 0) {
|
|
1289
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
1290
|
+
for (const [filePath, roots] of [...importsByFile.entries()].sort()) {
|
|
1291
|
+
const relPath = relImport(outDir, filePath);
|
|
1292
|
+
const specifiers = [];
|
|
1293
|
+
for (const root of [...roots].sort()) {
|
|
1294
|
+
if (emitted.has(root)) {
|
|
1295
|
+
const alias = `${root}_${emitted.size}`;
|
|
1296
|
+
specifiers.push(`${root} as ${alias}`);
|
|
1297
|
+
emitted.add(alias);
|
|
1298
|
+
refAlias.set(`${filePath}\0${root}`, alias);
|
|
1299
|
+
} else {
|
|
1300
|
+
specifiers.push(root);
|
|
1301
|
+
emitted.add(root);
|
|
1302
|
+
refAlias.set(`${filePath}\0${root}`, root);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
importLines.push(`import { ${specifiers.join(", ")} } from '${relPath}';`);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
const { globalSchemas, renamesByEntry } = planNestedSchemas(entries);
|
|
1309
|
+
const irNamed = /* @__PURE__ */ new Map();
|
|
1310
|
+
const irTypeAliases = /* @__PURE__ */ new Map();
|
|
1311
|
+
const irAnnotations = /* @__PURE__ */ new Map();
|
|
1312
|
+
const decls = [];
|
|
1313
|
+
const mapEntries = [];
|
|
1314
|
+
let used = false;
|
|
1315
|
+
const renderSource = (src, rename) => {
|
|
1316
|
+
if (src.schema) {
|
|
1317
|
+
const r = adapter.renderModule(src.schema);
|
|
1318
|
+
for (const [n, t] of r.namedNestedSchemas) irNamed.set(n, t);
|
|
1319
|
+
if (r.namedTypeAliases) for (const [n, t] of r.namedTypeAliases) irTypeAliases.set(n, t);
|
|
1320
|
+
if (r.namedAnnotations) for (const [n, a] of r.namedAnnotations) irAnnotations.set(n, a);
|
|
1321
|
+
return { text: r.schemaText };
|
|
1322
|
+
}
|
|
1323
|
+
if (src.zodText) {
|
|
1324
|
+
if (!acceptsRawZod) {
|
|
1325
|
+
return {
|
|
1326
|
+
text: "",
|
|
1327
|
+
warn: `is a defineContract (zod) schema; not translated to ${adapter.name} \u2014 use the zod adapter.`
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
return { text: applyRenames(src.zodText, rename) };
|
|
1331
|
+
}
|
|
1332
|
+
if (src.zodRef) {
|
|
1333
|
+
if (!acceptsRawZod) {
|
|
1334
|
+
return {
|
|
1335
|
+
text: "",
|
|
1336
|
+
warn: `is a defineContract (zod) schema; not translated to ${adapter.name} \u2014 use the zod adapter.`
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
const root = refRootIdentifier(src.zodRef.name);
|
|
1340
|
+
const alias = refAlias.get(`${src.zodRef.filePath}\0${root}`) ?? root;
|
|
1341
|
+
const member = src.zodRef.name.slice(root.length);
|
|
1342
|
+
return { text: `${alias}${member}` };
|
|
1343
|
+
}
|
|
1344
|
+
return null;
|
|
1345
|
+
};
|
|
1346
|
+
for (const entry of entries) {
|
|
1347
|
+
const block = [];
|
|
1348
|
+
const rename = renamesByEntry.get(entry) ?? null;
|
|
1349
|
+
let bodyConst;
|
|
1350
|
+
if (entry.warnings && entry.warnings.length > 0) {
|
|
1351
|
+
for (const w of entry.warnings) block.push(`// warning: ${w}`);
|
|
1352
|
+
}
|
|
1353
|
+
if (entry.body) {
|
|
1354
|
+
const rendered = renderSource(entry.body, rename);
|
|
1355
|
+
if (rendered?.warn) {
|
|
1356
|
+
block.push(`// warning: ${entry.routeName} body ${rendered.warn}`);
|
|
1357
|
+
} else if (rendered) {
|
|
1358
|
+
used = true;
|
|
1359
|
+
bodyConst = `${entry.baseName}BodySchema`;
|
|
1360
|
+
block.push(`export const ${bodyConst} = ${rendered.text};`);
|
|
1361
|
+
block.push(`export type ${entry.baseName}Body = ${adapter.inferType(bodyConst)};`);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
if (entry.query) {
|
|
1365
|
+
const rendered = renderSource(entry.query, rename);
|
|
1366
|
+
if (rendered?.warn) {
|
|
1367
|
+
block.push(`// warning: ${entry.routeName} query ${rendered.warn}`);
|
|
1368
|
+
} else if (rendered) {
|
|
1369
|
+
used = true;
|
|
1370
|
+
const queryConst = `${entry.baseName}QuerySchema`;
|
|
1371
|
+
block.push(`export const ${queryConst} = ${rendered.text};`);
|
|
1372
|
+
block.push(`export type ${entry.baseName}Query = ${adapter.inferType(queryConst)};`);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
if (block.length === 0) continue;
|
|
1376
|
+
decls.push(`// ${entry.routeName}`, ...block, "");
|
|
1377
|
+
if (bodyConst) mapEntries.push(` ${JSON.stringify(entry.routeName)}: ${bodyConst},`);
|
|
1378
|
+
}
|
|
1379
|
+
if (!used) return null;
|
|
1380
|
+
const lines = ["// Generated by @dudousxd/nestjs-codegen. Do not edit."];
|
|
1381
|
+
if (acceptsRawZod) {
|
|
1382
|
+
const zodImport = config?.zodImport ?? "zod";
|
|
1383
|
+
lines.push(`import { z } from '${zodImport}';`);
|
|
1384
|
+
} else {
|
|
1385
|
+
for (const imp of adapter.importStatements({ used: true })) lines.push(imp);
|
|
1386
|
+
}
|
|
1387
|
+
lines.push(...importLines);
|
|
1388
|
+
lines.push("");
|
|
1389
|
+
const allNested = /* @__PURE__ */ new Map();
|
|
1390
|
+
for (const [n, t] of globalSchemas) allNested.set(n, t);
|
|
1391
|
+
for (const [n, t] of irNamed) if (!allNested.has(n)) allNested.set(n, t);
|
|
1392
|
+
if (allNested.size > 0) {
|
|
1393
|
+
lines.push("// Hoisted nested schemas (shared across endpoints).");
|
|
1394
|
+
for (const [n, alias] of irTypeAliases) {
|
|
1395
|
+
if (allNested.has(n)) lines.push(`${alias};`);
|
|
1396
|
+
}
|
|
1397
|
+
for (const [n, t] of allNested) {
|
|
1398
|
+
const annotation = irAnnotations.get(n);
|
|
1399
|
+
lines.push(`const ${n}${annotation ? `: ${annotation}` : ""} = ${t};`);
|
|
1400
|
+
}
|
|
1401
|
+
lines.push("");
|
|
1402
|
+
}
|
|
1403
|
+
lines.push(...decls);
|
|
1404
|
+
lines.push("/** Route name \u2192 body schema map. */");
|
|
1405
|
+
lines.push("export const formSchemas = {");
|
|
1406
|
+
lines.push(...mapEntries);
|
|
1407
|
+
lines.push("} as const;");
|
|
1408
|
+
lines.push("");
|
|
1409
|
+
return lines.join("\n");
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
// src/emit/emit-index.ts
|
|
1413
|
+
var import_promises6 = require("fs/promises");
|
|
1520
1414
|
var import_node_path7 = require("path");
|
|
1415
|
+
async function emitIndex(outDir, hasContracts = false, hasForms = false) {
|
|
1416
|
+
await (0, import_promises6.mkdir)(outDir, { recursive: true });
|
|
1417
|
+
const exports2 = ["export * from './pages.js';", "export * from './routes.js';"];
|
|
1418
|
+
if (hasContracts) {
|
|
1419
|
+
exports2.push("export * from './api.js';");
|
|
1420
|
+
}
|
|
1421
|
+
if (hasForms) {
|
|
1422
|
+
exports2.push("export * from './forms.js';");
|
|
1423
|
+
}
|
|
1424
|
+
const content = ["// Generated by @dudousxd/nestjs-codegen. Do not edit.", ...exports2, ""].join(
|
|
1425
|
+
"\n"
|
|
1426
|
+
);
|
|
1427
|
+
await (0, import_promises6.writeFile)((0, import_node_path7.join)(outDir, "index.d.ts"), content, "utf8");
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
// src/emit/emit-pages.ts
|
|
1431
|
+
var import_promises7 = require("fs/promises");
|
|
1432
|
+
var import_node_path8 = require("path");
|
|
1521
1433
|
async function emitPages(pages, outDir, _options = {}) {
|
|
1522
1434
|
await (0, import_promises7.mkdir)(outDir, { recursive: true });
|
|
1523
1435
|
const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
|
|
@@ -1538,7 +1450,7 @@ ${augBody}
|
|
|
1538
1450
|
}
|
|
1539
1451
|
${sharedPropsBlock}}
|
|
1540
1452
|
`;
|
|
1541
|
-
await (0, import_promises7.writeFile)((0,
|
|
1453
|
+
await (0, import_promises7.writeFile)((0, import_node_path8.join)(outDir, "pages.d.ts"), content, "utf8");
|
|
1542
1454
|
}
|
|
1543
1455
|
function buildSharedPropsBlock(sharedProps) {
|
|
1544
1456
|
if (!sharedProps) return "";
|
|
@@ -1557,7 +1469,7 @@ ${propsBody}
|
|
|
1557
1469
|
`;
|
|
1558
1470
|
}
|
|
1559
1471
|
function buildAugmentationType(page, outDir) {
|
|
1560
|
-
let importPath = (0,
|
|
1472
|
+
let importPath = (0, import_node_path8.relative)(outDir, page.absolutePath).replace(/\.(tsx?|vue|svelte)$/, "");
|
|
1561
1473
|
if (!importPath.startsWith(".")) {
|
|
1562
1474
|
importPath = `./${importPath}`;
|
|
1563
1475
|
}
|
|
@@ -1569,11 +1481,11 @@ function needsQuotes(name) {
|
|
|
1569
1481
|
|
|
1570
1482
|
// src/emit/emit-routes.ts
|
|
1571
1483
|
var import_promises8 = require("fs/promises");
|
|
1572
|
-
var
|
|
1484
|
+
var import_node_path9 = require("path");
|
|
1573
1485
|
async function emitRoutes(routes, outDir) {
|
|
1574
1486
|
await (0, import_promises8.mkdir)(outDir, { recursive: true });
|
|
1575
1487
|
const content = buildRoutesFile(routes);
|
|
1576
|
-
await (0, import_promises8.writeFile)((0,
|
|
1488
|
+
await (0, import_promises8.writeFile)((0, import_node_path9.join)(outDir, "routes.ts"), content, "utf8");
|
|
1577
1489
|
}
|
|
1578
1490
|
function buildRoutesFile(routes) {
|
|
1579
1491
|
if (routes.length === 0) {
|
|
@@ -1701,30 +1613,7 @@ async function generate(config, inputRoutes = []) {
|
|
|
1701
1613
|
propsExport: pagesConfig.propsExport,
|
|
1702
1614
|
componentNameStrategy: pagesConfig.componentNameStrategy
|
|
1703
1615
|
});
|
|
1704
|
-
|
|
1705
|
-
if (config.app?.moduleEntry) {
|
|
1706
|
-
try {
|
|
1707
|
-
const tsconfigPath = config.app.tsconfig ?? (0, import_node_path9.join)(config.codegen.cwd, "tsconfig.json");
|
|
1708
|
-
let project;
|
|
1709
|
-
try {
|
|
1710
|
-
project = new import_ts_morph3.Project({
|
|
1711
|
-
tsConfigFilePath: tsconfigPath,
|
|
1712
|
-
skipAddingFilesFromTsConfig: true,
|
|
1713
|
-
skipLoadingLibFiles: true,
|
|
1714
|
-
skipFileDependencyResolution: true
|
|
1715
|
-
});
|
|
1716
|
-
} catch {
|
|
1717
|
-
project = new import_ts_morph3.Project({
|
|
1718
|
-
skipAddingFilesFromTsConfig: true,
|
|
1719
|
-
skipLoadingLibFiles: true,
|
|
1720
|
-
skipFileDependencyResolution: true,
|
|
1721
|
-
compilerOptions: { allowJs: true, strict: false }
|
|
1722
|
-
});
|
|
1723
|
-
}
|
|
1724
|
-
sharedProps = discoverSharedProps(project, config.app.moduleEntry);
|
|
1725
|
-
} catch {
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1616
|
+
const sharedProps = discoverSharedPropsFromConfig(config);
|
|
1728
1617
|
await emitPages(pages, config.codegen.outDir, {
|
|
1729
1618
|
propsExport: pagesConfig.propsExport,
|
|
1730
1619
|
sharedProps
|
|
@@ -1748,8 +1637,8 @@ async function generate(config, inputRoutes = []) {
|
|
|
1748
1637
|
if (extensions.length > 0) {
|
|
1749
1638
|
const extraFiles = await collectEmittedFiles(extensions, ctx);
|
|
1750
1639
|
for (const file of extraFiles) {
|
|
1751
|
-
const dest = (0,
|
|
1752
|
-
await (0, import_promises9.mkdir)((0,
|
|
1640
|
+
const dest = (0, import_node_path10.join)(config.codegen.outDir, file.path);
|
|
1641
|
+
await (0, import_promises9.mkdir)((0, import_node_path10.dirname)(dest), { recursive: true });
|
|
1753
1642
|
await (0, import_promises9.writeFile)(dest, file.contents, "utf8");
|
|
1754
1643
|
}
|
|
1755
1644
|
}
|
|
@@ -1757,35 +1646,31 @@ async function generate(config, inputRoutes = []) {
|
|
|
1757
1646
|
|
|
1758
1647
|
// src/watch/watcher.ts
|
|
1759
1648
|
var import_promises12 = require("fs/promises");
|
|
1760
|
-
var
|
|
1649
|
+
var import_node_path14 = require("path");
|
|
1761
1650
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
1762
1651
|
|
|
1763
1652
|
// src/discovery/contracts-fast.ts
|
|
1764
|
-
var
|
|
1653
|
+
var import_node_path12 = require("path");
|
|
1765
1654
|
var import_fast_glob2 = __toESM(require("fast-glob"), 1);
|
|
1766
1655
|
var import_ts_morph9 = require("ts-morph");
|
|
1767
1656
|
|
|
1657
|
+
// src/discovery/dto-type-resolver.ts
|
|
1658
|
+
var import_ts_morph7 = require("ts-morph");
|
|
1659
|
+
|
|
1768
1660
|
// src/discovery/dto-to-ir.ts
|
|
1769
|
-
var
|
|
1661
|
+
var import_ts_morph4 = require("ts-morph");
|
|
1770
1662
|
|
|
1771
1663
|
// src/discovery/type-ref-resolution.ts
|
|
1772
1664
|
var import_node_fs = require("fs");
|
|
1773
|
-
var
|
|
1774
|
-
var
|
|
1775
|
-
var
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
return prev;
|
|
1780
|
-
}
|
|
1781
|
-
function restoreDiscoveryContext(ctx) {
|
|
1782
|
-
_ctx = ctx;
|
|
1783
|
-
}
|
|
1784
|
-
function _projectRoot() {
|
|
1785
|
-
return _ctx.projectRoot;
|
|
1665
|
+
var import_node_path11 = require("path");
|
|
1666
|
+
var import_ts_morph3 = require("ts-morph");
|
|
1667
|
+
var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
|
|
1668
|
+
var _ctxByProject = /* @__PURE__ */ new WeakMap();
|
|
1669
|
+
function setDiscoveryContext(project, ctx) {
|
|
1670
|
+
_ctxByProject.set(project, ctx);
|
|
1786
1671
|
}
|
|
1787
|
-
function
|
|
1788
|
-
return
|
|
1672
|
+
function _ctxFor(project) {
|
|
1673
|
+
return _ctxByProject.get(project) ?? _EMPTY_CTX;
|
|
1789
1674
|
}
|
|
1790
1675
|
var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
|
|
1791
1676
|
function dbg(...args) {
|
|
@@ -1827,18 +1712,19 @@ function findTypeInFile(name, file) {
|
|
|
1827
1712
|
}
|
|
1828
1713
|
return null;
|
|
1829
1714
|
}
|
|
1830
|
-
function resolveModuleSpecifier(moduleSpecifier, sourceFile,
|
|
1715
|
+
function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
1831
1716
|
if (moduleSpecifier.startsWith(".")) {
|
|
1832
|
-
const dir = (0,
|
|
1717
|
+
const dir = (0, import_node_path11.dirname)(sourceFile.getFilePath());
|
|
1833
1718
|
const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
|
|
1834
1719
|
return [
|
|
1835
|
-
(0,
|
|
1836
|
-
(0,
|
|
1837
|
-
(0,
|
|
1720
|
+
(0, import_node_path11.resolve)(dir, `${noExt}.ts`),
|
|
1721
|
+
(0, import_node_path11.resolve)(dir, `${moduleSpecifier}.ts`),
|
|
1722
|
+
(0, import_node_path11.resolve)(dir, moduleSpecifier, "index.ts")
|
|
1838
1723
|
];
|
|
1839
1724
|
}
|
|
1840
|
-
const
|
|
1841
|
-
const
|
|
1725
|
+
const ctx = _ctxFor(project);
|
|
1726
|
+
const baseUrl = ctx.projectRoot;
|
|
1727
|
+
const tsconfigPaths = ctx.tsconfigPaths;
|
|
1842
1728
|
dbg(
|
|
1843
1729
|
"resolveModuleSpecifier",
|
|
1844
1730
|
moduleSpecifier,
|
|
@@ -1854,8 +1740,8 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, _project) {
|
|
|
1854
1740
|
const rest = moduleSpecifier.slice(prefix.length);
|
|
1855
1741
|
const candidates = [];
|
|
1856
1742
|
for (const mapping of mappings) {
|
|
1857
|
-
const resolved = (0,
|
|
1858
|
-
candidates.push(`${resolved}.ts`, (0,
|
|
1743
|
+
const resolved = (0, import_node_path11.resolve)(baseUrl, mapping.replace("*", rest));
|
|
1744
|
+
candidates.push(`${resolved}.ts`, (0, import_node_path11.resolve)(resolved, "index.ts"));
|
|
1859
1745
|
}
|
|
1860
1746
|
dbg(" resolved candidates:", candidates);
|
|
1861
1747
|
return candidates;
|
|
@@ -1955,13 +1841,13 @@ function resolveTypeRef(nodeOrName, sourceFile, project, opts) {
|
|
|
1955
1841
|
name = nodeOrName;
|
|
1956
1842
|
} else {
|
|
1957
1843
|
const typeNode = nodeOrName;
|
|
1958
|
-
if (opts.unwrapContainers &&
|
|
1844
|
+
if (opts.unwrapContainers && import_ts_morph3.Node.isArrayTypeNode(typeNode)) {
|
|
1959
1845
|
const inner = resolveTypeRef(typeNode.getElementTypeNode(), sourceFile, project, opts);
|
|
1960
1846
|
return inner ? { ...inner, isArray: true } : null;
|
|
1961
1847
|
}
|
|
1962
|
-
if (!
|
|
1848
|
+
if (!import_ts_morph3.Node.isTypeReference(typeNode)) return null;
|
|
1963
1849
|
const typeName = typeNode.getTypeName();
|
|
1964
|
-
const refName =
|
|
1850
|
+
const refName = import_ts_morph3.Node.isIdentifier(typeName) ? typeName.getText() : null;
|
|
1965
1851
|
if (!refName) return null;
|
|
1966
1852
|
if (opts.unwrapContainers && refName === "Promise") {
|
|
1967
1853
|
const first = typeNode.getTypeArguments()[0];
|
|
@@ -1984,7 +1870,7 @@ function resolveTypeRef(nodeOrName, sourceFile, project, opts) {
|
|
|
1984
1870
|
if (!namedImport) continue;
|
|
1985
1871
|
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
1986
1872
|
if (opts.allowBareSpecifier && !moduleSpecifier.startsWith(".") && !moduleSpecifier.startsWith("/")) {
|
|
1987
|
-
const tsconfigPaths =
|
|
1873
|
+
const tsconfigPaths = _ctxFor(project).tsconfigPaths;
|
|
1988
1874
|
const isAlias = tsconfigPaths != null && Object.keys(tsconfigPaths).some((p) => {
|
|
1989
1875
|
const prefix = p.replace("*", "");
|
|
1990
1876
|
return moduleSpecifier.startsWith(prefix);
|
|
@@ -2053,10 +1939,7 @@ function extractSchemaFromDto(classDecl, sourceFile, project) {
|
|
|
2053
1939
|
depth: 0
|
|
2054
1940
|
};
|
|
2055
1941
|
const root = buildObject(classDecl, sourceFile, ctx);
|
|
2056
|
-
|
|
2057
|
-
ctx.named.set(schemaName, { kind: "unknown", note: "recursive type \u2014 not expanded" });
|
|
2058
|
-
}
|
|
2059
|
-
return { root, named: ctx.named, warnings: ctx.warnings };
|
|
1942
|
+
return { root, named: ctx.named, warnings: ctx.warnings, recursive: ctx.recursiveSchemas };
|
|
2060
1943
|
}
|
|
2061
1944
|
function buildObject(classDecl, classFile, ctx) {
|
|
2062
1945
|
const props = classDecl.getProperties();
|
|
@@ -2076,7 +1959,7 @@ function buildProperty(prop, classFile, ctx) {
|
|
|
2076
1959
|
const dec = (n) => decorators.get(n);
|
|
2077
1960
|
const typeNode = prop.getTypeNode();
|
|
2078
1961
|
const typeText = typeNode?.getText() ?? "unknown";
|
|
2079
|
-
const isArrayType = !!typeNode &&
|
|
1962
|
+
const isArrayType = !!typeNode && import_ts_morph4.Node.isArrayTypeNode(typeNode);
|
|
2080
1963
|
const typeRefName = resolveTypeFactoryName(dec("Type"));
|
|
2081
1964
|
if (has("ValidateNested") || typeRefName) {
|
|
2082
1965
|
const childName = typeRefName ?? singularClassName(typeText);
|
|
@@ -2207,18 +2090,27 @@ function baseFromType(typeText, isArrayType) {
|
|
|
2207
2090
|
}
|
|
2208
2091
|
}
|
|
2209
2092
|
function buildNestedReference(className, fromFile, ctx) {
|
|
2210
|
-
if (ctx.visiting.has(className)
|
|
2093
|
+
if (ctx.visiting.has(className)) {
|
|
2211
2094
|
const reserved = ctx.emittedClasses.get(className) ?? aliasFor(className, ctx);
|
|
2212
2095
|
ctx.emittedClasses.set(className, reserved);
|
|
2213
2096
|
ctx.recursiveSchemas.add(reserved);
|
|
2214
2097
|
if (!ctx.warnedDecorators.has(`recursive:${reserved}`)) {
|
|
2215
2098
|
ctx.warnedDecorators.add(`recursive:${reserved}`);
|
|
2216
|
-
const msg = `${className} is a recursive type
|
|
2099
|
+
const msg = `${className} is a recursive type; the generated schema validates it via a lazy self-reference.`;
|
|
2217
2100
|
ctx.warnings.push(msg);
|
|
2218
2101
|
console.warn(`[nestjs-codegen] ${msg}`);
|
|
2219
2102
|
}
|
|
2220
2103
|
return { kind: "lazyRef", name: reserved };
|
|
2221
2104
|
}
|
|
2105
|
+
if (ctx.depth >= 8) {
|
|
2106
|
+
if (!ctx.warnedDecorators.has(`deep:${className}`)) {
|
|
2107
|
+
ctx.warnedDecorators.add(`deep:${className}`);
|
|
2108
|
+
const msg = `${className} nesting is too deep to expand; the generated schema uses unknown for it.`;
|
|
2109
|
+
ctx.warnings.push(msg);
|
|
2110
|
+
console.warn(`[nestjs-codegen] ${msg}`);
|
|
2111
|
+
}
|
|
2112
|
+
return { kind: "unknown", note: "nesting too deep \u2014 not expanded" };
|
|
2113
|
+
}
|
|
2222
2114
|
const existing = ctx.emittedClasses.get(className);
|
|
2223
2115
|
if (existing) return { kind: "ref", name: existing };
|
|
2224
2116
|
const schemaName = aliasFor(className, ctx);
|
|
@@ -2256,22 +2148,22 @@ function firstArgText(decorator) {
|
|
|
2256
2148
|
}
|
|
2257
2149
|
function numericArg(decorator) {
|
|
2258
2150
|
const arg = firstArg(decorator);
|
|
2259
|
-
if (arg &&
|
|
2151
|
+
if (arg && import_ts_morph4.Node.isNumericLiteral(arg)) return arg.getText();
|
|
2260
2152
|
return null;
|
|
2261
2153
|
}
|
|
2262
2154
|
function numericArgs(decorator) {
|
|
2263
2155
|
const args = decorator?.getArguments() ?? [];
|
|
2264
|
-
const num = (n) => n &&
|
|
2156
|
+
const num = (n) => n && import_ts_morph4.Node.isNumericLiteral(n) ? n.getText() : null;
|
|
2265
2157
|
return [num(args[0]), num(args[1])];
|
|
2266
2158
|
}
|
|
2267
2159
|
function messageRaw(decorator) {
|
|
2268
2160
|
const args = decorator?.getArguments() ?? [];
|
|
2269
2161
|
for (const arg of args) {
|
|
2270
|
-
if (
|
|
2162
|
+
if (import_ts_morph4.Node.isObjectLiteralExpression(arg)) {
|
|
2271
2163
|
for (const prop of arg.getProperties()) {
|
|
2272
|
-
if (
|
|
2164
|
+
if (import_ts_morph4.Node.isPropertyAssignment(prop) && prop.getName() === "message") {
|
|
2273
2165
|
const init = prop.getInitializer();
|
|
2274
|
-
if (init &&
|
|
2166
|
+
if (init && import_ts_morph4.Node.isStringLiteral(init)) return init.getText();
|
|
2275
2167
|
}
|
|
2276
2168
|
}
|
|
2277
2169
|
}
|
|
@@ -2281,9 +2173,9 @@ function messageRaw(decorator) {
|
|
|
2281
2173
|
function resolveTypeFactoryName(decorator) {
|
|
2282
2174
|
const arg = firstArg(decorator);
|
|
2283
2175
|
if (!arg) return null;
|
|
2284
|
-
if (
|
|
2176
|
+
if (import_ts_morph4.Node.isArrowFunction(arg)) {
|
|
2285
2177
|
const body = arg.getBody();
|
|
2286
|
-
if (
|
|
2178
|
+
if (import_ts_morph4.Node.isIdentifier(body)) return body.getText();
|
|
2287
2179
|
}
|
|
2288
2180
|
return null;
|
|
2289
2181
|
}
|
|
@@ -2294,7 +2186,7 @@ function singularClassName(typeText) {
|
|
|
2294
2186
|
function enumSchemaFromDecorator(decorator, classFile, ctx) {
|
|
2295
2187
|
const arg = firstArg(decorator);
|
|
2296
2188
|
if (!arg) return null;
|
|
2297
|
-
if (
|
|
2189
|
+
if (import_ts_morph4.Node.isIdentifier(arg)) {
|
|
2298
2190
|
const name = arg.getText();
|
|
2299
2191
|
const resolved = findType(name, classFile, ctx.project);
|
|
2300
2192
|
if (resolved && resolved.kind === "enum") {
|
|
@@ -2308,12 +2200,12 @@ function enumSchemaFromDecorator(decorator, classFile, ctx) {
|
|
|
2308
2200
|
}
|
|
2309
2201
|
return { kind: "unknown", note: `@IsEnum(${name}): enum not resolvable to literals` };
|
|
2310
2202
|
}
|
|
2311
|
-
if (
|
|
2203
|
+
if (import_ts_morph4.Node.isObjectLiteralExpression(arg)) {
|
|
2312
2204
|
const values = [];
|
|
2313
2205
|
for (const p of arg.getProperties()) {
|
|
2314
|
-
if (!
|
|
2206
|
+
if (!import_ts_morph4.Node.isPropertyAssignment(p)) continue;
|
|
2315
2207
|
const init = p.getInitializer();
|
|
2316
|
-
if (init &&
|
|
2208
|
+
if (init && import_ts_morph4.Node.isStringLiteral(init)) values.push(init.getText());
|
|
2317
2209
|
}
|
|
2318
2210
|
if (values.length > 0) return { kind: "enum", literals: values };
|
|
2319
2211
|
}
|
|
@@ -2321,9 +2213,9 @@ function enumSchemaFromDecorator(decorator, classFile, ctx) {
|
|
|
2321
2213
|
}
|
|
2322
2214
|
function inSchemaFromDecorator(decorator) {
|
|
2323
2215
|
const arg = firstArg(decorator);
|
|
2324
|
-
if (arg &&
|
|
2216
|
+
if (arg && import_ts_morph4.Node.isArrayLiteralExpression(arg)) {
|
|
2325
2217
|
const elements = arg.getElements();
|
|
2326
|
-
const allStrings = elements.every((e) =>
|
|
2218
|
+
const allStrings = elements.every((e) => import_ts_morph4.Node.isStringLiteral(e));
|
|
2327
2219
|
if (allStrings && elements.length > 0) {
|
|
2328
2220
|
return { kind: "enum", literals: elements.map((e) => e.getText()) };
|
|
2329
2221
|
}
|
|
@@ -2337,458 +2229,130 @@ function inSchemaFromDecorator(decorator) {
|
|
|
2337
2229
|
return null;
|
|
2338
2230
|
}
|
|
2339
2231
|
|
|
2340
|
-
// src/discovery/
|
|
2232
|
+
// src/discovery/filter-for.ts
|
|
2341
2233
|
var import_ts_morph6 = require("ts-morph");
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
"
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
"Matches",
|
|
2359
|
-
"IsEnum",
|
|
2360
|
-
"IsIn",
|
|
2361
|
-
"IsOptional",
|
|
2362
|
-
"IsNotEmpty",
|
|
2363
|
-
"IsArray",
|
|
2364
|
-
"ValidateNested",
|
|
2365
|
-
"Type",
|
|
2366
|
-
"IsObject",
|
|
2367
|
-
"Allow",
|
|
2368
|
-
"IsDefined"
|
|
2369
|
-
]);
|
|
2370
|
-
function extractZodFromDto(classDecl, sourceFile, project) {
|
|
2371
|
-
const ctx = {
|
|
2372
|
-
sourceFile,
|
|
2373
|
-
project,
|
|
2374
|
-
namedNestedSchemas: /* @__PURE__ */ new Map(),
|
|
2375
|
-
warnings: [],
|
|
2376
|
-
warnedDecorators: /* @__PURE__ */ new Set(),
|
|
2377
|
-
emittedClasses: /* @__PURE__ */ new Map(),
|
|
2378
|
-
visiting: /* @__PURE__ */ new Set(),
|
|
2379
|
-
recursiveSchemas: /* @__PURE__ */ new Set(),
|
|
2380
|
-
depth: 0
|
|
2381
|
-
};
|
|
2382
|
-
const schemaText = buildObjectSchema(classDecl, sourceFile, ctx);
|
|
2383
|
-
for (const schemaName of ctx.recursiveSchemas) {
|
|
2384
|
-
ctx.namedNestedSchemas.set(schemaName, "z.unknown() /* recursive type \u2014 not expanded */");
|
|
2385
|
-
}
|
|
2386
|
-
return {
|
|
2387
|
-
schemaText,
|
|
2388
|
-
namedNestedSchemas: ctx.namedNestedSchemas,
|
|
2389
|
-
warnings: ctx.warnings
|
|
2390
|
-
};
|
|
2234
|
+
|
|
2235
|
+
// src/discovery/filter-field-types.ts
|
|
2236
|
+
var import_ts_morph5 = require("ts-morph");
|
|
2237
|
+
|
|
2238
|
+
// src/discovery/enum-resolution.ts
|
|
2239
|
+
function resolveEnumValues(name, sourceFile, project) {
|
|
2240
|
+
const resolved = findType(name, sourceFile, project);
|
|
2241
|
+
if (!resolved || resolved.kind !== "enum") return null;
|
|
2242
|
+
let numeric = true;
|
|
2243
|
+
const values = resolved.members.map((m) => {
|
|
2244
|
+
const parsed = JSON.parse(m);
|
|
2245
|
+
if (typeof parsed === "string") numeric = false;
|
|
2246
|
+
return String(parsed);
|
|
2247
|
+
});
|
|
2248
|
+
if (values.length === 0) return null;
|
|
2249
|
+
return { values, numeric };
|
|
2391
2250
|
}
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2251
|
+
|
|
2252
|
+
// src/discovery/filter-field-types.ts
|
|
2253
|
+
var STRING_TYPE_KEYWORDS = ["varchar", "text", "string", "char", "uuid", "enum"];
|
|
2254
|
+
var NUMBER_TYPE_KEYWORDS = ["int", "float", "double", "decimal", "number", "numeric", "real"];
|
|
2255
|
+
var BOOLEAN_TYPE_KEYWORDS = ["bool", "boolean", "bit"];
|
|
2256
|
+
var DATE_TYPE_KEYWORDS = ["date", "time", "timestamp", "datetime"];
|
|
2257
|
+
var JSON_TYPE_KEYWORDS = ["json", "jsonb"];
|
|
2258
|
+
function classifyTypeKeyword(raw) {
|
|
2259
|
+
const t = raw.toLowerCase();
|
|
2260
|
+
if (STRING_TYPE_KEYWORDS.some((s) => t.includes(s))) return "string";
|
|
2261
|
+
if (NUMBER_TYPE_KEYWORDS.some((s) => t.includes(s))) return "number";
|
|
2262
|
+
if (BOOLEAN_TYPE_KEYWORDS.some((s) => t.includes(s))) return "boolean";
|
|
2263
|
+
if (DATE_TYPE_KEYWORDS.some((s) => t.includes(s))) return "date";
|
|
2264
|
+
if (JSON_TYPE_KEYWORDS.some((s) => t.includes(s))) return "json";
|
|
2265
|
+
return null;
|
|
2404
2266
|
}
|
|
2405
|
-
function
|
|
2406
|
-
return
|
|
2267
|
+
function markNullable(r, nullable) {
|
|
2268
|
+
return nullable ? { ...r, nullable: true } : r;
|
|
2407
2269
|
}
|
|
2408
|
-
function
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2270
|
+
function classifyTypeNode(typeNode, sourceFile, project, opts) {
|
|
2271
|
+
if (import_ts_morph5.Node.isUnionTypeNode(typeNode)) {
|
|
2272
|
+
let nullable = false;
|
|
2273
|
+
const stringLits = [];
|
|
2274
|
+
const numberLits = [];
|
|
2275
|
+
const others = [];
|
|
2276
|
+
for (const member of typeNode.getTypeNodes()) {
|
|
2277
|
+
const kind = member.getKind();
|
|
2278
|
+
if (kind === import_ts_morph5.SyntaxKind.NullKeyword || kind === import_ts_morph5.SyntaxKind.UndefinedKeyword) {
|
|
2279
|
+
nullable = true;
|
|
2280
|
+
continue;
|
|
2281
|
+
}
|
|
2282
|
+
if (import_ts_morph5.Node.isLiteralTypeNode(member)) {
|
|
2283
|
+
const lit = member.getLiteral();
|
|
2284
|
+
if (import_ts_morph5.Node.isStringLiteral(lit)) {
|
|
2285
|
+
stringLits.push(lit.getLiteralValue());
|
|
2286
|
+
continue;
|
|
2287
|
+
}
|
|
2288
|
+
if (import_ts_morph5.Node.isNumericLiteral(lit)) {
|
|
2289
|
+
numberLits.push(lit.getText());
|
|
2290
|
+
continue;
|
|
2291
|
+
}
|
|
2292
|
+
if (lit.getKind() === import_ts_morph5.SyntaxKind.NullKeyword) {
|
|
2293
|
+
nullable = true;
|
|
2294
|
+
continue;
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
others.push(member);
|
|
2298
|
+
}
|
|
2299
|
+
if (others.length === 0 && stringLits.length > 0 && numberLits.length === 0) {
|
|
2300
|
+
return markNullable({ kind: "string", enumValues: stringLits }, nullable);
|
|
2301
|
+
}
|
|
2302
|
+
if (others.length === 0 && numberLits.length > 0 && stringLits.length === 0) {
|
|
2303
|
+
return markNullable({ kind: "number", enumValues: numberLits, numericEnum: true }, nullable);
|
|
2304
|
+
}
|
|
2305
|
+
if (others.length === 1) {
|
|
2306
|
+
const inner = classifyTypeNode(others[0], sourceFile, project, opts);
|
|
2307
|
+
return markNullable(inner, nullable || inner.nullable === true);
|
|
2308
|
+
}
|
|
2309
|
+
return markNullable({ kind: "unknown" }, nullable);
|
|
2444
2310
|
}
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2311
|
+
switch (typeNode.getKind()) {
|
|
2312
|
+
case import_ts_morph5.SyntaxKind.StringKeyword:
|
|
2313
|
+
return { kind: "string" };
|
|
2314
|
+
case import_ts_morph5.SyntaxKind.NumberKeyword:
|
|
2315
|
+
return { kind: "number" };
|
|
2316
|
+
case import_ts_morph5.SyntaxKind.BooleanKeyword:
|
|
2317
|
+
return { kind: "boolean" };
|
|
2318
|
+
case import_ts_morph5.SyntaxKind.AnyKeyword:
|
|
2319
|
+
case import_ts_morph5.SyntaxKind.UnknownKeyword:
|
|
2320
|
+
return { kind: "unknown" };
|
|
2321
|
+
default:
|
|
2322
|
+
break;
|
|
2448
2323
|
}
|
|
2449
|
-
if (
|
|
2450
|
-
const
|
|
2451
|
-
if (
|
|
2452
|
-
|
|
2453
|
-
|
|
2324
|
+
if (import_ts_morph5.Node.isTypeReference(typeNode)) {
|
|
2325
|
+
const refName = typeNode.getTypeName().getText();
|
|
2326
|
+
if (refName === "Date") return { kind: "date" };
|
|
2327
|
+
if (refName === "Record" || refName === "Object") return { kind: "json" };
|
|
2328
|
+
const typeRef = opts?.resolveRef?.(refName) ?? null;
|
|
2329
|
+
const en = resolveEnumValues(refName, sourceFile, project);
|
|
2330
|
+
if (en) {
|
|
2331
|
+
const base = en.numeric ? { kind: "number", enumValues: en.values, numericEnum: true } : { kind: "string", enumValues: en.values };
|
|
2332
|
+
return typeRef ? { ...base, typeRef } : base;
|
|
2454
2333
|
}
|
|
2334
|
+
if (typeRef) return { kind: "unknown", typeRef };
|
|
2335
|
+
return { kind: "unknown" };
|
|
2455
2336
|
}
|
|
2456
|
-
if (
|
|
2457
|
-
const n = numericArg2(dec("MinLength"));
|
|
2458
|
-
if (n !== null) refinements.push(`.min(${n})`);
|
|
2459
|
-
}
|
|
2460
|
-
if (has("MaxLength")) {
|
|
2461
|
-
const n = numericArg2(dec("MaxLength"));
|
|
2462
|
-
if (n !== null) refinements.push(`.max(${n})`);
|
|
2463
|
-
}
|
|
2464
|
-
if (has("Length")) {
|
|
2465
|
-
const [min, max] = numericArgs2(dec("Length"));
|
|
2466
|
-
if (min !== null) refinements.push(`.min(${min})`);
|
|
2467
|
-
if (max !== null) refinements.push(`.max(${max})`);
|
|
2468
|
-
}
|
|
2469
|
-
if (has("Min")) {
|
|
2470
|
-
const n = numericArg2(dec("Min"));
|
|
2471
|
-
if (n !== null) refinements.push(`.min(${n})`);
|
|
2472
|
-
}
|
|
2473
|
-
if (has("Max")) {
|
|
2474
|
-
const n = numericArg2(dec("Max"));
|
|
2475
|
-
if (n !== null) refinements.push(`.max(${n})`);
|
|
2476
|
-
}
|
|
2477
|
-
if (has("IsPositive")) refinements.push(".positive()");
|
|
2478
|
-
if (has("IsNegative")) refinements.push(".negative()");
|
|
2479
|
-
if (has("IsNotEmpty") && isStringBase(base)) refinements.push(".min(1)");
|
|
2480
|
-
if (has("IsEnum")) {
|
|
2481
|
-
const enumExpr = enumSchemaFromDecorator2(dec("IsEnum"), classFile, ctx);
|
|
2482
|
-
if (enumExpr) base = enumExpr;
|
|
2483
|
-
}
|
|
2484
|
-
if (has("IsIn")) {
|
|
2485
|
-
const inExpr = inSchemaFromDecorator2(dec("IsIn"));
|
|
2486
|
-
if (inExpr) base = inExpr;
|
|
2487
|
-
}
|
|
2488
|
-
for (const name of decorators.keys()) {
|
|
2489
|
-
if (!KNOWN_DECORATORS2.has(name)) {
|
|
2490
|
-
comments.push(`/* @${name}: not translatable to zod (server-only) */`);
|
|
2491
|
-
if (!ctx.warnedDecorators.has(name)) {
|
|
2492
|
-
ctx.warnedDecorators.add(name);
|
|
2493
|
-
const msg = `@${name} is not translatable to zod and was skipped (server-only validation).`;
|
|
2494
|
-
ctx.warnings.push(msg);
|
|
2495
|
-
console.warn(`[nestjs-codegen/forms] ${msg}`);
|
|
2496
|
-
}
|
|
2497
|
-
}
|
|
2498
|
-
}
|
|
2499
|
-
let expr = base + refinements.join("");
|
|
2500
|
-
if (isArrayType && !expr.startsWith("z.array(")) {
|
|
2501
|
-
expr = `z.array(${expr})`;
|
|
2502
|
-
}
|
|
2503
|
-
expr = applyPresence2(expr, decorators);
|
|
2504
|
-
if (comments.length > 0) {
|
|
2505
|
-
expr = `${expr} ${comments.join(" ")}`;
|
|
2506
|
-
}
|
|
2507
|
-
return expr;
|
|
2508
|
-
}
|
|
2509
|
-
function applyPresence2(expr, decorators) {
|
|
2510
|
-
if (decorators.has("IsDefined")) return expr;
|
|
2511
|
-
if (decorators.has("IsOptional")) return `${expr}.optional()`;
|
|
2512
|
-
return expr;
|
|
2513
|
-
}
|
|
2514
|
-
function baseFromType2(typeText, isArrayType, ctx, classFile) {
|
|
2515
|
-
const inner = isArrayType ? typeText.slice(0, -2).trim() : typeText;
|
|
2516
|
-
switch (inner) {
|
|
2517
|
-
case "string":
|
|
2518
|
-
return "z.string()";
|
|
2519
|
-
case "number":
|
|
2520
|
-
return "z.number()";
|
|
2521
|
-
case "boolean":
|
|
2522
|
-
return "z.boolean()";
|
|
2523
|
-
case "Date":
|
|
2524
|
-
return "z.coerce.date()";
|
|
2525
|
-
case "File":
|
|
2526
|
-
case "Express.Multer.File":
|
|
2527
|
-
return "z.instanceof(File)";
|
|
2528
|
-
default:
|
|
2529
|
-
return "z.unknown()";
|
|
2530
|
-
}
|
|
2531
|
-
}
|
|
2532
|
-
function ensureStringBase(base) {
|
|
2533
|
-
return isStringBase(base) ? base : "z.string()";
|
|
2534
|
-
}
|
|
2535
|
-
function isStringBase(base) {
|
|
2536
|
-
return base.startsWith("z.string(");
|
|
2537
|
-
}
|
|
2538
|
-
function buildNestedReference2(className, fromFile, ctx) {
|
|
2539
|
-
if (ctx.visiting.has(className) || ctx.depth >= 8) {
|
|
2540
|
-
const reserved = ctx.emittedClasses.get(className) ?? aliasFor2(className, ctx);
|
|
2541
|
-
ctx.emittedClasses.set(className, reserved);
|
|
2542
|
-
ctx.recursiveSchemas.add(reserved);
|
|
2543
|
-
if (!ctx.warnedDecorators.has(`recursive:${reserved}`)) {
|
|
2544
|
-
ctx.warnedDecorators.add(`recursive:${reserved}`);
|
|
2545
|
-
const msg = `${className} is a recursive type and was not expanded; the generated form schema uses z.unknown() for it.`;
|
|
2546
|
-
ctx.warnings.push(msg);
|
|
2547
|
-
console.warn(`[nestjs-codegen/forms] ${msg}`);
|
|
2548
|
-
}
|
|
2549
|
-
return `z.lazy(() => ${reserved})`;
|
|
2550
|
-
}
|
|
2551
|
-
const existing = ctx.emittedClasses.get(className);
|
|
2552
|
-
if (existing) return existing;
|
|
2553
|
-
const schemaName = aliasFor2(className, ctx);
|
|
2554
|
-
const resolved = findType(className, fromFile, ctx.project);
|
|
2555
|
-
if (!resolved || resolved.kind !== "class") {
|
|
2556
|
-
return "z.object({}).passthrough()";
|
|
2557
|
-
}
|
|
2558
|
-
ctx.emittedClasses.set(className, schemaName);
|
|
2559
|
-
ctx.visiting.add(className);
|
|
2560
|
-
ctx.depth += 1;
|
|
2561
|
-
const childText = buildObjectSchema(resolved.decl, resolved.file, ctx);
|
|
2562
|
-
ctx.depth -= 1;
|
|
2563
|
-
ctx.visiting.delete(className);
|
|
2564
|
-
ctx.namedNestedSchemas.set(schemaName, childText);
|
|
2565
|
-
return schemaName;
|
|
2566
|
-
}
|
|
2567
|
-
function aliasFor2(className, ctx) {
|
|
2568
|
-
const baseName = `${className}Schema`;
|
|
2569
|
-
let candidate = baseName;
|
|
2570
|
-
let i = 1;
|
|
2571
|
-
const used = new Set(ctx.namedNestedSchemas.keys());
|
|
2572
|
-
for (const v of ctx.emittedClasses.values()) used.add(v);
|
|
2573
|
-
while (used.has(candidate)) {
|
|
2574
|
-
candidate = `${baseName}_${i}`;
|
|
2575
|
-
i += 1;
|
|
2576
|
-
}
|
|
2577
|
-
return candidate;
|
|
2578
|
-
}
|
|
2579
|
-
function firstArg2(decorator) {
|
|
2580
|
-
return decorator?.getArguments()[0];
|
|
2581
|
-
}
|
|
2582
|
-
function firstArgText2(decorator) {
|
|
2583
|
-
const arg = firstArg2(decorator);
|
|
2584
|
-
return arg ? arg.getText() : null;
|
|
2585
|
-
}
|
|
2586
|
-
function numericArg2(decorator) {
|
|
2587
|
-
const arg = firstArg2(decorator);
|
|
2588
|
-
if (arg && import_ts_morph6.Node.isNumericLiteral(arg)) return arg.getText();
|
|
2589
|
-
return null;
|
|
2590
|
-
}
|
|
2591
|
-
function numericArgs2(decorator) {
|
|
2592
|
-
const args = decorator?.getArguments() ?? [];
|
|
2593
|
-
const num = (n) => n && import_ts_morph6.Node.isNumericLiteral(n) ? n.getText() : null;
|
|
2594
|
-
return [num(args[0]), num(args[1])];
|
|
2595
|
-
}
|
|
2596
|
-
function messageArg(decorator) {
|
|
2597
|
-
const args = decorator?.getArguments() ?? [];
|
|
2598
|
-
for (const arg of args) {
|
|
2599
|
-
if (import_ts_morph6.Node.isObjectLiteralExpression(arg)) {
|
|
2600
|
-
for (const prop of arg.getProperties()) {
|
|
2601
|
-
if (import_ts_morph6.Node.isPropertyAssignment(prop) && prop.getName() === "message") {
|
|
2602
|
-
const init = prop.getInitializer();
|
|
2603
|
-
if (init && import_ts_morph6.Node.isStringLiteral(init)) {
|
|
2604
|
-
return `{ message: ${init.getText()} }`;
|
|
2605
|
-
}
|
|
2606
|
-
}
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
}
|
|
2610
|
-
return "";
|
|
2611
|
-
}
|
|
2612
|
-
function resolveTypeFactoryName2(decorator) {
|
|
2613
|
-
const arg = firstArg2(decorator);
|
|
2614
|
-
if (!arg) return null;
|
|
2615
|
-
if (import_ts_morph6.Node.isArrowFunction(arg)) {
|
|
2616
|
-
const body = arg.getBody();
|
|
2617
|
-
if (import_ts_morph6.Node.isIdentifier(body)) return body.getText();
|
|
2618
|
-
}
|
|
2619
|
-
return null;
|
|
2620
|
-
}
|
|
2621
|
-
function singularClassName2(typeText) {
|
|
2622
|
-
const inner = typeText.endsWith("[]") ? typeText.slice(0, -2).trim() : typeText;
|
|
2623
|
-
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(inner) ? inner : null;
|
|
2624
|
-
}
|
|
2625
|
-
function enumSchemaFromDecorator2(decorator, classFile, ctx) {
|
|
2626
|
-
const arg = firstArg2(decorator);
|
|
2627
|
-
if (!arg) return null;
|
|
2628
|
-
if (import_ts_morph6.Node.isIdentifier(arg)) {
|
|
2629
|
-
const name = arg.getText();
|
|
2630
|
-
const resolved = findType(name, classFile, ctx.project);
|
|
2631
|
-
if (resolved && resolved.kind === "enum") {
|
|
2632
|
-
return `z.enum([${resolved.members.join(", ")}])`;
|
|
2633
|
-
}
|
|
2634
|
-
const msg = `@IsEnum(${name}): enum could not be resolved to literal members and is not importable into the generated form schema; falling back to z.unknown().`;
|
|
2635
|
-
if (!ctx.warnedDecorators.has(`IsEnum:${name}`)) {
|
|
2636
|
-
ctx.warnedDecorators.add(`IsEnum:${name}`);
|
|
2637
|
-
ctx.warnings.push(msg);
|
|
2638
|
-
console.warn(`[nestjs-codegen/forms] ${msg}`);
|
|
2639
|
-
}
|
|
2640
|
-
return `z.unknown() /* @IsEnum(${name}): enum not resolvable to literals */`;
|
|
2641
|
-
}
|
|
2642
|
-
if (import_ts_morph6.Node.isObjectLiteralExpression(arg)) {
|
|
2643
|
-
const values = [];
|
|
2644
|
-
for (const p of arg.getProperties()) {
|
|
2645
|
-
if (!import_ts_morph6.Node.isPropertyAssignment(p)) continue;
|
|
2646
|
-
const init = p.getInitializer();
|
|
2647
|
-
if (init && import_ts_morph6.Node.isStringLiteral(init)) values.push(init.getText());
|
|
2648
|
-
}
|
|
2649
|
-
if (values.length > 0) return `z.enum([${values.join(", ")}])`;
|
|
2650
|
-
}
|
|
2651
|
-
return null;
|
|
2652
|
-
}
|
|
2653
|
-
function inSchemaFromDecorator2(decorator) {
|
|
2654
|
-
const arg = firstArg2(decorator);
|
|
2655
|
-
if (arg && import_ts_morph6.Node.isArrayLiteralExpression(arg)) {
|
|
2656
|
-
const elements = arg.getElements();
|
|
2657
|
-
const allStrings = elements.every((e) => import_ts_morph6.Node.isStringLiteral(e));
|
|
2658
|
-
if (allStrings && elements.length > 0) {
|
|
2659
|
-
return `z.enum([${elements.map((e) => e.getText()).join(", ")}])`;
|
|
2660
|
-
}
|
|
2661
|
-
if (elements.length > 0) {
|
|
2662
|
-
return `z.union([${elements.map((e) => `z.literal(${e.getText()})`).join(", ")}])`;
|
|
2663
|
-
}
|
|
2664
|
-
}
|
|
2665
|
-
return null;
|
|
2666
|
-
}
|
|
2667
|
-
|
|
2668
|
-
// src/discovery/filter-for.ts
|
|
2669
|
-
var import_ts_morph8 = require("ts-morph");
|
|
2670
|
-
|
|
2671
|
-
// src/discovery/filter-field-types.ts
|
|
2672
|
-
var import_ts_morph7 = require("ts-morph");
|
|
2673
|
-
|
|
2674
|
-
// src/discovery/enum-resolution.ts
|
|
2675
|
-
function resolveEnumValues(name, sourceFile, project) {
|
|
2676
|
-
const resolved = findType(name, sourceFile, project);
|
|
2677
|
-
if (!resolved || resolved.kind !== "enum") return null;
|
|
2678
|
-
let numeric = true;
|
|
2679
|
-
const values = resolved.members.map((m) => {
|
|
2680
|
-
const parsed = JSON.parse(m);
|
|
2681
|
-
if (typeof parsed === "string") numeric = false;
|
|
2682
|
-
return String(parsed);
|
|
2683
|
-
});
|
|
2684
|
-
if (values.length === 0) return null;
|
|
2685
|
-
return { values, numeric };
|
|
2686
|
-
}
|
|
2687
|
-
|
|
2688
|
-
// src/discovery/filter-field-types.ts
|
|
2689
|
-
var STRING_TYPE_KEYWORDS = ["varchar", "text", "string", "char", "uuid", "enum"];
|
|
2690
|
-
var NUMBER_TYPE_KEYWORDS = ["int", "float", "double", "decimal", "number", "numeric", "real"];
|
|
2691
|
-
var BOOLEAN_TYPE_KEYWORDS = ["bool", "boolean", "bit"];
|
|
2692
|
-
var DATE_TYPE_KEYWORDS = ["date", "time", "timestamp", "datetime"];
|
|
2693
|
-
var JSON_TYPE_KEYWORDS = ["json", "jsonb"];
|
|
2694
|
-
function classifyTypeKeyword(raw) {
|
|
2695
|
-
const t = raw.toLowerCase();
|
|
2696
|
-
if (STRING_TYPE_KEYWORDS.some((s) => t.includes(s))) return "string";
|
|
2697
|
-
if (NUMBER_TYPE_KEYWORDS.some((s) => t.includes(s))) return "number";
|
|
2698
|
-
if (BOOLEAN_TYPE_KEYWORDS.some((s) => t.includes(s))) return "boolean";
|
|
2699
|
-
if (DATE_TYPE_KEYWORDS.some((s) => t.includes(s))) return "date";
|
|
2700
|
-
if (JSON_TYPE_KEYWORDS.some((s) => t.includes(s))) return "json";
|
|
2701
|
-
return null;
|
|
2702
|
-
}
|
|
2703
|
-
function markNullable(r, nullable) {
|
|
2704
|
-
return nullable ? { ...r, nullable: true } : r;
|
|
2705
|
-
}
|
|
2706
|
-
function classifyTypeNode(typeNode, sourceFile, project, opts) {
|
|
2707
|
-
if (import_ts_morph7.Node.isUnionTypeNode(typeNode)) {
|
|
2708
|
-
let nullable = false;
|
|
2709
|
-
const stringLits = [];
|
|
2710
|
-
const numberLits = [];
|
|
2711
|
-
const others = [];
|
|
2712
|
-
for (const member of typeNode.getTypeNodes()) {
|
|
2713
|
-
const kind = member.getKind();
|
|
2714
|
-
if (kind === import_ts_morph7.SyntaxKind.NullKeyword || kind === import_ts_morph7.SyntaxKind.UndefinedKeyword) {
|
|
2715
|
-
nullable = true;
|
|
2716
|
-
continue;
|
|
2717
|
-
}
|
|
2718
|
-
if (import_ts_morph7.Node.isLiteralTypeNode(member)) {
|
|
2719
|
-
const lit = member.getLiteral();
|
|
2720
|
-
if (import_ts_morph7.Node.isStringLiteral(lit)) {
|
|
2721
|
-
stringLits.push(lit.getLiteralValue());
|
|
2722
|
-
continue;
|
|
2723
|
-
}
|
|
2724
|
-
if (import_ts_morph7.Node.isNumericLiteral(lit)) {
|
|
2725
|
-
numberLits.push(lit.getText());
|
|
2726
|
-
continue;
|
|
2727
|
-
}
|
|
2728
|
-
if (lit.getKind() === import_ts_morph7.SyntaxKind.NullKeyword) {
|
|
2729
|
-
nullable = true;
|
|
2730
|
-
continue;
|
|
2731
|
-
}
|
|
2732
|
-
}
|
|
2733
|
-
others.push(member);
|
|
2734
|
-
}
|
|
2735
|
-
if (others.length === 0 && stringLits.length > 0 && numberLits.length === 0) {
|
|
2736
|
-
return markNullable({ kind: "string", enumValues: stringLits }, nullable);
|
|
2737
|
-
}
|
|
2738
|
-
if (others.length === 0 && numberLits.length > 0 && stringLits.length === 0) {
|
|
2739
|
-
return markNullable({ kind: "number", enumValues: numberLits, numericEnum: true }, nullable);
|
|
2740
|
-
}
|
|
2741
|
-
if (others.length === 1) {
|
|
2742
|
-
const inner = classifyTypeNode(others[0], sourceFile, project, opts);
|
|
2743
|
-
return markNullable(inner, nullable || inner.nullable === true);
|
|
2744
|
-
}
|
|
2745
|
-
return markNullable({ kind: "unknown" }, nullable);
|
|
2746
|
-
}
|
|
2747
|
-
switch (typeNode.getKind()) {
|
|
2748
|
-
case import_ts_morph7.SyntaxKind.StringKeyword:
|
|
2749
|
-
return { kind: "string" };
|
|
2750
|
-
case import_ts_morph7.SyntaxKind.NumberKeyword:
|
|
2751
|
-
return { kind: "number" };
|
|
2752
|
-
case import_ts_morph7.SyntaxKind.BooleanKeyword:
|
|
2753
|
-
return { kind: "boolean" };
|
|
2754
|
-
case import_ts_morph7.SyntaxKind.AnyKeyword:
|
|
2755
|
-
case import_ts_morph7.SyntaxKind.UnknownKeyword:
|
|
2756
|
-
return { kind: "unknown" };
|
|
2757
|
-
default:
|
|
2758
|
-
break;
|
|
2759
|
-
}
|
|
2760
|
-
if (import_ts_morph7.Node.isTypeReference(typeNode)) {
|
|
2761
|
-
const refName = typeNode.getTypeName().getText();
|
|
2762
|
-
if (refName === "Date") return { kind: "date" };
|
|
2763
|
-
if (refName === "Record" || refName === "Object") return { kind: "json" };
|
|
2764
|
-
const typeRef = opts?.resolveRef?.(refName) ?? null;
|
|
2765
|
-
const en = resolveEnumValues(refName, sourceFile, project);
|
|
2766
|
-
if (en) {
|
|
2767
|
-
const base = en.numeric ? { kind: "number", enumValues: en.values, numericEnum: true } : { kind: "string", enumValues: en.values };
|
|
2768
|
-
return typeRef ? { ...base, typeRef } : base;
|
|
2769
|
-
}
|
|
2770
|
-
if (typeRef) return { kind: "unknown", typeRef };
|
|
2771
|
-
return { kind: "unknown" };
|
|
2772
|
-
}
|
|
2773
|
-
if (import_ts_morph7.Node.isTypeLiteral(typeNode)) return { kind: "json" };
|
|
2337
|
+
if (import_ts_morph5.Node.isTypeLiteral(typeNode)) return { kind: "json" };
|
|
2774
2338
|
return { kind: "unknown" };
|
|
2775
2339
|
}
|
|
2776
2340
|
function enumFromDecoratorArgs(args, sourceFile, project) {
|
|
2777
2341
|
for (const arg of args) {
|
|
2778
|
-
if (
|
|
2342
|
+
if (import_ts_morph5.Node.isArrowFunction(arg)) {
|
|
2779
2343
|
const body = arg.getBody();
|
|
2780
|
-
if (
|
|
2344
|
+
if (import_ts_morph5.Node.isIdentifier(body)) {
|
|
2781
2345
|
const en = resolveEnumValues(body.getText(), sourceFile, project);
|
|
2782
2346
|
if (en) return en;
|
|
2783
2347
|
}
|
|
2784
2348
|
}
|
|
2785
|
-
if (
|
|
2349
|
+
if (import_ts_morph5.Node.isObjectLiteralExpression(arg)) {
|
|
2786
2350
|
const itemsProp = arg.getProperty("items");
|
|
2787
|
-
if (itemsProp &&
|
|
2351
|
+
if (itemsProp && import_ts_morph5.Node.isPropertyAssignment(itemsProp)) {
|
|
2788
2352
|
const init = itemsProp.getInitializer();
|
|
2789
|
-
if (init &&
|
|
2353
|
+
if (init && import_ts_morph5.Node.isArrowFunction(init)) {
|
|
2790
2354
|
const body = init.getBody();
|
|
2791
|
-
if (
|
|
2355
|
+
if (import_ts_morph5.Node.isIdentifier(body)) {
|
|
2792
2356
|
const en = resolveEnumValues(body.getText(), sourceFile, project);
|
|
2793
2357
|
if (en) return en;
|
|
2794
2358
|
}
|
|
@@ -2811,7 +2375,7 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
|
|
|
2811
2375
|
return { kind: "string" };
|
|
2812
2376
|
}
|
|
2813
2377
|
for (const arg of args) {
|
|
2814
|
-
if (
|
|
2378
|
+
if (import_ts_morph5.Node.isStringLiteral(arg)) {
|
|
2815
2379
|
const raw = arg.getLiteralValue();
|
|
2816
2380
|
const kind = classifyTypeKeyword(raw);
|
|
2817
2381
|
if (kind) {
|
|
@@ -2819,11 +2383,11 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
|
|
|
2819
2383
|
return { kind };
|
|
2820
2384
|
}
|
|
2821
2385
|
}
|
|
2822
|
-
if (
|
|
2386
|
+
if (import_ts_morph5.Node.isObjectLiteralExpression(arg)) {
|
|
2823
2387
|
const enumProp = arg.getProperty("enum");
|
|
2824
|
-
if (enumProp &&
|
|
2388
|
+
if (enumProp && import_ts_morph5.Node.isPropertyAssignment(enumProp)) {
|
|
2825
2389
|
const init = enumProp.getInitializer();
|
|
2826
|
-
if (init &&
|
|
2390
|
+
if (init && import_ts_morph5.Node.isIdentifier(init)) {
|
|
2827
2391
|
const en = resolveEnumValues(init.getText(), sourceFile, project);
|
|
2828
2392
|
if (en) {
|
|
2829
2393
|
return en.numeric ? { kind: "number", enumValues: en.values, numericEnum: true } : { kind: "string", enumValues: en.values };
|
|
@@ -2832,9 +2396,9 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
|
|
|
2832
2396
|
}
|
|
2833
2397
|
}
|
|
2834
2398
|
const typeProp = arg.getProperty("type");
|
|
2835
|
-
if (typeProp &&
|
|
2399
|
+
if (typeProp && import_ts_morph5.Node.isPropertyAssignment(typeProp)) {
|
|
2836
2400
|
const init = typeProp.getInitializer();
|
|
2837
|
-
if (init &&
|
|
2401
|
+
if (init && import_ts_morph5.Node.isStringLiteral(init)) {
|
|
2838
2402
|
const kind = classifyTypeKeyword(init.getLiteralValue());
|
|
2839
2403
|
if (kind) return { kind };
|
|
2840
2404
|
}
|
|
@@ -2870,7 +2434,7 @@ function toFilterFieldType(name, r) {
|
|
|
2870
2434
|
|
|
2871
2435
|
// src/discovery/filter-for.ts
|
|
2872
2436
|
function classifyFilterForHint(typeInit) {
|
|
2873
|
-
if (
|
|
2437
|
+
if (import_ts_morph6.Node.isStringLiteral(typeInit)) {
|
|
2874
2438
|
switch (typeInit.getLiteralValue()) {
|
|
2875
2439
|
case "string":
|
|
2876
2440
|
return { kind: "string" };
|
|
@@ -2884,10 +2448,10 @@ function classifyFilterForHint(typeInit) {
|
|
|
2884
2448
|
return null;
|
|
2885
2449
|
}
|
|
2886
2450
|
}
|
|
2887
|
-
if (
|
|
2451
|
+
if (import_ts_morph6.Node.isArrayLiteralExpression(typeInit)) {
|
|
2888
2452
|
const values = [];
|
|
2889
2453
|
for (const el of typeInit.getElements()) {
|
|
2890
|
-
if (!
|
|
2454
|
+
if (!import_ts_morph6.Node.isStringLiteral(el)) return null;
|
|
2891
2455
|
values.push(el.getLiteralValue());
|
|
2892
2456
|
}
|
|
2893
2457
|
if (values.length === 0) return null;
|
|
@@ -2922,11 +2486,11 @@ function extractFilterForHints(classDecl, project) {
|
|
|
2922
2486
|
if (!filterForDec) continue;
|
|
2923
2487
|
const args = filterForDec.getArguments();
|
|
2924
2488
|
const keyArg = args[0];
|
|
2925
|
-
const inputKey = keyArg &&
|
|
2489
|
+
const inputKey = keyArg && import_ts_morph6.Node.isStringLiteral(keyArg) ? keyArg.getLiteralValue() : method.getName();
|
|
2926
2490
|
const optsArg = args[1];
|
|
2927
|
-
if (optsArg &&
|
|
2491
|
+
if (optsArg && import_ts_morph6.Node.isObjectLiteralExpression(optsArg)) {
|
|
2928
2492
|
const typeProp = optsArg.getProperty("type");
|
|
2929
|
-
if (typeProp &&
|
|
2493
|
+
if (typeProp && import_ts_morph6.Node.isPropertyAssignment(typeProp)) {
|
|
2930
2494
|
const typeInit = typeProp.getInitializer();
|
|
2931
2495
|
if (typeInit) {
|
|
2932
2496
|
const classified = classifyFilterForHint(typeInit);
|
|
@@ -2949,14 +2513,14 @@ function extractApplyFilterInfo(method, sourceFile, project) {
|
|
|
2949
2513
|
const args = filterDecorator.getArguments();
|
|
2950
2514
|
if (args.length === 0) continue;
|
|
2951
2515
|
const filterClassArg = args[0];
|
|
2952
|
-
if (!filterClassArg || !
|
|
2516
|
+
if (!filterClassArg || !import_ts_morph6.Node.isIdentifier(filterClassArg)) continue;
|
|
2953
2517
|
let source = "query";
|
|
2954
2518
|
const optionsArg = args[1];
|
|
2955
|
-
if (optionsArg &&
|
|
2519
|
+
if (optionsArg && import_ts_morph6.Node.isObjectLiteralExpression(optionsArg)) {
|
|
2956
2520
|
const sourceProp = optionsArg.getProperty("source");
|
|
2957
|
-
if (sourceProp &&
|
|
2521
|
+
if (sourceProp && import_ts_morph6.Node.isPropertyAssignment(sourceProp)) {
|
|
2958
2522
|
const init = sourceProp.getInitializer();
|
|
2959
|
-
if (init &&
|
|
2523
|
+
if (init && import_ts_morph6.Node.isStringLiteral(init) && init.getLiteralValue() === "body") {
|
|
2960
2524
|
source = "body";
|
|
2961
2525
|
}
|
|
2962
2526
|
}
|
|
@@ -3019,22 +2583,22 @@ function resolveRelationEntity(prop, sourceFile, project) {
|
|
|
3019
2583
|
const args = dec.getArguments();
|
|
3020
2584
|
if (args.length === 0) continue;
|
|
3021
2585
|
const arg = args[0];
|
|
3022
|
-
if (
|
|
2586
|
+
if (import_ts_morph6.Node.isObjectLiteralExpression(arg)) {
|
|
3023
2587
|
const entityProp = arg.getProperty("entity");
|
|
3024
|
-
if (entityProp &&
|
|
2588
|
+
if (entityProp && import_ts_morph6.Node.isPropertyAssignment(entityProp)) {
|
|
3025
2589
|
const init = entityProp.getInitializer();
|
|
3026
|
-
if (init &&
|
|
2590
|
+
if (init && import_ts_morph6.Node.isArrowFunction(init)) {
|
|
3027
2591
|
const body = init.getBody();
|
|
3028
|
-
if (
|
|
2592
|
+
if (import_ts_morph6.Node.isIdentifier(body)) {
|
|
3029
2593
|
const resolved = findType(body.getText(), prop.getSourceFile(), project);
|
|
3030
2594
|
if (resolved?.kind === "class") return resolved.decl;
|
|
3031
2595
|
}
|
|
3032
2596
|
}
|
|
3033
2597
|
}
|
|
3034
2598
|
}
|
|
3035
|
-
if (
|
|
2599
|
+
if (import_ts_morph6.Node.isArrowFunction(arg)) {
|
|
3036
2600
|
const body = arg.getBody();
|
|
3037
|
-
if (
|
|
2601
|
+
if (import_ts_morph6.Node.isIdentifier(body)) {
|
|
3038
2602
|
const resolved = findType(body.getText(), prop.getSourceFile(), project);
|
|
3039
2603
|
if (resolved?.kind === "class") return resolved.decl;
|
|
3040
2604
|
}
|
|
@@ -3058,11 +2622,11 @@ function extractFilterableEntityFields(filterClass, project) {
|
|
|
3058
2622
|
const args = filterableDecorator.getArguments();
|
|
3059
2623
|
if (args.length === 0) return [];
|
|
3060
2624
|
const optionsArg = args[0];
|
|
3061
|
-
if (!
|
|
2625
|
+
if (!import_ts_morph6.Node.isObjectLiteralExpression(optionsArg)) return [];
|
|
3062
2626
|
const entityProp = optionsArg.getProperty("entity");
|
|
3063
|
-
if (!entityProp || !
|
|
2627
|
+
if (!entityProp || !import_ts_morph6.Node.isPropertyAssignment(entityProp)) return [];
|
|
3064
2628
|
const entityInit = entityProp.getInitializer();
|
|
3065
|
-
if (!entityInit || !
|
|
2629
|
+
if (!entityInit || !import_ts_morph6.Node.isIdentifier(entityInit)) return [];
|
|
3066
2630
|
const entityName = entityInit.getText();
|
|
3067
2631
|
const filterSourceFile = filterClass.getSourceFile();
|
|
3068
2632
|
const resolvedEntity = findType(entityName, filterSourceFile, project);
|
|
@@ -3078,17 +2642,17 @@ function extractFilterableEntityFields(filterClass, project) {
|
|
|
3078
2642
|
const relationsDecorator = filterClass.getDecorators().find((d) => d.getName() === "Relations");
|
|
3079
2643
|
if (relationsDecorator) {
|
|
3080
2644
|
const relArgs = relationsDecorator.getArguments();
|
|
3081
|
-
if (relArgs.length > 0 &&
|
|
2645
|
+
if (relArgs.length > 0 && import_ts_morph6.Node.isObjectLiteralExpression(relArgs[0])) {
|
|
3082
2646
|
for (const relProp of relArgs[0].getProperties()) {
|
|
3083
|
-
if (!
|
|
2647
|
+
if (!import_ts_morph6.Node.isPropertyAssignment(relProp)) continue;
|
|
3084
2648
|
const relInit = relProp.getInitializer();
|
|
3085
|
-
if (!relInit || !
|
|
2649
|
+
if (!relInit || !import_ts_morph6.Node.isObjectLiteralExpression(relInit)) continue;
|
|
3086
2650
|
const keysProp = relInit.getProperty("keys");
|
|
3087
|
-
if (!keysProp || !
|
|
2651
|
+
if (!keysProp || !import_ts_morph6.Node.isPropertyAssignment(keysProp)) continue;
|
|
3088
2652
|
const keysInit = keysProp.getInitializer();
|
|
3089
|
-
if (!keysInit || !
|
|
2653
|
+
if (!keysInit || !import_ts_morph6.Node.isArrayLiteralExpression(keysInit)) continue;
|
|
3090
2654
|
for (const el of keysInit.getElements()) {
|
|
3091
|
-
if (
|
|
2655
|
+
if (import_ts_morph6.Node.isStringLiteral(el)) {
|
|
3092
2656
|
fields.push(toFilterFieldType(el.getLiteralValue(), { kind: "unknown" }));
|
|
3093
2657
|
}
|
|
3094
2658
|
}
|
|
@@ -3098,267 +2662,65 @@ function extractFilterableEntityFields(filterClass, project) {
|
|
|
3098
2662
|
return fields;
|
|
3099
2663
|
}
|
|
3100
2664
|
|
|
3101
|
-
// src/discovery/
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
2665
|
+
// src/discovery/dto-type-resolver.ts
|
|
2666
|
+
var WRAPPER_TYPES = {
|
|
2667
|
+
// MikroORM Ref/Reference/LoadedReference/IdentifiedReference are server-side
|
|
2668
|
+
// wrappers around related entities; the wire shape is just the referenced
|
|
2669
|
+
// entity. Unwrap to the type argument.
|
|
2670
|
+
Ref: "unwrap",
|
|
2671
|
+
Reference: "unwrap",
|
|
2672
|
+
LoadedReference: "unwrap",
|
|
2673
|
+
IdentifiedReference: "unwrap",
|
|
2674
|
+
// MikroORM Opt<T> is a marker, Loaded<T, ...> is a wrapper; both reduce to T.
|
|
2675
|
+
Opt: "unwrap",
|
|
2676
|
+
Loaded: "unwrap",
|
|
2677
|
+
// Promise<T> — unwrap
|
|
2678
|
+
Promise: "unwrap",
|
|
2679
|
+
// MikroORM Collection<T> serializes as an array of T on the wire.
|
|
2680
|
+
Collection: "arrayOf",
|
|
2681
|
+
// Array<T> generic form
|
|
2682
|
+
Array: "arrayOf"
|
|
2683
|
+
};
|
|
2684
|
+
var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
|
|
2685
|
+
"Record",
|
|
2686
|
+
"Omit",
|
|
2687
|
+
"Pick",
|
|
2688
|
+
"Partial",
|
|
2689
|
+
"Required",
|
|
2690
|
+
"Readonly",
|
|
2691
|
+
"Map",
|
|
2692
|
+
"Set"
|
|
2693
|
+
]);
|
|
2694
|
+
function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
2695
|
+
if (depth <= 0) return "unknown";
|
|
2696
|
+
if (import_ts_morph7.Node.isArrayTypeNode(typeNode)) {
|
|
2697
|
+
const elementType = typeNode.getElementTypeNode();
|
|
2698
|
+
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth)}>`;
|
|
3124
2699
|
}
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
project.addSourceFileAtPath(f);
|
|
2700
|
+
if (import_ts_morph7.Node.isUnionTypeNode(typeNode)) {
|
|
2701
|
+
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" | ");
|
|
3128
2702
|
}
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
projectRoot: cwd,
|
|
3132
|
-
tsconfigPaths: loadTsconfigPaths(tsconfigPath)
|
|
3133
|
-
});
|
|
3134
|
-
try {
|
|
3135
|
-
for (const sourceFile of project.getSourceFiles()) {
|
|
3136
|
-
routes.push(...extractFromSourceFile(sourceFile, project));
|
|
3137
|
-
}
|
|
3138
|
-
} finally {
|
|
3139
|
-
restoreDiscoveryContext(prevCtx);
|
|
2703
|
+
if (import_ts_morph7.Node.isIntersectionTypeNode(typeNode)) {
|
|
2704
|
+
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" & ");
|
|
3140
2705
|
}
|
|
3141
|
-
|
|
3142
|
-
}
|
|
3143
|
-
function zodAstToTs(node) {
|
|
3144
|
-
if (!import_ts_morph9.Node.isCallExpression(node)) return "unknown";
|
|
3145
|
-
const expr = node.getExpression();
|
|
3146
|
-
if (import_ts_morph9.Node.isPropertyAccessExpression(expr)) {
|
|
3147
|
-
const methodName = expr.getName();
|
|
3148
|
-
const receiver = expr.getExpression();
|
|
3149
|
-
if (methodName === "optional") {
|
|
3150
|
-
return `${zodAstToTs(receiver)} | undefined`;
|
|
3151
|
-
}
|
|
3152
|
-
if (methodName === "nullable") {
|
|
3153
|
-
return `${zodAstToTs(receiver)} | null`;
|
|
3154
|
-
}
|
|
3155
|
-
const args = node.getArguments();
|
|
3156
|
-
switch (methodName) {
|
|
3157
|
-
case "string":
|
|
3158
|
-
return "string";
|
|
3159
|
-
case "number":
|
|
3160
|
-
return "number";
|
|
3161
|
-
case "boolean":
|
|
3162
|
-
return "boolean";
|
|
3163
|
-
case "unknown":
|
|
3164
|
-
return "unknown";
|
|
3165
|
-
case "any":
|
|
3166
|
-
return "unknown";
|
|
3167
|
-
case "literal": {
|
|
3168
|
-
const lit = args[0];
|
|
3169
|
-
if (!lit) return "unknown";
|
|
3170
|
-
if (import_ts_morph9.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
3171
|
-
if (import_ts_morph9.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
3172
|
-
if (lit.getKind() === import_ts_morph9.SyntaxKind.TrueKeyword) return "true";
|
|
3173
|
-
if (lit.getKind() === import_ts_morph9.SyntaxKind.FalseKeyword) return "false";
|
|
3174
|
-
return "unknown";
|
|
3175
|
-
}
|
|
3176
|
-
case "enum": {
|
|
3177
|
-
const arrArg = args[0];
|
|
3178
|
-
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
3179
|
-
const members = arrArg.getElements().map(
|
|
3180
|
-
(el) => import_ts_morph9.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
3181
|
-
);
|
|
3182
|
-
return members.join(" | ");
|
|
3183
|
-
}
|
|
3184
|
-
case "array": {
|
|
3185
|
-
const inner = args[0];
|
|
3186
|
-
if (!inner) return "unknown";
|
|
3187
|
-
return `Array<${zodAstToTs(inner)}>`;
|
|
3188
|
-
}
|
|
3189
|
-
case "object": {
|
|
3190
|
-
const objArg = args[0];
|
|
3191
|
-
if (!objArg || !import_ts_morph9.Node.isObjectLiteralExpression(objArg)) return "unknown";
|
|
3192
|
-
const lines = [];
|
|
3193
|
-
for (const prop of objArg.getProperties()) {
|
|
3194
|
-
if (!import_ts_morph9.Node.isPropertyAssignment(prop)) continue;
|
|
3195
|
-
const key = prop.getName();
|
|
3196
|
-
const valNode = prop.getInitializer();
|
|
3197
|
-
if (!valNode) continue;
|
|
3198
|
-
const tsType = zodAstToTs(valNode);
|
|
3199
|
-
const isOpt = isOptionalChain(valNode);
|
|
3200
|
-
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
3201
|
-
}
|
|
3202
|
-
return `{ ${lines.join("; ")} }`;
|
|
3203
|
-
}
|
|
3204
|
-
case "union": {
|
|
3205
|
-
const arrArg = args[0];
|
|
3206
|
-
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
3207
|
-
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
3208
|
-
}
|
|
3209
|
-
case "record": {
|
|
3210
|
-
const valArg = args.length === 1 ? args[0] : args[1];
|
|
3211
|
-
if (!valArg) return "unknown";
|
|
3212
|
-
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
3213
|
-
}
|
|
3214
|
-
case "tuple": {
|
|
3215
|
-
const arrArg = args[0];
|
|
3216
|
-
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
3217
|
-
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
3218
|
-
}
|
|
3219
|
-
default:
|
|
3220
|
-
return "unknown";
|
|
3221
|
-
}
|
|
3222
|
-
}
|
|
3223
|
-
return "unknown";
|
|
3224
|
-
}
|
|
3225
|
-
function isOptionalChain(node) {
|
|
3226
|
-
if (!import_ts_morph9.Node.isCallExpression(node)) return false;
|
|
3227
|
-
const expr = node.getExpression();
|
|
3228
|
-
return import_ts_morph9.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
3229
|
-
}
|
|
3230
|
-
function decoratorStringArg(decoratorExpr) {
|
|
3231
|
-
if (!decoratorExpr) return void 0;
|
|
3232
|
-
if (import_ts_morph9.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
3233
|
-
if (import_ts_morph9.Node.isArrayLiteralExpression(decoratorExpr)) {
|
|
3234
|
-
const first = decoratorExpr.getElements()[0];
|
|
3235
|
-
if (first && import_ts_morph9.Node.isStringLiteral(first)) return first.getLiteralValue();
|
|
3236
|
-
}
|
|
3237
|
-
return void 0;
|
|
3238
|
-
}
|
|
3239
|
-
function parseDefineContractCall(callExpr) {
|
|
3240
|
-
if (!import_ts_morph9.Node.isCallExpression(callExpr)) return null;
|
|
3241
|
-
const callee = callExpr.getExpression();
|
|
3242
|
-
const calleeName = import_ts_morph9.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph9.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
3243
|
-
if (calleeName !== "defineContract") return null;
|
|
3244
|
-
const args = callExpr.getArguments();
|
|
3245
|
-
const optsArg = args[0];
|
|
3246
|
-
if (!optsArg || !import_ts_morph9.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
3247
|
-
let query = null;
|
|
3248
|
-
let body = null;
|
|
3249
|
-
let response = "unknown";
|
|
3250
|
-
let bodyZodText = null;
|
|
3251
|
-
let queryZodText = null;
|
|
3252
|
-
for (const prop of optsArg.getProperties()) {
|
|
3253
|
-
if (!import_ts_morph9.Node.isPropertyAssignment(prop)) continue;
|
|
3254
|
-
const propName = prop.getName();
|
|
3255
|
-
const val = prop.getInitializer();
|
|
3256
|
-
if (!val) continue;
|
|
3257
|
-
if (propName === "query") {
|
|
3258
|
-
query = zodAstToTs(val);
|
|
3259
|
-
queryZodText = val.getText();
|
|
3260
|
-
} else if (propName === "body") {
|
|
3261
|
-
body = zodAstToTs(val);
|
|
3262
|
-
bodyZodText = val.getText();
|
|
3263
|
-
} else if (propName === "response") {
|
|
3264
|
-
response = zodAstToTs(val);
|
|
3265
|
-
}
|
|
3266
|
-
}
|
|
3267
|
-
return { query, body, response, bodyZodText, queryZodText };
|
|
3268
|
-
}
|
|
3269
|
-
function deriveClassSegment(className) {
|
|
3270
|
-
const noSuffix = className.replace(/Controller$/, "");
|
|
3271
|
-
if (!noSuffix) {
|
|
3272
|
-
throw new Error(
|
|
3273
|
-
`Controller class name "${className}" derives empty route segment after stripping "Controller". Add an @As(...) override at the class level.`
|
|
3274
|
-
);
|
|
3275
|
-
}
|
|
3276
|
-
return noSuffix.charAt(0).toLowerCase() + noSuffix.slice(1);
|
|
3277
|
-
}
|
|
3278
|
-
function resolveRouteName(className, methodName, classAs, methodAs) {
|
|
3279
|
-
const classPortion = classAs ?? deriveClassSegment(className);
|
|
3280
|
-
const methodPortion = methodAs ?? methodName;
|
|
3281
|
-
return `${classPortion}.${methodPortion}`;
|
|
3282
|
-
}
|
|
3283
|
-
function joinPaths(prefix, suffix) {
|
|
3284
|
-
if (!prefix && !suffix) return "/";
|
|
3285
|
-
if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
3286
|
-
if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
3287
|
-
const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
3288
|
-
const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
3289
|
-
const combined = p + s;
|
|
3290
|
-
return combined === "" ? "/" : combined;
|
|
3291
|
-
}
|
|
3292
|
-
function extractParams(path) {
|
|
3293
|
-
const matches = path.matchAll(/:(\w+)/g);
|
|
3294
|
-
return Array.from(matches).map((m) => ({ name: m[1], source: "path" }));
|
|
3295
|
-
}
|
|
3296
|
-
function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
3297
|
-
if (depth <= 0) return "unknown";
|
|
3298
|
-
if (import_ts_morph9.Node.isArrayTypeNode(typeNode)) {
|
|
3299
|
-
const elementType = typeNode.getElementTypeNode();
|
|
3300
|
-
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth)}>`;
|
|
3301
|
-
}
|
|
3302
|
-
if (import_ts_morph9.Node.isUnionTypeNode(typeNode)) {
|
|
3303
|
-
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" | ");
|
|
3304
|
-
}
|
|
3305
|
-
if (import_ts_morph9.Node.isIntersectionTypeNode(typeNode)) {
|
|
3306
|
-
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" & ");
|
|
3307
|
-
}
|
|
3308
|
-
if (import_ts_morph9.Node.isParenthesizedTypeNode(typeNode)) {
|
|
2706
|
+
if (import_ts_morph7.Node.isParenthesizedTypeNode(typeNode)) {
|
|
3309
2707
|
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth)})`;
|
|
3310
2708
|
}
|
|
3311
|
-
if (
|
|
2709
|
+
if (import_ts_morph7.Node.isTypeReference(typeNode)) {
|
|
3312
2710
|
const typeName = typeNode.getTypeName();
|
|
3313
|
-
const name =
|
|
2711
|
+
const name = import_ts_morph7.Node.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
|
|
3314
2712
|
if (name === "string" || name === "number" || name === "boolean") return name;
|
|
3315
2713
|
if (name === "Date") return "string";
|
|
3316
2714
|
if (name === "unknown" || name === "any" || name === "void") return "unknown";
|
|
3317
2715
|
if (name === "StreamableFile" || name === "Observable" || name === "ReadableStream")
|
|
3318
2716
|
return "unknown";
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
3323
|
-
return resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
|
|
3324
|
-
}
|
|
3325
|
-
return "unknown";
|
|
2717
|
+
const wrapperMode = WRAPPER_TYPES[name];
|
|
2718
|
+
if (wrapperMode) {
|
|
2719
|
+
return unwrapFirstTypeArg(typeNode, sourceFile, project, depth, wrapperMode);
|
|
3326
2720
|
}
|
|
3327
|
-
if (name
|
|
3328
|
-
const typeArgs = typeNode.getTypeArguments();
|
|
3329
|
-
const firstTypeArg = typeArgs[0];
|
|
3330
|
-
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
3331
|
-
return `Array<${resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth)}>`;
|
|
3332
|
-
}
|
|
3333
|
-
return "Array<unknown>";
|
|
3334
|
-
}
|
|
3335
|
-
if (name === "Opt" || name === "Loaded") {
|
|
3336
|
-
const typeArgs = typeNode.getTypeArguments();
|
|
3337
|
-
const firstTypeArg = typeArgs[0];
|
|
3338
|
-
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
3339
|
-
return resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
|
|
3340
|
-
}
|
|
3341
|
-
return "unknown";
|
|
3342
|
-
}
|
|
3343
|
-
if (name === "Array") {
|
|
3344
|
-
const typeArgs = typeNode.getTypeArguments();
|
|
3345
|
-
const firstTypeArg = typeArgs[0];
|
|
3346
|
-
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
3347
|
-
return `Array<${resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth)}>`;
|
|
3348
|
-
}
|
|
3349
|
-
return "Array<unknown>";
|
|
3350
|
-
}
|
|
3351
|
-
if (["Record", "Omit", "Pick", "Partial", "Required", "Readonly", "Map", "Set"].includes(name)) {
|
|
2721
|
+
if (PASSTHROUGH_UTILITY.has(name)) {
|
|
3352
2722
|
return typeNode.getText();
|
|
3353
2723
|
}
|
|
3354
|
-
if (name === "Promise") {
|
|
3355
|
-
const typeArgs = typeNode.getTypeArguments();
|
|
3356
|
-
const firstTypeArg = typeArgs[0];
|
|
3357
|
-
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
3358
|
-
return resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
|
|
3359
|
-
}
|
|
3360
|
-
return "unknown";
|
|
3361
|
-
}
|
|
3362
2724
|
const resolved = findType(name, sourceFile, project);
|
|
3363
2725
|
if (resolved) {
|
|
3364
2726
|
return expandTypeDecl(resolved, project, depth - 1);
|
|
@@ -3367,13 +2729,22 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
|
3367
2729
|
return "unknown";
|
|
3368
2730
|
}
|
|
3369
2731
|
const kind = typeNode.getKind();
|
|
3370
|
-
if (kind ===
|
|
3371
|
-
if (kind ===
|
|
3372
|
-
if (kind ===
|
|
3373
|
-
if (kind ===
|
|
3374
|
-
if (kind ===
|
|
2732
|
+
if (kind === import_ts_morph7.SyntaxKind.StringKeyword) return "string";
|
|
2733
|
+
if (kind === import_ts_morph7.SyntaxKind.NumberKeyword) return "number";
|
|
2734
|
+
if (kind === import_ts_morph7.SyntaxKind.BooleanKeyword) return "boolean";
|
|
2735
|
+
if (kind === import_ts_morph7.SyntaxKind.UnknownKeyword) return "unknown";
|
|
2736
|
+
if (kind === import_ts_morph7.SyntaxKind.AnyKeyword) return "unknown";
|
|
3375
2737
|
return typeNode.getText();
|
|
3376
2738
|
}
|
|
2739
|
+
function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode) {
|
|
2740
|
+
const typeArgs = typeNode.getTypeArguments();
|
|
2741
|
+
const firstTypeArg = typeArgs[0];
|
|
2742
|
+
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
2743
|
+
const inner = resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
|
|
2744
|
+
return mode === "arrayOf" ? `Array<${inner}>` : inner;
|
|
2745
|
+
}
|
|
2746
|
+
return mode === "arrayOf" ? "Array<unknown>" : "unknown";
|
|
2747
|
+
}
|
|
3377
2748
|
function expandTypeDecl(result, project, depth) {
|
|
3378
2749
|
if (depth < 0) return "unknown";
|
|
3379
2750
|
switch (result.kind) {
|
|
@@ -3439,7 +2810,7 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
3439
2810
|
const paramArgs = paramDecorator.getArguments();
|
|
3440
2811
|
if (paramArgs.length === 0) continue;
|
|
3441
2812
|
const nameArg = paramArgs[0];
|
|
3442
|
-
if (!
|
|
2813
|
+
if (!import_ts_morph7.Node.isStringLiteral(nameArg)) continue;
|
|
3443
2814
|
const paramName = nameArg.getLiteralValue();
|
|
3444
2815
|
const typeNode = param.getTypeNode();
|
|
3445
2816
|
const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
@@ -3452,13 +2823,13 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
3452
2823
|
if (apiResponseDecorator) {
|
|
3453
2824
|
const args = apiResponseDecorator.getArguments();
|
|
3454
2825
|
const optsArg = args[0];
|
|
3455
|
-
if (optsArg &&
|
|
2826
|
+
if (optsArg && import_ts_morph7.Node.isObjectLiteralExpression(optsArg)) {
|
|
3456
2827
|
for (const prop of optsArg.getProperties()) {
|
|
3457
|
-
if (!
|
|
2828
|
+
if (!import_ts_morph7.Node.isPropertyAssignment(prop)) continue;
|
|
3458
2829
|
if (prop.getName() !== "type") continue;
|
|
3459
2830
|
const val = prop.getInitializer();
|
|
3460
2831
|
if (!val) continue;
|
|
3461
|
-
if (
|
|
2832
|
+
if (import_ts_morph7.Node.isArrayLiteralExpression(val)) {
|
|
3462
2833
|
const elements = val.getElements();
|
|
3463
2834
|
const firstEl = elements[0];
|
|
3464
2835
|
if (elements.length > 0 && firstEl !== void 0) {
|
|
@@ -3478,7 +2849,7 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
3478
2849
|
return "unknown";
|
|
3479
2850
|
}
|
|
3480
2851
|
function resolveIdentifierToClassType(node, sourceFile, project, depth) {
|
|
3481
|
-
if (!
|
|
2852
|
+
if (!import_ts_morph7.Node.isIdentifier(node)) return "unknown";
|
|
3482
2853
|
const name = node.getText();
|
|
3483
2854
|
const resolved = findType(name, sourceFile, project);
|
|
3484
2855
|
if (resolved) {
|
|
@@ -3525,11 +2896,11 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3525
2896
|
if (apiResp) {
|
|
3526
2897
|
const args = apiResp.getArguments();
|
|
3527
2898
|
const optsArg = args[0];
|
|
3528
|
-
if (optsArg &&
|
|
2899
|
+
if (optsArg && import_ts_morph7.Node.isObjectLiteralExpression(optsArg)) {
|
|
3529
2900
|
for (const prop of optsArg.getProperties()) {
|
|
3530
|
-
if (
|
|
2901
|
+
if (import_ts_morph7.Node.isPropertyAssignment(prop) && prop.getName() === "type") {
|
|
3531
2902
|
const val = prop.getInitializer();
|
|
3532
|
-
if (val &&
|
|
2903
|
+
if (val && import_ts_morph7.Node.isIdentifier(val)) {
|
|
3533
2904
|
const name = val.getText();
|
|
3534
2905
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
3535
2906
|
if (localDecl?.isExported()) {
|
|
@@ -3546,27 +2917,18 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3546
2917
|
}
|
|
3547
2918
|
}
|
|
3548
2919
|
}
|
|
3549
|
-
let bodyZodText = null;
|
|
3550
|
-
let queryZodText = null;
|
|
3551
2920
|
let bodySchema = null;
|
|
3552
2921
|
let querySchema = null;
|
|
3553
|
-
const formNested = {};
|
|
3554
2922
|
const formWarnings = [];
|
|
3555
2923
|
const bodyClass = resolveParamClass(method, "Body", sourceFile, project);
|
|
3556
2924
|
if (bodyClass) {
|
|
3557
|
-
const result = extractZodFromDto(bodyClass.decl, bodyClass.file, project);
|
|
3558
|
-
bodyZodText = result.schemaText;
|
|
3559
|
-
for (const [k, v] of result.namedNestedSchemas) formNested[k] = v;
|
|
3560
|
-
formWarnings.push(...result.warnings);
|
|
3561
2925
|
bodySchema = extractSchemaFromDto(bodyClass.decl, bodyClass.file, project);
|
|
2926
|
+
formWarnings.push(...bodySchema.warnings);
|
|
3562
2927
|
}
|
|
3563
2928
|
const queryClass = resolveParamClass(method, "Query", sourceFile, project);
|
|
3564
2929
|
if (queryClass) {
|
|
3565
|
-
const result = extractZodFromDto(queryClass.decl, queryClass.file, project);
|
|
3566
|
-
queryZodText = result.schemaText;
|
|
3567
|
-
for (const [k, v] of result.namedNestedSchemas) formNested[k] = v;
|
|
3568
|
-
formWarnings.push(...result.warnings);
|
|
3569
2930
|
querySchema = extractSchemaFromDto(queryClass.decl, queryClass.file, project);
|
|
2931
|
+
formWarnings.push(...querySchema.warnings);
|
|
3570
2932
|
}
|
|
3571
2933
|
return {
|
|
3572
2934
|
query,
|
|
@@ -3579,9 +2941,6 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3579
2941
|
filterFields: filterInfo?.fieldNames ?? null,
|
|
3580
2942
|
filterFieldTypes: filterInfo?.fieldTypes ?? null,
|
|
3581
2943
|
filterSource: filterInfo?.source ?? null,
|
|
3582
|
-
bodyZodText,
|
|
3583
|
-
queryZodText,
|
|
3584
|
-
formNestedSchemas: Object.keys(formNested).length > 0 ? formNested : null,
|
|
3585
2944
|
formWarnings,
|
|
3586
2945
|
bodySchema,
|
|
3587
2946
|
querySchema
|
|
@@ -3601,6 +2960,201 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
|
3601
2960
|
}
|
|
3602
2961
|
return null;
|
|
3603
2962
|
}
|
|
2963
|
+
|
|
2964
|
+
// src/discovery/zod-ast-to-ts.ts
|
|
2965
|
+
var import_ts_morph8 = require("ts-morph");
|
|
2966
|
+
function zodAstToTs(node) {
|
|
2967
|
+
if (!import_ts_morph8.Node.isCallExpression(node)) return "unknown";
|
|
2968
|
+
const expr = node.getExpression();
|
|
2969
|
+
if (import_ts_morph8.Node.isPropertyAccessExpression(expr)) {
|
|
2970
|
+
const methodName = expr.getName();
|
|
2971
|
+
const receiver = expr.getExpression();
|
|
2972
|
+
if (methodName === "optional") {
|
|
2973
|
+
return `${zodAstToTs(receiver)} | undefined`;
|
|
2974
|
+
}
|
|
2975
|
+
if (methodName === "nullable") {
|
|
2976
|
+
return `${zodAstToTs(receiver)} | null`;
|
|
2977
|
+
}
|
|
2978
|
+
const args = node.getArguments();
|
|
2979
|
+
switch (methodName) {
|
|
2980
|
+
case "string":
|
|
2981
|
+
return "string";
|
|
2982
|
+
case "number":
|
|
2983
|
+
return "number";
|
|
2984
|
+
case "boolean":
|
|
2985
|
+
return "boolean";
|
|
2986
|
+
case "unknown":
|
|
2987
|
+
return "unknown";
|
|
2988
|
+
case "any":
|
|
2989
|
+
return "unknown";
|
|
2990
|
+
case "literal": {
|
|
2991
|
+
const lit = args[0];
|
|
2992
|
+
if (!lit) return "unknown";
|
|
2993
|
+
if (import_ts_morph8.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
2994
|
+
if (import_ts_morph8.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
2995
|
+
if (lit.getKind() === import_ts_morph8.SyntaxKind.TrueKeyword) return "true";
|
|
2996
|
+
if (lit.getKind() === import_ts_morph8.SyntaxKind.FalseKeyword) return "false";
|
|
2997
|
+
return "unknown";
|
|
2998
|
+
}
|
|
2999
|
+
case "enum": {
|
|
3000
|
+
const arrArg = args[0];
|
|
3001
|
+
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
3002
|
+
const members = arrArg.getElements().map(
|
|
3003
|
+
(el) => import_ts_morph8.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
3004
|
+
);
|
|
3005
|
+
return members.join(" | ");
|
|
3006
|
+
}
|
|
3007
|
+
case "array": {
|
|
3008
|
+
const inner = args[0];
|
|
3009
|
+
if (!inner) return "unknown";
|
|
3010
|
+
return `Array<${zodAstToTs(inner)}>`;
|
|
3011
|
+
}
|
|
3012
|
+
case "object": {
|
|
3013
|
+
const objArg = args[0];
|
|
3014
|
+
if (!objArg || !import_ts_morph8.Node.isObjectLiteralExpression(objArg)) return "unknown";
|
|
3015
|
+
const lines = [];
|
|
3016
|
+
for (const prop of objArg.getProperties()) {
|
|
3017
|
+
if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
|
|
3018
|
+
const key = prop.getName();
|
|
3019
|
+
const valNode = prop.getInitializer();
|
|
3020
|
+
if (!valNode) continue;
|
|
3021
|
+
const tsType = zodAstToTs(valNode);
|
|
3022
|
+
const isOpt = isOptionalChain(valNode);
|
|
3023
|
+
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
3024
|
+
}
|
|
3025
|
+
return `{ ${lines.join("; ")} }`;
|
|
3026
|
+
}
|
|
3027
|
+
case "union": {
|
|
3028
|
+
const arrArg = args[0];
|
|
3029
|
+
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
3030
|
+
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
3031
|
+
}
|
|
3032
|
+
case "record": {
|
|
3033
|
+
const valArg = args.length === 1 ? args[0] : args[1];
|
|
3034
|
+
if (!valArg) return "unknown";
|
|
3035
|
+
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
3036
|
+
}
|
|
3037
|
+
case "tuple": {
|
|
3038
|
+
const arrArg = args[0];
|
|
3039
|
+
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
3040
|
+
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
3041
|
+
}
|
|
3042
|
+
default:
|
|
3043
|
+
return "unknown";
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
return "unknown";
|
|
3047
|
+
}
|
|
3048
|
+
function isOptionalChain(node) {
|
|
3049
|
+
if (!import_ts_morph8.Node.isCallExpression(node)) return false;
|
|
3050
|
+
const expr = node.getExpression();
|
|
3051
|
+
return import_ts_morph8.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
3052
|
+
}
|
|
3053
|
+
function parseDefineContractCall(callExpr) {
|
|
3054
|
+
if (!import_ts_morph8.Node.isCallExpression(callExpr)) return null;
|
|
3055
|
+
const callee = callExpr.getExpression();
|
|
3056
|
+
const calleeName = import_ts_morph8.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph8.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
3057
|
+
if (calleeName !== "defineContract") return null;
|
|
3058
|
+
const args = callExpr.getArguments();
|
|
3059
|
+
const optsArg = args[0];
|
|
3060
|
+
if (!optsArg || !import_ts_morph8.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
3061
|
+
let query = null;
|
|
3062
|
+
let body = null;
|
|
3063
|
+
let response = "unknown";
|
|
3064
|
+
let bodyZodText = null;
|
|
3065
|
+
let queryZodText = null;
|
|
3066
|
+
for (const prop of optsArg.getProperties()) {
|
|
3067
|
+
if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
|
|
3068
|
+
const propName = prop.getName();
|
|
3069
|
+
const val = prop.getInitializer();
|
|
3070
|
+
if (!val) continue;
|
|
3071
|
+
if (propName === "query") {
|
|
3072
|
+
query = zodAstToTs(val);
|
|
3073
|
+
queryZodText = val.getText();
|
|
3074
|
+
} else if (propName === "body") {
|
|
3075
|
+
body = zodAstToTs(val);
|
|
3076
|
+
bodyZodText = val.getText();
|
|
3077
|
+
} else if (propName === "response") {
|
|
3078
|
+
response = zodAstToTs(val);
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
return { query, body, response, bodyZodText, queryZodText };
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
// src/discovery/contracts-fast.ts
|
|
3085
|
+
async function discoverContractsFast(opts) {
|
|
3086
|
+
const { cwd, glob, tsconfig } = opts;
|
|
3087
|
+
const tsconfigPath = tsconfig ? (0, import_node_path12.resolve)(tsconfig) : (0, import_node_path12.join)(cwd, "tsconfig.json");
|
|
3088
|
+
let project;
|
|
3089
|
+
try {
|
|
3090
|
+
project = new import_ts_morph9.Project({
|
|
3091
|
+
tsConfigFilePath: tsconfigPath,
|
|
3092
|
+
skipAddingFilesFromTsConfig: true,
|
|
3093
|
+
skipLoadingLibFiles: true,
|
|
3094
|
+
skipFileDependencyResolution: true
|
|
3095
|
+
});
|
|
3096
|
+
} catch {
|
|
3097
|
+
project = new import_ts_morph9.Project({
|
|
3098
|
+
skipAddingFilesFromTsConfig: true,
|
|
3099
|
+
skipLoadingLibFiles: true,
|
|
3100
|
+
skipFileDependencyResolution: true,
|
|
3101
|
+
compilerOptions: {
|
|
3102
|
+
allowJs: true,
|
|
3103
|
+
resolveJsonModule: false,
|
|
3104
|
+
strict: false
|
|
3105
|
+
}
|
|
3106
|
+
});
|
|
3107
|
+
}
|
|
3108
|
+
const files = await (0, import_fast_glob2.default)(glob, { cwd, absolute: true, onlyFiles: true });
|
|
3109
|
+
for (const f of files) {
|
|
3110
|
+
project.addSourceFileAtPath(f);
|
|
3111
|
+
}
|
|
3112
|
+
const routes = [];
|
|
3113
|
+
setDiscoveryContext(project, {
|
|
3114
|
+
projectRoot: cwd,
|
|
3115
|
+
tsconfigPaths: loadTsconfigPaths(tsconfigPath)
|
|
3116
|
+
});
|
|
3117
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
3118
|
+
routes.push(...extractFromSourceFile(sourceFile, project));
|
|
3119
|
+
}
|
|
3120
|
+
return routes;
|
|
3121
|
+
}
|
|
3122
|
+
function decoratorStringArg(decoratorExpr) {
|
|
3123
|
+
if (!decoratorExpr) return void 0;
|
|
3124
|
+
if (import_ts_morph9.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
3125
|
+
if (import_ts_morph9.Node.isArrayLiteralExpression(decoratorExpr)) {
|
|
3126
|
+
const first = decoratorExpr.getElements()[0];
|
|
3127
|
+
if (first && import_ts_morph9.Node.isStringLiteral(first)) return first.getLiteralValue();
|
|
3128
|
+
}
|
|
3129
|
+
return void 0;
|
|
3130
|
+
}
|
|
3131
|
+
function deriveClassSegment(className) {
|
|
3132
|
+
const noSuffix = className.replace(/Controller$/, "");
|
|
3133
|
+
if (!noSuffix) {
|
|
3134
|
+
throw new Error(
|
|
3135
|
+
`Controller class name "${className}" derives empty route segment after stripping "Controller". Add an @As(...) override at the class level.`
|
|
3136
|
+
);
|
|
3137
|
+
}
|
|
3138
|
+
return noSuffix.charAt(0).toLowerCase() + noSuffix.slice(1);
|
|
3139
|
+
}
|
|
3140
|
+
function resolveRouteName(className, methodName, classAs, methodAs) {
|
|
3141
|
+
const classPortion = classAs ?? deriveClassSegment(className);
|
|
3142
|
+
const methodPortion = methodAs ?? methodName;
|
|
3143
|
+
return `${classPortion}.${methodPortion}`;
|
|
3144
|
+
}
|
|
3145
|
+
function joinPaths(prefix, suffix) {
|
|
3146
|
+
if (!prefix && !suffix) return "/";
|
|
3147
|
+
if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
3148
|
+
if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
3149
|
+
const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
3150
|
+
const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
3151
|
+
const combined = p + s;
|
|
3152
|
+
return combined === "" ? "/" : combined;
|
|
3153
|
+
}
|
|
3154
|
+
function extractParams(path) {
|
|
3155
|
+
const matches = path.matchAll(/:(\w+)/g);
|
|
3156
|
+
return Array.from(matches).map((m) => ({ name: m[1], source: "path" }));
|
|
3157
|
+
}
|
|
3604
3158
|
var HTTP_METHOD_DECORATORS = {
|
|
3605
3159
|
Get: "GET",
|
|
3606
3160
|
Post: "POST",
|
|
@@ -3611,176 +3165,186 @@ var HTTP_METHOD_DECORATORS = {
|
|
|
3611
3165
|
Head: "HEAD",
|
|
3612
3166
|
All: "ALL"
|
|
3613
3167
|
};
|
|
3168
|
+
function resolveVerb(method) {
|
|
3169
|
+
for (const [decoratorName, verb] of Object.entries(HTTP_METHOD_DECORATORS)) {
|
|
3170
|
+
const httpDecorator = method.getDecorator(decoratorName);
|
|
3171
|
+
if (httpDecorator) {
|
|
3172
|
+
const httpArgs = httpDecorator.getArguments();
|
|
3173
|
+
const pathArg = httpArgs[0];
|
|
3174
|
+
return { httpMethod: verb, handlerPath: decoratorStringArg(pathArg) ?? "" };
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
return null;
|
|
3178
|
+
}
|
|
3179
|
+
function readAsDecorator(node, label) {
|
|
3180
|
+
const asDecorator = node.getDecorator("As");
|
|
3181
|
+
if (!asDecorator) return void 0;
|
|
3182
|
+
const asName = decoratorStringArg(asDecorator.getArguments()[0]);
|
|
3183
|
+
if (!asName) {
|
|
3184
|
+
throw new Error(`@As decorator on ${label} must have a non-empty string argument.`);
|
|
3185
|
+
}
|
|
3186
|
+
return asName;
|
|
3187
|
+
}
|
|
3188
|
+
function buildRoute(args) {
|
|
3189
|
+
const {
|
|
3190
|
+
className,
|
|
3191
|
+
methodName,
|
|
3192
|
+
resolvedMethod,
|
|
3193
|
+
combinedPath,
|
|
3194
|
+
classAs,
|
|
3195
|
+
methodAs,
|
|
3196
|
+
sourceFile,
|
|
3197
|
+
seenNames,
|
|
3198
|
+
contractSource
|
|
3199
|
+
} = args;
|
|
3200
|
+
const routeName = resolveRouteName(className, methodName, classAs, methodAs);
|
|
3201
|
+
const qualifiedRef = `${className}.${methodName}`;
|
|
3202
|
+
const existing = seenNames.get(routeName);
|
|
3203
|
+
if (existing !== void 0) {
|
|
3204
|
+
throw new Error(
|
|
3205
|
+
`Route name collision: "${routeName}" is used by both "${existing}" and "${qualifiedRef}". Use @As(...) to give one of them a unique name.`
|
|
3206
|
+
);
|
|
3207
|
+
}
|
|
3208
|
+
seenNames.set(routeName, qualifiedRef);
|
|
3209
|
+
return {
|
|
3210
|
+
method: resolvedMethod,
|
|
3211
|
+
path: combinedPath,
|
|
3212
|
+
name: routeName,
|
|
3213
|
+
params: extractParams(combinedPath),
|
|
3214
|
+
controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
|
|
3215
|
+
contract: { contractSource }
|
|
3216
|
+
};
|
|
3217
|
+
}
|
|
3218
|
+
function extractContractRoute(args) {
|
|
3219
|
+
const { cls, method, applyContractDecorator, verb, prefix, className, sourceFile, seenNames } = args;
|
|
3220
|
+
const firstDecoratorArg = applyContractDecorator.getArguments()[0];
|
|
3221
|
+
if (!firstDecoratorArg) return null;
|
|
3222
|
+
let contractDef = null;
|
|
3223
|
+
let bodyZodRef = null;
|
|
3224
|
+
let queryZodRef = null;
|
|
3225
|
+
if (import_ts_morph9.Node.isCallExpression(firstDecoratorArg)) {
|
|
3226
|
+
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
3227
|
+
} else if (import_ts_morph9.Node.isIdentifier(firstDecoratorArg)) {
|
|
3228
|
+
const identName = firstDecoratorArg.getText();
|
|
3229
|
+
const varDecl = sourceFile.getVariableDeclaration(identName);
|
|
3230
|
+
if (!varDecl) {
|
|
3231
|
+
console.warn(
|
|
3232
|
+
`[nestjs-codegen/fast] Cannot resolve '${identName}' in ${sourceFile.getFilePath()} (cross-file imports are out-of-scope for v1) \u2014 skipping`
|
|
3233
|
+
);
|
|
3234
|
+
return null;
|
|
3235
|
+
}
|
|
3236
|
+
const initializer = varDecl.getInitializer();
|
|
3237
|
+
if (!initializer) return null;
|
|
3238
|
+
contractDef = parseDefineContractCall(initializer);
|
|
3239
|
+
if (contractDef && varDecl.isExported()) {
|
|
3240
|
+
const filePath = sourceFile.getFilePath();
|
|
3241
|
+
if (contractDef.body !== null) {
|
|
3242
|
+
bodyZodRef = { name: `${identName}.body`, filePath };
|
|
3243
|
+
}
|
|
3244
|
+
if (contractDef.query !== null) {
|
|
3245
|
+
queryZodRef = { name: `${identName}.query`, filePath };
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
} else {
|
|
3249
|
+
console.warn(
|
|
3250
|
+
`[nestjs-codegen/fast] @ApplyContract arg is not an identifier or call expression in ${sourceFile.getFilePath()} \u2014 skipping`
|
|
3251
|
+
);
|
|
3252
|
+
return null;
|
|
3253
|
+
}
|
|
3254
|
+
if (!contractDef) return null;
|
|
3255
|
+
if (!verb) return null;
|
|
3256
|
+
const resolvedPath = joinPaths(prefix, verb.handlerPath);
|
|
3257
|
+
const methodName = method.getName();
|
|
3258
|
+
const classAs = readAsDecorator(cls, `class ${className}`);
|
|
3259
|
+
const methodAs = readAsDecorator(method, `${className}.${methodName}`);
|
|
3260
|
+
return buildRoute({
|
|
3261
|
+
className,
|
|
3262
|
+
methodName,
|
|
3263
|
+
resolvedMethod: verb.httpMethod,
|
|
3264
|
+
combinedPath: resolvedPath,
|
|
3265
|
+
classAs,
|
|
3266
|
+
methodAs,
|
|
3267
|
+
sourceFile,
|
|
3268
|
+
seenNames,
|
|
3269
|
+
contractSource: {
|
|
3270
|
+
query: contractDef.query,
|
|
3271
|
+
body: contractDef.body,
|
|
3272
|
+
response: contractDef.response,
|
|
3273
|
+
// Path A: capture both the importable ref and the raw text. The emitter
|
|
3274
|
+
// prefers inlining the text (client-safe — re-exporting from a controller
|
|
3275
|
+
// would drag server-only deps into the client bundle).
|
|
3276
|
+
bodyZodRef,
|
|
3277
|
+
bodyZodText: contractDef.bodyZodText,
|
|
3278
|
+
queryZodRef,
|
|
3279
|
+
queryZodText: contractDef.queryZodText
|
|
3280
|
+
}
|
|
3281
|
+
});
|
|
3282
|
+
}
|
|
3283
|
+
function extractDtoRoute(args) {
|
|
3284
|
+
const { cls, method, verb, prefix, className, sourceFile, project, seenNames } = args;
|
|
3285
|
+
if (!verb) return null;
|
|
3286
|
+
const combined = joinPaths(prefix, verb.handlerPath);
|
|
3287
|
+
const methodName = method.getName();
|
|
3288
|
+
const classAs = readAsDecorator(cls, `class ${className}`);
|
|
3289
|
+
const methodAs = readAsDecorator(method, `${className}.${methodName}`);
|
|
3290
|
+
const dtoContract = extractDtoContract(method, sourceFile, project);
|
|
3291
|
+
return buildRoute({
|
|
3292
|
+
className,
|
|
3293
|
+
methodName,
|
|
3294
|
+
resolvedMethod: verb.httpMethod,
|
|
3295
|
+
combinedPath: combined,
|
|
3296
|
+
classAs,
|
|
3297
|
+
methodAs,
|
|
3298
|
+
sourceFile,
|
|
3299
|
+
seenNames,
|
|
3300
|
+
contractSource: {
|
|
3301
|
+
query: dtoContract?.query ?? null,
|
|
3302
|
+
body: dtoContract?.body ?? null,
|
|
3303
|
+
response: dtoContract?.response ?? "unknown",
|
|
3304
|
+
queryRef: dtoContract?.queryRef ?? null,
|
|
3305
|
+
bodyRef: dtoContract?.bodyRef ?? null,
|
|
3306
|
+
responseRef: dtoContract?.responseRef ?? null,
|
|
3307
|
+
filterFields: dtoContract?.filterFields ?? null,
|
|
3308
|
+
filterFieldTypes: dtoContract?.filterFieldTypes ?? null,
|
|
3309
|
+
filterSource: dtoContract?.filterSource ?? null,
|
|
3310
|
+
formWarnings: dtoContract?.formWarnings ?? [],
|
|
3311
|
+
bodySchema: dtoContract?.bodySchema ?? null,
|
|
3312
|
+
querySchema: dtoContract?.querySchema ?? null
|
|
3313
|
+
}
|
|
3314
|
+
});
|
|
3315
|
+
}
|
|
3614
3316
|
function extractFromSourceFile(sourceFile, project) {
|
|
3615
3317
|
const routes = [];
|
|
3616
3318
|
const seenNames = /* @__PURE__ */ new Map();
|
|
3617
|
-
const
|
|
3618
|
-
for (const cls of classes) {
|
|
3319
|
+
for (const cls of sourceFile.getClasses()) {
|
|
3619
3320
|
const controllerDecorator = cls.getDecorator("Controller");
|
|
3620
3321
|
if (!controllerDecorator) continue;
|
|
3621
|
-
const
|
|
3622
|
-
const
|
|
3623
|
-
const prefix = decoratorStringArg(firstArg3) ?? "";
|
|
3322
|
+
const firstArg2 = controllerDecorator.getArguments()[0];
|
|
3323
|
+
const prefix = decoratorStringArg(firstArg2) ?? "";
|
|
3624
3324
|
const className = cls.getName() ?? "Unknown";
|
|
3625
3325
|
for (const method of cls.getMethods()) {
|
|
3626
|
-
|
|
3627
|
-
let handlerPath = "";
|
|
3628
|
-
for (const [decoratorName, verb] of Object.entries(HTTP_METHOD_DECORATORS)) {
|
|
3629
|
-
const httpDecorator = method.getDecorator(decoratorName);
|
|
3630
|
-
if (httpDecorator) {
|
|
3631
|
-
httpMethod = verb;
|
|
3632
|
-
const httpArgs = httpDecorator.getArguments();
|
|
3633
|
-
const pathArg = httpArgs[0];
|
|
3634
|
-
handlerPath = decoratorStringArg(pathArg) ?? "";
|
|
3635
|
-
break;
|
|
3636
|
-
}
|
|
3637
|
-
}
|
|
3326
|
+
const verb = resolveVerb(method);
|
|
3638
3327
|
const applyContractDecorator = method.getDecorator("ApplyContract");
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
contractDef = parseDefineContractCall(initializer);
|
|
3660
|
-
if (contractDef && varDecl.isExported()) {
|
|
3661
|
-
const filePath = sourceFile.getFilePath();
|
|
3662
|
-
if (contractDef.body !== null) {
|
|
3663
|
-
bodyZodRef = { name: `${identName}.body`, filePath };
|
|
3664
|
-
}
|
|
3665
|
-
if (contractDef.query !== null) {
|
|
3666
|
-
queryZodRef = { name: `${identName}.query`, filePath };
|
|
3667
|
-
}
|
|
3668
|
-
}
|
|
3669
|
-
} else {
|
|
3670
|
-
console.warn(
|
|
3671
|
-
`[nestjs-codegen/fast] @ApplyContract arg is not an identifier or call expression in ${sourceFile.getFilePath()} \u2014 skipping`
|
|
3672
|
-
);
|
|
3673
|
-
continue;
|
|
3674
|
-
}
|
|
3675
|
-
if (!contractDef) continue;
|
|
3676
|
-
if (!httpMethod) continue;
|
|
3677
|
-
const resolvedMethod = httpMethod;
|
|
3678
|
-
const resolvedPath = joinPaths(prefix, handlerPath);
|
|
3679
|
-
const combined = resolvedPath;
|
|
3680
|
-
const params = extractParams(combined);
|
|
3681
|
-
const methodName = method.getName();
|
|
3682
|
-
const classAsDecorator = cls.getDecorator("As");
|
|
3683
|
-
let classAs;
|
|
3684
|
-
if (classAsDecorator) {
|
|
3685
|
-
const classAsArgs = classAsDecorator.getArguments();
|
|
3686
|
-
const classAsName = decoratorStringArg(classAsArgs[0]);
|
|
3687
|
-
if (!classAsName) {
|
|
3688
|
-
throw new Error(
|
|
3689
|
-
`@As decorator on class ${className} must have a non-empty string argument.`
|
|
3690
|
-
);
|
|
3691
|
-
}
|
|
3692
|
-
classAs = classAsName;
|
|
3693
|
-
}
|
|
3694
|
-
const methodAsDecorator = method.getDecorator("As");
|
|
3695
|
-
let methodAs;
|
|
3696
|
-
if (methodAsDecorator) {
|
|
3697
|
-
const methodAsArgs = methodAsDecorator.getArguments();
|
|
3698
|
-
const methodAsName = decoratorStringArg(methodAsArgs[0]);
|
|
3699
|
-
if (!methodAsName) {
|
|
3700
|
-
throw new Error(
|
|
3701
|
-
`@As decorator on ${className}.${methodName} must have a non-empty string argument.`
|
|
3702
|
-
);
|
|
3703
|
-
}
|
|
3704
|
-
methodAs = methodAsName;
|
|
3705
|
-
}
|
|
3706
|
-
const routeName = resolveRouteName(className, methodName, classAs, methodAs);
|
|
3707
|
-
const qualifiedRef = `${className}.${methodName}`;
|
|
3708
|
-
const existing = seenNames.get(routeName);
|
|
3709
|
-
if (existing !== void 0) {
|
|
3710
|
-
throw new Error(
|
|
3711
|
-
`Route name collision: "${routeName}" is used by both "${existing}" and "${qualifiedRef}". Use @As(...) to give one of them a unique name.`
|
|
3712
|
-
);
|
|
3713
|
-
}
|
|
3714
|
-
seenNames.set(routeName, qualifiedRef);
|
|
3715
|
-
routes.push({
|
|
3716
|
-
method: resolvedMethod,
|
|
3717
|
-
path: combined,
|
|
3718
|
-
name: routeName,
|
|
3719
|
-
params,
|
|
3720
|
-
controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
|
|
3721
|
-
contract: {
|
|
3722
|
-
contractSource: {
|
|
3723
|
-
query: contractDef.query,
|
|
3724
|
-
body: contractDef.body,
|
|
3725
|
-
response: contractDef.response,
|
|
3726
|
-
// Path A: capture both the importable ref and the raw text. The
|
|
3727
|
-
// emitter prefers inlining the text (client-safe — re-exporting from
|
|
3728
|
-
// a controller would drag server-only deps into the client bundle).
|
|
3729
|
-
bodyZodRef,
|
|
3730
|
-
bodyZodText: contractDef.bodyZodText,
|
|
3731
|
-
queryZodRef,
|
|
3732
|
-
queryZodText: contractDef.queryZodText
|
|
3733
|
-
}
|
|
3734
|
-
}
|
|
3735
|
-
});
|
|
3736
|
-
} else {
|
|
3737
|
-
if (!httpMethod) continue;
|
|
3738
|
-
const combined = joinPaths(prefix, handlerPath);
|
|
3739
|
-
const params = extractParams(combined);
|
|
3740
|
-
const methodName = method.getName();
|
|
3741
|
-
const classAsDecorator = cls.getDecorator("As");
|
|
3742
|
-
let classAs;
|
|
3743
|
-
if (classAsDecorator) {
|
|
3744
|
-
const classAsArgs = classAsDecorator.getArguments();
|
|
3745
|
-
const classAsName = decoratorStringArg(classAsArgs[0]);
|
|
3746
|
-
if (classAsName) classAs = classAsName;
|
|
3747
|
-
}
|
|
3748
|
-
const methodAsDecorator = method.getDecorator("As");
|
|
3749
|
-
let methodAs;
|
|
3750
|
-
if (methodAsDecorator) {
|
|
3751
|
-
const methodAsArgs = methodAsDecorator.getArguments();
|
|
3752
|
-
const methodAsName = decoratorStringArg(methodAsArgs[0]);
|
|
3753
|
-
if (methodAsName) methodAs = methodAsName;
|
|
3754
|
-
}
|
|
3755
|
-
const routeName = resolveRouteName(className, methodName, classAs, methodAs);
|
|
3756
|
-
const dtoContract = extractDtoContract(method, sourceFile, project);
|
|
3757
|
-
routes.push({
|
|
3758
|
-
method: httpMethod,
|
|
3759
|
-
path: combined,
|
|
3760
|
-
name: routeName,
|
|
3761
|
-
params,
|
|
3762
|
-
controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
|
|
3763
|
-
contract: {
|
|
3764
|
-
contractSource: {
|
|
3765
|
-
query: dtoContract?.query ?? null,
|
|
3766
|
-
body: dtoContract?.body ?? null,
|
|
3767
|
-
response: dtoContract?.response ?? "unknown",
|
|
3768
|
-
queryRef: dtoContract?.queryRef ?? null,
|
|
3769
|
-
bodyRef: dtoContract?.bodyRef ?? null,
|
|
3770
|
-
responseRef: dtoContract?.responseRef ?? null,
|
|
3771
|
-
filterFields: dtoContract?.filterFields ?? null,
|
|
3772
|
-
filterFieldTypes: dtoContract?.filterFieldTypes ?? null,
|
|
3773
|
-
filterSource: dtoContract?.filterSource ?? null,
|
|
3774
|
-
bodyZodText: dtoContract?.bodyZodText ?? null,
|
|
3775
|
-
queryZodText: dtoContract?.queryZodText ?? null,
|
|
3776
|
-
formNestedSchemas: dtoContract?.formNestedSchemas ?? null,
|
|
3777
|
-
formWarnings: dtoContract?.formWarnings ?? [],
|
|
3778
|
-
bodySchema: dtoContract?.bodySchema ?? null,
|
|
3779
|
-
querySchema: dtoContract?.querySchema ?? null
|
|
3780
|
-
}
|
|
3781
|
-
}
|
|
3782
|
-
});
|
|
3783
|
-
}
|
|
3328
|
+
const route = applyContractDecorator ? extractContractRoute({
|
|
3329
|
+
cls,
|
|
3330
|
+
method,
|
|
3331
|
+
applyContractDecorator,
|
|
3332
|
+
verb,
|
|
3333
|
+
prefix,
|
|
3334
|
+
className,
|
|
3335
|
+
sourceFile,
|
|
3336
|
+
seenNames
|
|
3337
|
+
}) : extractDtoRoute({
|
|
3338
|
+
cls,
|
|
3339
|
+
method,
|
|
3340
|
+
verb,
|
|
3341
|
+
prefix,
|
|
3342
|
+
className,
|
|
3343
|
+
sourceFile,
|
|
3344
|
+
project,
|
|
3345
|
+
seenNames
|
|
3346
|
+
});
|
|
3347
|
+
if (route) routes.push(route);
|
|
3784
3348
|
}
|
|
3785
3349
|
}
|
|
3786
3350
|
return routes;
|
|
@@ -3789,7 +3353,7 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
3789
3353
|
// src/watch/lock-file.ts
|
|
3790
3354
|
var import_promises10 = require("fs/promises");
|
|
3791
3355
|
var import_promises11 = require("fs/promises");
|
|
3792
|
-
var
|
|
3356
|
+
var import_node_path13 = require("path");
|
|
3793
3357
|
var LOCK_FILE = ".watcher.lock";
|
|
3794
3358
|
function isProcessAlive(pid) {
|
|
3795
3359
|
try {
|
|
@@ -3801,7 +3365,7 @@ function isProcessAlive(pid) {
|
|
|
3801
3365
|
}
|
|
3802
3366
|
async function acquireLock(outDir) {
|
|
3803
3367
|
await (0, import_promises11.mkdir)(outDir, { recursive: true });
|
|
3804
|
-
const lockPath = (0,
|
|
3368
|
+
const lockPath = (0, import_node_path13.join)(outDir, LOCK_FILE);
|
|
3805
3369
|
const lockData = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3806
3370
|
try {
|
|
3807
3371
|
const fd = await (0, import_promises10.open)(lockPath, "wx");
|
|
@@ -3841,7 +3405,7 @@ async function watch(config, onChange) {
|
|
|
3841
3405
|
if (lock === null) {
|
|
3842
3406
|
let holderPid = "unknown";
|
|
3843
3407
|
try {
|
|
3844
|
-
const raw = await (0, import_promises12.readFile)((0,
|
|
3408
|
+
const raw = await (0, import_promises12.readFile)((0, import_node_path14.join)(config.codegen.outDir, ".watcher.lock"), "utf8");
|
|
3845
3409
|
const data = JSON.parse(raw);
|
|
3846
3410
|
if (data.pid !== void 0) holderPid = String(data.pid);
|
|
3847
3411
|
} catch {
|
|
@@ -3869,7 +3433,7 @@ async function watch(config, onChange) {
|
|
|
3869
3433
|
}
|
|
3870
3434
|
let pagesDebounceTimer;
|
|
3871
3435
|
const pagesGlob = config.pages?.glob ?? ".nestjs-codegen-no-pages";
|
|
3872
|
-
const pagesWatcher = import_chokidar.default.watch((0,
|
|
3436
|
+
const pagesWatcher = import_chokidar.default.watch((0, import_node_path14.join)(config.codegen.cwd, pagesGlob), {
|
|
3873
3437
|
ignoreInitial: true,
|
|
3874
3438
|
persistent: true,
|
|
3875
3439
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -3895,7 +3459,7 @@ async function watch(config, onChange) {
|
|
|
3895
3459
|
pagesWatcher.on("change", schedulePagesRegenerate);
|
|
3896
3460
|
pagesWatcher.on("unlink", schedulePagesRegenerate);
|
|
3897
3461
|
let contractsDebounceTimer;
|
|
3898
|
-
const contractsWatcher = import_chokidar.default.watch((0,
|
|
3462
|
+
const contractsWatcher = import_chokidar.default.watch((0, import_node_path14.join)(config.codegen.cwd, config.contracts.glob), {
|
|
3899
3463
|
ignoreInitial: true,
|
|
3900
3464
|
persistent: true,
|
|
3901
3465
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -3925,7 +3489,7 @@ async function watch(config, onChange) {
|
|
|
3925
3489
|
contractsWatcher.on("add", scheduleContractsRegenerate);
|
|
3926
3490
|
contractsWatcher.on("change", scheduleContractsRegenerate);
|
|
3927
3491
|
contractsWatcher.on("unlink", scheduleContractsRegenerate);
|
|
3928
|
-
const formsWatcher = import_chokidar.default.watch((0,
|
|
3492
|
+
const formsWatcher = import_chokidar.default.watch((0, import_node_path14.join)(config.codegen.cwd, config.forms.watch), {
|
|
3929
3493
|
ignoreInitial: true,
|
|
3930
3494
|
persistent: true,
|
|
3931
3495
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -3951,8 +3515,57 @@ async function watch(config, onChange) {
|
|
|
3951
3515
|
};
|
|
3952
3516
|
}
|
|
3953
3517
|
|
|
3518
|
+
// src/ir/render-ts-type.ts
|
|
3519
|
+
function tsKey(name) {
|
|
3520
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
3521
|
+
}
|
|
3522
|
+
function renderTsType(node, ctx) {
|
|
3523
|
+
switch (node.kind) {
|
|
3524
|
+
case "string":
|
|
3525
|
+
return "string";
|
|
3526
|
+
case "number":
|
|
3527
|
+
return "number";
|
|
3528
|
+
case "boolean":
|
|
3529
|
+
return "boolean";
|
|
3530
|
+
case "date":
|
|
3531
|
+
return "Date";
|
|
3532
|
+
case "unknown":
|
|
3533
|
+
return "unknown";
|
|
3534
|
+
case "instanceof":
|
|
3535
|
+
return node.ctor;
|
|
3536
|
+
case "enum":
|
|
3537
|
+
return node.literals.join(" | ");
|
|
3538
|
+
case "literal":
|
|
3539
|
+
return node.raw;
|
|
3540
|
+
case "union":
|
|
3541
|
+
return node.options.map((o) => renderTsType(o, ctx)).join(" | ");
|
|
3542
|
+
case "array":
|
|
3543
|
+
return `Array<${renderTsType(node.element, ctx)}>`;
|
|
3544
|
+
case "optional":
|
|
3545
|
+
return `${renderTsType(node.inner, ctx)} | undefined`;
|
|
3546
|
+
case "annotated":
|
|
3547
|
+
return renderTsType(node.inner, ctx);
|
|
3548
|
+
case "object": {
|
|
3549
|
+
if (node.fields.length === 0) return node.passthrough ? "Record<string, unknown>" : "{}";
|
|
3550
|
+
const inner = node.fields.map((f) => {
|
|
3551
|
+
if (f.value.kind === "optional") {
|
|
3552
|
+
return `${tsKey(f.key)}?: ${renderTsType(f.value.inner, ctx)}`;
|
|
3553
|
+
}
|
|
3554
|
+
return `${tsKey(f.key)}: ${renderTsType(f.value, ctx)}`;
|
|
3555
|
+
}).join("; ");
|
|
3556
|
+
return `{ ${inner} }`;
|
|
3557
|
+
}
|
|
3558
|
+
case "ref":
|
|
3559
|
+
case "lazyRef": {
|
|
3560
|
+
if (ctx.recursive.has(node.name)) return ctx.typeNameFor(node.name);
|
|
3561
|
+
const target = ctx.named.get(node.name);
|
|
3562
|
+
return target ? renderTsType(target, ctx) : "unknown";
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3954
3567
|
// src/index.ts
|
|
3955
|
-
var VERSION = "0.
|
|
3568
|
+
var VERSION = "0.4.0";
|
|
3956
3569
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3957
3570
|
0 && (module.exports = {
|
|
3958
3571
|
CodegenError,
|
|
@@ -3967,9 +3580,9 @@ var VERSION = "0.2.1";
|
|
|
3967
3580
|
extractSchemaFromDto,
|
|
3968
3581
|
generate,
|
|
3969
3582
|
loadConfig,
|
|
3583
|
+
renderTsType,
|
|
3970
3584
|
resolveAdapter,
|
|
3971
3585
|
resolveConfig,
|
|
3972
|
-
watch
|
|
3973
|
-
zodAdapter
|
|
3586
|
+
watch
|
|
3974
3587
|
});
|
|
3975
3588
|
//# sourceMappingURL=index.cjs.map
|