@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/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaModule, b as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext } from './index-oH5t7x4G.cjs';
2
- export { A as AdapterUsage, c as ContractDescriptor, d as ContractSource, e as ControllerRef, N as NumberCheck, f as RenderContext, g as RenderedModule, h as SchemaNode, i as ScopeConfig, j as StringCheck, T as TypeRef, k as ValidationOption, r as resolveAdapter } from './index-oH5t7x4G.cjs';
1
+ import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as SchemaModule, c as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext } from './index-DA4uySjo.cjs';
2
+ export { A as AdapterUsage, d as ContractDescriptor, e as ContractSource, f as ControllerRef, N as NumberCheck, g as RenderContext, h as RenderedModule, i as ScopeConfig, j as StringCheck, T as TypeRef, k as ValidationOption, r as resolveAdapter } from './index-DA4uySjo.cjs';
3
3
  import { ClassDeclaration, SourceFile, Project } from 'ts-morph';
4
4
 
5
5
  declare function defineConfig(c: UserConfig): UserConfig;
@@ -73,6 +73,31 @@ declare function acquireLock(outDir: string): Promise<{
73
73
  release: () => Promise<void>;
74
74
  } | null>;
75
75
 
76
+ /**
77
+ * Renders the neutral {@link SchemaNode} IR to a TypeScript *type* expression
78
+ * (not a validation-lib schema). Used to synthesize the hoisted structural type
79
+ * that annotates a recursive zod/valibot const, breaking the implicit-any
80
+ * inference cycle (`type X = {...}` + `const XSchema: z.ZodType<X> = ...`).
81
+ *
82
+ * References to other named schemas resolve two ways:
83
+ * - a `ref`/`lazyRef` to a *recursive* schema → its type-alias name (so the
84
+ * emitted `type` aliases reference each other, terminating the recursion);
85
+ * - a `ref` to a *non-recursive* schema → inlined structurally (keeps the set
86
+ * of emitted `type` aliases limited to exactly the recursive cluster).
87
+ * Inlining always terminates: every reference cycle passes through a recursive
88
+ * name, which is rendered by alias rather than expanded.
89
+ */
90
+
91
+ interface TsTypeContext {
92
+ /** All hoisted named schemas (for inlining non-recursive refs). */
93
+ named: Map<string, SchemaNode>;
94
+ /** Names that are genuinely recursive (rendered by alias, never inlined). */
95
+ recursive: Set<string>;
96
+ /** schema const name (e.g. `ColumnFilterSchema`) → TS type-alias name. */
97
+ typeNameFor: (schemaName: string) => string;
98
+ }
99
+ declare function renderTsType(node: SchemaNode, ctx: TsTypeContext): string;
100
+
76
101
  /**
77
102
  * Pure-AST translation of class-validator-decorated DTO classes into the neutral
78
103
  * {@link SchemaModule} IR. Reads decorator names + literal args via ts-morph — it
@@ -139,6 +164,6 @@ interface FastDiscoveryOptions {
139
164
  }
140
165
  declare function discoverContractsFast(opts: FastDiscoveryOptions): Promise<RouteDescriptor[]>;
141
166
 
142
- declare const VERSION = "0.3.0";
167
+ declare const VERSION = "0.4.1";
143
168
 
144
- export { CodegenError, ConfigError, type FastDiscoveryOptions, ResolvedConfig, RouteDescriptor, SchemaModule, UserConfig, VERSION, ValidationAdapter, type Watcher, acquireLock, defineConfig, discoverContractsFast, emitApi, emitForms, emitRoutes, extractSchemaFromDto, generate, loadConfig, resolveConfig, watch };
169
+ export { CodegenError, ConfigError, type FastDiscoveryOptions, ResolvedConfig, RouteDescriptor, SchemaModule, SchemaNode, type TsTypeContext, UserConfig, VERSION, ValidationAdapter, type Watcher, acquireLock, defineConfig, discoverContractsFast, emitApi, emitForms, emitRoutes, extractSchemaFromDto, generate, loadConfig, renderTsType, resolveConfig, watch };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaModule, b as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext } from './index-oH5t7x4G.js';
2
- export { A as AdapterUsage, c as ContractDescriptor, d as ContractSource, e as ControllerRef, N as NumberCheck, f as RenderContext, g as RenderedModule, h as SchemaNode, i as ScopeConfig, j as StringCheck, T as TypeRef, k as ValidationOption, r as resolveAdapter } from './index-oH5t7x4G.js';
1
+ import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as SchemaModule, c as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext } from './index-DA4uySjo.js';
2
+ export { A as AdapterUsage, d as ContractDescriptor, e as ContractSource, f as ControllerRef, N as NumberCheck, g as RenderContext, h as RenderedModule, i as ScopeConfig, j as StringCheck, T as TypeRef, k as ValidationOption, r as resolveAdapter } from './index-DA4uySjo.js';
3
3
  import { ClassDeclaration, SourceFile, Project } from 'ts-morph';
4
4
 
5
5
  declare function defineConfig(c: UserConfig): UserConfig;
@@ -73,6 +73,31 @@ declare function acquireLock(outDir: string): Promise<{
73
73
  release: () => Promise<void>;
74
74
  } | null>;
75
75
 
76
+ /**
77
+ * Renders the neutral {@link SchemaNode} IR to a TypeScript *type* expression
78
+ * (not a validation-lib schema). Used to synthesize the hoisted structural type
79
+ * that annotates a recursive zod/valibot const, breaking the implicit-any
80
+ * inference cycle (`type X = {...}` + `const XSchema: z.ZodType<X> = ...`).
81
+ *
82
+ * References to other named schemas resolve two ways:
83
+ * - a `ref`/`lazyRef` to a *recursive* schema → its type-alias name (so the
84
+ * emitted `type` aliases reference each other, terminating the recursion);
85
+ * - a `ref` to a *non-recursive* schema → inlined structurally (keeps the set
86
+ * of emitted `type` aliases limited to exactly the recursive cluster).
87
+ * Inlining always terminates: every reference cycle passes through a recursive
88
+ * name, which is rendered by alias rather than expanded.
89
+ */
90
+
91
+ interface TsTypeContext {
92
+ /** All hoisted named schemas (for inlining non-recursive refs). */
93
+ named: Map<string, SchemaNode>;
94
+ /** Names that are genuinely recursive (rendered by alias, never inlined). */
95
+ recursive: Set<string>;
96
+ /** schema const name (e.g. `ColumnFilterSchema`) → TS type-alias name. */
97
+ typeNameFor: (schemaName: string) => string;
98
+ }
99
+ declare function renderTsType(node: SchemaNode, ctx: TsTypeContext): string;
100
+
76
101
  /**
77
102
  * Pure-AST translation of class-validator-decorated DTO classes into the neutral
78
103
  * {@link SchemaModule} IR. Reads decorator names + literal args via ts-morph — it
@@ -139,6 +164,6 @@ interface FastDiscoveryOptions {
139
164
  }
140
165
  declare function discoverContractsFast(opts: FastDiscoveryOptions): Promise<RouteDescriptor[]>;
141
166
 
142
- declare const VERSION = "0.3.0";
167
+ declare const VERSION = "0.4.1";
143
168
 
144
- export { CodegenError, ConfigError, type FastDiscoveryOptions, ResolvedConfig, RouteDescriptor, SchemaModule, UserConfig, VERSION, ValidationAdapter, type Watcher, acquireLock, defineConfig, discoverContractsFast, emitApi, emitForms, emitRoutes, extractSchemaFromDto, generate, loadConfig, resolveConfig, watch };
169
+ export { CodegenError, ConfigError, type FastDiscoveryOptions, ResolvedConfig, RouteDescriptor, SchemaModule, SchemaNode, type TsTypeContext, UserConfig, VERSION, ValidationAdapter, type Watcher, acquireLock, defineConfig, discoverContractsFast, emitApi, emitForms, emitRoutes, extractSchemaFromDto, generate, loadConfig, renderTsType, resolveConfig, watch };
package/dist/index.js CHANGED
@@ -1256,6 +1256,8 @@ function buildFormsFileWithAdapter(routes, outDir, adapter, config) {
1256
1256
  }
1257
1257
  const { globalSchemas, renamesByEntry } = planNestedSchemas(entries);
1258
1258
  const irNamed = /* @__PURE__ */ new Map();
