@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/nest/index.cjs
CHANGED
|
@@ -68,112 +68,9 @@ var CodegenError = class extends Error {
|
|
|
68
68
|
}
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
-
// src/adapters/zod.ts
|
|
72
|
-
function toObjectKey(name) {
|
|
73
|
-
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
74
|
-
}
|
|
75
|
-
function messageSuffix(messageRaw2) {
|
|
76
|
-
return messageRaw2 ? `{ message: ${messageRaw2} }` : "";
|
|
77
|
-
}
|
|
78
|
-
function renderStringChecks(checks) {
|
|
79
|
-
let out = "";
|
|
80
|
-
for (const c of checks) {
|
|
81
|
-
switch (c.check) {
|
|
82
|
-
case "email":
|
|
83
|
-
out += `.email(${messageSuffix(c.messageRaw)})`;
|
|
84
|
-
break;
|
|
85
|
-
case "url":
|
|
86
|
-
out += `.url(${messageSuffix(c.messageRaw)})`;
|
|
87
|
-
break;
|
|
88
|
-
case "uuid":
|
|
89
|
-
out += `.uuid(${messageSuffix(c.messageRaw)})`;
|
|
90
|
-
break;
|
|
91
|
-
case "regex":
|
|
92
|
-
out += `.regex(${c.pattern})`;
|
|
93
|
-
break;
|
|
94
|
-
case "min":
|
|
95
|
-
out += `.min(${c.value})`;
|
|
96
|
-
break;
|
|
97
|
-
case "max":
|
|
98
|
-
out += `.max(${c.value})`;
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return out;
|
|
103
|
-
}
|
|
104
|
-
function render(node, ctx) {
|
|
105
|
-
switch (node.kind) {
|
|
106
|
-
case "string":
|
|
107
|
-
return `z.string()${renderStringChecks(node.checks)}`;
|
|
108
|
-
case "number": {
|
|
109
|
-
let out = "z.number()";
|
|
110
|
-
for (const c of node.checks) {
|
|
111
|
-
if (c.check === "int") out += ".int()";
|
|
112
|
-
else if (c.check === "positive") out += ".positive()";
|
|
113
|
-
else if (c.check === "negative") out += ".negative()";
|
|
114
|
-
else if (c.check === "min") out += `.min(${c.value})`;
|
|
115
|
-
else if (c.check === "max") out += `.max(${c.value})`;
|
|
116
|
-
}
|
|
117
|
-
return out;
|
|
118
|
-
}
|
|
119
|
-
case "boolean":
|
|
120
|
-
return "z.boolean()";
|
|
121
|
-
case "date":
|
|
122
|
-
return "z.coerce.date()";
|
|
123
|
-
case "unknown":
|
|
124
|
-
return node.note ? `z.unknown() /* ${node.note} */` : "z.unknown()";
|
|
125
|
-
case "instanceof":
|
|
126
|
-
return `z.instanceof(${node.ctor})`;
|
|
127
|
-
case "enum":
|
|
128
|
-
return `z.enum([${node.literals.join(", ")}])`;
|
|
129
|
-
case "literal":
|
|
130
|
-
return `z.literal(${node.raw})`;
|
|
131
|
-
case "union":
|
|
132
|
-
return `z.union([${node.options.map((o) => render(o, ctx)).join(", ")}])`;
|
|
133
|
-
case "object": {
|
|
134
|
-
if (node.fields.length === 0) {
|
|
135
|
-
return node.passthrough ? "z.object({}).passthrough()" : "z.object({})";
|
|
136
|
-
}
|
|
137
|
-
const inner = node.fields.map((f) => `${toObjectKey(f.key)}: ${render(f.value, ctx)}`).join(", ");
|
|
138
|
-
return `z.object({ ${inner} })${node.passthrough ? ".passthrough()" : ""}`;
|
|
139
|
-
}
|
|
140
|
-
case "array":
|
|
141
|
-
return `z.array(${render(node.element, ctx)})`;
|
|
142
|
-
case "optional":
|
|
143
|
-
return `${render(node.inner, ctx)}.optional()`;
|
|
144
|
-
case "ref":
|
|
145
|
-
return node.name;
|
|
146
|
-
case "lazyRef":
|
|
147
|
-
return `z.lazy(() => ${node.name})`;
|
|
148
|
-
case "annotated": {
|
|
149
|
-
const comments = node.unmappable.map((n) => `/* @${n}: not translatable to zod (server-only) */`).join(" ");
|
|
150
|
-
return `${render(node.inner, ctx)} ${comments}`;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
var zodAdapter = {
|
|
155
|
-
name: "zod",
|
|
156
|
-
importStatements(usage) {
|
|
157
|
-
return usage.used ? ["import { z } from 'zod';"] : [];
|
|
158
|
-
},
|
|
159
|
-
render,
|
|
160
|
-
inferType(schemaConst) {
|
|
161
|
-
return `z.infer<typeof ${schemaConst}>`;
|
|
162
|
-
},
|
|
163
|
-
renderModule(mod) {
|
|
164
|
-
const ctx = { named: mod.named };
|
|
165
|
-
const namedNestedSchemas = /* @__PURE__ */ new Map();
|
|
166
|
-
for (const [name, node] of mod.named) {
|
|
167
|
-
namedNestedSchemas.set(name, render(node, ctx));
|
|
168
|
-
}
|
|
169
|
-
return { schemaText: render(mod.root, ctx), namedNestedSchemas, warnings: mod.warnings };
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
|
|
173
71
|
// src/adapters/registry.ts
|
|
174
72
|
function resolveAdapter(option) {
|
|
175
73
|
if (typeof option !== "string") return option;
|
|
176
|
-
if (option === "zod") return zodAdapter;
|
|
177
74
|
const pkg = `@dudousxd/nestjs-codegen-${option}`;
|
|
178
75
|
const named = `${option}Adapter`;
|
|
179
76
|
throw new ConfigError(
|
|
@@ -203,8 +100,21 @@ If this is intentional, move the file inside your project directory.`
|
|
|
203
100
|
function resolveConfig(userConfig, cwd) {
|
|
204
101
|
return applyDefaults(userConfig, cwd ?? process.cwd());
|
|
205
102
|
}
|
|
103
|
+
function validateUserConfig(userConfig) {
|
|
104
|
+
if (userConfig.validation == null) {
|
|
105
|
+
throw new ConfigError(
|
|
106
|
+
"validation adapter is required \u2014 install @dudousxd/nestjs-codegen-zod and pass zodAdapter, or use @dudousxd/nestjs-codegen-valibot / -arktype"
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (userConfig.pages && typeof userConfig.pages.glob !== "string") {
|
|
110
|
+
throw new ConfigError(
|
|
111
|
+
"Config validation failed: `pages.glob` must be a string when `pages` is set"
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
206
115
|
function applyDefaults(userConfig, cwd) {
|
|
207
|
-
|
|
116
|
+
validateUserConfig(userConfig);
|
|
117
|
+
const outDir = userConfig.codegen?.outDir ? resolveAbsolute(cwd, userConfig.codegen.outDir) : (0, import_node_path.join)(cwd, ".nestjs-codegen");
|
|
208
118
|
const resolvedCwd = userConfig.codegen?.cwd ? resolveAbsolute(cwd, userConfig.codegen.cwd) : cwd;
|
|
209
119
|
let app = null;
|
|
210
120
|
if (userConfig.app) {
|
|
@@ -222,7 +132,8 @@ function applyDefaults(userConfig, cwd) {
|
|
|
222
132
|
}
|
|
223
133
|
return {
|
|
224
134
|
extensions: userConfig.extensions ?? [],
|
|
225
|
-
|
|
135
|
+
// Non-null: validateUserConfig() above throws when `validation` is absent.
|
|
136
|
+
validation: resolveAdapter(userConfig.validation),
|
|
226
137
|
pages: userConfig.pages ? {
|
|
227
138
|
glob: userConfig.pages.glob,
|
|
228
139
|
propsExport: userConfig.pages.propsExport ?? "ComponentProps",
|
|
@@ -249,13 +160,16 @@ function applyDefaults(userConfig, cwd) {
|
|
|
249
160
|
|
|
250
161
|
// src/watch/watcher.ts
|
|
251
162
|
var import_promises12 = require("fs/promises");
|
|
252
|
-
var
|
|
163
|
+
var import_node_path14 = require("path");
|
|
253
164
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
254
165
|
|
|
255
166
|
// src/discovery/contracts-fast.ts
|
|
256
167
|
var import_node_path3 = require("path");
|
|
257
168
|
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
258
|
-
var
|
|
169
|
+
var import_ts_morph7 = require("ts-morph");
|
|
170
|
+
|
|
171
|
+
// src/discovery/dto-type-resolver.ts
|
|
172
|
+
var import_ts_morph5 = require("ts-morph");
|
|
259
173
|
|
|
260
174
|
// src/discovery/dto-to-ir.ts
|
|
261
175
|
var import_ts_morph2 = require("ts-morph");
|
|
@@ -264,20 +178,13 @@ var import_ts_morph2 = require("ts-morph");
|
|
|
264
178
|
var import_node_fs = require("fs");
|
|
265
179
|
var import_node_path2 = require("path");
|
|
266
180
|
var import_ts_morph = require("ts-morph");
|
|
267
|
-
var
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return prev;
|
|
181
|
+
var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
|
|
182
|
+
var _ctxByProject = /* @__PURE__ */ new WeakMap();
|
|
183
|
+
function setDiscoveryContext(project, ctx) {
|
|
184
|
+
_ctxByProject.set(project, ctx);
|
|
272
185
|
}
|
|
273
|
-
function
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
function _projectRoot() {
|
|
277
|
-
return _ctx.projectRoot;
|
|
278
|
-
}
|
|
279
|
-
function _tsconfigPaths() {
|
|
280
|
-
return _ctx.tsconfigPaths;
|
|
186
|
+
function _ctxFor(project) {
|
|
187
|
+
return _ctxByProject.get(project) ?? _EMPTY_CTX;
|
|
281
188
|
}
|
|
282
189
|
var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
|
|
283
190
|
function dbg(...args) {
|
|
@@ -319,7 +226,7 @@ function findTypeInFile(name, file) {
|
|
|
319
226
|
}
|
|
320
227
|
return null;
|
|
321
228
|
}
|
|
322
|
-
function resolveModuleSpecifier(moduleSpecifier, sourceFile,
|
|
229
|
+
function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
323
230
|
if (moduleSpecifier.startsWith(".")) {
|
|
324
231
|
const dir = (0, import_node_path2.dirname)(sourceFile.getFilePath());
|
|
325
232
|
const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
|
|
@@ -329,8 +236,9 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, _project) {
|
|
|
329
236
|
(0, import_node_path2.resolve)(dir, moduleSpecifier, "index.ts")
|
|
330
237
|
];
|
|
331
238
|
}
|
|
332
|
-
const
|
|
333
|
-
const
|
|
239
|
+
const ctx = _ctxFor(project);
|
|
240
|
+
const baseUrl = ctx.projectRoot;
|
|
241
|
+
const tsconfigPaths = ctx.tsconfigPaths;
|
|
334
242
|
dbg(
|
|
335
243
|
"resolveModuleSpecifier",
|
|
336
244
|
moduleSpecifier,
|
|
@@ -476,7 +384,7 @@ function resolveTypeRef(nodeOrName, sourceFile, project, opts) {
|
|
|
476
384
|
if (!namedImport) continue;
|
|
477
385
|
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
478
386
|
if (opts.allowBareSpecifier && !moduleSpecifier.startsWith(".") && !moduleSpecifier.startsWith("/")) {
|
|
479
|
-
const tsconfigPaths =
|
|
387
|
+
const tsconfigPaths = _ctxFor(project).tsconfigPaths;
|
|
480
388
|
const isAlias = tsconfigPaths != null && Object.keys(tsconfigPaths).some((p) => {
|
|
481
389
|
const prefix = p.replace("*", "");
|
|
482
390
|
return moduleSpecifier.startsWith(prefix);
|
|
@@ -545,10 +453,7 @@ function extractSchemaFromDto(classDecl, sourceFile, project) {
|
|
|
545
453
|
depth: 0
|
|
546
454
|
};
|
|
547
455
|
const root = buildObject(classDecl, sourceFile, ctx);
|
|
548
|
-
|
|
549
|
-
ctx.named.set(schemaName, { kind: "unknown", note: "recursive type \u2014 not expanded" });
|
|
550
|
-
}
|
|
551
|
-
return { root, named: ctx.named, warnings: ctx.warnings };
|
|
456
|
+
return { root, named: ctx.named, warnings: ctx.warnings, recursive: ctx.recursiveSchemas };
|
|
552
457
|
}
|
|
553
458
|
function buildObject(classDecl, classFile, ctx) {
|
|
554
459
|
const props = classDecl.getProperties();
|
|
@@ -568,7 +473,7 @@ function buildProperty(prop, classFile, ctx) {
|
|
|
568
473
|
const dec = (n) => decorators.get(n);
|
|
569
474
|
const typeNode = prop.getTypeNode();
|
|
570
475
|
const typeText = typeNode?.getText() ?? "unknown";
|
|
571
|
-
const isArrayType = !!typeNode &&
|
|
476
|
+
const isArrayType = !!typeNode && import_ts_morph2.Node.isArrayTypeNode(typeNode);
|
|
572
477
|
const typeRefName = resolveTypeFactoryName(dec("Type"));
|
|
573
478
|
if (has("ValidateNested") || typeRefName) {
|
|
574
479
|
const childName = typeRefName ?? singularClassName(typeText);
|
|
@@ -699,18 +604,27 @@ function baseFromType(typeText, isArrayType) {
|
|
|
699
604
|
}
|
|
700
605
|
}
|
|
701
606
|
function buildNestedReference(className, fromFile, ctx) {
|
|
702
|
-
if (ctx.visiting.has(className)
|
|
607
|
+
if (ctx.visiting.has(className)) {
|
|
703
608
|
const reserved = ctx.emittedClasses.get(className) ?? aliasFor(className, ctx);
|
|
704
609
|
ctx.emittedClasses.set(className, reserved);
|
|
705
610
|
ctx.recursiveSchemas.add(reserved);
|
|
706
611
|
if (!ctx.warnedDecorators.has(`recursive:${reserved}`)) {
|
|
707
612
|
ctx.warnedDecorators.add(`recursive:${reserved}`);
|
|
708
|
-
const msg = `${className} is a recursive type
|
|
613
|
+
const msg = `${className} is a recursive type; the generated schema validates it via a lazy self-reference.`;
|
|
709
614
|
ctx.warnings.push(msg);
|
|
710
615
|
console.warn(`[nestjs-codegen] ${msg}`);
|
|
711
616
|
}
|
|
712
617
|
return { kind: "lazyRef", name: reserved };
|
|
713
618
|
}
|
|
619
|
+
if (ctx.depth >= 8) {
|
|
620
|
+
if (!ctx.warnedDecorators.has(`deep:${className}`)) {
|
|
621
|
+
ctx.warnedDecorators.add(`deep:${className}`);
|
|
622
|
+
const msg = `${className} nesting is too deep to expand; the generated schema uses unknown for it.`;
|
|
623
|
+
ctx.warnings.push(msg);
|
|
624
|
+
console.warn(`[nestjs-codegen] ${msg}`);
|
|
625
|
+
}
|
|
626
|
+
return { kind: "unknown", note: "nesting too deep \u2014 not expanded" };
|
|
627
|
+
}
|
|
714
628
|
const existing = ctx.emittedClasses.get(className);
|
|
715
629
|
if (existing) return { kind: "ref", name: existing };
|
|
716
630
|
const schemaName = aliasFor(className, ctx);
|
|
@@ -829,339 +743,11 @@ function inSchemaFromDecorator(decorator) {
|
|
|
829
743
|
return null;
|
|
830
744
|
}
|
|
831
745
|
|
|
832
|
-
// src/discovery/dto-to-zod.ts
|
|
833
|
-
var import_ts_morph3 = require("ts-morph");
|
|
834
|
-
var KNOWN_DECORATORS2 = /* @__PURE__ */ new Set([
|
|
835
|
-
"IsString",
|
|
836
|
-
"IsNumber",
|
|
837
|
-
"IsInt",
|
|
838
|
-
"IsBoolean",
|
|
839
|
-
"IsDate",
|
|
840
|
-
"IsEmail",
|
|
841
|
-
"IsUrl",
|
|
842
|
-
"IsUUID",
|
|
843
|
-
"MinLength",
|
|
844
|
-
"MaxLength",
|
|
845
|
-
"Length",
|
|
846
|
-
"Min",
|
|
847
|
-
"Max",
|
|
848
|
-
"IsPositive",
|
|
849
|
-
"IsNegative",
|
|
850
|
-
"Matches",
|
|
851
|
-
"IsEnum",
|
|
852
|
-
"IsIn",
|
|
853
|
-
"IsOptional",
|
|
854
|
-
"IsNotEmpty",
|
|
855
|
-
"IsArray",
|
|
856
|
-
"ValidateNested",
|
|
857
|
-
"Type",
|
|
858
|
-
"IsObject",
|
|
859
|
-
"Allow",
|
|
860
|
-
"IsDefined"
|
|
861
|
-
]);
|
|
862
|
-
function extractZodFromDto(classDecl, sourceFile, project) {
|
|
863
|
-
const ctx = {
|
|
864
|
-
sourceFile,
|
|
865
|
-
project,
|
|
866
|
-
namedNestedSchemas: /* @__PURE__ */ new Map(),
|
|
867
|
-
warnings: [],
|
|
868
|
-
warnedDecorators: /* @__PURE__ */ new Set(),
|
|
869
|
-
emittedClasses: /* @__PURE__ */ new Map(),
|
|
870
|
-
visiting: /* @__PURE__ */ new Set(),
|
|
871
|
-
recursiveSchemas: /* @__PURE__ */ new Set(),
|
|
872
|
-
depth: 0
|
|
873
|
-
};
|
|
874
|
-
const schemaText = buildObjectSchema(classDecl, sourceFile, ctx);
|
|
875
|
-
for (const schemaName of ctx.recursiveSchemas) {
|
|
876
|
-
ctx.namedNestedSchemas.set(schemaName, "z.unknown() /* recursive type \u2014 not expanded */");
|
|
877
|
-
}
|
|
878
|
-
return {
|
|
879
|
-
schemaText,
|
|
880
|
-
namedNestedSchemas: ctx.namedNestedSchemas,
|
|
881
|
-
warnings: ctx.warnings
|
|
882
|
-
};
|
|
883
|
-
}
|
|
884
|
-
function buildObjectSchema(classDecl, classFile, ctx) {
|
|
885
|
-
const props = classDecl.getProperties();
|
|
886
|
-
if (props.length === 0) {
|
|
887
|
-
return "z.object({}).passthrough()";
|
|
888
|
-
}
|
|
889
|
-
const fields = [];
|
|
890
|
-
for (const prop of props) {
|
|
891
|
-
const name = prop.getName();
|
|
892
|
-
const expr = buildPropertySchema(prop, classFile, ctx);
|
|
893
|
-
fields.push(`${toObjectKey2(name)}: ${expr}`);
|
|
894
|
-
}
|
|
895
|
-
return `z.object({ ${fields.join(", ")} })`;
|
|
896
|
-
}
|
|
897
|
-
function toObjectKey2(name) {
|
|
898
|
-
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
899
|
-
}
|
|
900
|
-
function buildPropertySchema(prop, classFile, ctx) {
|
|
901
|
-
const decorators = /* @__PURE__ */ new Map();
|
|
902
|
-
for (const d of prop.getDecorators()) decorators.set(d.getName(), d);
|
|
903
|
-
const has = (n) => decorators.has(n);
|
|
904
|
-
const dec = (n) => decorators.get(n);
|
|
905
|
-
const typeNode = prop.getTypeNode();
|
|
906
|
-
const typeText = typeNode?.getText() ?? "unknown";
|
|
907
|
-
const isArrayType = !!typeNode && typeNode.getText().endsWith("[]");
|
|
908
|
-
const comments = [];
|
|
909
|
-
const typeRefName = resolveTypeFactoryName2(dec("Type"));
|
|
910
|
-
if (has("ValidateNested") || typeRefName) {
|
|
911
|
-
const childName = typeRefName ?? singularClassName2(typeText);
|
|
912
|
-
if (childName) {
|
|
913
|
-
const childExpr = buildNestedReference2(childName, classFile, ctx);
|
|
914
|
-
const wrapArray = has("IsArray") || isArrayType;
|
|
915
|
-
let expr2 = wrapArray ? `z.array(${childExpr})` : childExpr;
|
|
916
|
-
expr2 = applyPresence2(expr2, decorators);
|
|
917
|
-
return expr2;
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
let base = baseFromType2(typeText, isArrayType, ctx, classFile);
|
|
921
|
-
const refinements = [];
|
|
922
|
-
if (has("IsString")) base = "z.string()";
|
|
923
|
-
if (has("IsBoolean")) base = "z.boolean()";
|
|
924
|
-
if (has("IsDate")) base = "z.coerce.date()";
|
|
925
|
-
if (has("IsNumber")) base = "z.number()";
|
|
926
|
-
if (has("IsInt")) base = "z.number().int()";
|
|
927
|
-
if (has("IsObject") && !has("ValidateNested")) base = "z.object({}).passthrough()";
|
|
928
|
-
if (has("Allow")) base = "z.unknown()";
|
|
929
|
-
if (has("IsEmail")) {
|
|
930
|
-
base = ensureStringBase(base);
|
|
931
|
-
refinements.push(`.email(${messageArg(dec("IsEmail"))})`);
|
|
932
|
-
}
|
|
933
|
-
if (has("IsUrl")) {
|
|
934
|
-
base = ensureStringBase(base);
|
|
935
|
-
refinements.push(`.url(${messageArg(dec("IsUrl"))})`);
|
|
936
|
-
}
|
|
937
|
-
if (has("IsUUID")) {
|
|
938
|
-
base = ensureStringBase(base);
|
|
939
|
-
refinements.push(`.uuid(${messageArg(dec("IsUUID"))})`);
|
|
940
|
-
}
|
|
941
|
-
if (has("Matches")) {
|
|
942
|
-
const re = firstArgText2(dec("Matches"));
|
|
943
|
-
if (re) {
|
|
944
|
-
base = ensureStringBase(base);
|
|
945
|
-
refinements.push(`.regex(${re})`);
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
if (has("MinLength")) {
|
|
949
|
-
const n = numericArg2(dec("MinLength"));
|
|
950
|
-
if (n !== null) refinements.push(`.min(${n})`);
|
|
951
|
-
}
|
|
952
|
-
if (has("MaxLength")) {
|
|
953
|
-
const n = numericArg2(dec("MaxLength"));
|
|
954
|
-
if (n !== null) refinements.push(`.max(${n})`);
|
|
955
|
-
}
|
|
956
|
-
if (has("Length")) {
|
|
957
|
-
const [min, max] = numericArgs2(dec("Length"));
|
|
958
|
-
if (min !== null) refinements.push(`.min(${min})`);
|
|
959
|
-
if (max !== null) refinements.push(`.max(${max})`);
|
|
960
|
-
}
|
|
961
|
-
if (has("Min")) {
|
|
962
|
-
const n = numericArg2(dec("Min"));
|
|
963
|
-
if (n !== null) refinements.push(`.min(${n})`);
|
|
964
|
-
}
|
|
965
|
-
if (has("Max")) {
|
|
966
|
-
const n = numericArg2(dec("Max"));
|
|
967
|
-
if (n !== null) refinements.push(`.max(${n})`);
|
|
968
|
-
}
|
|
969
|
-
if (has("IsPositive")) refinements.push(".positive()");
|
|
970
|
-
if (has("IsNegative")) refinements.push(".negative()");
|
|
971
|
-
if (has("IsNotEmpty") && isStringBase(base)) refinements.push(".min(1)");
|
|
972
|
-
if (has("IsEnum")) {
|
|
973
|
-
const enumExpr = enumSchemaFromDecorator2(dec("IsEnum"), classFile, ctx);
|
|
974
|
-
if (enumExpr) base = enumExpr;
|
|
975
|
-
}
|
|
976
|
-
if (has("IsIn")) {
|
|
977
|
-
const inExpr = inSchemaFromDecorator2(dec("IsIn"));
|
|
978
|
-
if (inExpr) base = inExpr;
|
|
979
|
-
}
|
|
980
|
-
for (const name of decorators.keys()) {
|
|
981
|
-
if (!KNOWN_DECORATORS2.has(name)) {
|
|
982
|
-
comments.push(`/* @${name}: not translatable to zod (server-only) */`);
|
|
983
|
-
if (!ctx.warnedDecorators.has(name)) {
|
|
984
|
-
ctx.warnedDecorators.add(name);
|
|
985
|
-
const msg = `@${name} is not translatable to zod and was skipped (server-only validation).`;
|
|
986
|
-
ctx.warnings.push(msg);
|
|
987
|
-
console.warn(`[nestjs-codegen/forms] ${msg}`);
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
let expr = base + refinements.join("");
|
|
992
|
-
if (isArrayType && !expr.startsWith("z.array(")) {
|
|
993
|
-
expr = `z.array(${expr})`;
|
|
994
|
-
}
|
|
995
|
-
expr = applyPresence2(expr, decorators);
|
|
996
|
-
if (comments.length > 0) {
|
|
997
|
-
expr = `${expr} ${comments.join(" ")}`;
|
|
998
|
-
}
|
|
999
|
-
return expr;
|
|
1000
|
-
}
|
|
1001
|
-
function applyPresence2(expr, decorators) {
|
|
1002
|
-
if (decorators.has("IsDefined")) return expr;
|
|
1003
|
-
if (decorators.has("IsOptional")) return `${expr}.optional()`;
|
|
1004
|
-
return expr;
|
|
1005
|
-
}
|
|
1006
|
-
function baseFromType2(typeText, isArrayType, ctx, classFile) {
|
|
1007
|
-
const inner = isArrayType ? typeText.slice(0, -2).trim() : typeText;
|
|
1008
|
-
switch (inner) {
|
|
1009
|
-
case "string":
|
|
1010
|
-
return "z.string()";
|
|
1011
|
-
case "number":
|
|
1012
|
-
return "z.number()";
|
|
1013
|
-
case "boolean":
|
|
1014
|
-
return "z.boolean()";
|
|
1015
|
-
case "Date":
|
|
1016
|
-
return "z.coerce.date()";
|
|
1017
|
-
case "File":
|
|
1018
|
-
case "Express.Multer.File":
|
|
1019
|
-
return "z.instanceof(File)";
|
|
1020
|
-
default:
|
|
1021
|
-
return "z.unknown()";
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
function ensureStringBase(base) {
|
|
1025
|
-
return isStringBase(base) ? base : "z.string()";
|
|
1026
|
-
}
|
|
1027
|
-
function isStringBase(base) {
|
|
1028
|
-
return base.startsWith("z.string(");
|
|
1029
|
-
}
|
|
1030
|
-
function buildNestedReference2(className, fromFile, ctx) {
|
|
1031
|
-
if (ctx.visiting.has(className) || ctx.depth >= 8) {
|
|
1032
|
-
const reserved = ctx.emittedClasses.get(className) ?? aliasFor2(className, ctx);
|
|
1033
|
-
ctx.emittedClasses.set(className, reserved);
|
|
1034
|
-
ctx.recursiveSchemas.add(reserved);
|
|
1035
|
-
if (!ctx.warnedDecorators.has(`recursive:${reserved}`)) {
|
|
1036
|
-
ctx.warnedDecorators.add(`recursive:${reserved}`);
|
|
1037
|
-
const msg = `${className} is a recursive type and was not expanded; the generated form schema uses z.unknown() for it.`;
|
|
1038
|
-
ctx.warnings.push(msg);
|
|
1039
|
-
console.warn(`[nestjs-codegen/forms] ${msg}`);
|
|
1040
|
-
}
|
|
1041
|
-
return `z.lazy(() => ${reserved})`;
|
|
1042
|
-
}
|
|
1043
|
-
const existing = ctx.emittedClasses.get(className);
|
|
1044
|
-
if (existing) return existing;
|
|
1045
|
-
const schemaName = aliasFor2(className, ctx);
|
|
1046
|
-
const resolved = findType(className, fromFile, ctx.project);
|
|
1047
|
-
if (!resolved || resolved.kind !== "class") {
|
|
1048
|
-
return "z.object({}).passthrough()";
|
|
1049
|
-
}
|
|
1050
|
-
ctx.emittedClasses.set(className, schemaName);
|
|
1051
|
-
ctx.visiting.add(className);
|
|
1052
|
-
ctx.depth += 1;
|
|
1053
|
-
const childText = buildObjectSchema(resolved.decl, resolved.file, ctx);
|
|
1054
|
-
ctx.depth -= 1;
|
|
1055
|
-
ctx.visiting.delete(className);
|
|
1056
|
-
ctx.namedNestedSchemas.set(schemaName, childText);
|
|
1057
|
-
return schemaName;
|
|
1058
|
-
}
|
|
1059
|
-
function aliasFor2(className, ctx) {
|
|
1060
|
-
const baseName = `${className}Schema`;
|
|
1061
|
-
let candidate = baseName;
|
|
1062
|
-
let i = 1;
|
|
1063
|
-
const used = new Set(ctx.namedNestedSchemas.keys());
|
|
1064
|
-
for (const v of ctx.emittedClasses.values()) used.add(v);
|
|
1065
|
-
while (used.has(candidate)) {
|
|
1066
|
-
candidate = `${baseName}_${i}`;
|
|
1067
|
-
i += 1;
|
|
1068
|
-
}
|
|
1069
|
-
return candidate;
|
|
1070
|
-
}
|
|
1071
|
-
function firstArg2(decorator) {
|
|
1072
|
-
return decorator?.getArguments()[0];
|
|
1073
|
-
}
|
|
1074
|
-
function firstArgText2(decorator) {
|
|
1075
|
-
const arg = firstArg2(decorator);
|
|
1076
|
-
return arg ? arg.getText() : null;
|
|
1077
|
-
}
|
|
1078
|
-
function numericArg2(decorator) {
|
|
1079
|
-
const arg = firstArg2(decorator);
|
|
1080
|
-
if (arg && import_ts_morph3.Node.isNumericLiteral(arg)) return arg.getText();
|
|
1081
|
-
return null;
|
|
1082
|
-
}
|
|
1083
|
-
function numericArgs2(decorator) {
|
|
1084
|
-
const args = decorator?.getArguments() ?? [];
|
|
1085
|
-
const num = (n) => n && import_ts_morph3.Node.isNumericLiteral(n) ? n.getText() : null;
|
|
1086
|
-
return [num(args[0]), num(args[1])];
|
|
1087
|
-
}
|
|
1088
|
-
function messageArg(decorator) {
|
|
1089
|
-
const args = decorator?.getArguments() ?? [];
|
|
1090
|
-
for (const arg of args) {
|
|
1091
|
-
if (import_ts_morph3.Node.isObjectLiteralExpression(arg)) {
|
|
1092
|
-
for (const prop of arg.getProperties()) {
|
|
1093
|
-
if (import_ts_morph3.Node.isPropertyAssignment(prop) && prop.getName() === "message") {
|
|
1094
|
-
const init = prop.getInitializer();
|
|
1095
|
-
if (init && import_ts_morph3.Node.isStringLiteral(init)) {
|
|
1096
|
-
return `{ message: ${init.getText()} }`;
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
return "";
|
|
1103
|
-
}
|
|
1104
|
-
function resolveTypeFactoryName2(decorator) {
|
|
1105
|
-
const arg = firstArg2(decorator);
|
|
1106
|
-
if (!arg) return null;
|
|
1107
|
-
if (import_ts_morph3.Node.isArrowFunction(arg)) {
|
|
1108
|
-
const body = arg.getBody();
|
|
1109
|
-
if (import_ts_morph3.Node.isIdentifier(body)) return body.getText();
|
|
1110
|
-
}
|
|
1111
|
-
return null;
|
|
1112
|
-
}
|
|
1113
|
-
function singularClassName2(typeText) {
|
|
1114
|
-
const inner = typeText.endsWith("[]") ? typeText.slice(0, -2).trim() : typeText;
|
|
1115
|
-
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(inner) ? inner : null;
|
|
1116
|
-
}
|
|
1117
|
-
function enumSchemaFromDecorator2(decorator, classFile, ctx) {
|
|
1118
|
-
const arg = firstArg2(decorator);
|
|
1119
|
-
if (!arg) return null;
|
|
1120
|
-
if (import_ts_morph3.Node.isIdentifier(arg)) {
|
|
1121
|
-
const name = arg.getText();
|
|
1122
|
-
const resolved = findType(name, classFile, ctx.project);
|
|
1123
|
-
if (resolved && resolved.kind === "enum") {
|
|
1124
|
-
return `z.enum([${resolved.members.join(", ")}])`;
|
|
1125
|
-
}
|
|
1126
|
-
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().`;
|
|
1127
|
-
if (!ctx.warnedDecorators.has(`IsEnum:${name}`)) {
|
|
1128
|
-
ctx.warnedDecorators.add(`IsEnum:${name}`);
|
|
1129
|
-
ctx.warnings.push(msg);
|
|
1130
|
-
console.warn(`[nestjs-codegen/forms] ${msg}`);
|
|
1131
|
-
}
|
|
1132
|
-
return `z.unknown() /* @IsEnum(${name}): enum not resolvable to literals */`;
|
|
1133
|
-
}
|
|
1134
|
-
if (import_ts_morph3.Node.isObjectLiteralExpression(arg)) {
|
|
1135
|
-
const values = [];
|
|
1136
|
-
for (const p of arg.getProperties()) {
|
|
1137
|
-
if (!import_ts_morph3.Node.isPropertyAssignment(p)) continue;
|
|
1138
|
-
const init = p.getInitializer();
|
|
1139
|
-
if (init && import_ts_morph3.Node.isStringLiteral(init)) values.push(init.getText());
|
|
1140
|
-
}
|
|
1141
|
-
if (values.length > 0) return `z.enum([${values.join(", ")}])`;
|
|
1142
|
-
}
|
|
1143
|
-
return null;
|
|
1144
|
-
}
|
|
1145
|
-
function inSchemaFromDecorator2(decorator) {
|
|
1146
|
-
const arg = firstArg2(decorator);
|
|
1147
|
-
if (arg && import_ts_morph3.Node.isArrayLiteralExpression(arg)) {
|
|
1148
|
-
const elements = arg.getElements();
|
|
1149
|
-
const allStrings = elements.every((e) => import_ts_morph3.Node.isStringLiteral(e));
|
|
1150
|
-
if (allStrings && elements.length > 0) {
|
|
1151
|
-
return `z.enum([${elements.map((e) => e.getText()).join(", ")}])`;
|
|
1152
|
-
}
|
|
1153
|
-
if (elements.length > 0) {
|
|
1154
|
-
return `z.union([${elements.map((e) => `z.literal(${e.getText()})`).join(", ")}])`;
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
return null;
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
746
|
// src/discovery/filter-for.ts
|
|
1161
|
-
var
|
|
747
|
+
var import_ts_morph4 = require("ts-morph");
|
|
1162
748
|
|
|
1163
749
|
// src/discovery/filter-field-types.ts
|
|
1164
|
-
var
|
|
750
|
+
var import_ts_morph3 = require("ts-morph");
|
|
1165
751
|
|
|
1166
752
|
// src/discovery/enum-resolution.ts
|
|
1167
753
|
function resolveEnumValues(name, sourceFile, project) {
|
|
@@ -1196,28 +782,28 @@ function markNullable(r, nullable) {
|
|
|
1196
782
|
return nullable ? { ...r, nullable: true } : r;
|
|
1197
783
|
}
|
|
1198
784
|
function classifyTypeNode(typeNode, sourceFile, project, opts) {
|
|
1199
|
-
if (
|
|
785
|
+
if (import_ts_morph3.Node.isUnionTypeNode(typeNode)) {
|
|
1200
786
|
let nullable = false;
|
|
1201
787
|
const stringLits = [];
|
|
1202
788
|
const numberLits = [];
|
|
1203
789
|
const others = [];
|
|
1204
790
|
for (const member of typeNode.getTypeNodes()) {
|
|
1205
791
|
const kind = member.getKind();
|
|
1206
|
-
if (kind ===
|
|
792
|
+
if (kind === import_ts_morph3.SyntaxKind.NullKeyword || kind === import_ts_morph3.SyntaxKind.UndefinedKeyword) {
|
|
1207
793
|
nullable = true;
|
|
1208
794
|
continue;
|
|
1209
795
|
}
|
|
1210
|
-
if (
|
|
796
|
+
if (import_ts_morph3.Node.isLiteralTypeNode(member)) {
|
|
1211
797
|
const lit = member.getLiteral();
|
|
1212
|
-
if (
|
|
798
|
+
if (import_ts_morph3.Node.isStringLiteral(lit)) {
|
|
1213
799
|
stringLits.push(lit.getLiteralValue());
|
|
1214
800
|
continue;
|
|
1215
801
|
}
|
|
1216
|
-
if (
|
|
802
|
+
if (import_ts_morph3.Node.isNumericLiteral(lit)) {
|
|
1217
803
|
numberLits.push(lit.getText());
|
|
1218
804
|
continue;
|
|
1219
805
|
}
|
|
1220
|
-
if (lit.getKind() ===
|
|
806
|
+
if (lit.getKind() === import_ts_morph3.SyntaxKind.NullKeyword) {
|
|
1221
807
|
nullable = true;
|
|
1222
808
|
continue;
|
|
1223
809
|
}
|
|
@@ -1237,19 +823,19 @@ function classifyTypeNode(typeNode, sourceFile, project, opts) {
|
|
|
1237
823
|
return markNullable({ kind: "unknown" }, nullable);
|
|
1238
824
|
}
|
|
1239
825
|
switch (typeNode.getKind()) {
|
|
1240
|
-
case
|
|
826
|
+
case import_ts_morph3.SyntaxKind.StringKeyword:
|
|
1241
827
|
return { kind: "string" };
|
|
1242
|
-
case
|
|
828
|
+
case import_ts_morph3.SyntaxKind.NumberKeyword:
|
|
1243
829
|
return { kind: "number" };
|
|
1244
|
-
case
|
|
830
|
+
case import_ts_morph3.SyntaxKind.BooleanKeyword:
|
|
1245
831
|
return { kind: "boolean" };
|
|
1246
|
-
case
|
|
1247
|
-
case
|
|
832
|
+
case import_ts_morph3.SyntaxKind.AnyKeyword:
|
|
833
|
+
case import_ts_morph3.SyntaxKind.UnknownKeyword:
|
|
1248
834
|
return { kind: "unknown" };
|
|
1249
835
|
default:
|
|
1250
836
|
break;
|
|
1251
837
|
}
|
|
1252
|
-
if (
|
|
838
|
+
if (import_ts_morph3.Node.isTypeReference(typeNode)) {
|
|
1253
839
|
const refName = typeNode.getTypeName().getText();
|
|
1254
840
|
if (refName === "Date") return { kind: "date" };
|
|
1255
841
|
if (refName === "Record" || refName === "Object") return { kind: "json" };
|
|
@@ -1262,25 +848,25 @@ function classifyTypeNode(typeNode, sourceFile, project, opts) {
|
|
|
1262
848
|
if (typeRef) return { kind: "unknown", typeRef };
|
|
1263
849
|
return { kind: "unknown" };
|
|
1264
850
|
}
|
|
1265
|
-
if (
|
|
851
|
+
if (import_ts_morph3.Node.isTypeLiteral(typeNode)) return { kind: "json" };
|
|
1266
852
|
return { kind: "unknown" };
|
|
1267
853
|
}
|
|
1268
854
|
function enumFromDecoratorArgs(args, sourceFile, project) {
|
|
1269
855
|
for (const arg of args) {
|
|
1270
|
-
if (
|
|
856
|
+
if (import_ts_morph3.Node.isArrowFunction(arg)) {
|
|
1271
857
|
const body = arg.getBody();
|
|
1272
|
-
if (
|
|
858
|
+
if (import_ts_morph3.Node.isIdentifier(body)) {
|
|
1273
859
|
const en = resolveEnumValues(body.getText(), sourceFile, project);
|
|
1274
860
|
if (en) return en;
|
|
1275
861
|
}
|
|
1276
862
|
}
|
|
1277
|
-
if (
|
|
863
|
+
if (import_ts_morph3.Node.isObjectLiteralExpression(arg)) {
|
|
1278
864
|
const itemsProp = arg.getProperty("items");
|
|
1279
|
-
if (itemsProp &&
|
|
865
|
+
if (itemsProp && import_ts_morph3.Node.isPropertyAssignment(itemsProp)) {
|
|
1280
866
|
const init = itemsProp.getInitializer();
|
|
1281
|
-
if (init &&
|
|
867
|
+
if (init && import_ts_morph3.Node.isArrowFunction(init)) {
|
|
1282
868
|
const body = init.getBody();
|
|
1283
|
-
if (
|
|
869
|
+
if (import_ts_morph3.Node.isIdentifier(body)) {
|
|
1284
870
|
const en = resolveEnumValues(body.getText(), sourceFile, project);
|
|
1285
871
|
if (en) return en;
|
|
1286
872
|
}
|
|
@@ -1303,7 +889,7 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
|
|
|
1303
889
|
return { kind: "string" };
|
|
1304
890
|
}
|
|
1305
891
|
for (const arg of args) {
|
|
1306
|
-
if (
|
|
892
|
+
if (import_ts_morph3.Node.isStringLiteral(arg)) {
|
|
1307
893
|
const raw = arg.getLiteralValue();
|
|
1308
894
|
const kind = classifyTypeKeyword(raw);
|
|
1309
895
|
if (kind) {
|
|
@@ -1311,11 +897,11 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
|
|
|
1311
897
|
return { kind };
|
|
1312
898
|
}
|
|
1313
899
|
}
|
|
1314
|
-
if (
|
|
900
|
+
if (import_ts_morph3.Node.isObjectLiteralExpression(arg)) {
|
|
1315
901
|
const enumProp = arg.getProperty("enum");
|
|
1316
|
-
if (enumProp &&
|
|
902
|
+
if (enumProp && import_ts_morph3.Node.isPropertyAssignment(enumProp)) {
|
|
1317
903
|
const init = enumProp.getInitializer();
|
|
1318
|
-
if (init &&
|
|
904
|
+
if (init && import_ts_morph3.Node.isIdentifier(init)) {
|
|
1319
905
|
const en = resolveEnumValues(init.getText(), sourceFile, project);
|
|
1320
906
|
if (en) {
|
|
1321
907
|
return en.numeric ? { kind: "number", enumValues: en.values, numericEnum: true } : { kind: "string", enumValues: en.values };
|
|
@@ -1324,9 +910,9 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
|
|
|
1324
910
|
}
|
|
1325
911
|
}
|
|
1326
912
|
const typeProp = arg.getProperty("type");
|
|
1327
|
-
if (typeProp &&
|
|
913
|
+
if (typeProp && import_ts_morph3.Node.isPropertyAssignment(typeProp)) {
|
|
1328
914
|
const init = typeProp.getInitializer();
|
|
1329
|
-
if (init &&
|
|
915
|
+
if (init && import_ts_morph3.Node.isStringLiteral(init)) {
|
|
1330
916
|
const kind = classifyTypeKeyword(init.getLiteralValue());
|
|
1331
917
|
if (kind) return { kind };
|
|
1332
918
|
}
|
|
@@ -1362,7 +948,7 @@ function toFilterFieldType(name, r) {
|
|
|
1362
948
|
|
|
1363
949
|
// src/discovery/filter-for.ts
|
|
1364
950
|
function classifyFilterForHint(typeInit) {
|
|
1365
|
-
if (
|
|
951
|
+
if (import_ts_morph4.Node.isStringLiteral(typeInit)) {
|
|
1366
952
|
switch (typeInit.getLiteralValue()) {
|
|
1367
953
|
case "string":
|
|
1368
954
|
return { kind: "string" };
|
|
@@ -1376,10 +962,10 @@ function classifyFilterForHint(typeInit) {
|
|
|
1376
962
|
return null;
|
|
1377
963
|
}
|
|
1378
964
|
}
|
|
1379
|
-
if (
|
|
965
|
+
if (import_ts_morph4.Node.isArrayLiteralExpression(typeInit)) {
|
|
1380
966
|
const values = [];
|
|
1381
967
|
for (const el of typeInit.getElements()) {
|
|
1382
|
-
if (!
|
|
968
|
+
if (!import_ts_morph4.Node.isStringLiteral(el)) return null;
|
|
1383
969
|
values.push(el.getLiteralValue());
|
|
1384
970
|
}
|
|
1385
971
|
if (values.length === 0) return null;
|
|
@@ -1414,11 +1000,11 @@ function extractFilterForHints(classDecl, project) {
|
|
|
1414
1000
|
if (!filterForDec) continue;
|
|
1415
1001
|
const args = filterForDec.getArguments();
|
|
1416
1002
|
const keyArg = args[0];
|
|
1417
|
-
const inputKey = keyArg &&
|
|
1003
|
+
const inputKey = keyArg && import_ts_morph4.Node.isStringLiteral(keyArg) ? keyArg.getLiteralValue() : method.getName();
|
|
1418
1004
|
const optsArg = args[1];
|
|
1419
|
-
if (optsArg &&
|
|
1005
|
+
if (optsArg && import_ts_morph4.Node.isObjectLiteralExpression(optsArg)) {
|
|
1420
1006
|
const typeProp = optsArg.getProperty("type");
|
|
1421
|
-
if (typeProp &&
|
|
1007
|
+
if (typeProp && import_ts_morph4.Node.isPropertyAssignment(typeProp)) {
|
|
1422
1008
|
const typeInit = typeProp.getInitializer();
|
|
1423
1009
|
if (typeInit) {
|
|
1424
1010
|
const classified = classifyFilterForHint(typeInit);
|
|
@@ -1441,14 +1027,14 @@ function extractApplyFilterInfo(method, sourceFile, project) {
|
|
|
1441
1027
|
const args = filterDecorator.getArguments();
|
|
1442
1028
|
if (args.length === 0) continue;
|
|
1443
1029
|
const filterClassArg = args[0];
|
|
1444
|
-
if (!filterClassArg || !
|
|
1030
|
+
if (!filterClassArg || !import_ts_morph4.Node.isIdentifier(filterClassArg)) continue;
|
|
1445
1031
|
let source = "query";
|
|
1446
1032
|
const optionsArg = args[1];
|
|
1447
|
-
if (optionsArg &&
|
|
1033
|
+
if (optionsArg && import_ts_morph4.Node.isObjectLiteralExpression(optionsArg)) {
|
|
1448
1034
|
const sourceProp = optionsArg.getProperty("source");
|
|
1449
|
-
if (sourceProp &&
|
|
1035
|
+
if (sourceProp && import_ts_morph4.Node.isPropertyAssignment(sourceProp)) {
|
|
1450
1036
|
const init = sourceProp.getInitializer();
|
|
1451
|
-
if (init &&
|
|
1037
|
+
if (init && import_ts_morph4.Node.isStringLiteral(init) && init.getLiteralValue() === "body") {
|
|
1452
1038
|
source = "body";
|
|
1453
1039
|
}
|
|
1454
1040
|
}
|
|
@@ -1511,22 +1097,22 @@ function resolveRelationEntity(prop, sourceFile, project) {
|
|
|
1511
1097
|
const args = dec.getArguments();
|
|
1512
1098
|
if (args.length === 0) continue;
|
|
1513
1099
|
const arg = args[0];
|
|
1514
|
-
if (
|
|
1100
|
+
if (import_ts_morph4.Node.isObjectLiteralExpression(arg)) {
|
|
1515
1101
|
const entityProp = arg.getProperty("entity");
|
|
1516
|
-
if (entityProp &&
|
|
1102
|
+
if (entityProp && import_ts_morph4.Node.isPropertyAssignment(entityProp)) {
|
|
1517
1103
|
const init = entityProp.getInitializer();
|
|
1518
|
-
if (init &&
|
|
1104
|
+
if (init && import_ts_morph4.Node.isArrowFunction(init)) {
|
|
1519
1105
|
const body = init.getBody();
|
|
1520
|
-
if (
|
|
1106
|
+
if (import_ts_morph4.Node.isIdentifier(body)) {
|
|
1521
1107
|
const resolved = findType(body.getText(), prop.getSourceFile(), project);
|
|
1522
1108
|
if (resolved?.kind === "class") return resolved.decl;
|
|
1523
1109
|
}
|
|
1524
1110
|
}
|
|
1525
1111
|
}
|
|
1526
1112
|
}
|
|
1527
|
-
if (
|
|
1113
|
+
if (import_ts_morph4.Node.isArrowFunction(arg)) {
|
|
1528
1114
|
const body = arg.getBody();
|
|
1529
|
-
if (
|
|
1115
|
+
if (import_ts_morph4.Node.isIdentifier(body)) {
|
|
1530
1116
|
const resolved = findType(body.getText(), prop.getSourceFile(), project);
|
|
1531
1117
|
if (resolved?.kind === "class") return resolved.decl;
|
|
1532
1118
|
}
|
|
@@ -1550,11 +1136,11 @@ function extractFilterableEntityFields(filterClass, project) {
|
|
|
1550
1136
|
const args = filterableDecorator.getArguments();
|
|
1551
1137
|
if (args.length === 0) return [];
|
|
1552
1138
|
const optionsArg = args[0];
|
|
1553
|
-
if (!
|
|
1139
|
+
if (!import_ts_morph4.Node.isObjectLiteralExpression(optionsArg)) return [];
|
|
1554
1140
|
const entityProp = optionsArg.getProperty("entity");
|
|
1555
|
-
if (!entityProp || !
|
|
1141
|
+
if (!entityProp || !import_ts_morph4.Node.isPropertyAssignment(entityProp)) return [];
|
|
1556
1142
|
const entityInit = entityProp.getInitializer();
|
|
1557
|
-
if (!entityInit || !
|
|
1143
|
+
if (!entityInit || !import_ts_morph4.Node.isIdentifier(entityInit)) return [];
|
|
1558
1144
|
const entityName = entityInit.getText();
|
|
1559
1145
|
const filterSourceFile = filterClass.getSourceFile();
|
|
1560
1146
|
const resolvedEntity = findType(entityName, filterSourceFile, project);
|
|
@@ -1570,17 +1156,17 @@ function extractFilterableEntityFields(filterClass, project) {
|
|
|
1570
1156
|
const relationsDecorator = filterClass.getDecorators().find((d) => d.getName() === "Relations");
|
|
1571
1157
|
if (relationsDecorator) {
|
|
1572
1158
|
const relArgs = relationsDecorator.getArguments();
|
|
1573
|
-
if (relArgs.length > 0 &&
|
|
1159
|
+
if (relArgs.length > 0 && import_ts_morph4.Node.isObjectLiteralExpression(relArgs[0])) {
|
|
1574
1160
|
for (const relProp of relArgs[0].getProperties()) {
|
|
1575
|
-
if (!
|
|
1161
|
+
if (!import_ts_morph4.Node.isPropertyAssignment(relProp)) continue;
|
|
1576
1162
|
const relInit = relProp.getInitializer();
|
|
1577
|
-
if (!relInit || !
|
|
1163
|
+
if (!relInit || !import_ts_morph4.Node.isObjectLiteralExpression(relInit)) continue;
|
|
1578
1164
|
const keysProp = relInit.getProperty("keys");
|
|
1579
|
-
if (!keysProp || !
|
|
1165
|
+
if (!keysProp || !import_ts_morph4.Node.isPropertyAssignment(keysProp)) continue;
|
|
1580
1166
|
const keysInit = keysProp.getInitializer();
|
|
1581
|
-
if (!keysInit || !
|
|
1167
|
+
if (!keysInit || !import_ts_morph4.Node.isArrayLiteralExpression(keysInit)) continue;
|
|
1582
1168
|
for (const el of keysInit.getElements()) {
|
|
1583
|
-
if (
|
|
1169
|
+
if (import_ts_morph4.Node.isStringLiteral(el)) {
|
|
1584
1170
|
fields.push(toFilterFieldType(el.getLiteralValue(), { kind: "unknown" }));
|
|
1585
1171
|
}
|
|
1586
1172
|
}
|
|
@@ -1590,267 +1176,65 @@ function extractFilterableEntityFields(filterClass, project) {
|
|
|
1590
1176
|
return fields;
|
|
1591
1177
|
}
|
|
1592
1178
|
|
|
1593
|
-
// src/discovery/
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
const prevCtx = setDiscoveryContext({
|
|
1623
|
-
projectRoot: cwd,
|
|
1624
|
-
tsconfigPaths: loadTsconfigPaths(tsconfigPath)
|
|
1625
|
-
});
|
|
1626
|
-
try {
|
|
1627
|
-
for (const sourceFile of project.getSourceFiles()) {
|
|
1628
|
-
routes.push(...extractFromSourceFile(sourceFile, project));
|
|
1629
|
-
}
|
|
1630
|
-
} finally {
|
|
1631
|
-
restoreDiscoveryContext(prevCtx);
|
|
1632
|
-
}
|
|
1633
|
-
return routes;
|
|
1634
|
-
}
|
|
1635
|
-
function zodAstToTs(node) {
|
|
1636
|
-
if (!import_ts_morph6.Node.isCallExpression(node)) return "unknown";
|
|
1637
|
-
const expr = node.getExpression();
|
|
1638
|
-
if (import_ts_morph6.Node.isPropertyAccessExpression(expr)) {
|
|
1639
|
-
const methodName = expr.getName();
|
|
1640
|
-
const receiver = expr.getExpression();
|
|
1641
|
-
if (methodName === "optional") {
|
|
1642
|
-
return `${zodAstToTs(receiver)} | undefined`;
|
|
1643
|
-
}
|
|
1644
|
-
if (methodName === "nullable") {
|
|
1645
|
-
return `${zodAstToTs(receiver)} | null`;
|
|
1646
|
-
}
|
|
1647
|
-
const args = node.getArguments();
|
|
1648
|
-
switch (methodName) {
|
|
1649
|
-
case "string":
|
|
1650
|
-
return "string";
|
|
1651
|
-
case "number":
|
|
1652
|
-
return "number";
|
|
1653
|
-
case "boolean":
|
|
1654
|
-
return "boolean";
|
|
1655
|
-
case "unknown":
|
|
1656
|
-
return "unknown";
|
|
1657
|
-
case "any":
|
|
1658
|
-
return "unknown";
|
|
1659
|
-
case "literal": {
|
|
1660
|
-
const lit = args[0];
|
|
1661
|
-
if (!lit) return "unknown";
|
|
1662
|
-
if (import_ts_morph6.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
1663
|
-
if (import_ts_morph6.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
1664
|
-
if (lit.getKind() === import_ts_morph6.SyntaxKind.TrueKeyword) return "true";
|
|
1665
|
-
if (lit.getKind() === import_ts_morph6.SyntaxKind.FalseKeyword) return "false";
|
|
1666
|
-
return "unknown";
|
|
1667
|
-
}
|
|
1668
|
-
case "enum": {
|
|
1669
|
-
const arrArg = args[0];
|
|
1670
|
-
if (!arrArg || !import_ts_morph6.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
1671
|
-
const members = arrArg.getElements().map(
|
|
1672
|
-
(el) => import_ts_morph6.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
1673
|
-
);
|
|
1674
|
-
return members.join(" | ");
|
|
1675
|
-
}
|
|
1676
|
-
case "array": {
|
|
1677
|
-
const inner = args[0];
|
|
1678
|
-
if (!inner) return "unknown";
|
|
1679
|
-
return `Array<${zodAstToTs(inner)}>`;
|
|
1680
|
-
}
|
|
1681
|
-
case "object": {
|
|
1682
|
-
const objArg = args[0];
|
|
1683
|
-
if (!objArg || !import_ts_morph6.Node.isObjectLiteralExpression(objArg)) return "unknown";
|
|
1684
|
-
const lines = [];
|
|
1685
|
-
for (const prop of objArg.getProperties()) {
|
|
1686
|
-
if (!import_ts_morph6.Node.isPropertyAssignment(prop)) continue;
|
|
1687
|
-
const key = prop.getName();
|
|
1688
|
-
const valNode = prop.getInitializer();
|
|
1689
|
-
if (!valNode) continue;
|
|
1690
|
-
const tsType = zodAstToTs(valNode);
|
|
1691
|
-
const isOpt = isOptionalChain(valNode);
|
|
1692
|
-
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
1693
|
-
}
|
|
1694
|
-
return `{ ${lines.join("; ")} }`;
|
|
1695
|
-
}
|
|
1696
|
-
case "union": {
|
|
1697
|
-
const arrArg = args[0];
|
|
1698
|
-
if (!arrArg || !import_ts_morph6.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
1699
|
-
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
1700
|
-
}
|
|
1701
|
-
case "record": {
|
|
1702
|
-
const valArg = args.length === 1 ? args[0] : args[1];
|
|
1703
|
-
if (!valArg) return "unknown";
|
|
1704
|
-
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
1705
|
-
}
|
|
1706
|
-
case "tuple": {
|
|
1707
|
-
const arrArg = args[0];
|
|
1708
|
-
if (!arrArg || !import_ts_morph6.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
1709
|
-
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
1710
|
-
}
|
|
1711
|
-
default:
|
|
1712
|
-
return "unknown";
|
|
1713
|
-
}
|
|
1714
|
-
}
|
|
1715
|
-
return "unknown";
|
|
1716
|
-
}
|
|
1717
|
-
function isOptionalChain(node) {
|
|
1718
|
-
if (!import_ts_morph6.Node.isCallExpression(node)) return false;
|
|
1719
|
-
const expr = node.getExpression();
|
|
1720
|
-
return import_ts_morph6.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
1721
|
-
}
|
|
1722
|
-
function decoratorStringArg(decoratorExpr) {
|
|
1723
|
-
if (!decoratorExpr) return void 0;
|
|
1724
|
-
if (import_ts_morph6.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
1725
|
-
if (import_ts_morph6.Node.isArrayLiteralExpression(decoratorExpr)) {
|
|
1726
|
-
const first = decoratorExpr.getElements()[0];
|
|
1727
|
-
if (first && import_ts_morph6.Node.isStringLiteral(first)) return first.getLiteralValue();
|
|
1728
|
-
}
|
|
1729
|
-
return void 0;
|
|
1730
|
-
}
|
|
1731
|
-
function parseDefineContractCall(callExpr) {
|
|
1732
|
-
if (!import_ts_morph6.Node.isCallExpression(callExpr)) return null;
|
|
1733
|
-
const callee = callExpr.getExpression();
|
|
1734
|
-
const calleeName = import_ts_morph6.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph6.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
1735
|
-
if (calleeName !== "defineContract") return null;
|
|
1736
|
-
const args = callExpr.getArguments();
|
|
1737
|
-
const optsArg = args[0];
|
|
1738
|
-
if (!optsArg || !import_ts_morph6.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
1739
|
-
let query = null;
|
|
1740
|
-
let body = null;
|
|
1741
|
-
let response = "unknown";
|
|
1742
|
-
let bodyZodText = null;
|
|
1743
|
-
let queryZodText = null;
|
|
1744
|
-
for (const prop of optsArg.getProperties()) {
|
|
1745
|
-
if (!import_ts_morph6.Node.isPropertyAssignment(prop)) continue;
|
|
1746
|
-
const propName = prop.getName();
|
|
1747
|
-
const val = prop.getInitializer();
|
|
1748
|
-
if (!val) continue;
|
|
1749
|
-
if (propName === "query") {
|
|
1750
|
-
query = zodAstToTs(val);
|
|
1751
|
-
queryZodText = val.getText();
|
|
1752
|
-
} else if (propName === "body") {
|
|
1753
|
-
body = zodAstToTs(val);
|
|
1754
|
-
bodyZodText = val.getText();
|
|
1755
|
-
} else if (propName === "response") {
|
|
1756
|
-
response = zodAstToTs(val);
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
return { query, body, response, bodyZodText, queryZodText };
|
|
1760
|
-
}
|
|
1761
|
-
function deriveClassSegment(className) {
|
|
1762
|
-
const noSuffix = className.replace(/Controller$/, "");
|
|
1763
|
-
if (!noSuffix) {
|
|
1764
|
-
throw new Error(
|
|
1765
|
-
`Controller class name "${className}" derives empty route segment after stripping "Controller". Add an @As(...) override at the class level.`
|
|
1766
|
-
);
|
|
1767
|
-
}
|
|
1768
|
-
return noSuffix.charAt(0).toLowerCase() + noSuffix.slice(1);
|
|
1769
|
-
}
|
|
1770
|
-
function resolveRouteName(className, methodName, classAs, methodAs) {
|
|
1771
|
-
const classPortion = classAs ?? deriveClassSegment(className);
|
|
1772
|
-
const methodPortion = methodAs ?? methodName;
|
|
1773
|
-
return `${classPortion}.${methodPortion}`;
|
|
1774
|
-
}
|
|
1775
|
-
function joinPaths(prefix, suffix) {
|
|
1776
|
-
if (!prefix && !suffix) return "/";
|
|
1777
|
-
if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
1778
|
-
if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
1779
|
-
const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
1780
|
-
const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
1781
|
-
const combined = p + s;
|
|
1782
|
-
return combined === "" ? "/" : combined;
|
|
1783
|
-
}
|
|
1784
|
-
function extractParams(path) {
|
|
1785
|
-
const matches = path.matchAll(/:(\w+)/g);
|
|
1786
|
-
return Array.from(matches).map((m) => ({ name: m[1], source: "path" }));
|
|
1787
|
-
}
|
|
1179
|
+
// src/discovery/dto-type-resolver.ts
|
|
1180
|
+
var WRAPPER_TYPES = {
|
|
1181
|
+
// MikroORM Ref/Reference/LoadedReference/IdentifiedReference are server-side
|
|
1182
|
+
// wrappers around related entities; the wire shape is just the referenced
|
|
1183
|
+
// entity. Unwrap to the type argument.
|
|
1184
|
+
Ref: "unwrap",
|
|
1185
|
+
Reference: "unwrap",
|
|
1186
|
+
LoadedReference: "unwrap",
|
|
1187
|
+
IdentifiedReference: "unwrap",
|
|
1188
|
+
// MikroORM Opt<T> is a marker, Loaded<T, ...> is a wrapper; both reduce to T.
|
|
1189
|
+
Opt: "unwrap",
|
|
1190
|
+
Loaded: "unwrap",
|
|
1191
|
+
// Promise<T> — unwrap
|
|
1192
|
+
Promise: "unwrap",
|
|
1193
|
+
// MikroORM Collection<T> serializes as an array of T on the wire.
|
|
1194
|
+
Collection: "arrayOf",
|
|
1195
|
+
// Array<T> generic form
|
|
1196
|
+
Array: "arrayOf"
|
|
1197
|
+
};
|
|
1198
|
+
var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
|
|
1199
|
+
"Record",
|
|
1200
|
+
"Omit",
|
|
1201
|
+
"Pick",
|
|
1202
|
+
"Partial",
|
|
1203
|
+
"Required",
|
|
1204
|
+
"Readonly",
|
|
1205
|
+
"Map",
|
|
1206
|
+
"Set"
|
|
1207
|
+
]);
|
|
1788
1208
|
function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
1789
1209
|
if (depth <= 0) return "unknown";
|
|
1790
|
-
if (
|
|
1210
|
+
if (import_ts_morph5.Node.isArrayTypeNode(typeNode)) {
|
|
1791
1211
|
const elementType = typeNode.getElementTypeNode();
|
|
1792
1212
|
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth)}>`;
|
|
1793
1213
|
}
|
|
1794
|
-
if (
|
|
1214
|
+
if (import_ts_morph5.Node.isUnionTypeNode(typeNode)) {
|
|
1795
1215
|
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" | ");
|
|
1796
1216
|
}
|
|
1797
|
-
if (
|
|
1217
|
+
if (import_ts_morph5.Node.isIntersectionTypeNode(typeNode)) {
|
|
1798
1218
|
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" & ");
|
|
1799
1219
|
}
|
|
1800
|
-
if (
|
|
1220
|
+
if (import_ts_morph5.Node.isParenthesizedTypeNode(typeNode)) {
|
|
1801
1221
|
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth)})`;
|
|
1802
1222
|
}
|
|
1803
|
-
if (
|
|
1223
|
+
if (import_ts_morph5.Node.isTypeReference(typeNode)) {
|
|
1804
1224
|
const typeName = typeNode.getTypeName();
|
|
1805
|
-
const name =
|
|
1225
|
+
const name = import_ts_morph5.Node.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
|
|
1806
1226
|
if (name === "string" || name === "number" || name === "boolean") return name;
|
|
1807
1227
|
if (name === "Date") return "string";
|
|
1808
1228
|
if (name === "unknown" || name === "any" || name === "void") return "unknown";
|
|
1809
1229
|
if (name === "StreamableFile" || name === "Observable" || name === "ReadableStream")
|
|
1810
1230
|
return "unknown";
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
1815
|
-
return resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
|
|
1816
|
-
}
|
|
1817
|
-
return "unknown";
|
|
1231
|
+
const wrapperMode = WRAPPER_TYPES[name];
|
|
1232
|
+
if (wrapperMode) {
|
|
1233
|
+
return unwrapFirstTypeArg(typeNode, sourceFile, project, depth, wrapperMode);
|
|
1818
1234
|
}
|
|
1819
|
-
if (name
|
|
1820
|
-
const typeArgs = typeNode.getTypeArguments();
|
|
1821
|
-
const firstTypeArg = typeArgs[0];
|
|
1822
|
-
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
1823
|
-
return `Array<${resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth)}>`;
|
|
1824
|
-
}
|
|
1825
|
-
return "Array<unknown>";
|
|
1826
|
-
}
|
|
1827
|
-
if (name === "Opt" || name === "Loaded") {
|
|
1828
|
-
const typeArgs = typeNode.getTypeArguments();
|
|
1829
|
-
const firstTypeArg = typeArgs[0];
|
|
1830
|
-
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
1831
|
-
return resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
|
|
1832
|
-
}
|
|
1833
|
-
return "unknown";
|
|
1834
|
-
}
|
|
1835
|
-
if (name === "Array") {
|
|
1836
|
-
const typeArgs = typeNode.getTypeArguments();
|
|
1837
|
-
const firstTypeArg = typeArgs[0];
|
|
1838
|
-
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
1839
|
-
return `Array<${resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth)}>`;
|
|
1840
|
-
}
|
|
1841
|
-
return "Array<unknown>";
|
|
1842
|
-
}
|
|
1843
|
-
if (["Record", "Omit", "Pick", "Partial", "Required", "Readonly", "Map", "Set"].includes(name)) {
|
|
1235
|
+
if (PASSTHROUGH_UTILITY.has(name)) {
|
|
1844
1236
|
return typeNode.getText();
|
|
1845
1237
|
}
|
|
1846
|
-
if (name === "Promise") {
|
|
1847
|
-
const typeArgs = typeNode.getTypeArguments();
|
|
1848
|
-
const firstTypeArg = typeArgs[0];
|
|
1849
|
-
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
1850
|
-
return resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
|
|
1851
|
-
}
|
|
1852
|
-
return "unknown";
|
|
1853
|
-
}
|
|
1854
1238
|
const resolved = findType(name, sourceFile, project);
|
|
1855
1239
|
if (resolved) {
|
|
1856
1240
|
return expandTypeDecl(resolved, project, depth - 1);
|
|
@@ -1859,13 +1243,22 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
|
1859
1243
|
return "unknown";
|
|
1860
1244
|
}
|
|
1861
1245
|
const kind = typeNode.getKind();
|
|
1862
|
-
if (kind ===
|
|
1863
|
-
if (kind ===
|
|
1864
|
-
if (kind ===
|
|
1865
|
-
if (kind ===
|
|
1866
|
-
if (kind ===
|
|
1246
|
+
if (kind === import_ts_morph5.SyntaxKind.StringKeyword) return "string";
|
|
1247
|
+
if (kind === import_ts_morph5.SyntaxKind.NumberKeyword) return "number";
|
|
1248
|
+
if (kind === import_ts_morph5.SyntaxKind.BooleanKeyword) return "boolean";
|
|
1249
|
+
if (kind === import_ts_morph5.SyntaxKind.UnknownKeyword) return "unknown";
|
|
1250
|
+
if (kind === import_ts_morph5.SyntaxKind.AnyKeyword) return "unknown";
|
|
1867
1251
|
return typeNode.getText();
|
|
1868
1252
|
}
|
|
1253
|
+
function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode) {
|
|
1254
|
+
const typeArgs = typeNode.getTypeArguments();
|
|
1255
|
+
const firstTypeArg = typeArgs[0];
|
|
1256
|
+
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
1257
|
+
const inner = resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
|
|
1258
|
+
return mode === "arrayOf" ? `Array<${inner}>` : inner;
|
|
1259
|
+
}
|
|
1260
|
+
return mode === "arrayOf" ? "Array<unknown>" : "unknown";
|
|
1261
|
+
}
|
|
1869
1262
|
function expandTypeDecl(result, project, depth) {
|
|
1870
1263
|
if (depth < 0) return "unknown";
|
|
1871
1264
|
switch (result.kind) {
|
|
@@ -1931,7 +1324,7 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
1931
1324
|
const paramArgs = paramDecorator.getArguments();
|
|
1932
1325
|
if (paramArgs.length === 0) continue;
|
|
1933
1326
|
const nameArg = paramArgs[0];
|
|
1934
|
-
if (!
|
|
1327
|
+
if (!import_ts_morph5.Node.isStringLiteral(nameArg)) continue;
|
|
1935
1328
|
const paramName = nameArg.getLiteralValue();
|
|
1936
1329
|
const typeNode = param.getTypeNode();
|
|
1937
1330
|
const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
@@ -1944,13 +1337,13 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
1944
1337
|
if (apiResponseDecorator) {
|
|
1945
1338
|
const args = apiResponseDecorator.getArguments();
|
|
1946
1339
|
const optsArg = args[0];
|
|
1947
|
-
if (optsArg &&
|
|
1340
|
+
if (optsArg && import_ts_morph5.Node.isObjectLiteralExpression(optsArg)) {
|
|
1948
1341
|
for (const prop of optsArg.getProperties()) {
|
|
1949
|
-
if (!
|
|
1342
|
+
if (!import_ts_morph5.Node.isPropertyAssignment(prop)) continue;
|
|
1950
1343
|
if (prop.getName() !== "type") continue;
|
|
1951
1344
|
const val = prop.getInitializer();
|
|
1952
1345
|
if (!val) continue;
|
|
1953
|
-
if (
|
|
1346
|
+
if (import_ts_morph5.Node.isArrayLiteralExpression(val)) {
|
|
1954
1347
|
const elements = val.getElements();
|
|
1955
1348
|
const firstEl = elements[0];
|
|
1956
1349
|
if (elements.length > 0 && firstEl !== void 0) {
|
|
@@ -1970,7 +1363,7 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
1970
1363
|
return "unknown";
|
|
1971
1364
|
}
|
|
1972
1365
|
function resolveIdentifierToClassType(node, sourceFile, project, depth) {
|
|
1973
|
-
if (!
|
|
1366
|
+
if (!import_ts_morph5.Node.isIdentifier(node)) return "unknown";
|
|
1974
1367
|
const name = node.getText();
|
|
1975
1368
|
const resolved = findType(name, sourceFile, project);
|
|
1976
1369
|
if (resolved) {
|
|
@@ -2017,11 +1410,11 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
2017
1410
|
if (apiResp) {
|
|
2018
1411
|
const args = apiResp.getArguments();
|
|
2019
1412
|
const optsArg = args[0];
|
|
2020
|
-
if (optsArg &&
|
|
1413
|
+
if (optsArg && import_ts_morph5.Node.isObjectLiteralExpression(optsArg)) {
|
|
2021
1414
|
for (const prop of optsArg.getProperties()) {
|
|
2022
|
-
if (
|
|
1415
|
+
if (import_ts_morph5.Node.isPropertyAssignment(prop) && prop.getName() === "type") {
|
|
2023
1416
|
const val = prop.getInitializer();
|
|
2024
|
-
if (val &&
|
|
1417
|
+
if (val && import_ts_morph5.Node.isIdentifier(val)) {
|
|
2025
1418
|
const name = val.getText();
|
|
2026
1419
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
2027
1420
|
if (localDecl?.isExported()) {
|
|
@@ -2038,27 +1431,18 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
2038
1431
|
}
|
|
2039
1432
|
}
|
|
2040
1433
|
}
|
|
2041
|
-
let bodyZodText = null;
|
|
2042
|
-
let queryZodText = null;
|
|
2043
1434
|
let bodySchema = null;
|
|
2044
1435
|
let querySchema = null;
|
|
2045
|
-
const formNested = {};
|
|
2046
1436
|
const formWarnings = [];
|
|
2047
1437
|
const bodyClass = resolveParamClass(method, "Body", sourceFile, project);
|
|
2048
1438
|
if (bodyClass) {
|
|
2049
|
-
const result = extractZodFromDto(bodyClass.decl, bodyClass.file, project);
|
|
2050
|
-
bodyZodText = result.schemaText;
|
|
2051
|
-
for (const [k, v] of result.namedNestedSchemas) formNested[k] = v;
|
|
2052
|
-
formWarnings.push(...result.warnings);
|
|
2053
1439
|
bodySchema = extractSchemaFromDto(bodyClass.decl, bodyClass.file, project);
|
|
1440
|
+
formWarnings.push(...bodySchema.warnings);
|
|
2054
1441
|
}
|
|
2055
1442
|
const queryClass = resolveParamClass(method, "Query", sourceFile, project);
|
|
2056
1443
|
if (queryClass) {
|
|
2057
|
-
const result = extractZodFromDto(queryClass.decl, queryClass.file, project);
|
|
2058
|
-
queryZodText = result.schemaText;
|
|
2059
|
-
for (const [k, v] of result.namedNestedSchemas) formNested[k] = v;
|
|
2060
|
-
formWarnings.push(...result.warnings);
|
|
2061
1444
|
querySchema = extractSchemaFromDto(queryClass.decl, queryClass.file, project);
|
|
1445
|
+
formWarnings.push(...querySchema.warnings);
|
|
2062
1446
|
}
|
|
2063
1447
|
return {
|
|
2064
1448
|
query,
|
|
@@ -2071,9 +1455,6 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
2071
1455
|
filterFields: filterInfo?.fieldNames ?? null,
|
|
2072
1456
|
filterFieldTypes: filterInfo?.fieldTypes ?? null,
|
|
2073
1457
|
filterSource: filterInfo?.source ?? null,
|
|
2074
|
-
bodyZodText,
|
|
2075
|
-
queryZodText,
|
|
2076
|
-
formNestedSchemas: Object.keys(formNested).length > 0 ? formNested : null,
|
|
2077
1458
|
formWarnings,
|
|
2078
1459
|
bodySchema,
|
|
2079
1460
|
querySchema
|
|
@@ -2093,6 +1474,201 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
|
2093
1474
|
}
|
|
2094
1475
|
return null;
|
|
2095
1476
|
}
|
|
1477
|
+
|
|
1478
|
+
// src/discovery/zod-ast-to-ts.ts
|
|
1479
|
+
var import_ts_morph6 = require("ts-morph");
|
|
1480
|
+
function zodAstToTs(node) {
|
|
1481
|
+
if (!import_ts_morph6.Node.isCallExpression(node)) return "unknown";
|
|
1482
|
+
const expr = node.getExpression();
|
|
1483
|
+
if (import_ts_morph6.Node.isPropertyAccessExpression(expr)) {
|
|
1484
|
+
const methodName = expr.getName();
|
|
1485
|
+
const receiver = expr.getExpression();
|
|
1486
|
+
if (methodName === "optional") {
|
|
1487
|
+
return `${zodAstToTs(receiver)} | undefined`;
|
|
1488
|
+
}
|
|
1489
|
+
if (methodName === "nullable") {
|
|
1490
|
+
return `${zodAstToTs(receiver)} | null`;
|
|
1491
|
+
}
|
|
1492
|
+
const args = node.getArguments();
|
|
1493
|
+
switch (methodName) {
|
|
1494
|
+
case "string":
|
|
1495
|
+
return "string";
|
|
1496
|
+
case "number":
|
|
1497
|
+
return "number";
|
|
1498
|
+
case "boolean":
|
|
1499
|
+
return "boolean";
|
|
1500
|
+
case "unknown":
|
|
1501
|
+
return "unknown";
|
|
1502
|
+
case "any":
|
|
1503
|
+
return "unknown";
|
|
1504
|
+
case "literal": {
|
|
1505
|
+
const lit = args[0];
|
|
1506
|
+
if (!lit) return "unknown";
|
|
1507
|
+
if (import_ts_morph6.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
1508
|
+
if (import_ts_morph6.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
1509
|
+
if (lit.getKind() === import_ts_morph6.SyntaxKind.TrueKeyword) return "true";
|
|
1510
|
+
if (lit.getKind() === import_ts_morph6.SyntaxKind.FalseKeyword) return "false";
|
|
1511
|
+
return "unknown";
|
|
1512
|
+
}
|
|
1513
|
+
case "enum": {
|
|
1514
|
+
const arrArg = args[0];
|
|
1515
|
+
if (!arrArg || !import_ts_morph6.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
1516
|
+
const members = arrArg.getElements().map(
|
|
1517
|
+
(el) => import_ts_morph6.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
1518
|
+
);
|
|
1519
|
+
return members.join(" | ");
|
|
1520
|
+
}
|
|
1521
|
+
case "array": {
|
|
1522
|
+
const inner = args[0];
|
|
1523
|
+
if (!inner) return "unknown";
|
|
1524
|
+
return `Array<${zodAstToTs(inner)}>`;
|
|
1525
|
+
}
|
|
1526
|
+
case "object": {
|
|
1527
|
+
const objArg = args[0];
|
|
1528
|
+
if (!objArg || !import_ts_morph6.Node.isObjectLiteralExpression(objArg)) return "unknown";
|
|
1529
|
+
const lines = [];
|
|
1530
|
+
for (const prop of objArg.getProperties()) {
|
|
1531
|
+
if (!import_ts_morph6.Node.isPropertyAssignment(prop)) continue;
|
|
1532
|
+
const key = prop.getName();
|
|
1533
|
+
const valNode = prop.getInitializer();
|
|
1534
|
+
if (!valNode) continue;
|
|
1535
|
+
const tsType = zodAstToTs(valNode);
|
|
1536
|
+
const isOpt = isOptionalChain(valNode);
|
|
1537
|
+
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
1538
|
+
}
|
|
1539
|
+
return `{ ${lines.join("; ")} }`;
|
|
1540
|
+
}
|
|
1541
|
+
case "union": {
|
|
1542
|
+
const arrArg = args[0];
|
|
1543
|
+
if (!arrArg || !import_ts_morph6.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
1544
|
+
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
1545
|
+
}
|
|
1546
|
+
case "record": {
|
|
1547
|
+
const valArg = args.length === 1 ? args[0] : args[1];
|
|
1548
|
+
if (!valArg) return "unknown";
|
|
1549
|
+
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
1550
|
+
}
|
|
1551
|
+
case "tuple": {
|
|
1552
|
+
const arrArg = args[0];
|
|
1553
|
+
if (!arrArg || !import_ts_morph6.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
1554
|
+
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
1555
|
+
}
|
|
1556
|
+
default:
|
|
1557
|
+
return "unknown";
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
return "unknown";
|
|
1561
|
+
}
|
|
1562
|
+
function isOptionalChain(node) {
|
|
1563
|
+
if (!import_ts_morph6.Node.isCallExpression(node)) return false;
|
|
1564
|
+
const expr = node.getExpression();
|
|
1565
|
+
return import_ts_morph6.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
1566
|
+
}
|
|
1567
|
+
function parseDefineContractCall(callExpr) {
|
|
1568
|
+
if (!import_ts_morph6.Node.isCallExpression(callExpr)) return null;
|
|
1569
|
+
const callee = callExpr.getExpression();
|
|
1570
|
+
const calleeName = import_ts_morph6.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph6.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
1571
|
+
if (calleeName !== "defineContract") return null;
|
|
1572
|
+
const args = callExpr.getArguments();
|
|
1573
|
+
const optsArg = args[0];
|
|
1574
|
+
if (!optsArg || !import_ts_morph6.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
1575
|
+
let query = null;
|
|
1576
|
+
let body = null;
|
|
1577
|
+
let response = "unknown";
|
|
1578
|
+
let bodyZodText = null;
|
|
1579
|
+
let queryZodText = null;
|
|
1580
|
+
for (const prop of optsArg.getProperties()) {
|
|
1581
|
+
if (!import_ts_morph6.Node.isPropertyAssignment(prop)) continue;
|
|
1582
|
+
const propName = prop.getName();
|
|
1583
|
+
const val = prop.getInitializer();
|
|
1584
|
+
if (!val) continue;
|
|
1585
|
+
if (propName === "query") {
|
|
1586
|
+
query = zodAstToTs(val);
|
|
1587
|
+
queryZodText = val.getText();
|
|
1588
|
+
} else if (propName === "body") {
|
|
1589
|
+
body = zodAstToTs(val);
|
|
1590
|
+
bodyZodText = val.getText();
|
|
1591
|
+
} else if (propName === "response") {
|
|
1592
|
+
response = zodAstToTs(val);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
return { query, body, response, bodyZodText, queryZodText };
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// src/discovery/contracts-fast.ts
|
|
1599
|
+
async function discoverContractsFast(opts) {
|
|
1600
|
+
const { cwd, glob, tsconfig } = opts;
|
|
1601
|
+
const tsconfigPath = tsconfig ? (0, import_node_path3.resolve)(tsconfig) : (0, import_node_path3.join)(cwd, "tsconfig.json");
|
|
1602
|
+
let project;
|
|
1603
|
+
try {
|
|
1604
|
+
project = new import_ts_morph7.Project({
|
|
1605
|
+
tsConfigFilePath: tsconfigPath,
|
|
1606
|
+
skipAddingFilesFromTsConfig: true,
|
|
1607
|
+
skipLoadingLibFiles: true,
|
|
1608
|
+
skipFileDependencyResolution: true
|
|
1609
|
+
});
|
|
1610
|
+
} catch {
|
|
1611
|
+
project = new import_ts_morph7.Project({
|
|
1612
|
+
skipAddingFilesFromTsConfig: true,
|
|
1613
|
+
skipLoadingLibFiles: true,
|
|
1614
|
+
skipFileDependencyResolution: true,
|
|
1615
|
+
compilerOptions: {
|
|
1616
|
+
allowJs: true,
|
|
1617
|
+
resolveJsonModule: false,
|
|
1618
|
+
strict: false
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
const files = await (0, import_fast_glob.default)(glob, { cwd, absolute: true, onlyFiles: true });
|
|
1623
|
+
for (const f of files) {
|
|
1624
|
+
project.addSourceFileAtPath(f);
|
|
1625
|
+
}
|
|
1626
|
+
const routes = [];
|
|
1627
|
+
setDiscoveryContext(project, {
|
|
1628
|
+
projectRoot: cwd,
|
|
1629
|
+
tsconfigPaths: loadTsconfigPaths(tsconfigPath)
|
|
1630
|
+
});
|
|
1631
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
1632
|
+
routes.push(...extractFromSourceFile(sourceFile, project));
|
|
1633
|
+
}
|
|
1634
|
+
return routes;
|
|
1635
|
+
}
|
|
1636
|
+
function decoratorStringArg(decoratorExpr) {
|
|
1637
|
+
if (!decoratorExpr) return void 0;
|
|
1638
|
+
if (import_ts_morph7.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
1639
|
+
if (import_ts_morph7.Node.isArrayLiteralExpression(decoratorExpr)) {
|
|
1640
|
+
const first = decoratorExpr.getElements()[0];
|
|
1641
|
+
if (first && import_ts_morph7.Node.isStringLiteral(first)) return first.getLiteralValue();
|
|
1642
|
+
}
|
|
1643
|
+
return void 0;
|
|
1644
|
+
}
|
|
1645
|
+
function deriveClassSegment(className) {
|
|
1646
|
+
const noSuffix = className.replace(/Controller$/, "");
|
|
1647
|
+
if (!noSuffix) {
|
|
1648
|
+
throw new Error(
|
|
1649
|
+
`Controller class name "${className}" derives empty route segment after stripping "Controller". Add an @As(...) override at the class level.`
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1652
|
+
return noSuffix.charAt(0).toLowerCase() + noSuffix.slice(1);
|
|
1653
|
+
}
|
|
1654
|
+
function resolveRouteName(className, methodName, classAs, methodAs) {
|
|
1655
|
+
const classPortion = classAs ?? deriveClassSegment(className);
|
|
1656
|
+
const methodPortion = methodAs ?? methodName;
|
|
1657
|
+
return `${classPortion}.${methodPortion}`;
|
|
1658
|
+
}
|
|
1659
|
+
function joinPaths(prefix, suffix) {
|
|
1660
|
+
if (!prefix && !suffix) return "/";
|
|
1661
|
+
if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
1662
|
+
if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
1663
|
+
const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
1664
|
+
const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
1665
|
+
const combined = p + s;
|
|
1666
|
+
return combined === "" ? "/" : combined;
|
|
1667
|
+
}
|
|
1668
|
+
function extractParams(path) {
|
|
1669
|
+
const matches = path.matchAll(/:(\w+)/g);
|
|
1670
|
+
return Array.from(matches).map((m) => ({ name: m[1], source: "path" }));
|
|
1671
|
+
}
|
|
2096
1672
|
var HTTP_METHOD_DECORATORS = {
|
|
2097
1673
|
Get: "GET",
|
|
2098
1674
|
Post: "POST",
|
|
@@ -2103,176 +1679,186 @@ var HTTP_METHOD_DECORATORS = {
|
|
|
2103
1679
|
Head: "HEAD",
|
|
2104
1680
|
All: "ALL"
|
|
2105
1681
|
};
|
|
1682
|
+
function resolveVerb(method) {
|
|
1683
|
+
for (const [decoratorName, verb] of Object.entries(HTTP_METHOD_DECORATORS)) {
|
|
1684
|
+
const httpDecorator = method.getDecorator(decoratorName);
|
|
1685
|
+
if (httpDecorator) {
|
|
1686
|
+
const httpArgs = httpDecorator.getArguments();
|
|
1687
|
+
const pathArg = httpArgs[0];
|
|
1688
|
+
return { httpMethod: verb, handlerPath: decoratorStringArg(pathArg) ?? "" };
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
return null;
|
|
1692
|
+
}
|
|
1693
|
+
function readAsDecorator(node, label) {
|
|
1694
|
+
const asDecorator = node.getDecorator("As");
|
|
1695
|
+
if (!asDecorator) return void 0;
|
|
1696
|
+
const asName = decoratorStringArg(asDecorator.getArguments()[0]);
|
|
1697
|
+
if (!asName) {
|
|
1698
|
+
throw new Error(`@As decorator on ${label} must have a non-empty string argument.`);
|
|
1699
|
+
}
|
|
1700
|
+
return asName;
|
|
1701
|
+
}
|
|
1702
|
+
function buildRoute(args) {
|
|
1703
|
+
const {
|
|
1704
|
+
className,
|
|
1705
|
+
methodName,
|
|
1706
|
+
resolvedMethod,
|
|
1707
|
+
combinedPath,
|
|
1708
|
+
classAs,
|
|
1709
|
+
methodAs,
|
|
1710
|
+
sourceFile,
|
|
1711
|
+
seenNames,
|
|
1712
|
+
contractSource
|
|
1713
|
+
} = args;
|
|
1714
|
+
const routeName = resolveRouteName(className, methodName, classAs, methodAs);
|
|
1715
|
+
const qualifiedRef = `${className}.${methodName}`;
|
|
1716
|
+
const existing = seenNames.get(routeName);
|
|
1717
|
+
if (existing !== void 0) {
|
|
1718
|
+
throw new Error(
|
|
1719
|
+
`Route name collision: "${routeName}" is used by both "${existing}" and "${qualifiedRef}". Use @As(...) to give one of them a unique name.`
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1722
|
+
seenNames.set(routeName, qualifiedRef);
|
|
1723
|
+
return {
|
|
1724
|
+
method: resolvedMethod,
|
|
1725
|
+
path: combinedPath,
|
|
1726
|
+
name: routeName,
|
|
1727
|
+
params: extractParams(combinedPath),
|
|
1728
|
+
controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
|
|
1729
|
+
contract: { contractSource }
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
function extractContractRoute(args) {
|
|
1733
|
+
const { cls, method, applyContractDecorator, verb, prefix, className, sourceFile, seenNames } = args;
|
|
1734
|
+
const firstDecoratorArg = applyContractDecorator.getArguments()[0];
|
|
1735
|
+
if (!firstDecoratorArg) return null;
|
|
1736
|
+
let contractDef = null;
|
|
1737
|
+
let bodyZodRef = null;
|
|
1738
|
+
let queryZodRef = null;
|
|
1739
|
+
if (import_ts_morph7.Node.isCallExpression(firstDecoratorArg)) {
|
|
1740
|
+
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
1741
|
+
} else if (import_ts_morph7.Node.isIdentifier(firstDecoratorArg)) {
|
|
1742
|
+
const identName = firstDecoratorArg.getText();
|
|
1743
|
+
const varDecl = sourceFile.getVariableDeclaration(identName);
|
|
1744
|
+
if (!varDecl) {
|
|
1745
|
+
console.warn(
|
|
1746
|
+
`[nestjs-codegen/fast] Cannot resolve '${identName}' in ${sourceFile.getFilePath()} (cross-file imports are out-of-scope for v1) \u2014 skipping`
|
|
1747
|
+
);
|
|
1748
|
+
return null;
|
|
1749
|
+
}
|
|
1750
|
+
const initializer = varDecl.getInitializer();
|
|
1751
|
+
if (!initializer) return null;
|
|
1752
|
+
contractDef = parseDefineContractCall(initializer);
|
|
1753
|
+
if (contractDef && varDecl.isExported()) {
|
|
1754
|
+
const filePath = sourceFile.getFilePath();
|
|
1755
|
+
if (contractDef.body !== null) {
|
|
1756
|
+
bodyZodRef = { name: `${identName}.body`, filePath };
|
|
1757
|
+
}
|
|
1758
|
+
if (contractDef.query !== null) {
|
|
1759
|
+
queryZodRef = { name: `${identName}.query`, filePath };
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
} else {
|
|
1763
|
+
console.warn(
|
|
1764
|
+
`[nestjs-codegen/fast] @ApplyContract arg is not an identifier or call expression in ${sourceFile.getFilePath()} \u2014 skipping`
|
|
1765
|
+
);
|
|
1766
|
+
return null;
|
|
1767
|
+
}
|
|
1768
|
+
if (!contractDef) return null;
|
|
1769
|
+
if (!verb) return null;
|
|
1770
|
+
const resolvedPath = joinPaths(prefix, verb.handlerPath);
|
|
1771
|
+
const methodName = method.getName();
|
|
1772
|
+
const classAs = readAsDecorator(cls, `class ${className}`);
|
|
1773
|
+
const methodAs = readAsDecorator(method, `${className}.${methodName}`);
|
|
1774
|
+
return buildRoute({
|
|
1775
|
+
className,
|
|
1776
|
+
methodName,
|
|
1777
|
+
resolvedMethod: verb.httpMethod,
|
|
1778
|
+
combinedPath: resolvedPath,
|
|
1779
|
+
classAs,
|
|
1780
|
+
methodAs,
|
|
1781
|
+
sourceFile,
|
|
1782
|
+
seenNames,
|
|
1783
|
+
contractSource: {
|
|
1784
|
+
query: contractDef.query,
|
|
1785
|
+
body: contractDef.body,
|
|
1786
|
+
response: contractDef.response,
|
|
1787
|
+
// Path A: capture both the importable ref and the raw text. The emitter
|
|
1788
|
+
// prefers inlining the text (client-safe — re-exporting from a controller
|
|
1789
|
+
// would drag server-only deps into the client bundle).
|
|
1790
|
+
bodyZodRef,
|
|
1791
|
+
bodyZodText: contractDef.bodyZodText,
|
|
1792
|
+
queryZodRef,
|
|
1793
|
+
queryZodText: contractDef.queryZodText
|
|
1794
|
+
}
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
function extractDtoRoute(args) {
|
|
1798
|
+
const { cls, method, verb, prefix, className, sourceFile, project, seenNames } = args;
|
|
1799
|
+
if (!verb) return null;
|
|
1800
|
+
const combined = joinPaths(prefix, verb.handlerPath);
|
|
1801
|
+
const methodName = method.getName();
|
|
1802
|
+
const classAs = readAsDecorator(cls, `class ${className}`);
|
|
1803
|
+
const methodAs = readAsDecorator(method, `${className}.${methodName}`);
|
|
1804
|
+
const dtoContract = extractDtoContract(method, sourceFile, project);
|
|
1805
|
+
return buildRoute({
|
|
1806
|
+
className,
|
|
1807
|
+
methodName,
|
|
1808
|
+
resolvedMethod: verb.httpMethod,
|
|
1809
|
+
combinedPath: combined,
|
|
1810
|
+
classAs,
|
|
1811
|
+
methodAs,
|
|
1812
|
+
sourceFile,
|
|
1813
|
+
seenNames,
|
|
1814
|
+
contractSource: {
|
|
1815
|
+
query: dtoContract?.query ?? null,
|
|
1816
|
+
body: dtoContract?.body ?? null,
|
|
1817
|
+
response: dtoContract?.response ?? "unknown",
|
|
1818
|
+
queryRef: dtoContract?.queryRef ?? null,
|
|
1819
|
+
bodyRef: dtoContract?.bodyRef ?? null,
|
|
1820
|
+
responseRef: dtoContract?.responseRef ?? null,
|
|
1821
|
+
filterFields: dtoContract?.filterFields ?? null,
|
|
1822
|
+
filterFieldTypes: dtoContract?.filterFieldTypes ?? null,
|
|
1823
|
+
filterSource: dtoContract?.filterSource ?? null,
|
|
1824
|
+
formWarnings: dtoContract?.formWarnings ?? [],
|
|
1825
|
+
bodySchema: dtoContract?.bodySchema ?? null,
|
|
1826
|
+
querySchema: dtoContract?.querySchema ?? null
|
|
1827
|
+
}
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
2106
1830
|
function extractFromSourceFile(sourceFile, project) {
|
|
2107
1831
|
const routes = [];
|
|
2108
1832
|
const seenNames = /* @__PURE__ */ new Map();
|
|
2109
|
-
const
|
|
2110
|
-
for (const cls of classes) {
|
|
1833
|
+
for (const cls of sourceFile.getClasses()) {
|
|
2111
1834
|
const controllerDecorator = cls.getDecorator("Controller");
|
|
2112
1835
|
if (!controllerDecorator) continue;
|
|
2113
|
-
const
|
|
2114
|
-
const
|
|
2115
|
-
const prefix = decoratorStringArg(firstArg3) ?? "";
|
|
1836
|
+
const firstArg2 = controllerDecorator.getArguments()[0];
|
|
1837
|
+
const prefix = decoratorStringArg(firstArg2) ?? "";
|
|
2116
1838
|
const className = cls.getName() ?? "Unknown";
|
|
2117
1839
|
for (const method of cls.getMethods()) {
|
|
2118
|
-
|
|
2119
|
-
let handlerPath = "";
|
|
2120
|
-
for (const [decoratorName, verb] of Object.entries(HTTP_METHOD_DECORATORS)) {
|
|
2121
|
-
const httpDecorator = method.getDecorator(decoratorName);
|
|
2122
|
-
if (httpDecorator) {
|
|
2123
|
-
httpMethod = verb;
|
|
2124
|
-
const httpArgs = httpDecorator.getArguments();
|
|
2125
|
-
const pathArg = httpArgs[0];
|
|
2126
|
-
handlerPath = decoratorStringArg(pathArg) ?? "";
|
|
2127
|
-
break;
|
|
2128
|
-
}
|
|
2129
|
-
}
|
|
1840
|
+
const verb = resolveVerb(method);
|
|
2130
1841
|
const applyContractDecorator = method.getDecorator("ApplyContract");
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
contractDef = parseDefineContractCall(initializer);
|
|
2152
|
-
if (contractDef && varDecl.isExported()) {
|
|
2153
|
-
const filePath = sourceFile.getFilePath();
|
|
2154
|
-
if (contractDef.body !== null) {
|
|
2155
|
-
bodyZodRef = { name: `${identName}.body`, filePath };
|
|
2156
|
-
}
|
|
2157
|
-
if (contractDef.query !== null) {
|
|
2158
|
-
queryZodRef = { name: `${identName}.query`, filePath };
|
|
2159
|
-
}
|
|
2160
|
-
}
|
|
2161
|
-
} else {
|
|
2162
|
-
console.warn(
|
|
2163
|
-
`[nestjs-codegen/fast] @ApplyContract arg is not an identifier or call expression in ${sourceFile.getFilePath()} \u2014 skipping`
|
|
2164
|
-
);
|
|
2165
|
-
continue;
|
|
2166
|
-
}
|
|
2167
|
-
if (!contractDef) continue;
|
|
2168
|
-
if (!httpMethod) continue;
|
|
2169
|
-
const resolvedMethod = httpMethod;
|
|
2170
|
-
const resolvedPath = joinPaths(prefix, handlerPath);
|
|
2171
|
-
const combined = resolvedPath;
|
|
2172
|
-
const params = extractParams(combined);
|
|
2173
|
-
const methodName = method.getName();
|
|
2174
|
-
const classAsDecorator = cls.getDecorator("As");
|
|
2175
|
-
let classAs;
|
|
2176
|
-
if (classAsDecorator) {
|
|
2177
|
-
const classAsArgs = classAsDecorator.getArguments();
|
|
2178
|
-
const classAsName = decoratorStringArg(classAsArgs[0]);
|
|
2179
|
-
if (!classAsName) {
|
|
2180
|
-
throw new Error(
|
|
2181
|
-
`@As decorator on class ${className} must have a non-empty string argument.`
|
|
2182
|
-
);
|
|
2183
|
-
}
|
|
2184
|
-
classAs = classAsName;
|
|
2185
|
-
}
|
|
2186
|
-
const methodAsDecorator = method.getDecorator("As");
|
|
2187
|
-
let methodAs;
|
|
2188
|
-
if (methodAsDecorator) {
|
|
2189
|
-
const methodAsArgs = methodAsDecorator.getArguments();
|
|
2190
|
-
const methodAsName = decoratorStringArg(methodAsArgs[0]);
|
|
2191
|
-
if (!methodAsName) {
|
|
2192
|
-
throw new Error(
|
|
2193
|
-
`@As decorator on ${className}.${methodName} must have a non-empty string argument.`
|
|
2194
|
-
);
|
|
2195
|
-
}
|
|
2196
|
-
methodAs = methodAsName;
|
|
2197
|
-
}
|
|
2198
|
-
const routeName = resolveRouteName(className, methodName, classAs, methodAs);
|
|
2199
|
-
const qualifiedRef = `${className}.${methodName}`;
|
|
2200
|
-
const existing = seenNames.get(routeName);
|
|
2201
|
-
if (existing !== void 0) {
|
|
2202
|
-
throw new Error(
|
|
2203
|
-
`Route name collision: "${routeName}" is used by both "${existing}" and "${qualifiedRef}". Use @As(...) to give one of them a unique name.`
|
|
2204
|
-
);
|
|
2205
|
-
}
|
|
2206
|
-
seenNames.set(routeName, qualifiedRef);
|
|
2207
|
-
routes.push({
|
|
2208
|
-
method: resolvedMethod,
|
|
2209
|
-
path: combined,
|
|
2210
|
-
name: routeName,
|
|
2211
|
-
params,
|
|
2212
|
-
controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
|
|
2213
|
-
contract: {
|
|
2214
|
-
contractSource: {
|
|
2215
|
-
query: contractDef.query,
|
|
2216
|
-
body: contractDef.body,
|
|
2217
|
-
response: contractDef.response,
|
|
2218
|
-
// Path A: capture both the importable ref and the raw text. The
|
|
2219
|
-
// emitter prefers inlining the text (client-safe — re-exporting from
|
|
2220
|
-
// a controller would drag server-only deps into the client bundle).
|
|
2221
|
-
bodyZodRef,
|
|
2222
|
-
bodyZodText: contractDef.bodyZodText,
|
|
2223
|
-
queryZodRef,
|
|
2224
|
-
queryZodText: contractDef.queryZodText
|
|
2225
|
-
}
|
|
2226
|
-
}
|
|
2227
|
-
});
|
|
2228
|
-
} else {
|
|
2229
|
-
if (!httpMethod) continue;
|
|
2230
|
-
const combined = joinPaths(prefix, handlerPath);
|
|
2231
|
-
const params = extractParams(combined);
|
|
2232
|
-
const methodName = method.getName();
|
|
2233
|
-
const classAsDecorator = cls.getDecorator("As");
|
|
2234
|
-
let classAs;
|
|
2235
|
-
if (classAsDecorator) {
|
|
2236
|
-
const classAsArgs = classAsDecorator.getArguments();
|
|
2237
|
-
const classAsName = decoratorStringArg(classAsArgs[0]);
|
|
2238
|
-
if (classAsName) classAs = classAsName;
|
|
2239
|
-
}
|
|
2240
|
-
const methodAsDecorator = method.getDecorator("As");
|
|
2241
|
-
let methodAs;
|
|
2242
|
-
if (methodAsDecorator) {
|
|
2243
|
-
const methodAsArgs = methodAsDecorator.getArguments();
|
|
2244
|
-
const methodAsName = decoratorStringArg(methodAsArgs[0]);
|
|
2245
|
-
if (methodAsName) methodAs = methodAsName;
|
|
2246
|
-
}
|
|
2247
|
-
const routeName = resolveRouteName(className, methodName, classAs, methodAs);
|
|
2248
|
-
const dtoContract = extractDtoContract(method, sourceFile, project);
|
|
2249
|
-
routes.push({
|
|
2250
|
-
method: httpMethod,
|
|
2251
|
-
path: combined,
|
|
2252
|
-
name: routeName,
|
|
2253
|
-
params,
|
|
2254
|
-
controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
|
|
2255
|
-
contract: {
|
|
2256
|
-
contractSource: {
|
|
2257
|
-
query: dtoContract?.query ?? null,
|
|
2258
|
-
body: dtoContract?.body ?? null,
|
|
2259
|
-
response: dtoContract?.response ?? "unknown",
|
|
2260
|
-
queryRef: dtoContract?.queryRef ?? null,
|
|
2261
|
-
bodyRef: dtoContract?.bodyRef ?? null,
|
|
2262
|
-
responseRef: dtoContract?.responseRef ?? null,
|
|
2263
|
-
filterFields: dtoContract?.filterFields ?? null,
|
|
2264
|
-
filterFieldTypes: dtoContract?.filterFieldTypes ?? null,
|
|
2265
|
-
filterSource: dtoContract?.filterSource ?? null,
|
|
2266
|
-
bodyZodText: dtoContract?.bodyZodText ?? null,
|
|
2267
|
-
queryZodText: dtoContract?.queryZodText ?? null,
|
|
2268
|
-
formNestedSchemas: dtoContract?.formNestedSchemas ?? null,
|
|
2269
|
-
formWarnings: dtoContract?.formWarnings ?? [],
|
|
2270
|
-
bodySchema: dtoContract?.bodySchema ?? null,
|
|
2271
|
-
querySchema: dtoContract?.querySchema ?? null
|
|
2272
|
-
}
|
|
2273
|
-
}
|
|
2274
|
-
});
|
|
2275
|
-
}
|
|
1842
|
+
const route = applyContractDecorator ? extractContractRoute({
|
|
1843
|
+
cls,
|
|
1844
|
+
method,
|
|
1845
|
+
applyContractDecorator,
|
|
1846
|
+
verb,
|
|
1847
|
+
prefix,
|
|
1848
|
+
className,
|
|
1849
|
+
sourceFile,
|
|
1850
|
+
seenNames
|
|
1851
|
+
}) : extractDtoRoute({
|
|
1852
|
+
cls,
|
|
1853
|
+
method,
|
|
1854
|
+
verb,
|
|
1855
|
+
prefix,
|
|
1856
|
+
className,
|
|
1857
|
+
sourceFile,
|
|
1858
|
+
project,
|
|
1859
|
+
seenNames
|
|
1860
|
+
});
|
|
1861
|
+
if (route) routes.push(route);
|
|
2276
1862
|
}
|
|
2277
1863
|
}
|
|
2278
1864
|
return routes;
|
|
@@ -2280,8 +1866,7 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
2280
1866
|
|
|
2281
1867
|
// src/generate.ts
|
|
2282
1868
|
var import_promises9 = require("fs/promises");
|
|
2283
|
-
var
|
|
2284
|
-
var import_ts_morph9 = require("ts-morph");
|
|
1869
|
+
var import_node_path12 = require("path");
|
|
2285
1870
|
|
|
2286
1871
|
// src/discovery/pages.ts
|
|
2287
1872
|
var import_promises2 = require("fs/promises");
|
|
@@ -2336,7 +1921,8 @@ function extractPropsSource(source, exportName) {
|
|
|
2336
1921
|
}
|
|
2337
1922
|
|
|
2338
1923
|
// src/discovery/shared-props.ts
|
|
2339
|
-
var
|
|
1924
|
+
var import_node_path5 = require("path");
|
|
1925
|
+
var import_ts_morph8 = require("ts-morph");
|
|
2340
1926
|
function discoverSharedProps(project, moduleEntry) {
|
|
2341
1927
|
try {
|
|
2342
1928
|
let sourceFile = project.getSourceFile(moduleEntry);
|
|
@@ -2356,14 +1942,39 @@ function discoverSharedProps(project, moduleEntry) {
|
|
|
2356
1942
|
return null;
|
|
2357
1943
|
}
|
|
2358
1944
|
}
|
|
1945
|
+
function discoverSharedPropsFromConfig(config) {
|
|
1946
|
+
if (!config.app?.moduleEntry) return null;
|
|
1947
|
+
try {
|
|
1948
|
+
const tsconfigPath = config.app.tsconfig ?? (0, import_node_path5.join)(config.codegen.cwd, "tsconfig.json");
|
|
1949
|
+
let project;
|
|
1950
|
+
try {
|
|
1951
|
+
project = new import_ts_morph8.Project({
|
|
1952
|
+
tsConfigFilePath: tsconfigPath,
|
|
1953
|
+
skipAddingFilesFromTsConfig: true,
|
|
1954
|
+
skipLoadingLibFiles: true,
|
|
1955
|
+
skipFileDependencyResolution: true
|
|
1956
|
+
});
|
|
1957
|
+
} catch {
|
|
1958
|
+
project = new import_ts_morph8.Project({
|
|
1959
|
+
skipAddingFilesFromTsConfig: true,
|
|
1960
|
+
skipLoadingLibFiles: true,
|
|
1961
|
+
skipFileDependencyResolution: true,
|
|
1962
|
+
compilerOptions: { allowJs: true, strict: false }
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
return discoverSharedProps(project, config.app.moduleEntry);
|
|
1966
|
+
} catch {
|
|
1967
|
+
return null;
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
2359
1970
|
function findForRootCall(sourceFile) {
|
|
2360
|
-
const callExpressions = sourceFile.getDescendantsOfKind(
|
|
1971
|
+
const callExpressions = sourceFile.getDescendantsOfKind(import_ts_morph8.SyntaxKind.CallExpression);
|
|
2361
1972
|
for (const call of callExpressions) {
|
|
2362
1973
|
const expr = call.getExpression();
|
|
2363
|
-
if (!
|
|
1974
|
+
if (!import_ts_morph8.Node.isPropertyAccessExpression(expr)) continue;
|
|
2364
1975
|
const methodName = expr.getName();
|
|
2365
1976
|
const objectExpr = expr.getExpression();
|
|
2366
|
-
if (methodName === "forRoot" &&
|
|
1977
|
+
if (methodName === "forRoot" && import_ts_morph8.Node.isIdentifier(objectExpr)) {
|
|
2367
1978
|
const name = objectExpr.getText();
|
|
2368
1979
|
if (name === "InertiaModule") {
|
|
2369
1980
|
return call;
|
|
@@ -2373,19 +1984,19 @@ function findForRootCall(sourceFile) {
|
|
|
2373
1984
|
return null;
|
|
2374
1985
|
}
|
|
2375
1986
|
function findShareInitializer(forRootCall) {
|
|
2376
|
-
if (!
|
|
1987
|
+
if (!import_ts_morph8.Node.isCallExpression(forRootCall)) return null;
|
|
2377
1988
|
const args = forRootCall.getArguments();
|
|
2378
|
-
const
|
|
2379
|
-
if (!
|
|
2380
|
-
for (const prop of
|
|
2381
|
-
if (
|
|
1989
|
+
const firstArg2 = args[0];
|
|
1990
|
+
if (!firstArg2 || !import_ts_morph8.Node.isObjectLiteralExpression(firstArg2)) return null;
|
|
1991
|
+
for (const prop of firstArg2.getProperties()) {
|
|
1992
|
+
if (import_ts_morph8.Node.isPropertyAssignment(prop) && prop.getName() === "share") {
|
|
2382
1993
|
return prop.getInitializer() ?? null;
|
|
2383
1994
|
}
|
|
2384
1995
|
}
|
|
2385
1996
|
return null;
|
|
2386
1997
|
}
|
|
2387
1998
|
function extractShareType(node, sourceFile, project) {
|
|
2388
|
-
if (
|
|
1999
|
+
if (import_ts_morph8.Node.isIdentifier(node)) {
|
|
2389
2000
|
const ref = resolveIdentifierToImportRef(node, sourceFile, project);
|
|
2390
2001
|
if (ref) {
|
|
2391
2002
|
return {
|
|
@@ -2395,22 +2006,22 @@ function extractShareType(node, sourceFile, project) {
|
|
|
2395
2006
|
};
|
|
2396
2007
|
}
|
|
2397
2008
|
}
|
|
2398
|
-
if (
|
|
2009
|
+
if (import_ts_morph8.Node.isArrowFunction(node)) {
|
|
2399
2010
|
const result = extractFromFunctionLike(node, sourceFile);
|
|
2400
2011
|
return result ? { ...result, isImportRef: false } : null;
|
|
2401
2012
|
}
|
|
2402
|
-
if (
|
|
2013
|
+
if (import_ts_morph8.Node.isFunctionExpression(node)) {
|
|
2403
2014
|
const result = extractFromFunctionLike(node, sourceFile);
|
|
2404
2015
|
return result ? { ...result, isImportRef: false } : null;
|
|
2405
2016
|
}
|
|
2406
|
-
if (
|
|
2017
|
+
if (import_ts_morph8.Node.isObjectLiteralExpression(node)) {
|
|
2407
2018
|
const result = extractFromObjectLiteral(node);
|
|
2408
2019
|
return result ? { ...result, isImportRef: false } : null;
|
|
2409
2020
|
}
|
|
2410
2021
|
return null;
|
|
2411
2022
|
}
|
|
2412
2023
|
function resolveIdentifierToImportRef(id, sourceFile, project) {
|
|
2413
|
-
if (!
|
|
2024
|
+
if (!import_ts_morph8.Node.isIdentifier(id)) return null;
|
|
2414
2025
|
const name = id.getText();
|
|
2415
2026
|
const localFunc = sourceFile.getFunction(name);
|
|
2416
2027
|
if (localFunc?.isExported()) {
|
|
@@ -2444,49 +2055,49 @@ function resolveIdentifierToImportRef(id, sourceFile, project) {
|
|
|
2444
2055
|
return null;
|
|
2445
2056
|
}
|
|
2446
2057
|
function extractFromFunctionLike(node, _sourceFile) {
|
|
2447
|
-
const returnTypeNode =
|
|
2058
|
+
const returnTypeNode = import_ts_morph8.Node.isArrowFunction(node) || import_ts_morph8.Node.isFunctionExpression(node) ? node.getReturnTypeNode() : null;
|
|
2448
2059
|
if (returnTypeNode) {
|
|
2449
2060
|
return extractFromReturnTypeAnnotation(returnTypeNode);
|
|
2450
2061
|
}
|
|
2451
|
-
if (
|
|
2062
|
+
if (import_ts_morph8.Node.isArrowFunction(node)) {
|
|
2452
2063
|
const body = node.getBody();
|
|
2453
|
-
if (
|
|
2064
|
+
if (import_ts_morph8.Node.isParenthesizedExpression(body)) {
|
|
2454
2065
|
const inner = body.getExpression();
|
|
2455
|
-
if (
|
|
2066
|
+
if (import_ts_morph8.Node.isObjectLiteralExpression(inner)) {
|
|
2456
2067
|
return extractFromObjectLiteral(inner);
|
|
2457
2068
|
}
|
|
2458
2069
|
}
|
|
2459
|
-
if (
|
|
2070
|
+
if (import_ts_morph8.Node.isObjectLiteralExpression(body)) {
|
|
2460
2071
|
return extractFromObjectLiteral(body);
|
|
2461
2072
|
}
|
|
2462
|
-
if (
|
|
2073
|
+
if (import_ts_morph8.Node.isBlock(body)) {
|
|
2463
2074
|
return extractFromBlockReturn(body);
|
|
2464
2075
|
}
|
|
2465
2076
|
}
|
|
2466
|
-
if (
|
|
2077
|
+
if (import_ts_morph8.Node.isFunctionExpression(node)) {
|
|
2467
2078
|
const body = node.getBody();
|
|
2468
|
-
if (
|
|
2079
|
+
if (import_ts_morph8.Node.isBlock(body)) {
|
|
2469
2080
|
return extractFromBlockReturn(body);
|
|
2470
2081
|
}
|
|
2471
2082
|
}
|
|
2472
2083
|
return null;
|
|
2473
2084
|
}
|
|
2474
2085
|
function extractFromReturnTypeAnnotation(typeNode) {
|
|
2475
|
-
if (
|
|
2086
|
+
if (import_ts_morph8.Node.isTypeReference(typeNode)) {
|
|
2476
2087
|
const typeName = typeNode.getTypeName();
|
|
2477
|
-
if (
|
|
2088
|
+
if (import_ts_morph8.Node.isIdentifier(typeName) && typeName.getText() === "Promise") {
|
|
2478
2089
|
const typeArgs = typeNode.getTypeArguments();
|
|
2479
|
-
const
|
|
2480
|
-
if (
|
|
2481
|
-
return extractFromReturnTypeAnnotation(
|
|
2090
|
+
const firstArg2 = typeArgs[0];
|
|
2091
|
+
if (firstArg2) {
|
|
2092
|
+
return extractFromReturnTypeAnnotation(firstArg2);
|
|
2482
2093
|
}
|
|
2483
2094
|
return null;
|
|
2484
2095
|
}
|
|
2485
2096
|
}
|
|
2486
|
-
if (
|
|
2097
|
+
if (import_ts_morph8.Node.isTypeLiteral(typeNode)) {
|
|
2487
2098
|
const properties = [];
|
|
2488
2099
|
for (const member of typeNode.getMembers()) {
|
|
2489
|
-
if (
|
|
2100
|
+
if (import_ts_morph8.Node.isPropertySignature(member)) {
|
|
2490
2101
|
const name = member.getName();
|
|
2491
2102
|
const memberTypeNode = member.getTypeNode();
|
|
2492
2103
|
const type = memberTypeNode ? memberTypeNode.getText() : "unknown";
|
|
@@ -2500,19 +2111,19 @@ function extractFromReturnTypeAnnotation(typeNode) {
|
|
|
2500
2111
|
return null;
|
|
2501
2112
|
}
|
|
2502
2113
|
function extractFromBlockReturn(block) {
|
|
2503
|
-
if (!
|
|
2114
|
+
if (!import_ts_morph8.Node.isBlock(block)) return null;
|
|
2504
2115
|
const statements = block.getStatements();
|
|
2505
2116
|
for (let i = statements.length - 1; i >= 0; i--) {
|
|
2506
2117
|
const stmt = statements[i];
|
|
2507
|
-
if (!
|
|
2118
|
+
if (!import_ts_morph8.Node.isReturnStatement(stmt)) continue;
|
|
2508
2119
|
const expr = stmt.getExpression();
|
|
2509
2120
|
if (!expr) continue;
|
|
2510
|
-
if (
|
|
2121
|
+
if (import_ts_morph8.Node.isObjectLiteralExpression(expr)) {
|
|
2511
2122
|
return extractFromObjectLiteral(expr);
|
|
2512
2123
|
}
|
|
2513
|
-
if (
|
|
2124
|
+
if (import_ts_morph8.Node.isParenthesizedExpression(expr)) {
|
|
2514
2125
|
const inner = expr.getExpression();
|
|
2515
|
-
if (
|
|
2126
|
+
if (import_ts_morph8.Node.isObjectLiteralExpression(inner)) {
|
|
2516
2127
|
return extractFromObjectLiteral(inner);
|
|
2517
2128
|
}
|
|
2518
2129
|
}
|
|
@@ -2521,10 +2132,10 @@ function extractFromBlockReturn(block) {
|
|
|
2521
2132
|
return null;
|
|
2522
2133
|
}
|
|
2523
2134
|
function extractFromObjectLiteral(objLiteral) {
|
|
2524
|
-
if (!
|
|
2135
|
+
if (!import_ts_morph8.Node.isObjectLiteralExpression(objLiteral)) return null;
|
|
2525
2136
|
const properties = [];
|
|
2526
2137
|
for (const prop of objLiteral.getProperties()) {
|
|
2527
|
-
if (!
|
|
2138
|
+
if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
|
|
2528
2139
|
const name = prop.getName();
|
|
2529
2140
|
const initializer = prop.getInitializer();
|
|
2530
2141
|
if (!initializer) continue;
|
|
@@ -2536,21 +2147,21 @@ function extractFromObjectLiteral(objLiteral) {
|
|
|
2536
2147
|
return { typeString, properties, isImportRef: false };
|
|
2537
2148
|
}
|
|
2538
2149
|
function inferExpressionType(node) {
|
|
2539
|
-
if (
|
|
2540
|
-
if (
|
|
2150
|
+
if (import_ts_morph8.Node.isStringLiteral(node)) return "string";
|
|
2151
|
+
if (import_ts_morph8.Node.isTemplateExpression(node) || import_ts_morph8.Node.isNoSubstitutionTemplateLiteral(node))
|
|
2541
2152
|
return "string";
|
|
2542
|
-
if (
|
|
2543
|
-
if (node.getKind() ===
|
|
2153
|
+
if (import_ts_morph8.Node.isNumericLiteral(node)) return "number";
|
|
2154
|
+
if (node.getKind() === import_ts_morph8.SyntaxKind.TrueKeyword || node.getKind() === import_ts_morph8.SyntaxKind.FalseKeyword) {
|
|
2544
2155
|
return "boolean";
|
|
2545
2156
|
}
|
|
2546
|
-
if (node.getKind() ===
|
|
2547
|
-
if (
|
|
2548
|
-
if (
|
|
2157
|
+
if (node.getKind() === import_ts_morph8.SyntaxKind.NullKeyword) return "null";
|
|
2158
|
+
if (import_ts_morph8.Node.isIdentifier(node) && node.getText() === "undefined") return "undefined";
|
|
2159
|
+
if (import_ts_morph8.Node.isObjectLiteralExpression(node)) {
|
|
2549
2160
|
const props = node.getProperties();
|
|
2550
2161
|
if (props.length === 0) return "Record<string, unknown>";
|
|
2551
2162
|
const entries = [];
|
|
2552
2163
|
for (const prop of props) {
|
|
2553
|
-
if (!
|
|
2164
|
+
if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
|
|
2554
2165
|
const key = prop.getName();
|
|
2555
2166
|
const init = prop.getInitializer();
|
|
2556
2167
|
if (!init) continue;
|
|
@@ -2559,23 +2170,23 @@ function inferExpressionType(node) {
|
|
|
2559
2170
|
if (entries.length === 0) return "Record<string, unknown>";
|
|
2560
2171
|
return `{ ${entries.join("; ")} }`;
|
|
2561
2172
|
}
|
|
2562
|
-
if (
|
|
2173
|
+
if (import_ts_morph8.Node.isArrayLiteralExpression(node)) {
|
|
2563
2174
|
const elements = node.getElements();
|
|
2564
2175
|
if (elements.length === 0) return "Array<unknown>";
|
|
2565
2176
|
const first = elements[0];
|
|
2566
2177
|
if (first) return `Array<${inferExpressionType(first)}>`;
|
|
2567
2178
|
return "Array<unknown>";
|
|
2568
2179
|
}
|
|
2569
|
-
if (
|
|
2180
|
+
if (import_ts_morph8.Node.isConditionalExpression(node)) {
|
|
2570
2181
|
const whenTrue = inferExpressionType(node.getWhenTrue());
|
|
2571
2182
|
const whenFalse = inferExpressionType(node.getWhenFalse());
|
|
2572
2183
|
if (whenTrue === whenFalse) return whenTrue;
|
|
2573
2184
|
return `${whenTrue} | ${whenFalse}`;
|
|
2574
2185
|
}
|
|
2575
|
-
if (
|
|
2186
|
+
if (import_ts_morph8.Node.isParenthesizedExpression(node)) {
|
|
2576
2187
|
return inferExpressionType(node.getExpression());
|
|
2577
2188
|
}
|
|
2578
|
-
if (
|
|
2189
|
+
if (import_ts_morph8.Node.isAsExpression(node)) {
|
|
2579
2190
|
const typeNode = node.getTypeNode();
|
|
2580
2191
|
if (typeNode) return typeNode.getText();
|
|
2581
2192
|
}
|
|
@@ -2584,25 +2195,14 @@ function inferExpressionType(node) {
|
|
|
2584
2195
|
|
|
2585
2196
|
// src/emit/emit-api.ts
|
|
2586
2197
|
var import_promises3 = require("fs/promises");
|
|
2587
|
-
var
|
|
2198
|
+
var import_node_path6 = require("path");
|
|
2588
2199
|
|
|
2589
2200
|
// src/extension/registry.ts
|
|
2590
|
-
var
|
|
2201
|
+
var import_ts_morph9 = require("ts-morph");
|
|
2591
2202
|
function resolveApiSlots(extensions) {
|
|
2592
|
-
let
|
|
2593
|
-
let
|
|
2594
|
-
|
|
2595
|
-
let layerOwner;
|
|
2596
|
-
for (const ext of extensions) {
|
|
2597
|
-
if (ext.apiTransport) {
|
|
2598
|
-
if (transport) {
|
|
2599
|
-
throw new CodegenError(
|
|
2600
|
-
`api transport claimed by both "${transportOwner}" and "${ext.name}" \u2014 only one extension may set apiTransport.`
|
|
2601
|
-
);
|
|
2602
|
-
}
|
|
2603
|
-
transport = ext.apiTransport;
|
|
2604
|
-
transportOwner = ext.name;
|
|
2605
|
-
}
|
|
2203
|
+
let layer;
|
|
2204
|
+
let layerOwner;
|
|
2205
|
+
for (const ext of extensions) {
|
|
2606
2206
|
if (ext.apiClientLayer) {
|
|
2607
2207
|
if (layer) {
|
|
2608
2208
|
throw new CodegenError(
|
|
@@ -2614,11 +2214,22 @@ function resolveApiSlots(extensions) {
|
|
|
2614
2214
|
}
|
|
2615
2215
|
}
|
|
2616
2216
|
return {
|
|
2617
|
-
...transport ? { transport } : {},
|
|
2618
2217
|
...layer ? { layer } : {}
|
|
2619
2218
|
};
|
|
2620
2219
|
}
|
|
2621
2220
|
var CORE_FILES = /* @__PURE__ */ new Set(["routes.ts", "api.ts", "forms.ts", "index.d.ts", "pages.d.ts"]);
|
|
2221
|
+
function mergeExclusive(target, incoming, {
|
|
2222
|
+
owner,
|
|
2223
|
+
describe
|
|
2224
|
+
}) {
|
|
2225
|
+
for (const [key, value] of incoming) {
|
|
2226
|
+
const prev = target.get(key);
|
|
2227
|
+
if (prev !== void 0) {
|
|
2228
|
+
throw new CodegenError(describe(key, prev.owner, owner));
|
|
2229
|
+
}
|
|
2230
|
+
target.set(key, { value, owner });
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2622
2233
|
function createExtensionContext(config, getRoutes) {
|
|
2623
2234
|
let project;
|
|
2624
2235
|
return {
|
|
@@ -2630,7 +2241,7 @@ function createExtensionContext(config, getRoutes) {
|
|
|
2630
2241
|
},
|
|
2631
2242
|
project() {
|
|
2632
2243
|
if (!project) {
|
|
2633
|
-
project = new
|
|
2244
|
+
project = new import_ts_morph9.Project({
|
|
2634
2245
|
skipAddingFilesFromTsConfig: true,
|
|
2635
2246
|
skipLoadingLibFiles: true,
|
|
2636
2247
|
skipFileDependencyResolution: true,
|
|
@@ -2663,29 +2274,36 @@ async function collectEmittedFiles(extensions, ctx) {
|
|
|
2663
2274
|
`Extension "${ext.name}" tried to emit the core-owned file "${file.path}". Core files (${[...CORE_FILES].join(", ")}) cannot be produced by extensions.`
|
|
2664
2275
|
);
|
|
2665
2276
|
}
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
);
|
|
2671
|
-
}
|
|
2672
|
-
owners.set(key, ext.name);
|
|
2277
|
+
mergeExclusive(owners, [[key, file]], {
|
|
2278
|
+
owner: ext.name,
|
|
2279
|
+
describe: (_key, prevOwner, owner) => `Output file "${file.path}" is emitted by both "${prevOwner}" and "${owner}". Two extensions cannot write the same file.`
|
|
2280
|
+
});
|
|
2673
2281
|
files.push(file);
|
|
2674
2282
|
}
|
|
2675
2283
|
}
|
|
2676
2284
|
return files;
|
|
2677
2285
|
}
|
|
2678
2286
|
|
|
2287
|
+
// src/extension/types.ts
|
|
2288
|
+
function requestShape(route) {
|
|
2289
|
+
const cs = route.contract?.contractSource;
|
|
2290
|
+
const isGet = route.method.toUpperCase() === "GET";
|
|
2291
|
+
const isQuery = isGet || !!cs?.filterFields?.length;
|
|
2292
|
+
const hasBody = !!cs?.bodyRef || cs?.body != null && cs.body !== "never";
|
|
2293
|
+
const hasQuery = isGet || !!cs?.queryRef || cs?.query != null && cs.query !== "never";
|
|
2294
|
+
return { isGet, isQuery, hasBody, hasQuery };
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2679
2297
|
// src/emit/emit-api.ts
|
|
2680
2298
|
async function emitApi(routes, outDir, opts = {}) {
|
|
2681
2299
|
await (0, import_promises3.mkdir)(outDir, { recursive: true });
|
|
2682
2300
|
const content = buildApiFile(routes, outDir, opts);
|
|
2683
|
-
await (0, import_promises3.writeFile)((0,
|
|
2301
|
+
await (0, import_promises3.writeFile)((0, import_node_path6.join)(outDir, "api.ts"), content, "utf8");
|
|
2684
2302
|
}
|
|
2685
2303
|
function splitName(name) {
|
|
2686
2304
|
return name.split(".");
|
|
2687
2305
|
}
|
|
2688
|
-
function
|
|
2306
|
+
function toObjectKey(segment) {
|
|
2689
2307
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(segment)) {
|
|
2690
2308
|
return segment;
|
|
2691
2309
|
}
|
|
@@ -2778,7 +2396,7 @@ function emitFilterQueryType(c) {
|
|
|
2778
2396
|
}
|
|
2779
2397
|
function buildResponseType(c, outDir) {
|
|
2780
2398
|
if (c.controllerRef) {
|
|
2781
|
-
let relPath = (0,
|
|
2399
|
+
let relPath = (0, import_node_path6.relative)(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
|
|
2782
2400
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
2783
2401
|
return `Awaited<ReturnType<import('${relPath}').${c.controllerRef.className}['${c.controllerRef.methodName}']>>`;
|
|
2784
2402
|
}
|
|
@@ -2792,7 +2410,7 @@ function emitRouterTypeBlock(tree, indent, outDir) {
|
|
|
2792
2410
|
const pad = " ".repeat(indent);
|
|
2793
2411
|
const lines = [];
|
|
2794
2412
|
for (const [key, node] of tree) {
|
|
2795
|
-
const objKey =
|
|
2413
|
+
const objKey = toObjectKey(key);
|
|
2796
2414
|
if (node.kind === "leaf") {
|
|
2797
2415
|
const c = node;
|
|
2798
2416
|
const method = c.method.toUpperCase();
|
|
@@ -2818,15 +2436,12 @@ function emitRouterTypeBlock(tree, indent, outDir) {
|
|
|
2818
2436
|
return lines;
|
|
2819
2437
|
}
|
|
2820
2438
|
function buildRequestModel(c) {
|
|
2821
|
-
const isGet = c.method.toUpperCase() === "GET";
|
|
2822
2439
|
const m = c.method.toLowerCase();
|
|
2823
2440
|
const flat = JSON.stringify(c.name);
|
|
2824
2441
|
const path = JSON.stringify(c.path);
|
|
2825
2442
|
const TA = buildRouterTypeAccess(c.name);
|
|
2826
2443
|
const withParams = hasPathParams(c.params);
|
|
2827
|
-
const
|
|
2828
|
-
const isQuery = isGet || !!c.contractSource.filterFields?.length;
|
|
2829
|
-
const hasQuery = isGet || !!c.contractSource.queryRef || c.contractSource.query != null && c.contractSource.query !== "never";
|
|
2444
|
+
const { isGet, isQuery, hasBody, hasQuery } = requestShape(c.route);
|
|
2830
2445
|
const fields = [];
|
|
2831
2446
|
if (withParams) fields.push(`params: ${TA}['params']`);
|
|
2832
2447
|
if (hasQuery) fields.push(`query?: ${TA}['query']`);
|
|
@@ -2848,7 +2463,6 @@ function buildRequestModel(c) {
|
|
|
2848
2463
|
urlExpr,
|
|
2849
2464
|
optsExpr,
|
|
2850
2465
|
responseType: `${TA}['response']`,
|
|
2851
|
-
bodyType: `${TA}['body']`,
|
|
2852
2466
|
queryKeyExpr: `[${flat}, input] as const`
|
|
2853
2467
|
};
|
|
2854
2468
|
}
|
|
@@ -2896,7 +2510,7 @@ function emitApiObjectBlock(tree, indent, p) {
|
|
|
2896
2510
|
const pad = " ".repeat(indent);
|
|
2897
2511
|
const lines = [];
|
|
2898
2512
|
for (const [key, node] of tree) {
|
|
2899
|
-
const objKey =
|
|
2513
|
+
const objKey = toObjectKey(key);
|
|
2900
2514
|
if (node.kind === "branch") {
|
|
2901
2515
|
lines.push(`${pad}${objKey}: {`);
|
|
2902
2516
|
lines.push(...emitApiObjectBlock(node.children, indent + 2, p));
|
|
@@ -2904,30 +2518,28 @@ function emitApiObjectBlock(tree, indent, p) {
|
|
|
2904
2518
|
continue;
|
|
2905
2519
|
}
|
|
2906
2520
|
const req = buildRequestModel(node);
|
|
2907
|
-
const
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
params: node.params,
|
|
2912
|
-
contract: { contractSource: node.contractSource },
|
|
2913
|
-
...node.controllerRef ? { controllerRef: node.controllerRef } : {}
|
|
2521
|
+
const leaf = {
|
|
2522
|
+
route: node.route,
|
|
2523
|
+
request: req,
|
|
2524
|
+
requestExpr: renderFetcherRequest(req)
|
|
2914
2525
|
};
|
|
2915
|
-
const
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2526
|
+
const owned = /* @__PURE__ */ new Map();
|
|
2527
|
+
if (p.layer) {
|
|
2528
|
+
mergeExclusive(owned, Object.entries(p.layer.buildMembers(leaf.requestExpr, leaf, p.ctx)), {
|
|
2529
|
+
owner: p.layer.name,
|
|
2530
|
+
describe: (name, prevOwner, owner) => `api member "${name}" on route "${req.routeName}" is contributed by more than one extension (conflict between "${prevOwner}" and "${owner}").`
|
|
2531
|
+
});
|
|
2532
|
+
}
|
|
2919
2533
|
for (const ext of p.memberExts) {
|
|
2920
2534
|
const extra = ext.apiMembers?.(leaf, p.ctx);
|
|
2921
2535
|
if (!extra) continue;
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
);
|
|
2927
|
-
}
|
|
2928
|
-
members[name] = value;
|
|
2929
|
-
}
|
|
2536
|
+
mergeExclusive(owned, Object.entries(extra), {
|
|
2537
|
+
owner: ext.name,
|
|
2538
|
+
describe: (name, prevOwner, owner) => `api member "${name}" on route "${req.routeName}" is contributed by more than one extension (conflict between "${prevOwner}" and "${owner}").`
|
|
2539
|
+
});
|
|
2930
2540
|
}
|
|
2541
|
+
const members = {};
|
|
2542
|
+
for (const [name, { value }] of owned) members[name] = value;
|
|
2931
2543
|
lines.push(...renderLeaf(pad, objKey, req, leaf.requestExpr, members));
|
|
2932
2544
|
}
|
|
2933
2545
|
return lines;
|
|
@@ -2936,10 +2548,82 @@ function buildRouterTypeAccess(name) {
|
|
|
2936
2548
|
const segments = splitName(name);
|
|
2937
2549
|
return `ApiRouter${segments.map((s) => `[${JSON.stringify(s)}]`).join("")}`;
|
|
2938
2550
|
}
|
|
2551
|
+
var RESOLVER_HELPERS = [
|
|
2552
|
+
// --- Recursive helper type _RouterAt: walks nested ApiRouter by dot-path ---
|
|
2553
|
+
"type _RouterAt<R, P extends string> = P extends `${infer Head}.${infer Tail}`",
|
|
2554
|
+
" ? Head extends keyof R ? _RouterAt<R[Head], Tail> : never",
|
|
2555
|
+
" : P extends keyof R ? R[P] : never;",
|
|
2556
|
+
"",
|
|
2557
|
+
// --- ResolveByName: resolve a field from a dot-path name ---
|
|
2558
|
+
"type ResolveByName<K extends string, Field extends string> = _RouterAt<ApiRouter, K> extends infer R ? Field extends keyof R ? R[Field] : never : never;",
|
|
2559
|
+
"",
|
|
2560
|
+
// --- ResolveByPath: scan all leaves for matching method + url ---
|
|
2561
|
+
// Flattens ApiRouter recursively and finds the entry whose method === M and url === U.
|
|
2562
|
+
"type _LeafValues<T> = T extends { method: string; url: string }",
|
|
2563
|
+
" ? T",
|
|
2564
|
+
" : T extends object ? _LeafValues<T[keyof T]> : never;",
|
|
2565
|
+
"",
|
|
2566
|
+
"type ResolveByPath<M extends string, U extends string, Field extends string> = _LeafValues<ApiRouter> extends infer L",
|
|
2567
|
+
" ? L extends { method: M; url: U }",
|
|
2568
|
+
" ? Field extends keyof L ? L[Field] : never",
|
|
2569
|
+
" : never",
|
|
2570
|
+
" : never;",
|
|
2571
|
+
""
|
|
2572
|
+
];
|
|
2573
|
+
var ROUTE_NAMESPACE = [
|
|
2574
|
+
"export namespace Route {",
|
|
2575
|
+
' export type Response<K extends string> = ResolveByName<K, "response">;',
|
|
2576
|
+
' export type Body<K extends string> = ResolveByName<K, "body">;',
|
|
2577
|
+
' export type Query<K extends string> = ResolveByName<K, "query">;',
|
|
2578
|
+
' export type Params<K extends string> = ResolveByName<K, "params">;',
|
|
2579
|
+
' export type Error<K extends string> = ResolveByName<K, "error">;',
|
|
2580
|
+
' export type FilterFields<K extends string> = ResolveByName<K, "filterFields">;',
|
|
2581
|
+
" export type Request<K extends string> = {",
|
|
2582
|
+
" body: Body<K>;",
|
|
2583
|
+
" query: Query<K>;",
|
|
2584
|
+
" params: Params<K>;",
|
|
2585
|
+
" };",
|
|
2586
|
+
"}",
|
|
2587
|
+
""
|
|
2588
|
+
];
|
|
2589
|
+
var PATH_NAMESPACE = [
|
|
2590
|
+
"export namespace Path {",
|
|
2591
|
+
' export type Response<M extends string, U extends string> = ResolveByPath<M, U, "response">;',
|
|
2592
|
+
' export type Body<M extends string, U extends string> = ResolveByPath<M, U, "body">;',
|
|
2593
|
+
' export type Query<M extends string, U extends string> = ResolveByPath<M, U, "query">;',
|
|
2594
|
+
' export type Params<M extends string, U extends string> = ResolveByPath<M, U, "params">;',
|
|
2595
|
+
' export type Error<M extends string, U extends string> = ResolveByPath<M, U, "error">;',
|
|
2596
|
+
' export type FilterFields<M extends string, U extends string> = ResolveByPath<M, U, "filterFields">;',
|
|
2597
|
+
"}",
|
|
2598
|
+
""
|
|
2599
|
+
];
|
|
2600
|
+
var EMPTY_ROUTE_NAMESPACE = [
|
|
2601
|
+
"export namespace Route {",
|
|
2602
|
+
" export type Response<K extends string> = never;",
|
|
2603
|
+
" export type Body<K extends string> = never;",
|
|
2604
|
+
" export type Query<K extends string> = never;",
|
|
2605
|
+
" export type Params<K extends string> = never;",
|
|
2606
|
+
" export type Error<K extends string> = never;",
|
|
2607
|
+
" export type FilterFields<K extends string> = never;",
|
|
2608
|
+
" export type Request<K extends string> = { body: never; query: never; params: never };",
|
|
2609
|
+
"}",
|
|
2610
|
+
""
|
|
2611
|
+
];
|
|
2612
|
+
var EMPTY_PATH_NAMESPACE = [
|
|
2613
|
+
"export namespace Path {",
|
|
2614
|
+
" export type Response<M extends string, U extends string> = never;",
|
|
2615
|
+
" export type Body<M extends string, U extends string> = never;",
|
|
2616
|
+
" export type Query<M extends string, U extends string> = never;",
|
|
2617
|
+
" export type Params<M extends string, U extends string> = never;",
|
|
2618
|
+
" export type Error<M extends string, U extends string> = never;",
|
|
2619
|
+
" export type FilterFields<M extends string, U extends string> = never;",
|
|
2620
|
+
"}",
|
|
2621
|
+
""
|
|
2622
|
+
];
|
|
2939
2623
|
function buildApiFile(routes, outDir, opts = {}) {
|
|
2940
2624
|
const fetcherImportPath = opts.fetcherImportPath;
|
|
2941
2625
|
const extensions = opts.extensions ?? [];
|
|
2942
|
-
const {
|
|
2626
|
+
const { layer } = resolveApiSlots(extensions);
|
|
2943
2627
|
const memberExts = extensions.filter((e) => e.apiMembers);
|
|
2944
2628
|
const headerExts = extensions.filter((e) => e.apiHeader);
|
|
2945
2629
|
const contracted = routes.filter((r) => r.contract);
|
|
@@ -2984,7 +2668,6 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
2984
2668
|
seenImports.add(imp);
|
|
2985
2669
|
extImports.push(imp);
|
|
2986
2670
|
};
|
|
2987
|
-
for (const imp of transport?.imports?.(ctx) ?? []) pushImport(imp);
|
|
2988
2671
|
for (const imp of layer?.imports?.(ctx) ?? []) pushImport(imp);
|
|
2989
2672
|
for (const ext of headerExts) {
|
|
2990
2673
|
for (const imp of ext.apiHeader?.(ctx)?.imports ?? []) pushImport(imp);
|
|
@@ -3000,8 +2683,8 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3000
2683
|
const emittedNames = /* @__PURE__ */ new Set();
|
|
3001
2684
|
for (const [filePath, names] of importsByFile) {
|
|
3002
2685
|
let relPath;
|
|
3003
|
-
if ((0,
|
|
3004
|
-
relPath = (0,
|
|
2686
|
+
if ((0, import_node_path6.isAbsolute)(filePath)) {
|
|
2687
|
+
relPath = (0, import_node_path6.relative)(outDir, filePath).replace(/\.ts$/, "");
|
|
3005
2688
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
3006
2689
|
} else {
|
|
3007
2690
|
relPath = filePath;
|
|
@@ -3029,27 +2712,8 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3029
2712
|
lines.push("}");
|
|
3030
2713
|
lines.push("export type Api = ReturnType<typeof createApi>;");
|
|
3031
2714
|
lines.push("");
|
|
3032
|
-
lines.push(
|
|
3033
|
-
lines.push(
|
|
3034
|
-
lines.push(" export type Body<K extends string> = never;");
|
|
3035
|
-
lines.push(" export type Query<K extends string> = never;");
|
|
3036
|
-
lines.push(" export type Params<K extends string> = never;");
|
|
3037
|
-
lines.push(" export type Error<K extends string> = never;");
|
|
3038
|
-
lines.push(" export type FilterFields<K extends string> = never;");
|
|
3039
|
-
lines.push(
|
|
3040
|
-
" export type Request<K extends string> = { body: never; query: never; params: never };"
|
|
3041
|
-
);
|
|
3042
|
-
lines.push("}");
|
|
3043
|
-
lines.push("");
|
|
3044
|
-
lines.push("export namespace Path {");
|
|
3045
|
-
lines.push(" export type Response<M extends string, U extends string> = never;");
|
|
3046
|
-
lines.push(" export type Body<M extends string, U extends string> = never;");
|
|
3047
|
-
lines.push(" export type Query<M extends string, U extends string> = never;");
|
|
3048
|
-
lines.push(" export type Params<M extends string, U extends string> = never;");
|
|
3049
|
-
lines.push(" export type Error<M extends string, U extends string> = never;");
|
|
3050
|
-
lines.push(" export type FilterFields<M extends string, U extends string> = never;");
|
|
3051
|
-
lines.push("}");
|
|
3052
|
-
lines.push("");
|
|
2715
|
+
lines.push(...EMPTY_ROUTE_NAMESPACE);
|
|
2716
|
+
lines.push(...EMPTY_PATH_NAMESPACE);
|
|
3053
2717
|
return lines.join("\n");
|
|
3054
2718
|
}
|
|
3055
2719
|
const tree = /* @__PURE__ */ new Map();
|
|
@@ -3067,7 +2731,8 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3067
2731
|
path: r.path,
|
|
3068
2732
|
params: r.params,
|
|
3069
2733
|
controllerRef: r.controllerRef,
|
|
3070
|
-
contractSource: c.contractSource
|
|
2734
|
+
contractSource: c.contractSource,
|
|
2735
|
+
route: r
|
|
3071
2736
|
};
|
|
3072
2737
|
insertIntoTree(tree, segments, leaf, name);
|
|
3073
2738
|
}
|
|
@@ -3080,7 +2745,6 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3080
2745
|
lines.push(" return {");
|
|
3081
2746
|
lines.push(
|
|
3082
2747
|
...emitApiObjectBlock(tree, 4, {
|
|
3083
|
-
...transport ? { transport } : {},
|
|
3084
2748
|
...layer ? { layer } : {},
|
|
3085
2749
|
memberExts,
|
|
3086
2750
|
ctx
|
|
@@ -3091,61 +2755,9 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3091
2755
|
lines.push("");
|
|
3092
2756
|
lines.push("export type Api = ReturnType<typeof createApi>;");
|
|
3093
2757
|
lines.push("");
|
|
3094
|
-
lines.push(
|
|
3095
|
-
lines.push(
|
|
3096
|
-
lines.push(
|
|
3097
|
-
lines.push("");
|
|
3098
|
-
lines.push(
|
|
3099
|
-
"type ResolveByName<K extends string, Field extends string> = _RouterAt<ApiRouter, K> extends infer R ? Field extends keyof R ? R[Field] : never : never;"
|
|
3100
|
-
);
|
|
3101
|
-
lines.push("");
|
|
3102
|
-
lines.push("type _LeafValues<T> = T extends { method: string; url: string }");
|
|
3103
|
-
lines.push(" ? T");
|
|
3104
|
-
lines.push(" : T extends object ? _LeafValues<T[keyof T]> : never;");
|
|
3105
|
-
lines.push("");
|
|
3106
|
-
lines.push(
|
|
3107
|
-
"type ResolveByPath<M extends string, U extends string, Field extends string> = _LeafValues<ApiRouter> extends infer L"
|
|
3108
|
-
);
|
|
3109
|
-
lines.push(" ? L extends { method: M; url: U }");
|
|
3110
|
-
lines.push(" ? Field extends keyof L ? L[Field] : never");
|
|
3111
|
-
lines.push(" : never");
|
|
3112
|
-
lines.push(" : never;");
|
|
3113
|
-
lines.push("");
|
|
3114
|
-
lines.push("export namespace Route {");
|
|
3115
|
-
lines.push(' export type Response<K extends string> = ResolveByName<K, "response">;');
|
|
3116
|
-
lines.push(' export type Body<K extends string> = ResolveByName<K, "body">;');
|
|
3117
|
-
lines.push(' export type Query<K extends string> = ResolveByName<K, "query">;');
|
|
3118
|
-
lines.push(' export type Params<K extends string> = ResolveByName<K, "params">;');
|
|
3119
|
-
lines.push(' export type Error<K extends string> = ResolveByName<K, "error">;');
|
|
3120
|
-
lines.push(' export type FilterFields<K extends string> = ResolveByName<K, "filterFields">;');
|
|
3121
|
-
lines.push(" export type Request<K extends string> = {");
|
|
3122
|
-
lines.push(" body: Body<K>;");
|
|
3123
|
-
lines.push(" query: Query<K>;");
|
|
3124
|
-
lines.push(" params: Params<K>;");
|
|
3125
|
-
lines.push(" };");
|
|
3126
|
-
lines.push("}");
|
|
3127
|
-
lines.push("");
|
|
3128
|
-
lines.push("export namespace Path {");
|
|
3129
|
-
lines.push(
|
|
3130
|
-
' export type Response<M extends string, U extends string> = ResolveByPath<M, U, "response">;'
|
|
3131
|
-
);
|
|
3132
|
-
lines.push(
|
|
3133
|
-
' export type Body<M extends string, U extends string> = ResolveByPath<M, U, "body">;'
|
|
3134
|
-
);
|
|
3135
|
-
lines.push(
|
|
3136
|
-
' export type Query<M extends string, U extends string> = ResolveByPath<M, U, "query">;'
|
|
3137
|
-
);
|
|
3138
|
-
lines.push(
|
|
3139
|
-
' export type Params<M extends string, U extends string> = ResolveByPath<M, U, "params">;'
|
|
3140
|
-
);
|
|
3141
|
-
lines.push(
|
|
3142
|
-
' export type Error<M extends string, U extends string> = ResolveByPath<M, U, "error">;'
|
|
3143
|
-
);
|
|
3144
|
-
lines.push(
|
|
3145
|
-
' export type FilterFields<M extends string, U extends string> = ResolveByPath<M, U, "filterFields">;'
|
|
3146
|
-
);
|
|
3147
|
-
lines.push("}");
|
|
3148
|
-
lines.push("");
|
|
2758
|
+
lines.push(...RESOLVER_HELPERS);
|
|
2759
|
+
lines.push(...ROUTE_NAMESPACE);
|
|
2760
|
+
lines.push(...PATH_NAMESPACE);
|
|
3149
2761
|
for (const ext of headerExts) {
|
|
3150
2762
|
const statements = ext.apiHeader?.(ctx)?.statements;
|
|
3151
2763
|
if (statements?.length) {
|
|
@@ -3157,7 +2769,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3157
2769
|
|
|
3158
2770
|
// src/emit/emit-cache.ts
|
|
3159
2771
|
var import_promises4 = require("fs/promises");
|
|
3160
|
-
var
|
|
2772
|
+
var import_node_path7 = require("path");
|
|
3161
2773
|
async function emitCache(pages, outDir) {
|
|
3162
2774
|
await (0, import_promises4.mkdir)(outDir, { recursive: true });
|
|
3163
2775
|
const entries = await Promise.all(
|
|
@@ -3171,95 +2783,21 @@ async function emitCache(pages, outDir) {
|
|
|
3171
2783
|
})
|
|
3172
2784
|
);
|
|
3173
2785
|
const cache = { pages: entries };
|
|
3174
|
-
await (0, import_promises4.writeFile)((0,
|
|
2786
|
+
await (0, import_promises4.writeFile)((0, import_node_path7.join)(outDir, "components.json"), `${JSON.stringify(cache, null, 2)}
|
|
3175
2787
|
`, "utf8");
|
|
3176
2788
|
}
|
|
3177
2789
|
|
|
3178
2790
|
// src/emit/emit-forms.ts
|
|
3179
2791
|
var import_promises5 = require("fs/promises");
|
|
3180
|
-
var
|
|
2792
|
+
var import_node_path8 = require("path");
|
|
3181
2793
|
async function emitForms(routes, outDir, config, adapter) {
|
|
3182
2794
|
if (config && config.enabled === false) return false;
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
if (content2 === null) return false;
|
|
3186
|
-
await (0, import_promises5.mkdir)(outDir, { recursive: true });
|
|
3187
|
-
await (0, import_promises5.writeFile)((0, import_node_path7.join)(outDir, "forms.ts"), content2, "utf8");
|
|
3188
|
-
return true;
|
|
3189
|
-
}
|
|
3190
|
-
const entries = collectFormEntries(routes);
|
|
3191
|
-
if (entries.length === 0) return false;
|
|
2795
|
+
const content = buildFormsFileWithAdapter(routes, outDir, adapter, config);
|
|
2796
|
+
if (content === null) return false;
|
|
3192
2797
|
await (0, import_promises5.mkdir)(outDir, { recursive: true });
|
|
3193
|
-
|
|
3194
|
-
await (0, import_promises5.writeFile)((0, import_node_path7.join)(outDir, "forms.ts"), content, "utf8");
|
|
2798
|
+
await (0, import_promises5.writeFile)((0, import_node_path8.join)(outDir, "forms.ts"), content, "utf8");
|
|
3195
2799
|
return true;
|
|
3196
2800
|
}
|
|
3197
|
-
function buildFormsFileWithAdapter(routes, adapter) {
|
|
3198
|
-
const sorted = [...routes].filter((r) => r.contract).sort((a, b) => a.name.localeCompare(b.name));
|
|
3199
|
-
const methodNameCounts = /* @__PURE__ */ new Map();
|
|
3200
|
-
for (const route of sorted) {
|
|
3201
|
-
const cs = route.contract.contractSource;
|
|
3202
|
-
if (!cs.bodySchema && !cs.querySchema) continue;
|
|
3203
|
-
methodNameCounts.set(
|
|
3204
|
-
deriveBaseName(route.name).method,
|
|
3205
|
-
(methodNameCounts.get(deriveBaseName(route.name).method) ?? 0) + 1
|
|
3206
|
-
);
|
|
3207
|
-
}
|
|
3208
|
-
const named = /* @__PURE__ */ new Map();
|
|
3209
|
-
const decls = [];
|
|
3210
|
-
const mapEntries = [];
|
|
3211
|
-
let used = false;
|
|
3212
|
-
for (const route of sorted) {
|
|
3213
|
-
const cs = route.contract.contractSource;
|
|
3214
|
-
const { method, full } = deriveBaseName(route.name);
|
|
3215
|
-
const base = (methodNameCounts.get(method) ?? 0) > 1 ? full : method;
|
|
3216
|
-
const block = [];
|
|
3217
|
-
if (cs.bodyZodText && !cs.bodySchema) {
|
|
3218
|
-
block.push(
|
|
3219
|
-
`// warning: ${route.name} body is a defineContract (zod) schema; not translated to ${adapter.name} \u2014 use the zod adapter.`
|
|
3220
|
-
);
|
|
3221
|
-
}
|
|
3222
|
-
let bodyConst;
|
|
3223
|
-
if (cs.bodySchema) {
|
|
3224
|
-
used = true;
|
|
3225
|
-
const r = adapter.renderModule(cs.bodySchema);
|
|
3226
|
-
for (const [n, t] of r.namedNestedSchemas) named.set(n, t);
|
|
3227
|
-
bodyConst = `${base}BodySchema`;
|
|
3228
|
-
block.push(`export const ${bodyConst} = ${r.schemaText};`);
|
|
3229
|
-
block.push(`export type ${base}Body = ${adapter.inferType(bodyConst)};`);
|
|
3230
|
-
}
|
|
3231
|
-
if (cs.querySchema) {
|
|
3232
|
-
used = true;
|
|
3233
|
-
const r = adapter.renderModule(cs.querySchema);
|
|
3234
|
-
for (const [n, t] of r.namedNestedSchemas) named.set(n, t);
|
|
3235
|
-
const queryConst = `${base}QuerySchema`;
|
|
3236
|
-
block.push(`export const ${queryConst} = ${r.schemaText};`);
|
|
3237
|
-
block.push(`export type ${base}Query = ${adapter.inferType(queryConst)};`);
|
|
3238
|
-
}
|
|
3239
|
-
if (block.length === 0) continue;
|
|
3240
|
-
decls.push(`// ${route.name}`, ...block, "");
|
|
3241
|
-
if (bodyConst) mapEntries.push(` ${JSON.stringify(route.name)}: ${bodyConst},`);
|
|
3242
|
-
}
|
|
3243
|
-
if (!used) return null;
|
|
3244
|
-
const lines = ["// Generated by @dudousxd/nestjs-codegen. Do not edit."];
|
|
3245
|
-
for (const imp of adapter.importStatements({ used: true })) lines.push(imp);
|
|
3246
|
-
lines.push("");
|
|
3247
|
-
if (named.size > 0) {
|
|
3248
|
-
lines.push("// Hoisted nested schemas (shared across endpoints).");
|
|
3249
|
-
for (const [n, t] of named) lines.push(`const ${n} = ${t};`);
|
|
3250
|
-
lines.push("");
|
|
3251
|
-
}
|
|
3252
|
-
lines.push(...decls);
|
|
3253
|
-
lines.push("/** Route name \u2192 body schema map. */");
|
|
3254
|
-
lines.push("export const formSchemas = {");
|
|
3255
|
-
lines.push(...mapEntries);
|
|
3256
|
-
lines.push("} as const;");
|
|
3257
|
-
lines.push("");
|
|
3258
|
-
return lines.join("\n");
|
|
3259
|
-
}
|
|
3260
|
-
function hasSchema(src) {
|
|
3261
|
-
return !!src && (src.ref !== null || src.text !== null);
|
|
3262
|
-
}
|
|
3263
2801
|
function pascal(segment) {
|
|
3264
2802
|
return segment.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
3265
2803
|
}
|
|
@@ -3269,124 +2807,16 @@ function deriveBaseName(routeName) {
|
|
|
3269
2807
|
const full = segments.map(pascal).join("");
|
|
3270
2808
|
return { method, full };
|
|
3271
2809
|
}
|
|
3272
|
-
function collectFormEntries(routes) {
|
|
3273
|
-
const sorted = [...routes].filter((r) => r.contract).sort((a, b) => a.name.localeCompare(b.name));
|
|
3274
|
-
const methodNameCounts = /* @__PURE__ */ new Map();
|
|
3275
|
-
const candidates = [];
|
|
3276
|
-
for (const route of sorted) {
|
|
3277
|
-
const cs = route.contract.contractSource;
|
|
3278
|
-
const body = { ref: cs.bodyZodRef ?? null, text: cs.bodyZodText ?? null };
|
|
3279
|
-
const query = { ref: cs.queryZodRef ?? null, text: cs.queryZodText ?? null };
|
|
3280
|
-
if (!hasSchema(body) && !hasSchema(query)) continue;
|
|
3281
|
-
const { method, full } = deriveBaseName(route.name);
|
|
3282
|
-
methodNameCounts.set(method, (methodNameCounts.get(method) ?? 0) + 1);
|
|
3283
|
-
candidates.push({ route, method, full });
|
|
3284
|
-
}
|
|
3285
|
-
const entries = [];
|
|
3286
|
-
for (const { route, method, full } of candidates) {
|
|
3287
|
-
const cs = route.contract.contractSource;
|
|
3288
|
-
const collision = (methodNameCounts.get(method) ?? 0) > 1;
|
|
3289
|
-
const baseName = collision ? full : method;
|
|
3290
|
-
const body = { ref: cs.bodyZodRef ?? null, text: cs.bodyZodText ?? null };
|
|
3291
|
-
const query = { ref: cs.queryZodRef ?? null, text: cs.queryZodText ?? null };
|
|
3292
|
-
entries.push({
|
|
3293
|
-
routeName: route.name,
|
|
3294
|
-
baseName,
|
|
3295
|
-
body: hasSchema(body) ? body : void 0,
|
|
3296
|
-
query: hasSchema(query) ? query : void 0,
|
|
3297
|
-
nestedSchemas: cs.formNestedSchemas ?? null,
|
|
3298
|
-
warnings: cs.formWarnings ?? []
|
|
3299
|
-
});
|
|
3300
|
-
}
|
|
3301
|
-
return entries;
|
|
3302
|
-
}
|
|
3303
2810
|
function relImport(outDir, filePath) {
|
|
3304
|
-
let relPath = (0,
|
|
2811
|
+
let relPath = (0, import_node_path8.relative)(outDir, filePath).replace(/\.ts$/, "");
|
|
3305
2812
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
3306
2813
|
return relPath;
|
|
3307
2814
|
}
|
|
3308
2815
|
function refRootIdentifier(refName) {
|
|
3309
2816
|
return refName.split(".")[0] ?? refName;
|
|
3310
2817
|
}
|
|
3311
|
-
function
|
|
3312
|
-
|
|
3313
|
-
const lines = [
|
|
3314
|
-
"// Generated by @dudousxd/nestjs-codegen. Do not edit.",
|
|
3315
|
-
`import { z } from '${zodImport}';`
|
|
3316
|
-
];
|
|
3317
|
-
const importsByFile = /* @__PURE__ */ new Map();
|
|
3318
|
-
const refAlias = /* @__PURE__ */ new Map();
|
|
3319
|
-
for (const entry of entries) {
|
|
3320
|
-
for (const src of [entry.body, entry.query]) {
|
|
3321
|
-
if (src?.ref && !src.text) {
|
|
3322
|
-
const root = refRootIdentifier(src.ref.name);
|
|
3323
|
-
const set = importsByFile.get(src.ref.filePath) ?? /* @__PURE__ */ new Set();
|
|
3324
|
-
set.add(root);
|
|
3325
|
-
importsByFile.set(src.ref.filePath, set);
|
|
3326
|
-
}
|
|
3327
|
-
}
|
|
3328
|
-
}
|
|
3329
|
-
if (importsByFile.size > 0) {
|
|
3330
|
-
const emitted = /* @__PURE__ */ new Set();
|
|
3331
|
-
for (const [filePath, roots] of [...importsByFile.entries()].sort()) {
|
|
3332
|
-
const relPath = relImport(outDir, filePath);
|
|
3333
|
-
const specifiers = [];
|
|
3334
|
-
for (const root of [...roots].sort()) {
|
|
3335
|
-
if (emitted.has(root)) {
|
|
3336
|
-
const alias = `${root}_${emitted.size}`;
|
|
3337
|
-
specifiers.push(`${root} as ${alias}`);
|
|
3338
|
-
emitted.add(alias);
|
|
3339
|
-
refAlias.set(`${filePath}\0${root}`, alias);
|
|
3340
|
-
} else {
|
|
3341
|
-
specifiers.push(root);
|
|
3342
|
-
emitted.add(root);
|
|
3343
|
-
refAlias.set(`${filePath}\0${root}`, root);
|
|
3344
|
-
}
|
|
3345
|
-
}
|
|
3346
|
-
lines.push(`import { ${specifiers.join(", ")} } from '${relPath}';`);
|
|
3347
|
-
}
|
|
3348
|
-
}
|
|
3349
|
-
lines.push("");
|
|
3350
|
-
const { globalSchemas, renamesByEntry } = planNestedSchemas(entries);
|
|
3351
|
-
if (globalSchemas.size > 0) {
|
|
3352
|
-
lines.push("// Hoisted nested schemas (shared across endpoints).");
|
|
3353
|
-
for (const [name, text] of globalSchemas) {
|
|
3354
|
-
lines.push(`const ${name} = ${text};`);
|
|
3355
|
-
}
|
|
3356
|
-
lines.push("");
|
|
3357
|
-
}
|
|
3358
|
-
const mapEntries = [];
|
|
3359
|
-
for (const entry of entries) {
|
|
3360
|
-
lines.push(`// ${entry.routeName}`);
|
|
3361
|
-
if (entry.warnings && entry.warnings.length > 0) {
|
|
3362
|
-
for (const w of entry.warnings) {
|
|
3363
|
-
lines.push(`// warning: ${w}`);
|
|
3364
|
-
}
|
|
3365
|
-
}
|
|
3366
|
-
const rename = renamesByEntry.get(entry) ?? null;
|
|
3367
|
-
if (entry.body) {
|
|
3368
|
-
const schemaName = `${entry.baseName}BodySchema`;
|
|
3369
|
-
const typeName = `${entry.baseName}Body`;
|
|
3370
|
-
const text = applyRenames(renderSchema(entry.body, outDir, refAlias), rename);
|
|
3371
|
-
lines.push(`export const ${schemaName} = ${text};`);
|
|
3372
|
-
lines.push(`export type ${typeName} = z.infer<typeof ${schemaName}>;`);
|
|
3373
|
-
mapEntries.push(` ${JSON.stringify(entry.routeName)}: ${schemaName},`);
|
|
3374
|
-
}
|
|
3375
|
-
if (entry.query) {
|
|
3376
|
-
const schemaName = `${entry.baseName}QuerySchema`;
|
|
3377
|
-
const typeName = `${entry.baseName}Query`;
|
|
3378
|
-
const text = applyRenames(renderSchema(entry.query, outDir, refAlias), rename);
|
|
3379
|
-
lines.push(`export const ${schemaName} = ${text};`);
|
|
3380
|
-
lines.push(`export type ${typeName} = z.infer<typeof ${schemaName}>;`);
|
|
3381
|
-
}
|
|
3382
|
-
lines.push("");
|
|
3383
|
-
}
|
|
3384
|
-
lines.push("/** Route name \u2192 body schema map. */");
|
|
3385
|
-
lines.push("export const formSchemas = {");
|
|
3386
|
-
lines.push(...mapEntries);
|
|
3387
|
-
lines.push("} as const;");
|
|
3388
|
-
lines.push("");
|
|
3389
|
-
return lines.join("\n");
|
|
2818
|
+
function hasSource(src) {
|
|
2819
|
+
return !!(src.schema || src.zodText || src.zodRef);
|
|
3390
2820
|
}
|
|
3391
2821
|
function applyRenames(text, renames) {
|
|
3392
2822
|
if (!renames || renames.size === 0) return text;
|
|
@@ -3452,20 +2882,182 @@ function planNestedSchemas(entries) {
|
|
|
3452
2882
|
}
|
|
3453
2883
|
return { globalSchemas, renamesByEntry };
|
|
3454
2884
|
}
|
|
3455
|
-
function
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
2885
|
+
function buildFormsFileWithAdapter(routes, outDir, adapter, config) {
|
|
2886
|
+
const acceptsRawZod = adapter.acceptsRawZodSource === true;
|
|
2887
|
+
const sorted = [...routes].filter((r) => r.contract).sort((a, b) => a.name.localeCompare(b.name));
|
|
2888
|
+
const methodNameCounts = /* @__PURE__ */ new Map();
|
|
2889
|
+
const candidates = [];
|
|
2890
|
+
for (const route of sorted) {
|
|
2891
|
+
const cs = route.contract.contractSource;
|
|
2892
|
+
const body = {
|
|
2893
|
+
schema: cs.bodySchema ?? null,
|
|
2894
|
+
zodText: cs.bodyZodText ?? null,
|
|
2895
|
+
zodRef: cs.bodyZodRef ?? null
|
|
2896
|
+
};
|
|
2897
|
+
const query = {
|
|
2898
|
+
schema: cs.querySchema ?? null,
|
|
2899
|
+
zodText: cs.queryZodText ?? null,
|
|
2900
|
+
zodRef: cs.queryZodRef ?? null
|
|
2901
|
+
};
|
|
2902
|
+
if (!hasSource(body) && !hasSource(query)) continue;
|
|
2903
|
+
const { method, full } = deriveBaseName(route.name);
|
|
2904
|
+
methodNameCounts.set(method, (methodNameCounts.get(method) ?? 0) + 1);
|
|
2905
|
+
candidates.push({
|
|
2906
|
+
routeName: route.name,
|
|
2907
|
+
baseName: full,
|
|
2908
|
+
// resolved below
|
|
2909
|
+
body: hasSource(body) ? body : void 0,
|
|
2910
|
+
query: hasSource(query) ? query : void 0,
|
|
2911
|
+
nestedSchemas: cs.formNestedSchemas ?? null,
|
|
2912
|
+
warnings: cs.formWarnings ?? []
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2915
|
+
const entries = candidates.map((c) => {
|
|
2916
|
+
const { method, full } = deriveBaseName(c.routeName);
|
|
2917
|
+
const collision = (methodNameCounts.get(method) ?? 0) > 1;
|
|
2918
|
+
return { ...c, baseName: collision ? full : method };
|
|
2919
|
+
});
|
|
2920
|
+
if (entries.length === 0) return null;
|
|
2921
|
+
const importsByFile = /* @__PURE__ */ new Map();
|
|
2922
|
+
const refAlias = /* @__PURE__ */ new Map();
|
|
2923
|
+
for (const entry of entries) {
|
|
2924
|
+
for (const src of [entry.body, entry.query]) {
|
|
2925
|
+
if (src?.zodRef && !src.zodText && !src.schema) {
|
|
2926
|
+
const root = refRootIdentifier(src.zodRef.name);
|
|
2927
|
+
const set = importsByFile.get(src.zodRef.filePath) ?? /* @__PURE__ */ new Set();
|
|
2928
|
+
set.add(root);
|
|
2929
|
+
importsByFile.set(src.zodRef.filePath, set);
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
const importLines = [];
|
|
2934
|
+
if (importsByFile.size > 0) {
|
|
2935
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
2936
|
+
for (const [filePath, roots] of [...importsByFile.entries()].sort()) {
|
|
2937
|
+
const relPath = relImport(outDir, filePath);
|
|
2938
|
+
const specifiers = [];
|
|
2939
|
+
for (const root of [...roots].sort()) {
|
|
2940
|
+
if (emitted.has(root)) {
|
|
2941
|
+
const alias = `${root}_${emitted.size}`;
|
|
2942
|
+
specifiers.push(`${root} as ${alias}`);
|
|
2943
|
+
emitted.add(alias);
|
|
2944
|
+
refAlias.set(`${filePath}\0${root}`, alias);
|
|
2945
|
+
} else {
|
|
2946
|
+
specifiers.push(root);
|
|
2947
|
+
emitted.add(root);
|
|
2948
|
+
refAlias.set(`${filePath}\0${root}`, root);
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
importLines.push(`import { ${specifiers.join(", ")} } from '${relPath}';`);
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
const { globalSchemas, renamesByEntry } = planNestedSchemas(entries);
|
|
2955
|
+
const irNamed = /* @__PURE__ */ new Map();
|
|
2956
|
+
const irTypeAliases = /* @__PURE__ */ new Map();
|
|
2957
|
+
const irAnnotations = /* @__PURE__ */ new Map();
|
|
2958
|
+
const decls = [];
|
|
2959
|
+
const mapEntries = [];
|
|
2960
|
+
let used = false;
|
|
2961
|
+
const renderSource = (src, rename) => {
|
|
2962
|
+
if (src.schema) {
|
|
2963
|
+
const r = adapter.renderModule(src.schema);
|
|
2964
|
+
for (const [n, t] of r.namedNestedSchemas) irNamed.set(n, t);
|
|
2965
|
+
if (r.namedTypeAliases) for (const [n, t] of r.namedTypeAliases) irTypeAliases.set(n, t);
|
|
2966
|
+
if (r.namedAnnotations) for (const [n, a] of r.namedAnnotations) irAnnotations.set(n, a);
|
|
2967
|
+
return { text: r.schemaText };
|
|
2968
|
+
}
|
|
2969
|
+
if (src.zodText) {
|
|
2970
|
+
if (!acceptsRawZod) {
|
|
2971
|
+
return {
|
|
2972
|
+
text: "",
|
|
2973
|
+
warn: `is a defineContract (zod) schema; not translated to ${adapter.name} \u2014 use the zod adapter.`
|
|
2974
|
+
};
|
|
2975
|
+
}
|
|
2976
|
+
return { text: applyRenames(src.zodText, rename) };
|
|
2977
|
+
}
|
|
2978
|
+
if (src.zodRef) {
|
|
2979
|
+
if (!acceptsRawZod) {
|
|
2980
|
+
return {
|
|
2981
|
+
text: "",
|
|
2982
|
+
warn: `is a defineContract (zod) schema; not translated to ${adapter.name} \u2014 use the zod adapter.`
|
|
2983
|
+
};
|
|
2984
|
+
}
|
|
2985
|
+
const root = refRootIdentifier(src.zodRef.name);
|
|
2986
|
+
const alias = refAlias.get(`${src.zodRef.filePath}\0${root}`) ?? root;
|
|
2987
|
+
const member = src.zodRef.name.slice(root.length);
|
|
2988
|
+
return { text: `${alias}${member}` };
|
|
2989
|
+
}
|
|
2990
|
+
return null;
|
|
2991
|
+
};
|
|
2992
|
+
for (const entry of entries) {
|
|
2993
|
+
const block = [];
|
|
2994
|
+
const rename = renamesByEntry.get(entry) ?? null;
|
|
2995
|
+
let bodyConst;
|
|
2996
|
+
if (entry.warnings && entry.warnings.length > 0) {
|
|
2997
|
+
for (const w of entry.warnings) block.push(`// warning: ${w}`);
|
|
2998
|
+
}
|
|
2999
|
+
if (entry.body) {
|
|
3000
|
+
const rendered = renderSource(entry.body, rename);
|
|
3001
|
+
if (rendered?.warn) {
|
|
3002
|
+
block.push(`// warning: ${entry.routeName} body ${rendered.warn}`);
|
|
3003
|
+
} else if (rendered) {
|
|
3004
|
+
used = true;
|
|
3005
|
+
bodyConst = `${entry.baseName}BodySchema`;
|
|
3006
|
+
block.push(`export const ${bodyConst} = ${rendered.text};`);
|
|
3007
|
+
block.push(`export type ${entry.baseName}Body = ${adapter.inferType(bodyConst)};`);
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
if (entry.query) {
|
|
3011
|
+
const rendered = renderSource(entry.query, rename);
|
|
3012
|
+
if (rendered?.warn) {
|
|
3013
|
+
block.push(`// warning: ${entry.routeName} query ${rendered.warn}`);
|
|
3014
|
+
} else if (rendered) {
|
|
3015
|
+
used = true;
|
|
3016
|
+
const queryConst = `${entry.baseName}QuerySchema`;
|
|
3017
|
+
block.push(`export const ${queryConst} = ${rendered.text};`);
|
|
3018
|
+
block.push(`export type ${entry.baseName}Query = ${adapter.inferType(queryConst)};`);
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
if (block.length === 0) continue;
|
|
3022
|
+
decls.push(`// ${entry.routeName}`, ...block, "");
|
|
3023
|
+
if (bodyConst) mapEntries.push(` ${JSON.stringify(entry.routeName)}: ${bodyConst},`);
|
|
3462
3024
|
}
|
|
3463
|
-
|
|
3025
|
+
if (!used) return null;
|
|
3026
|
+
const lines = ["// Generated by @dudousxd/nestjs-codegen. Do not edit."];
|
|
3027
|
+
if (acceptsRawZod) {
|
|
3028
|
+
const zodImport = config?.zodImport ?? "zod";
|
|
3029
|
+
lines.push(`import { z } from '${zodImport}';`);
|
|
3030
|
+
} else {
|
|
3031
|
+
for (const imp of adapter.importStatements({ used: true })) lines.push(imp);
|
|
3032
|
+
}
|
|
3033
|
+
lines.push(...importLines);
|
|
3034
|
+
lines.push("");
|
|
3035
|
+
const allNested = /* @__PURE__ */ new Map();
|
|
3036
|
+
for (const [n, t] of globalSchemas) allNested.set(n, t);
|
|
3037
|
+
for (const [n, t] of irNamed) if (!allNested.has(n)) allNested.set(n, t);
|
|
3038
|
+
if (allNested.size > 0) {
|
|
3039
|
+
lines.push("// Hoisted nested schemas (shared across endpoints).");
|
|
3040
|
+
for (const [n, alias] of irTypeAliases) {
|
|
3041
|
+
if (allNested.has(n)) lines.push(`${alias};`);
|
|
3042
|
+
}
|
|
3043
|
+
for (const [n, t] of allNested) {
|
|
3044
|
+
const annotation = irAnnotations.get(n);
|
|
3045
|
+
lines.push(`const ${n}${annotation ? `: ${annotation}` : ""} = ${t};`);
|
|
3046
|
+
}
|
|
3047
|
+
lines.push("");
|
|
3048
|
+
}
|
|
3049
|
+
lines.push(...decls);
|
|
3050
|
+
lines.push("/** Route name \u2192 body schema map. */");
|
|
3051
|
+
lines.push("export const formSchemas = {");
|
|
3052
|
+
lines.push(...mapEntries);
|
|
3053
|
+
lines.push("} as const;");
|
|
3054
|
+
lines.push("");
|
|
3055
|
+
return lines.join("\n");
|
|
3464
3056
|
}
|
|
3465
3057
|
|
|
3466
3058
|
// src/emit/emit-index.ts
|
|
3467
3059
|
var import_promises6 = require("fs/promises");
|
|
3468
|
-
var
|
|
3060
|
+
var import_node_path9 = require("path");
|
|
3469
3061
|
async function emitIndex(outDir, hasContracts = false, hasForms = false) {
|
|
3470
3062
|
await (0, import_promises6.mkdir)(outDir, { recursive: true });
|
|
3471
3063
|
const exports2 = ["export * from './pages.js';", "export * from './routes.js';"];
|
|
@@ -3478,12 +3070,12 @@ async function emitIndex(outDir, hasContracts = false, hasForms = false) {
|
|
|
3478
3070
|
const content = ["// Generated by @dudousxd/nestjs-codegen. Do not edit.", ...exports2, ""].join(
|
|
3479
3071
|
"\n"
|
|
3480
3072
|
);
|
|
3481
|
-
await (0, import_promises6.writeFile)((0,
|
|
3073
|
+
await (0, import_promises6.writeFile)((0, import_node_path9.join)(outDir, "index.d.ts"), content, "utf8");
|
|
3482
3074
|
}
|
|
3483
3075
|
|
|
3484
3076
|
// src/emit/emit-pages.ts
|
|
3485
3077
|
var import_promises7 = require("fs/promises");
|
|
3486
|
-
var
|
|
3078
|
+
var import_node_path10 = require("path");
|
|
3487
3079
|
async function emitPages(pages, outDir, _options = {}) {
|
|
3488
3080
|
await (0, import_promises7.mkdir)(outDir, { recursive: true });
|
|
3489
3081
|
const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
|
|
@@ -3504,7 +3096,7 @@ ${augBody}
|
|
|
3504
3096
|
}
|
|
3505
3097
|
${sharedPropsBlock}}
|
|
3506
3098
|
`;
|
|
3507
|
-
await (0, import_promises7.writeFile)((0,
|
|
3099
|
+
await (0, import_promises7.writeFile)((0, import_node_path10.join)(outDir, "pages.d.ts"), content, "utf8");
|
|
3508
3100
|
}
|
|
3509
3101
|
function buildSharedPropsBlock(sharedProps) {
|
|
3510
3102
|
if (!sharedProps) return "";
|
|
@@ -3523,7 +3115,7 @@ ${propsBody}
|
|
|
3523
3115
|
`;
|
|
3524
3116
|
}
|
|
3525
3117
|
function buildAugmentationType(page, outDir) {
|
|
3526
|
-
let importPath = (0,
|
|
3118
|
+
let importPath = (0, import_node_path10.relative)(outDir, page.absolutePath).replace(/\.(tsx?|vue|svelte)$/, "");
|
|
3527
3119
|
if (!importPath.startsWith(".")) {
|
|
3528
3120
|
importPath = `./${importPath}`;
|
|
3529
3121
|
}
|
|
@@ -3535,11 +3127,11 @@ function needsQuotes(name) {
|
|
|
3535
3127
|
|
|
3536
3128
|
// src/emit/emit-routes.ts
|
|
3537
3129
|
var import_promises8 = require("fs/promises");
|
|
3538
|
-
var
|
|
3130
|
+
var import_node_path11 = require("path");
|
|
3539
3131
|
async function emitRoutes(routes, outDir) {
|
|
3540
3132
|
await (0, import_promises8.mkdir)(outDir, { recursive: true });
|
|
3541
3133
|
const content = buildRoutesFile(routes);
|
|
3542
|
-
await (0, import_promises8.writeFile)((0,
|
|
3134
|
+
await (0, import_promises8.writeFile)((0, import_node_path11.join)(outDir, "routes.ts"), content, "utf8");
|
|
3543
3135
|
}
|
|
3544
3136
|
function buildRoutesFile(routes) {
|
|
3545
3137
|
if (routes.length === 0) {
|
|
@@ -3667,30 +3259,7 @@ async function generate(config, inputRoutes = []) {
|
|
|
3667
3259
|
propsExport: pagesConfig.propsExport,
|
|
3668
3260
|
componentNameStrategy: pagesConfig.componentNameStrategy
|
|
3669
3261
|
});
|
|
3670
|
-
|
|
3671
|
-
if (config.app?.moduleEntry) {
|
|
3672
|
-
try {
|
|
3673
|
-
const tsconfigPath = config.app.tsconfig ?? (0, import_node_path11.join)(config.codegen.cwd, "tsconfig.json");
|
|
3674
|
-
let project;
|
|
3675
|
-
try {
|
|
3676
|
-
project = new import_ts_morph9.Project({
|
|
3677
|
-
tsConfigFilePath: tsconfigPath,
|
|
3678
|
-
skipAddingFilesFromTsConfig: true,
|
|
3679
|
-
skipLoadingLibFiles: true,
|
|
3680
|
-
skipFileDependencyResolution: true
|
|
3681
|
-
});
|
|
3682
|
-
} catch {
|
|
3683
|
-
project = new import_ts_morph9.Project({
|
|
3684
|
-
skipAddingFilesFromTsConfig: true,
|
|
3685
|
-
skipLoadingLibFiles: true,
|
|
3686
|
-
skipFileDependencyResolution: true,
|
|
3687
|
-
compilerOptions: { allowJs: true, strict: false }
|
|
3688
|
-
});
|
|
3689
|
-
}
|
|
3690
|
-
sharedProps = discoverSharedProps(project, config.app.moduleEntry);
|
|
3691
|
-
} catch {
|
|
3692
|
-
}
|
|
3693
|
-
}
|
|
3262
|
+
const sharedProps = discoverSharedPropsFromConfig(config);
|
|
3694
3263
|
await emitPages(pages, config.codegen.outDir, {
|
|
3695
3264
|
propsExport: pagesConfig.propsExport,
|
|
3696
3265
|
sharedProps
|
|
@@ -3714,8 +3283,8 @@ async function generate(config, inputRoutes = []) {
|
|
|
3714
3283
|
if (extensions.length > 0) {
|
|
3715
3284
|
const extraFiles = await collectEmittedFiles(extensions, ctx);
|
|
3716
3285
|
for (const file of extraFiles) {
|
|
3717
|
-
const dest = (0,
|
|
3718
|
-
await (0, import_promises9.mkdir)((0,
|
|
3286
|
+
const dest = (0, import_node_path12.join)(config.codegen.outDir, file.path);
|
|
3287
|
+
await (0, import_promises9.mkdir)((0, import_node_path12.dirname)(dest), { recursive: true });
|
|
3719
3288
|
await (0, import_promises9.writeFile)(dest, file.contents, "utf8");
|
|
3720
3289
|
}
|
|
3721
3290
|
}
|
|
@@ -3724,7 +3293,7 @@ async function generate(config, inputRoutes = []) {
|
|
|
3724
3293
|
// src/watch/lock-file.ts
|
|
3725
3294
|
var import_promises10 = require("fs/promises");
|
|
3726
3295
|
var import_promises11 = require("fs/promises");
|
|
3727
|
-
var
|
|
3296
|
+
var import_node_path13 = require("path");
|
|
3728
3297
|
var LOCK_FILE = ".watcher.lock";
|
|
3729
3298
|
function isProcessAlive(pid) {
|
|
3730
3299
|
try {
|
|
@@ -3736,7 +3305,7 @@ function isProcessAlive(pid) {
|
|
|
3736
3305
|
}
|
|
3737
3306
|
async function acquireLock(outDir) {
|
|
3738
3307
|
await (0, import_promises11.mkdir)(outDir, { recursive: true });
|
|
3739
|
-
const lockPath = (0,
|
|
3308
|
+
const lockPath = (0, import_node_path13.join)(outDir, LOCK_FILE);
|
|
3740
3309
|
const lockData = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3741
3310
|
try {
|
|
3742
3311
|
const fd = await (0, import_promises10.open)(lockPath, "wx");
|
|
@@ -3776,7 +3345,7 @@ async function watch(config, onChange) {
|
|
|
3776
3345
|
if (lock === null) {
|
|
3777
3346
|
let holderPid = "unknown";
|
|
3778
3347
|
try {
|
|
3779
|
-
const raw = await (0, import_promises12.readFile)((0,
|
|
3348
|
+
const raw = await (0, import_promises12.readFile)((0, import_node_path14.join)(config.codegen.outDir, ".watcher.lock"), "utf8");
|
|
3780
3349
|
const data = JSON.parse(raw);
|
|
3781
3350
|
if (data.pid !== void 0) holderPid = String(data.pid);
|
|
3782
3351
|
} catch {
|
|
@@ -3804,7 +3373,7 @@ async function watch(config, onChange) {
|
|
|
3804
3373
|
}
|
|
3805
3374
|
let pagesDebounceTimer;
|
|
3806
3375
|
const pagesGlob = config.pages?.glob ?? ".nestjs-codegen-no-pages";
|
|
3807
|
-
const pagesWatcher = import_chokidar.default.watch((0,
|
|
3376
|
+
const pagesWatcher = import_chokidar.default.watch((0, import_node_path14.join)(config.codegen.cwd, pagesGlob), {
|
|
3808
3377
|
ignoreInitial: true,
|
|
3809
3378
|
persistent: true,
|
|
3810
3379
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -3830,7 +3399,7 @@ async function watch(config, onChange) {
|
|
|
3830
3399
|
pagesWatcher.on("change", schedulePagesRegenerate);
|
|
3831
3400
|
pagesWatcher.on("unlink", schedulePagesRegenerate);
|
|
3832
3401
|
let contractsDebounceTimer;
|
|
3833
|
-
const contractsWatcher = import_chokidar.default.watch((0,
|
|
3402
|
+
const contractsWatcher = import_chokidar.default.watch((0, import_node_path14.join)(config.codegen.cwd, config.contracts.glob), {
|
|
3834
3403
|
ignoreInitial: true,
|
|
3835
3404
|
persistent: true,
|
|
3836
3405
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -3860,7 +3429,7 @@ async function watch(config, onChange) {
|
|
|
3860
3429
|
contractsWatcher.on("add", scheduleContractsRegenerate);
|
|
3861
3430
|
contractsWatcher.on("change", scheduleContractsRegenerate);
|
|
3862
3431
|
contractsWatcher.on("unlink", scheduleContractsRegenerate);
|
|
3863
|
-
const formsWatcher = import_chokidar.default.watch((0,
|
|
3432
|
+
const formsWatcher = import_chokidar.default.watch((0, import_node_path14.join)(config.codegen.cwd, config.forms.watch), {
|
|
3864
3433
|
ignoreInitial: true,
|
|
3865
3434
|
persistent: true,
|
|
3866
3435
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|