@dudousxd/nestjs-codegen 0.3.0 → 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 CHANGED
@@ -1,5 +1,32 @@
1
1
  # @dudousxd/nestjs-codegen
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ed04cdc: Validate recursive DTOs instead of degrading them to `unknown`.
8
+
9
+ Self/mutually-recursive `@ValidateNested` DTOs (e.g. a `ColumnFilter` whose `and`/`or`
10
+ reference `ColumnFilter[]`) used to be degraded to `unknown` with a warning, dropping all
11
+ client-side validation for that field. They are now expanded with a real lazy schema:
12
+
13
+ - **zod / valibot** hoist a structural TS `type` alias and annotate the recursive const
14
+ (`z.ZodType<T>` / `v.GenericSchema<T>`) so the implicit-any self-reference cycle is broken;
15
+ the recursion site uses `z.lazy` / `v.lazy`.
16
+ - **arktype** uses the native `this` keyword for self-recursion. Mutual recursion (A ↔ B)
17
+ cannot be expressed per-schema without a scope, so the back-edge schema still degrades to
18
+ `unknown` with a clear warning — use the zod or valibot adapter for full validation there.
19
+
20
+ The over-deep nesting guard is now reported separately ("nesting too deep") instead of being
21
+ mislabelled as recursion. The raw-zod `defineContract` path is unchanged.
22
+
23
+ ### Patch Changes
24
+
25
+ - ed04cdc: Fix array detection for union types. A property typed `unknown | unknown[]` (or any
26
+ union whose text happens to end in `[]`) was mistakenly treated as an array and wrapped
27
+ in `z.array(...)`. Array detection now uses the AST (`ArrayTypeNode`) instead of a
28
+ `.endsWith('[]')` text check, so only genuine `T[]` properties become arrays.
29
+
3
30
  ## 0.3.0
4
31
 
5
32
  ### 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, t] of allNested) lines.push(`const ${n} = ${t};`);
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);
@@ -1907,10 +1917,7 @@ function extractSchemaFromDto(classDecl, sourceFile, project) {
1907
1917
  depth: 0
1908
1918
  };
1909
1919
  const root = buildObject(classDecl, sourceFile, ctx);
1910
- for (const schemaName of ctx.recursiveSchemas) {
1911
- ctx.named.set(schemaName, { kind: "unknown", note: "recursive type \u2014 not expanded" });
1912
- }
1913
- return { root, named: ctx.named, warnings: ctx.warnings };
1920
+ return { root, named: ctx.named, warnings: ctx.warnings, recursive: ctx.recursiveSchemas };
1914
1921
  }
1915
1922
  function buildObject(classDecl, classFile, ctx) {
1916
1923
  const props = classDecl.getProperties();
@@ -1930,7 +1937,7 @@ function buildProperty(prop, classFile, ctx) {
1930
1937
  const dec = (n) => decorators.get(n);
1931
1938
  const typeNode = prop.getTypeNode();
1932
1939
  const typeText = typeNode?.getText() ?? "unknown";
1933
- const isArrayType = !!typeNode && typeNode.getText().endsWith("[]");
1940
+ const isArrayType = !!typeNode && import_ts_morph4.Node.isArrayTypeNode(typeNode);
1934
1941
  const typeRefName = resolveTypeFactoryName(dec("Type"));
1935
1942
  if (has("ValidateNested") || typeRefName) {
1936
1943
  const childName = typeRefName ?? singularClassName(typeText);
@@ -2061,18 +2068,27 @@ function baseFromType(typeText, isArrayType) {
2061
2068
  }
2062
2069
  }
2063
2070
  function buildNestedReference(className, fromFile, ctx) {
2064
- if (ctx.visiting.has(className) || ctx.depth >= 8) {
2071
+ if (ctx.visiting.has(className)) {
2065
2072
  const reserved = ctx.emittedClasses.get(className) ?? aliasFor(className, ctx);
2066
2073
  ctx.emittedClasses.set(className, reserved);
2067
2074
  ctx.recursiveSchemas.add(reserved);
2068
2075
  if (!ctx.warnedDecorators.has(`recursive:${reserved}`)) {
2069
2076
  ctx.warnedDecorators.add(`recursive:${reserved}`);
2070
- const msg = `${className} is a recursive type and was not expanded; the generated schema uses unknown for it.`;
2077
+ const msg = `${className} is a recursive type; the generated schema validates it via a lazy self-reference.`;
2071
2078
  ctx.warnings.push(msg);
2072
2079
  console.warn(`[nestjs-codegen] ${msg}`);
2073
2080
  }
2074
2081
  return { kind: "lazyRef", name: reserved };
2075
2082
  }
2083
+ if (ctx.depth >= 8) {
2084
+ if (!ctx.warnedDecorators.has(`deep:${className}`)) {
2085
+ ctx.warnedDecorators.add(`deep:${className}`);
2086
+ const msg = `${className} nesting is too deep to expand; the generated schema uses unknown for it.`;
2087
+ ctx.warnings.push(msg);
2088
+ console.warn(`[nestjs-codegen] ${msg}`);
2089
+ }
2090
+ return { kind: "unknown", note: "nesting too deep \u2014 not expanded" };
2091
+ }
2076
2092
  const existing = ctx.emittedClasses.get(className);
2077
2093
  if (existing) return { kind: "ref", name: existing };
2078
2094
  const schemaName = aliasFor(className, ctx);
@@ -3478,7 +3494,7 @@ async function watch(config, onChange) {
3478
3494
  }
3479
3495
 
3480
3496
  // src/index.ts
3481
- var VERSION = "0.3.0";
3497
+ var VERSION = "0.4.0";
3482
3498
 
3483
3499
  // src/cli/codegen.ts
3484
3500
  async function runCodegen(opts = {}) {