@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 +27 -0
- package/dist/cli/main.cjs +25 -9
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +25 -9
- 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 +76 -9
- 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 +75 -9
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +24 -8
- 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 +24 -8
- package/dist/nest/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { l as ApiClientLayer, m as ApiHeaderContribution, n as ApiModuleDeps, C as CodegenExtension, o as EmittedFile, E as ExtensionContext, L as LeafModel, p as RequestModel, q as RequestShape, s as defineExtension, t as requestShape } from '../index-
|
|
1
|
+
export { l as ApiClientLayer, m as ApiHeaderContribution, n as ApiModuleDeps, C as CodegenExtension, o as EmittedFile, E as ExtensionContext, L as LeafModel, p as RequestModel, q as RequestShape, s as defineExtension, t as requestShape } from '../index-DA4uySjo.cjs';
|
|
2
2
|
import 'ts-morph';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { l as ApiClientLayer, m as ApiHeaderContribution, n as ApiModuleDeps, C as CodegenExtension, o as EmittedFile, E as ExtensionContext, L as LeafModel, p as RequestModel, q as RequestShape, s as defineExtension, t as requestShape } from '../index-
|
|
1
|
+
export { l as ApiClientLayer, m as ApiHeaderContribution, n as ApiModuleDeps, C as CodegenExtension, o as EmittedFile, E as ExtensionContext, L as LeafModel, p as RequestModel, q as RequestShape, s as defineExtension, t as requestShape } from '../index-DA4uySjo.js';
|
|
2
2
|
import 'ts-morph';
|
|
@@ -111,6 +111,14 @@ interface SchemaModule {
|
|
|
111
111
|
root: SchemaNode;
|
|
112
112
|
named: Map<string, SchemaNode>;
|
|
113
113
|
warnings: string[];
|
|
114
|
+
/**
|
|
115
|
+
* Names (keys of {@link named}) that are genuinely self/mutually recursive,
|
|
116
|
+
* i.e. reachable from themselves through a `lazyRef` back-edge. Adapters use
|
|
117
|
+
* this to break the TypeScript inference cycle (annotated const + hoisted
|
|
118
|
+
* structural type for zod/valibot; `this`/degrade for arktype). Absent or
|
|
119
|
+
* empty means no recursion.
|
|
120
|
+
*/
|
|
121
|
+
recursive?: Set<string>;
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
/** Signals to an adapter what it must import to render the current output. */
|
|
@@ -121,12 +129,32 @@ interface AdapterUsage {
|
|
|
121
129
|
interface RenderContext {
|
|
122
130
|
/** Hoisted named schemas being emitted alongside the root. */
|
|
123
131
|
named: Map<string, SchemaNode>;
|
|
132
|
+
/**
|
|
133
|
+
* Name of the hoisted schema currently being rendered, when rendering a named
|
|
134
|
+
* schema's own body. Lets an adapter express a self-reference specially (e.g.
|
|
135
|
+
* arktype's `this` keyword). Absent when rendering the root.
|
|
136
|
+
*/
|
|
137
|
+
selfName?: string;
|
|
124
138
|
}
|
|
125
139
|
interface RenderedModule {
|
|
126
140
|
/** Root schema source text, e.g. `"z.object({ email: z.string().email() })"`. */
|
|
127
141
|
schemaText: string;
|
|
128
142
|
/** name → schema source text, hoisted above the parent. */
|
|
129
143
|
namedNestedSchemas: Map<string, string>;
|
|
144
|
+
/**
|
|
145
|
+
* name → hoisted TS `type` alias *body* for a recursive schema (e.g.
|
|
146
|
+
* `"{ field?: string; and?: Array<ColumnFilter> }"`), emitted as
|
|
147
|
+
* `type <TypeName> = <body>;` above the const. The alias name is derived from
|
|
148
|
+
* the schema name. Absent for adapters/schemas that don't need it (arktype,
|
|
149
|
+
* non-recursive schemas).
|
|
150
|
+
*/
|
|
151
|
+
namedTypeAliases?: Map<string, string>;
|
|
152
|
+
/**
|
|
153
|
+
* name → const type annotation for a recursive schema (e.g.
|
|
154
|
+
* `"z.ZodType<ColumnFilter>"`), emitted as `const <name>: <annotation> = ...`
|
|
155
|
+
* to break the implicit-any self-reference cycle.
|
|
156
|
+
*/
|
|
157
|
+
namedAnnotations?: Map<string, string>;
|
|
130
158
|
warnings: string[];
|
|
131
159
|
}
|
|
132
160
|
/**
|
|
@@ -521,4 +549,4 @@ declare function requestShape(route: RouteDescriptor): RequestShape;
|
|
|
521
549
|
/** Identity helper for authoring extensions with full type inference. */
|
|
522
550
|
declare function defineExtension(ext: CodegenExtension): CodegenExtension;
|
|
523
551
|
|
|
524
|
-
export { type AdapterUsage as A, type CodegenExtension as C, type ExtensionContext as E, type LeafModel as L, type NumberCheck as N, type ResolvedConfig as R, type
|
|
552
|
+
export { type AdapterUsage as A, type CodegenExtension as C, type ExtensionContext as E, type LeafModel as L, type NumberCheck as N, type ResolvedConfig as R, type SchemaNode as S, type TypeRef as T, type UserConfig as U, type ValidationAdapter as V, type RouteDescriptor as a, type SchemaModule as b, type ResolvedFormsConfig as c, type ContractDescriptor as d, type ContractSource as e, type ControllerRef as f, type RenderContext as g, type RenderedModule as h, type ScopeConfig as i, type StringCheck as j, type ValidationOption as k, type ApiClientLayer as l, type ApiHeaderContribution as m, type ApiModuleDeps as n, type EmittedFile as o, type RequestModel as p, type RequestShape as q, resolveAdapter as r, defineExtension as s, requestShape as t };
|
|
@@ -111,6 +111,14 @@ interface SchemaModule {
|
|
|
111
111
|
root: SchemaNode;
|
|
112
112
|
named: Map<string, SchemaNode>;
|
|
113
113
|
warnings: string[];
|
|
114
|
+
/**
|
|
115
|
+
* Names (keys of {@link named}) that are genuinely self/mutually recursive,
|
|
116
|
+
* i.e. reachable from themselves through a `lazyRef` back-edge. Adapters use
|
|
117
|
+
* this to break the TypeScript inference cycle (annotated const + hoisted
|
|
118
|
+
* structural type for zod/valibot; `this`/degrade for arktype). Absent or
|
|
119
|
+
* empty means no recursion.
|
|
120
|
+
*/
|
|
121
|
+
recursive?: Set<string>;
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
/** Signals to an adapter what it must import to render the current output. */
|
|
@@ -121,12 +129,32 @@ interface AdapterUsage {
|
|
|
121
129
|
interface RenderContext {
|
|
122
130
|
/** Hoisted named schemas being emitted alongside the root. */
|
|
123
131
|
named: Map<string, SchemaNode>;
|
|
132
|
+
/**
|
|
133
|
+
* Name of the hoisted schema currently being rendered, when rendering a named
|
|
134
|
+
* schema's own body. Lets an adapter express a self-reference specially (e.g.
|
|
135
|
+
* arktype's `this` keyword). Absent when rendering the root.
|
|
136
|
+
*/
|
|
137
|
+
selfName?: string;
|
|
124
138
|
}
|
|
125
139
|
interface RenderedModule {
|
|
126
140
|
/** Root schema source text, e.g. `"z.object({ email: z.string().email() })"`. */
|
|
127
141
|
schemaText: string;
|
|
128
142
|
/** name → schema source text, hoisted above the parent. */
|
|
129
143
|
namedNestedSchemas: Map<string, string>;
|
|
144
|
+
/**
|
|
145
|
+
* name → hoisted TS `type` alias *body* for a recursive schema (e.g.
|
|
146
|
+
* `"{ field?: string; and?: Array<ColumnFilter> }"`), emitted as
|
|
147
|
+
* `type <TypeName> = <body>;` above the const. The alias name is derived from
|
|
148
|
+
* the schema name. Absent for adapters/schemas that don't need it (arktype,
|
|
149
|
+
* non-recursive schemas).
|
|
150
|
+
*/
|
|
151
|
+
namedTypeAliases?: Map<string, string>;
|
|
152
|
+
/**
|
|
153
|
+
* name → const type annotation for a recursive schema (e.g.
|
|
154
|
+
* `"z.ZodType<ColumnFilter>"`), emitted as `const <name>: <annotation> = ...`
|
|
155
|
+
* to break the implicit-any self-reference cycle.
|
|
156
|
+
*/
|
|
157
|
+
namedAnnotations?: Map<string, string>;
|
|
130
158
|
warnings: string[];
|
|
131
159
|
}
|
|
132
160
|
/**
|
|
@@ -521,4 +549,4 @@ declare function requestShape(route: RouteDescriptor): RequestShape;
|
|
|
521
549
|
/** Identity helper for authoring extensions with full type inference. */
|
|
522
550
|
declare function defineExtension(ext: CodegenExtension): CodegenExtension;
|
|
523
551
|
|
|
524
|
-
export { type AdapterUsage as A, type CodegenExtension as C, type ExtensionContext as E, type LeafModel as L, type NumberCheck as N, type ResolvedConfig as R, type
|
|
552
|
+
export { type AdapterUsage as A, type CodegenExtension as C, type ExtensionContext as E, type LeafModel as L, type NumberCheck as N, type ResolvedConfig as R, type SchemaNode as S, type TypeRef as T, type UserConfig as U, type ValidationAdapter as V, type RouteDescriptor as a, type SchemaModule as b, type ResolvedFormsConfig as c, type ContractDescriptor as d, type ContractSource as e, type ControllerRef as f, type RenderContext as g, type RenderedModule as h, type ScopeConfig as i, type StringCheck as j, type ValidationOption as k, type ApiClientLayer as l, type ApiHeaderContribution as m, type ApiModuleDeps as n, type EmittedFile as o, type RequestModel as p, type RequestShape as q, resolveAdapter as r, defineExtension as s, requestShape as t };
|
package/dist/index.cjs
CHANGED
|
@@ -42,6 +42,7 @@ __export(src_exports, {
|
|
|
42
42
|
extractSchemaFromDto: () => extractSchemaFromDto,
|
|
43
43
|
generate: () => generate,
|
|
44
44
|
loadConfig: () => loadConfig,
|
|
45
|
+
renderTsType: () => renderTsType,
|
|
45
46
|
resolveAdapter: () => resolveAdapter,
|
|
46
47
|
resolveConfig: () => resolveConfig,
|
|
47
48
|
watch: () => watch
|
|
@@ -1306,6 +1307,8 @@ function buildFormsFileWithAdapter(routes, outDir, adapter, config) {
|
|
|
1306
1307
|
}
|
|
1307
1308
|
const { globalSchemas, renamesByEntry } = planNestedSchemas(entries);
|
|
1308
1309
|
const irNamed = /* @__PURE__ */ new Map();
|
|
1310
|
+
const irTypeAliases = /* @__PURE__ */ new Map();
|
|
1311
|
+
const irAnnotations = /* @__PURE__ */ new Map();
|
|
1309
1312
|
const decls = [];
|
|
1310
1313
|
const mapEntries = [];
|
|
1311
1314
|
let used = false;
|
|
@@ -1313,6 +1316,8 @@ function buildFormsFileWithAdapter(routes, outDir, adapter, config) {
|
|
|
1313
1316
|
if (src.schema) {
|
|
1314
1317
|
const r = adapter.renderModule(src.schema);
|
|
1315
1318
|
for (const [n, t] of r.namedNestedSchemas) irNamed.set(n, t);
|
|
1319
|
+
if (r.namedTypeAliases) for (const [n, t] of r.namedTypeAliases) irTypeAliases.set(n, t);
|
|
1320
|
+
if (r.namedAnnotations) for (const [n, a] of r.namedAnnotations) irAnnotations.set(n, a);
|
|
1316
1321
|
return { text: r.schemaText };
|
|
1317
1322
|
}
|
|
1318
1323
|
if (src.zodText) {
|
|
@@ -1386,7 +1391,13 @@ function buildFormsFileWithAdapter(routes, outDir, adapter, config) {
|
|
|
1386
1391
|
for (const [n, t] of irNamed) if (!allNested.has(n)) allNested.set(n, t);
|
|
1387
1392
|
if (allNested.size > 0) {
|
|
1388
1393
|
lines.push("// Hoisted nested schemas (shared across endpoints).");
|
|
1389
|
-
for (const [n,
|
|
1394
|
+
for (const [n, alias] of irTypeAliases) {
|
|
1395
|
+
if (allNested.has(n)) lines.push(`${alias};`);
|
|
1396
|
+
}
|
|
1397
|
+
for (const [n, t] of allNested) {
|
|
1398
|
+
const annotation = irAnnotations.get(n);
|
|
1399
|
+
lines.push(`const ${n}${annotation ? `: ${annotation}` : ""} = ${t};`);
|
|
1400
|
+
}
|
|
1390
1401
|
lines.push("");
|
|
1391
1402
|
}
|
|
1392
1403
|
lines.push(...decls);
|
|
@@ -1928,10 +1939,7 @@ function extractSchemaFromDto(classDecl, sourceFile, project) {
|
|
|
1928
1939
|
depth: 0
|
|
1929
1940
|
};
|
|
1930
1941
|
const root = buildObject(classDecl, sourceFile, ctx);
|
|
1931
|
-
|
|
1932
|
-
ctx.named.set(schemaName, { kind: "unknown", note: "recursive type \u2014 not expanded" });
|
|
1933
|
-
}
|
|
1934
|
-
return { root, named: ctx.named, warnings: ctx.warnings };
|
|
1942
|
+
return { root, named: ctx.named, warnings: ctx.warnings, recursive: ctx.recursiveSchemas };
|
|
1935
1943
|
}
|
|
1936
1944
|
function buildObject(classDecl, classFile, ctx) {
|
|
1937
1945
|
const props = classDecl.getProperties();
|
|
@@ -1951,7 +1959,7 @@ function buildProperty(prop, classFile, ctx) {
|
|
|
1951
1959
|
const dec = (n) => decorators.get(n);
|
|
1952
1960
|
const typeNode = prop.getTypeNode();
|
|
1953
1961
|
const typeText = typeNode?.getText() ?? "unknown";
|
|
1954
|
-
const isArrayType = !!typeNode &&
|
|
1962
|
+
const isArrayType = !!typeNode && import_ts_morph4.Node.isArrayTypeNode(typeNode);
|
|
1955
1963
|
const typeRefName = resolveTypeFactoryName(dec("Type"));
|
|
1956
1964
|
if (has("ValidateNested") || typeRefName) {
|
|
1957
1965
|
const childName = typeRefName ?? singularClassName(typeText);
|
|
@@ -2082,18 +2090,27 @@ function baseFromType(typeText, isArrayType) {
|
|
|
2082
2090
|
}
|
|
2083
2091
|
}
|
|
2084
2092
|
function buildNestedReference(className, fromFile, ctx) {
|
|
2085
|
-
if (ctx.visiting.has(className)
|
|
2093
|
+
if (ctx.visiting.has(className)) {
|
|
2086
2094
|
const reserved = ctx.emittedClasses.get(className) ?? aliasFor(className, ctx);
|
|
2087
2095
|
ctx.emittedClasses.set(className, reserved);
|
|
2088
2096
|
ctx.recursiveSchemas.add(reserved);
|
|
2089
2097
|
if (!ctx.warnedDecorators.has(`recursive:${reserved}`)) {
|
|
2090
2098
|
ctx.warnedDecorators.add(`recursive:${reserved}`);
|
|
2091
|
-
const msg = `${className} is a recursive type
|
|
2099
|
+
const msg = `${className} is a recursive type; the generated schema validates it via a lazy self-reference.`;
|
|
2092
2100
|
ctx.warnings.push(msg);
|
|
2093
2101
|
console.warn(`[nestjs-codegen] ${msg}`);
|
|
2094
2102
|
}
|
|
2095
2103
|
return { kind: "lazyRef", name: reserved };
|
|
2096
2104
|
}
|
|
2105
|
+
if (ctx.depth >= 8) {
|
|
2106
|
+
if (!ctx.warnedDecorators.has(`deep:${className}`)) {
|
|
2107
|
+
ctx.warnedDecorators.add(`deep:${className}`);
|
|
2108
|
+
const msg = `${className} nesting is too deep to expand; the generated schema uses unknown for it.`;
|
|
2109
|
+
ctx.warnings.push(msg);
|
|
2110
|
+
console.warn(`[nestjs-codegen] ${msg}`);
|
|
2111
|
+
}
|
|
2112
|
+
return { kind: "unknown", note: "nesting too deep \u2014 not expanded" };
|
|
2113
|
+
}
|
|
2097
2114
|
const existing = ctx.emittedClasses.get(className);
|
|
2098
2115
|
if (existing) return { kind: "ref", name: existing };
|
|
2099
2116
|
const schemaName = aliasFor(className, ctx);
|
|
@@ -3498,8 +3515,57 @@ async function watch(config, onChange) {
|
|
|
3498
3515
|
};
|
|
3499
3516
|
}
|
|
3500
3517
|
|
|
3518
|
+
// src/ir/render-ts-type.ts
|
|
3519
|
+
function tsKey(name) {
|
|
3520
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
3521
|
+
}
|
|
3522
|
+
function renderTsType(node, ctx) {
|
|
3523
|
+
switch (node.kind) {
|
|
3524
|
+
case "string":
|
|
3525
|
+
return "string";
|
|
3526
|
+
case "number":
|
|
3527
|
+
return "number";
|
|
3528
|
+
case "boolean":
|
|
3529
|
+
return "boolean";
|
|
3530
|
+
case "date":
|
|
3531
|
+
return "Date";
|
|
3532
|
+
case "unknown":
|
|
3533
|
+
return "unknown";
|
|
3534
|
+
case "instanceof":
|
|
3535
|
+
return node.ctor;
|
|
3536
|
+
case "enum":
|
|
3537
|
+
return node.literals.join(" | ");
|
|
3538
|
+
case "literal":
|
|
3539
|
+
return node.raw;
|
|
3540
|
+
case "union":
|
|
3541
|
+
return node.options.map((o) => renderTsType(o, ctx)).join(" | ");
|
|
3542
|
+
case "array":
|
|
3543
|
+
return `Array<${renderTsType(node.element, ctx)}>`;
|
|
3544
|
+
case "optional":
|
|
3545
|
+
return `${renderTsType(node.inner, ctx)} | undefined`;
|
|
3546
|
+
case "annotated":
|
|
3547
|
+
return renderTsType(node.inner, ctx);
|
|
3548
|
+
case "object": {
|
|
3549
|
+
if (node.fields.length === 0) return node.passthrough ? "Record<string, unknown>" : "{}";
|
|
3550
|
+
const inner = node.fields.map((f) => {
|
|
3551
|
+
if (f.value.kind === "optional") {
|
|
3552
|
+
return `${tsKey(f.key)}?: ${renderTsType(f.value.inner, ctx)}`;
|
|
3553
|
+
}
|
|
3554
|
+
return `${tsKey(f.key)}: ${renderTsType(f.value, ctx)}`;
|
|
3555
|
+
}).join("; ");
|
|
3556
|
+
return `{ ${inner} }`;
|
|
3557
|
+
}
|
|
3558
|
+
case "ref":
|
|
3559
|
+
case "lazyRef": {
|
|
3560
|
+
if (ctx.recursive.has(node.name)) return ctx.typeNameFor(node.name);
|
|
3561
|
+
const target = ctx.named.get(node.name);
|
|
3562
|
+
return target ? renderTsType(target, ctx) : "unknown";
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3501
3567
|
// src/index.ts
|
|
3502
|
-
var VERSION = "0.
|
|
3568
|
+
var VERSION = "0.4.0";
|
|
3503
3569
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3504
3570
|
0 && (module.exports = {
|
|
3505
3571
|
CodegenError,
|
|
@@ -3514,6 +3580,7 @@ var VERSION = "0.3.0";
|
|
|
3514
3580
|
extractSchemaFromDto,
|
|
3515
3581
|
generate,
|
|
3516
3582
|
loadConfig,
|
|
3583
|
+
renderTsType,
|
|
3517
3584
|
resolveAdapter,
|
|
3518
3585
|
resolveConfig,
|
|
3519
3586
|
watch
|