@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/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.0";
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.0";
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);
@@ -1888,10 +1898,7 @@ function extractSchemaFromDto(classDecl, sourceFile, project) {
1888
1898
  depth: 0
1889
1899
  };
1890
1900
  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 };
1901
+ return { root, named: ctx.named, warnings: ctx.warnings, recursive: ctx.recursiveSchemas };
1895
1902
  }
1896
1903
  function buildObject(classDecl, classFile, ctx) {
1897
1904
  const props = classDecl.getProperties();
@@ -1911,7 +1918,7 @@ function buildProperty(prop, classFile, ctx) {
1911
1918
  const dec = (n) => decorators.get(n);
1912
1919
  const typeNode = prop.getTypeNode();
1913
1920
  const typeText = typeNode?.getText() ?? "unknown";
1914
- const isArrayType = !!typeNode && typeNode.getText().endsWith("[]");
1921
+ const isArrayType = !!typeNode && Node3.isArrayTypeNode(typeNode);
1915
1922
  const typeRefName = resolveTypeFactoryName(dec("Type"));
1916
1923
  if (has("ValidateNested") || typeRefName) {
1917
1924
  const childName = typeRefName ?? singularClassName(typeText);
@@ -2042,18 +2049,27 @@ function baseFromType(typeText, isArrayType) {
2042
2049
  }
2043
2050
  }
2044
2051
  function buildNestedReference(className, fromFile, ctx) {
2045
- if (ctx.visiting.has(className) || ctx.depth >= 8) {
2052
+ if (ctx.visiting.has(className)) {
2046
2053
  const reserved = ctx.emittedClasses.get(className) ?? aliasFor(className, ctx);
2047
2054
  ctx.emittedClasses.set(className, reserved);
2048
2055
  ctx.recursiveSchemas.add(reserved);
2049
2056
  if (!ctx.warnedDecorators.has(`recursive:${reserved}`)) {
2050
2057
  ctx.warnedDecorators.add(`recursive:${reserved}`);
2051
- const msg = `${className} is a recursive type and was not expanded; the generated schema uses unknown for it.`;
2058
+ const msg = `${className} is a recursive type; the generated schema validates it via a lazy self-reference.`;
2052
2059
  ctx.warnings.push(msg);
2053
2060
  console.warn(`[nestjs-codegen] ${msg}`);
2054
2061
  }
2055
2062
  return { kind: "lazyRef", name: reserved };
2056
2063
  }
2064
+ if (ctx.depth >= 8) {
2065
+ if (!ctx.warnedDecorators.has(`deep:${className}`)) {
2066
+ ctx.warnedDecorators.add(`deep:${className}`);
2067
+ const msg = `${className} nesting is too deep to expand; the generated schema uses unknown for it.`;
2068
+ ctx.warnings.push(msg);
2069
+ console.warn(`[nestjs-codegen] ${msg}`);
2070
+ }
2071
+ return { kind: "unknown", note: "nesting too deep \u2014 not expanded" };
2072
+ }
2057
2073
  const existing = ctx.emittedClasses.get(className);
2058
2074
  if (existing) return { kind: "ref", name: existing };
2059
2075
  const schemaName = aliasFor(className, ctx);
@@ -3463,8 +3479,57 @@ async function watch(config, onChange) {
3463
3479
  };
3464
3480
  }
3465
3481
 
3482
+ // src/ir/render-ts-type.ts
3483
+ function tsKey(name) {
3484
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
3485
+ }
3486
+ function renderTsType(node, ctx) {
3487
+ switch (node.kind) {
3488
+ case "string":
3489
+ return "string";
3490
+ case "number":
3491
+ return "number";
3492
+ case "boolean":
3493
+ return "boolean";
3494
+ case "date":
3495
+ return "Date";
3496
+ case "unknown":
3497
+ return "unknown";
3498
+ case "instanceof":
3499
+ return node.ctor;
3500
+ case "enum":
3501
+ return node.literals.join(" | ");
3502
+ case "literal":
3503
+ return node.raw;
3504
+ case "union":
3505
+ return node.options.map((o) => renderTsType(o, ctx)).join(" | ");
3506
+ case "array":
3507
+ return `Array<${renderTsType(node.element, ctx)}>`;
3508
+ case "optional":
3509
+ return `${renderTsType(node.inner, ctx)} | undefined`;
3510
+ case "annotated":
3511
+ return renderTsType(node.inner, ctx);
3512
+ case "object": {
3513
+ if (node.fields.length === 0) return node.passthrough ? "Record<string, unknown>" : "{}";
3514
+ const inner = node.fields.map((f) => {
3515
+ if (f.value.kind === "optional") {
3516
+ return `${tsKey(f.key)}?: ${renderTsType(f.value.inner, ctx)}`;
3517
+ }
3518
+ return `${tsKey(f.key)}: ${renderTsType(f.value, ctx)}`;
3519
+ }).join("; ");
3520
+ return `{ ${inner} }`;
3521
+ }
3522
+ case "ref":
3523
+ case "lazyRef": {
3524
+ if (ctx.recursive.has(node.name)) return ctx.typeNameFor(node.name);
3525
+ const target = ctx.named.get(node.name);
3526
+ return target ? renderTsType(target, ctx) : "unknown";
3527
+ }
3528
+ }
3529
+ }
3530
+
3466
3531
  // src/index.ts
3467
- var VERSION = "0.3.0";
3532
+ var VERSION = "0.4.0";
3468
3533
  export {
3469
3534
  CodegenError,
3470
3535
  ConfigError,
@@ -3478,6 +3543,7 @@ export {
3478
3543
  extractSchemaFromDto,
3479
3544
  generate,
3480
3545
  loadConfig,
3546
+ renderTsType,
3481
3547
  resolveAdapter,
3482
3548
  resolveConfig,
3483
3549
  watch