1259
+ const irTypeAliases = /* @__PURE__ */ new Map();
1260
+ const irAnnotations = /* @__PURE__ */ new Map();
1259
1261
  const decls = [];
1260
1262
  const mapEntries = [];
1261
1263
  let used = false;
@@ -1263,6 +1265,8 @@ function buildFormsFileWithAdapter(routes, outDir, adapter, config) {
1263
1265
  if (src.schema) {
1264
1266
  const r = adapter.renderModule(src.schema);
1265
1267
  for (const [n, t] of r.namedNestedSchemas) irNamed.set(n, t);
1268
+ if (r.namedTypeAliases) for (const [n, t] of r.namedTypeAliases) irTypeAliases.set(n, t);
1269
+ if (r.namedAnnotations) for (const [n, a] of r.namedAnnotations) irAnnotations.set(n, a);
1266
1270
  return { text: r.schemaText };
1267
1271
  }
1268
1272
  if (src.zodText) {
@@ -1336,7 +1340,13 @@ function buildFormsFileWithAdapter(routes, outDir, adapter, config) {
1336
1340
  for (const [n, t] of irNamed) if (!allNested.has(n)) allNested.set(n, t);
1337
1341
  if (allNested.size > 0) {
1338
1342
  lines.push("// Hoisted nested schemas (shared across endpoints).");
1339
- for (const [n, t] of allNested) lines.push(`const ${n} = ${t};`);
1343
+ for (const [n, alias] of irTypeAliases) {
1344
+ if (allNested.has(n)) lines.push(`${alias};`);
1345
+ }
1346
+ for (const [n, t] of allNested) {
1347
+ const annotation = irAnnotations.get(n);
1348
+ lines.push(`const ${n}${annotation ? `: ${annotation}` : ""} = ${t};`);
1349
+ }
1340
1350
  lines.push("");
1341
1351
  }
1342
1352
  lines.push(...decls);
@@ -1771,10 +1781,19 @@ function followModuleForType(name, moduleSpecifier, fromFile, project, seen) {
1771
1781
  }
1772
1782
  return null;
1773
1783
  }
1784
+ var _findTypeCache = /* @__PURE__ */ new WeakMap();
1774
1785
  function findType(name, sourceFile, project) {
1786
+ let byKey = _findTypeCache.get(project);
1787
+ if (byKey === void 0) {
1788
+ byKey = /* @__PURE__ */ new Map();
1789
+ _findTypeCache.set(project, byKey);
1790
+ }
1791
+ const key = `${sourceFile.getFilePath()}\0${name}`;
1792
+ if (byKey.has(key)) return byKey.get(key) ?? null;
1775
1793
  const local = findTypeInFile(name, sourceFile);
1776
- if (local) return local;
1777
- return resolveImportedType(name, sourceFile, project);
1794
+ const result = local ?? resolveImportedType(name, sourceFile, project);
1795
+ byKey.set(key, result);
1796
+ return result;
1778
1797
  }
1779
1798
  var _NON_REF_NAMES = /* @__PURE__ */ new Set(["string", "number", "boolean", "void", "unknown", "any", "Date"]);
1780
1799
  function _localDeclForKinds(name, file, kinds) {
@@ -1811,6 +1830,26 @@ function resolveTypeRef(nodeOrName, sourceFile, project, opts) {
1811
1830
  if (_NON_REF_NAMES.has(refName)) return null;
1812
1831
  name = refName;
1813
1832
  }
1833
+ return _resolveNamedRef(name, sourceFile, project, opts);
1834
+ }
1835
+ var _resolveNamedRefCache = /* @__PURE__ */ new WeakMap();
1836
+ function _resolveNamedRef(name, sourceFile, project, opts) {
1837
+ let byKey = _resolveNamedRefCache.get(project);
1838
+ if (byKey === void 0) {
1839
+ byKey = /* @__PURE__ */ new Map();
1840
+ _resolveNamedRefCache.set(project, byKey);
1841
+ }
1842
+ const kindsKey = [...opts.kinds].sort().join(",");
1843
+ const key = `${sourceFile.getFilePath()}\0${name}\0${kindsKey}\0${opts.allowBareSpecifier ? 1 : 0}`;
1844
+ if (byKey.has(key)) {
1845
+ const cached = byKey.get(key) ?? null;
1846
+ return cached ? { ...cached } : null;
1847
+ }
1848
+ const computed = _computeNamedRef(name, sourceFile, project, opts);
1849
+ byKey.set(key, computed);
1850
+ return computed ? { ...computed } : null;
1851
+ }
1852
+ function _computeNamedRef(name, sourceFile, project, opts) {
1814
1853
  if (_localDeclForKinds(name, sourceFile, opts.kinds)) {
1815
1854
  return { name, filePath: sourceFile.getFilePath() };
1816
1855
  }
@@ -1888,10 +1927,7 @@ function extractSchemaFromDto(classDecl, sourceFile, project) {
1888
1927
  depth: 0
1889
1928
  };
1890
1929
  const root = buildObject(classDecl, sourceFile, ctx);
1891
- for (const schemaName of ctx.recursiveSchemas) {
1892
- ctx.named.set(schemaName, { kind: "unknown", note: "recursive type \u2014 not expanded" });
1893
- }
1894
- return { root, named: ctx.named, warnings: ctx.warnings };
1930
+ return { root, named: ctx.named, warnings: ctx.warnings, recursive: ctx.recursiveSchemas };
1895
1931
  }
1896
1932
  function buildObject(classDecl, classFile, ctx) {
1897
1933
  const props = classDecl.getProperties();
@@ -1911,7 +1947,7 @@ function buildProperty(prop, classFile, ctx) {
1911
1947
  const dec = (n) => decorators.get(n);
1912
1948
  const typeNode = prop.getTypeNode();
1913
1949
  const typeText = typeNode?.getText() ?? "unknown";
1914
- const isArrayType = !!typeNode && typeNode.getText().endsWith("[]");
1950
+ const isArrayType = !!typeNode && Node3.isArrayTypeNode(typeNode);
1915
1951
  const typeRefName = resolveTypeFactoryName(dec("Type"));
1916
1952
  if (has("ValidateNested") || typeRefName) {
1917
1953
  const childName = typeRefName ?? singularClassName(typeText);
@@ -2042,18 +2078,27 @@ function baseFromType(typeText, isArrayType) {
2042
2078
  }
2043
2079
  }
2044
2080
  function buildNestedReference(className, fromFile, ctx) {
2045
- if (ctx.visiting.has(className) || ctx.depth >= 8) {
2081
+ if (ctx.visiting.has(className)) {
2046
2082
  const reserved = ctx.emittedClasses.get(className) ?? aliasFor(className, ctx);
2047
2083
  ctx.emittedClasses.set(className, reserved);
2048
2084
  ctx.recursiveSchemas.add(reserved);
2049
2085
  if (!ctx.warnedDecorators.has(`recursive:${reserved}`)) {
2050
2086
  ctx.warnedDecorators.add(`recursive:${reserved}`);
2051
- const msg = `${className} is a recursive type and was not expanded; the generated schema uses unknown for it.`;
2087
+ const msg = `${className} is a recursive type; the generated schema validates it via a lazy self-reference.`;
2052
2088
  ctx.warnings.push(msg);
2053
2089
  console.warn(`[nestjs-codegen] ${msg}`);
2054
2090
  }
2055
2091
  return { kind: "lazyRef", name: reserved };
2056
2092
  }
2093
+ if (ctx.depth >= 8) {
2094
+ if (!ctx.warnedDecorators.has(`deep:${className}`)) {
2095
+ ctx.warnedDecorators.add(`deep:${className}`);
2096
+ const msg = `${className} nesting is too deep to expand; the generated schema uses unknown for it.`;
2097
+ ctx.warnings.push(msg);
2098
+ console.warn(`[nestjs-codegen] ${msg}`);
2099
+ }
2100
+ return { kind: "unknown", note: "nesting too deep \u2014 not expanded" };
2101
+ }
2057
2102
  const existing = ctx.emittedClasses.get(className);
2058
2103
  if (existing) return { kind: "ref", name: existing };
2059
2104
  const schemaName = aliasFor(className, ctx);
@@ -2184,17 +2229,31 @@ import {
2184
2229
  } from "ts-morph";
2185
2230
 
2186
2231
  // src/discovery/enum-resolution.ts
2232
+ var _enumCache = /* @__PURE__ */ new WeakMap();
2187
2233
  function resolveEnumValues(name, sourceFile, project) {
2234
+ let byKey = _enumCache.get(project);
2235
+ if (byKey === void 0) {
2236
+ byKey = /* @__PURE__ */ new Map();
2237
+ _enumCache.set(project, byKey);
2238
+ }
2239
+ const key = `${sourceFile.getFilePath()}\0${name}`;
2240
+ if (byKey.has(key)) {
2241
+ const cached = byKey.get(key) ?? null;
2242
+ return cached ? { values: [...cached.values], numeric: cached.numeric } : null;
2243
+ }
2188
2244
  const resolved = findType(name, sourceFile, project);
2189
- if (!resolved || resolved.kind !== "enum") return null;
2190
- let numeric = true;
2191
- const values = resolved.members.map((m) => {
2192
- const parsed = JSON.parse(m);
2193
- if (typeof parsed === "string") numeric = false;
2194
- return String(parsed);
2195
- });
2196
- if (values.length === 0) return null;
2197
- return { values, numeric };
2245
+ let result = null;
2246
+ if (resolved && resolved.kind === "enum") {
2247
+ let numeric = true;
2248
+ const values = resolved.members.map((m) => {
2249
+ const parsed = JSON.parse(m);
2250
+ if (typeof parsed === "string") numeric = false;
2251
+ return String(parsed);
2252
+ });
2253
+ if (values.length > 0) result = { values, numeric };
2254
+ }
2255
+ byKey.set(key, result);
2256
+ return result ? { values: [...result.values], numeric: result.numeric } : null;
2198
2257
  }
2199
2258
 
2200
2259
  // src/discovery/filter-field-types.ts
@@ -3463,8 +3522,57 @@ async function watch(config, onChange) {
3463
3522
  };
3464
3523
  }
3465
3524
 
3525
+ // src/ir/render-ts-type.ts
3526
+ function tsKey(name) {
3527
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
3528
+ }
3529
+ function renderTsType(node, ctx) {
3530
+ switch (node.kind) {
3531
+ case "string":
3532
+ return "string";
3533
+ case "number":
3534
+ return "number";
3535
+ case "boolean":
3536
+ return "boolean";
3537
+ case "date":
3538
+ return "Date";
3539
+ case "unknown":
3540
+ return "unknown";
3541
+ case "instanceof":
3542
+ return node.ctor;
3543
+ case "enum":
3544
+ return node.literals.join(" | ");
3545
+ case "literal":
3546
+ return node.raw;
3547
+ case "union":
3548
+ return node.options.map((o) => renderTsType(o, ctx)).join(" | ");
3549
+ case "array":
3550
+ return `Array<${renderTsType(node.element, ctx)}>`;
3551
+ case "optional":
3552
+ return `${renderTsType(node.inner, ctx)} | undefined`;
3553
+ case "annotated":
3554
+ return renderTsType(node.inner, ctx);
3555
+ case "object": {
3556
+ if (node.fields.length === 0) return node.passthrough ? "Record<string, unknown>" : "{}";
3557
+ const inner = node.fields.map((f) => {
3558
+ if (f.value.kind === "optional") {
3559
+ return `${tsKey(f.key)}?: ${renderTsType(f.value.inner, ctx)}`;
3560
+ }
3561
+ return `${tsKey(f.key)}: ${renderTsType(f.value, ctx)}`;
3562
+ }).join("; ");
3563
+ return `{ ${inner} }`;
3564
+ }
3565
+ case "ref":
3566
+ case "lazyRef": {
3567
+ if (ctx.recursive.has(node.name)) return ctx.typeNameFor(node.name);
3568
+ const target = ctx.named.get(node.name);
3569
+ return target ? renderTsType(target, ctx) : "unknown";
3570
+ }
3571
+ }
3572
+ }
3573
+
3466
3574
  // src/index.ts
3467
- var VERSION = "0.3.0";
3575
+ var VERSION = "0.4.1";
3468
3576
  export {
3469
3577
  CodegenError,
3470
3578
  ConfigError,
@@ -3478,6 +3586,7 @@ export {
3478
3586
  extractSchemaFromDto,
3479
3587
  generate,
3480
3588
  loadConfig,
3589
+ renderTsType,
3481
3590
  resolveAdapter,
3482
3591
  resolveConfig,
3483
3592
  watch