@dudousxd/nestjs-codegen 0.3.0 → 0.4.1
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 +33 -0
- package/dist/cli/main.cjs +79 -20
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +79 -20
- package/dist/cli/main.js.map +1 -1
- package/dist/extension/index.d.cts +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/{index-oH5t7x4G.d.cts → index-DA4uySjo.d.cts} +29 -1
- package/dist/{index-oH5t7x4G.d.ts → index-DA4uySjo.d.ts} +29 -1
- package/dist/index.cjs +130 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -4
- package/dist/index.d.ts +29 -4
- package/dist/index.js +129 -20
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +78 -19
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.d.cts +1 -1
- package/dist/nest/index.d.ts +1 -1
- package/dist/nest/index.js +78 -19
- package/dist/nest/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# @dudousxd/nestjs-codegen
|
|
2
2
|
|
|
3
|
+
## 0.4.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 6a6be24: perf: memoize type and enum resolution during generation — per-`Project` `WeakMap` caches for `findType`, `resolveTypeRef`'s named-symbol arm, and `resolveEnumValues`, so a type referenced N times is resolved once. Keyed by `Project` so each (watch) run gets a fresh cache; generated output is byte-identical.
|
|
8
|
+
|
|
9
|
+
## 0.4.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- ed04cdc: Validate recursive DTOs instead of degrading them to `unknown`.
|
|
14
|
+
|
|
15
|
+
Self/mutually-recursive `@ValidateNested` DTOs (e.g. a `ColumnFilter` whose `and`/`or`
|
|
16
|
+
reference `ColumnFilter[]`) used to be degraded to `unknown` with a warning, dropping all
|
|
17
|
+
client-side validation for that field. They are now expanded with a real lazy schema:
|
|
18
|
+
|
|
19
|
+
- **zod / valibot** hoist a structural TS `type` alias and annotate the recursive const
|
|
20
|
+
(`z.ZodType<T>` / `v.GenericSchema<T>`) so the implicit-any self-reference cycle is broken;
|
|
21
|
+
the recursion site uses `z.lazy` / `v.lazy`.
|
|
22
|
+
- **arktype** uses the native `this` keyword for self-recursion. Mutual recursion (A ↔ B)
|
|
23
|
+
cannot be expressed per-schema without a scope, so the back-edge schema still degrades to
|
|
24
|
+
`unknown` with a clear warning — use the zod or valibot adapter for full validation there.
|
|
25
|
+
|
|
26
|
+
The over-deep nesting guard is now reported separately ("nesting too deep") instead of being
|
|
27
|
+
mislabelled as recursion. The raw-zod `defineContract` path is unchanged.
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- ed04cdc: Fix array detection for union types. A property typed `unknown | unknown[]` (or any
|
|
32
|
+
union whose text happens to end in `[]`) was mistakenly treated as an array and wrapped
|
|
33
|
+
in `z.array(...)`. Array detection now uses the AST (`ArrayTypeNode`) instead of a
|
|
34
|
+
`.endsWith('[]')` text check, so only genuine `T[]` properties become arrays.
|
|
35
|
+
|
|
3
36
|
## 0.3.0
|
|
4
37
|
|
|
5
38
|
### Minor Changes
|
package/dist/cli/main.cjs
CHANGED
|
@@ -1285,6 +1285,8 @@ function buildFormsFileWithAdapter(routes, outDir, adapter, config) {
|
|
|
1285
1285
|
}
|
|
1286
1286
|
const { globalSchemas, renamesByEntry } = planNestedSchemas(entries);
|
|
1287
1287
|
const irNamed = /* @__PURE__ */ new Map();
|
|
1288
|
+
const irTypeAliases = /* @__PURE__ */ new Map();
|
|
1289
|
+
const irAnnotations = /* @__PURE__ */ new Map();
|
|
1288
1290
|
const decls = [];
|
|
1289
1291
|
const mapEntries = [];
|
|
1290
1292
|
let used = false;
|
|
@@ -1292,6 +1294,8 @@ function buildFormsFileWithAdapter(routes, outDir, adapter, config) {
|
|
|
1292
1294
|
if (src.schema) {
|
|
1293
1295
|
const r = adapter.renderModule(src.schema);
|
|
1294
1296
|
for (const [n, t] of r.namedNestedSchemas) irNamed.set(n, t);
|
|
1297
|
+
if (r.namedTypeAliases) for (const [n, t] of r.namedTypeAliases) irTypeAliases.set(n, t);
|
|
1298
|
+
if (r.namedAnnotations) for (const [n, a] of r.namedAnnotations) irAnnotations.set(n, a);
|
|
1295
1299
|
return { text: r.schemaText };
|
|
1296
1300
|
}
|
|
1297
1301
|
if (src.zodText) {
|
|
@@ -1365,7 +1369,13 @@ function buildFormsFileWithAdapter(routes, outDir, adapter, config) {
|
|
|
1365
1369
|
for (const [n, t] of irNamed) if (!allNested.has(n)) allNested.set(n, t);
|
|
1366
1370
|
if (allNested.size > 0) {
|
|
1367
1371
|
lines.push("// Hoisted nested schemas (shared across endpoints).");
|
|
1368
|
-
for (const [n,
|
|
1372
|
+
for (const [n, alias] of irTypeAliases) {
|
|
1373
|
+
if (allNested.has(n)) lines.push(`${alias};`);
|
|
1374
|
+
}
|
|
1375
|
+
for (const [n, t] of allNested) {
|
|
1376
|
+
const annotation = irAnnotations.get(n);
|
|
1377
|
+
lines.push(`const ${n}${annotation ? `: ${annotation}` : ""} = ${t};`);
|
|
1378
|
+
}
|
|
1369
1379
|
lines.push("");
|
|
1370
1380
|
}
|
|
1371
1381
|
lines.push(...decls);
|
|
@@ -1790,10 +1800,19 @@ function followModuleForType(name, moduleSpecifier, fromFile, project, seen) {
|
|
|
1790
1800
|
}
|
|
1791
1801
|
return null;
|
|
1792
1802
|
}
|
|
1803
|
+
var _findTypeCache = /* @__PURE__ */ new WeakMap();
|
|
1793
1804
|
function findType(name, sourceFile, project) {
|
|
1805
|
+
let byKey = _findTypeCache.get(project);
|
|
1806
|
+
if (byKey === void 0) {
|
|
1807
|
+
byKey = /* @__PURE__ */ new Map();
|
|
1808
|
+
_findTypeCache.set(project, byKey);
|
|
1809
|
+
}
|
|
1810
|
+
const key = `${sourceFile.getFilePath()}\0${name}`;
|
|
1811
|
+
if (byKey.has(key)) return byKey.get(key) ?? null;
|
|
1794
1812
|
const local = findTypeInFile(name, sourceFile);
|
|
1795
|
-
|
|
1796
|
-
|
|
1813
|
+
const result = local ?? resolveImportedType(name, sourceFile, project);
|
|
1814
|
+
byKey.set(key, result);
|
|
1815
|
+
return result;
|
|
1797
1816
|
}
|
|
1798
1817
|
var _NON_REF_NAMES = /* @__PURE__ */ new Set(["string", "number", "boolean", "void", "unknown", "any", "Date"]);
|
|
1799
1818
|
function _localDeclForKinds(name, file, kinds) {
|
|
@@ -1830,6 +1849,26 @@ function resolveTypeRef(nodeOrName, sourceFile, project, opts) {
|
|
|
1830
1849
|
if (_NON_REF_NAMES.has(refName)) return null;
|
|
1831
1850
|
name = refName;
|
|
1832
1851
|
}
|
|
1852
|
+
return _resolveNamedRef(name, sourceFile, project, opts);
|
|
1853
|
+
}
|
|
1854
|
+
var _resolveNamedRefCache = /* @__PURE__ */ new WeakMap();
|
|
1855
|
+
function _resolveNamedRef(name, sourceFile, project, opts) {
|
|
1856
|
+
let byKey = _resolveNamedRefCache.get(project);
|
|
1857
|
+
if (byKey === void 0) {
|
|
1858
|
+
byKey = /* @__PURE__ */ new Map();
|
|
1859
|
+
_resolveNamedRefCache.set(project, byKey);
|
|
1860
|
+
}
|
|
1861
|
+
const kindsKey = [...opts.kinds].sort().join(",");
|
|
1862
|
+
const key = `${sourceFile.getFilePath()}\0${name}\0${kindsKey}\0${opts.allowBareSpecifier ? 1 : 0}`;
|
|
1863
|
+
if (byKey.has(key)) {
|
|
1864
|
+
const cached = byKey.get(key) ?? null;
|
|
1865
|
+
return cached ? { ...cached } : null;
|
|
1866
|
+
}
|
|
1867
|
+
const computed = _computeNamedRef(name, sourceFile, project, opts);
|
|
1868
|
+
byKey.set(key, computed);
|
|
1869
|
+
return computed ? { ...computed } : null;
|
|
1870
|
+
}
|
|
1871
|
+
function _computeNamedRef(name, sourceFile, project, opts) {
|
|
1833
1872
|
if (_localDeclForKinds(name, sourceFile, opts.kinds)) {
|
|
1834
1873
|
return { name, filePath: sourceFile.getFilePath() };
|
|
1835
1874
|
}
|
|
@@ -1907,10 +1946,7 @@ function extractSchemaFromDto(classDecl, sourceFile, project) {
|
|
|
1907
1946
|
depth: 0
|
|
1908
1947
|
};
|
|
1909
1948
|
const root = buildObject(classDecl, sourceFile, ctx);
|
|
1910
|
-
|
|
1911
|
-
ctx.named.set(schemaName, { kind: "unknown", note: "recursive type \u2014 not expanded" });
|
|
1912
|
-
}
|
|
1913
|
-
return { root, named: ctx.named, warnings: ctx.warnings };
|
|
1949
|
+
return { root, named: ctx.named, warnings: ctx.warnings, recursive: ctx.recursiveSchemas };
|
|
1914
1950
|
}
|
|
1915
1951
|
function buildObject(classDecl, classFile, ctx) {
|
|
1916
1952
|
const props = classDecl.getProperties();
|
|
@@ -1930,7 +1966,7 @@ function buildProperty(prop, classFile, ctx) {
|
|
|
1930
1966
|
const dec = (n) => decorators.get(n);
|
|
1931
1967
|
const typeNode = prop.getTypeNode();
|
|
1932
1968
|
const typeText = typeNode?.getText() ?? "unknown";
|
|
1933
|
-
const isArrayType = !!typeNode &&
|
|
1969
|
+
const isArrayType = !!typeNode && import_ts_morph4.Node.isArrayTypeNode(typeNode);
|
|
1934
1970
|
const typeRefName = resolveTypeFactoryName(dec("Type"));
|
|
1935
1971
|
if (has("ValidateNested") || typeRefName) {
|
|
1936
1972
|
const childName = typeRefName ?? singularClassName(typeText);
|
|
@@ -2061,18 +2097,27 @@ function baseFromType(typeText, isArrayType) {
|
|
|
2061
2097
|
}
|
|
2062
2098
|
}
|
|
2063
2099
|
function buildNestedReference(className, fromFile, ctx) {
|
|
2064
|
-
if (ctx.visiting.has(className)
|
|
2100
|
+
if (ctx.visiting.has(className)) {
|
|
2065
2101
|
const reserved = ctx.emittedClasses.get(className) ?? aliasFor(className, ctx);
|
|
2066
2102
|
ctx.emittedClasses.set(className, reserved);
|
|
2067
2103
|
ctx.recursiveSchemas.add(reserved);
|
|
2068
2104
|
if (!ctx.warnedDecorators.has(`recursive:${reserved}`)) {
|
|
2069
2105
|
ctx.warnedDecorators.add(`recursive:${reserved}`);
|
|
2070
|
-
const msg = `${className} is a recursive type
|
|
2106
|
+
const msg = `${className} is a recursive type; the generated schema validates it via a lazy self-reference.`;
|
|
2071
2107
|
ctx.warnings.push(msg);
|
|
2072
2108
|
console.warn(`[nestjs-codegen] ${msg}`);
|
|
2073
2109
|
}
|
|
2074
2110
|
return { kind: "lazyRef", name: reserved };
|
|
2075
2111
|
}
|
|
2112
|
+
if (ctx.depth >= 8) {
|
|
2113
|
+
if (!ctx.warnedDecorators.has(`deep:${className}`)) {
|
|
2114
|
+
ctx.warnedDecorators.add(`deep:${className}`);
|
|
2115
|
+
const msg = `${className} nesting is too deep to expand; the generated schema uses unknown for it.`;
|
|
2116
|
+
ctx.warnings.push(msg);
|
|
2117
|
+
console.warn(`[nestjs-codegen] ${msg}`);
|
|
2118
|
+
}
|
|
2119
|
+
return { kind: "unknown", note: "nesting too deep \u2014 not expanded" };
|
|
2120
|
+
}
|
|
2076
2121
|
const existing = ctx.emittedClasses.get(className);
|
|
2077
2122
|
if (existing) return { kind: "ref", name: existing };
|
|
2078
2123
|
const schemaName = aliasFor(className, ctx);
|
|
@@ -2198,17 +2243,31 @@ var import_ts_morph6 = require("ts-morph");
|
|
|
2198
2243
|
var import_ts_morph5 = require("ts-morph");
|
|
2199
2244
|
|
|
2200
2245
|
// src/discovery/enum-resolution.ts
|
|
2246
|
+
var _enumCache = /* @__PURE__ */ new WeakMap();
|
|
2201
2247
|
function resolveEnumValues(name, sourceFile, project) {
|
|
2248
|
+
let byKey = _enumCache.get(project);
|
|
2249
|
+
if (byKey === void 0) {
|
|
2250
|
+
byKey = /* @__PURE__ */ new Map();
|
|
2251
|
+
_enumCache.set(project, byKey);
|
|
2252
|
+
}
|
|
2253
|
+
const key = `${sourceFile.getFilePath()}\0${name}`;
|
|
2254
|
+
if (byKey.has(key)) {
|
|
2255
|
+
const cached = byKey.get(key) ?? null;
|
|
2256
|
+
return cached ? { values: [...cached.values], numeric: cached.numeric } : null;
|
|
2257
|
+
}
|
|
2202
2258
|
const resolved = findType(name, sourceFile, project);
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
const
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2259
|
+
let result = null;
|
|
2260
|
+
if (resolved && resolved.kind === "enum") {
|
|
2261
|
+
let numeric = true;
|
|
2262
|
+
const values = resolved.members.map((m) => {
|
|
2263
|
+
const parsed = JSON.parse(m);
|
|
2264
|
+
if (typeof parsed === "string") numeric = false;
|
|
2265
|
+
return String(parsed);
|
|
2266
|
+
});
|
|
2267
|
+
if (values.length > 0) result = { values, numeric };
|
|
2268
|
+
}
|
|
2269
|
+
byKey.set(key, result);
|
|
2270
|
+
return result ? { values: [...result.values], numeric: result.numeric } : null;
|
|
2212
2271
|
}
|
|
2213
2272
|
|
|
2214
2273
|
// src/discovery/filter-field-types.ts
|
|
@@ -3478,7 +3537,7 @@ async function watch(config, onChange) {
|
|
|
3478
3537
|
}
|
|
3479
3538
|
|
|
3480
3539
|
// src/index.ts
|
|
3481
|
-
var VERSION = "0.
|
|
3540
|
+
var VERSION = "0.4.1";
|
|
3482
3541
|
|
|
3483
3542
|
// src/cli/codegen.ts
|
|
3484
3543
|
async function runCodegen(opts = {}) {
|