@dudousxd/nestjs-codegen 0.13.1 → 0.14.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 +38 -0
- package/dist/cli/main.cjs +104 -17
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +104 -17
- package/dist/cli/main.js.map +1 -1
- package/dist/extension/index.cjs +1 -1
- package/dist/extension/index.cjs.map +1 -1
- package/dist/extension/index.d.cts +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/extension/index.js +1 -1
- package/dist/extension/index.js.map +1 -1
- package/dist/{index-D8RIMVpU.d.cts → index-DT8SgPxp.d.cts} +35 -2
- package/dist/{index-D8RIMVpU.d.ts → index-DT8SgPxp.d.ts} +35 -2
- package/dist/index.cjs +104 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +64 -19
- package/dist/index.d.ts +64 -19
- package/dist/index.js +103 -15
- package/dist/index.js.map +1 -1
- package/dist/markers.cjs +34 -0
- package/dist/markers.cjs.map +1 -0
- package/dist/markers.d.cts +32 -0
- package/dist/markers.d.ts +32 -0
- package/dist/markers.js +9 -0
- package/dist/markers.js.map +1 -0
- package/dist/nest/index.cjs +4031 -3937
- 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 +4052 -3958
- package/dist/nest/index.js.map +1 -1
- package/package.json +11 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as RenderContext, c as SchemaModule, d as RenderedModule, e as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext, f as SerializationMode } from './index-
|
|
2
|
-
export { A as AdapterUsage, g as ContractDescriptor, h as ContractSource, i as ControllerRef, N as NumberCheck, j as ScopeConfig, k as StringCheck, T as TypeRef, l as ValidationOption, r as resolveAdapter } from './index-
|
|
1
|
+
import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as RenderContext, c as SchemaModule, d as RenderedModule, e as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext, f as SerializationMode } from './index-DT8SgPxp.cjs';
|
|
2
|
+
export { A as AdapterUsage, g as ContractDescriptor, h as ContractSource, i as ControllerRef, N as NumberCheck, j as ScopeConfig, k as StringCheck, T as TypeRef, l as ValidationOption, r as resolveAdapter } from './index-DT8SgPxp.cjs';
|
|
3
3
|
import { ClassDeclaration, SourceFile, Project } from 'ts-morph';
|
|
4
4
|
|
|
5
5
|
declare function defineConfig(c: UserConfig): UserConfig;
|
|
@@ -31,6 +31,52 @@ declare class CodegenError extends Error {
|
|
|
31
31
|
constructor(message: string, options?: ErrorOptions);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Which entry point produced a generate pass: the one-shot/`--watch` CLI
|
|
36
|
+
* (reads `nestjs-codegen.config.ts` from disk), or the Nest module
|
|
37
|
+
* (`NestjsCodegenModule.forRoot()`, options passed in-process). Recorded in
|
|
38
|
+
* the manifest so {@link CodegenManifest.configHash} drift between the two can
|
|
39
|
+
* be detected — see {@link DriftGuardError}.
|
|
40
|
+
*/
|
|
41
|
+
type EntryPoint = 'cli' | 'module';
|
|
42
|
+
/**
|
|
43
|
+
* Thrown by `generate()` when the drift guard (see `driftGuard` in
|
|
44
|
+
* {@link import('./config/types.js').UserConfig}) detects that the CLI and the
|
|
45
|
+
* Nest module are writing the same `outDir` from two different resolved
|
|
46
|
+
* configs. Distinguished from a generic `Error` so callers (the watcher's
|
|
47
|
+
* initial-pass fallback) can single it out instead of treating it as a
|
|
48
|
+
* transient discovery failure to retry.
|
|
49
|
+
*/
|
|
50
|
+
declare class DriftGuardError extends Error {
|
|
51
|
+
constructor(message: string);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Persisted record of the last successful generate, written to
|
|
55
|
+
* `<outDir>/.codegen-manifest.json`. Used to skip regeneration when nothing
|
|
56
|
+
* relevant changed (see {@link isManifestFresh}), and to detect CLI↔module
|
|
57
|
+
* config drift (see {@link DriftGuardError}).
|
|
58
|
+
*/
|
|
59
|
+
interface CodegenManifest {
|
|
60
|
+
/** Lib version that produced the output. A lib upgrade invalidates the manifest. */
|
|
61
|
+
version: string;
|
|
62
|
+
/** Content hash over all generate inputs (source files + resolved config + version). */
|
|
63
|
+
hash: string;
|
|
64
|
+
/**
|
|
65
|
+
* Which entry point produced this manifest. Absent on manifests written before
|
|
66
|
+
* this field existed — treated as "unknown", which never trips the drift guard.
|
|
67
|
+
*/
|
|
68
|
+
entryPoint?: EntryPoint;
|
|
69
|
+
/**
|
|
70
|
+
* Hash of ONLY the serialized resolved config (not source files / version) —
|
|
71
|
+
* a narrower signal than {@link hash}, used to tell "same config, different
|
|
72
|
+
* entry point" (fine) apart from "different config" (drift) regardless of
|
|
73
|
+
* unrelated source-file changes. Absent on pre-drift-guard manifests.
|
|
74
|
+
*/
|
|
75
|
+
configHash?: string;
|
|
76
|
+
/** Generated output files, relative to `outDir`, recorded after the last run. */
|
|
77
|
+
files: string[];
|
|
78
|
+
}
|
|
79
|
+
|
|
34
80
|
/**
|
|
35
81
|
* Run one full codegen pass: discover pages, emit pages.d.ts, components.json, index.d.ts.
|
|
36
82
|
* Route discovery is deliberately skipped — it requires spawning a Nest app and is
|
|
@@ -39,8 +85,11 @@ declare class CodegenError extends Error {
|
|
|
39
85
|
* Optionally accepts pre-discovered routes (e.g. from a full generate + route-discovery pass).
|
|
40
86
|
* When routes are present, emits routes.ts.
|
|
41
87
|
* When routes with contracts are present, also emits api.ts.
|
|
88
|
+
*
|
|
89
|
+
* `entryPoint` identifies which caller is running — the CLI or the Nest module — and is
|
|
90
|
+
* recorded in the manifest for the {@link DriftGuardError} check below.
|
|
42
91
|
*/
|
|
43
|
-
declare function generate(config: ResolvedConfig, inputRoutes?: RouteDescriptor[]): Promise<void>;
|
|
92
|
+
declare function generate(config: ResolvedConfig, inputRoutes?: RouteDescriptor[], entryPoint?: EntryPoint): Promise<void>;
|
|
44
93
|
|
|
45
94
|
interface Watcher {
|
|
46
95
|
close(): Promise<void>;
|
|
@@ -56,6 +105,16 @@ interface WatchOptions {
|
|
|
56
105
|
* @default false
|
|
57
106
|
*/
|
|
58
107
|
deferInitialGenerate?: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Which entry point is running the watcher — threaded through to every
|
|
110
|
+
* `generate()` call so the drift guard (see `driftGuard` in
|
|
111
|
+
* {@link import('../config/types.js').UserConfig}) can tell the CLI and the
|
|
112
|
+
* Nest module apart. The one-shot/`--watch` CLI passes `'cli'` (the
|
|
113
|
+
* default); `NestjsCodegenModule` passes `'module'`.
|
|
114
|
+
*
|
|
115
|
+
* @default 'cli'
|
|
116
|
+
*/
|
|
117
|
+
entryPoint?: EntryPoint;
|
|
59
118
|
}
|
|
60
119
|
/**
|
|
61
120
|
* Start two chokidar watchers:
|
|
@@ -85,20 +144,6 @@ declare function acquireLock(outDir: string): Promise<{
|
|
|
85
144
|
release: () => Promise<void>;
|
|
86
145
|
} | null>;
|
|
87
146
|
|
|
88
|
-
/**
|
|
89
|
-
* Persisted record of the last successful generate, written to
|
|
90
|
-
* `<outDir>/.codegen-manifest.json`. Used to skip regeneration when nothing
|
|
91
|
-
* relevant changed (see {@link isManifestFresh}).
|
|
92
|
-
*/
|
|
93
|
-
interface CodegenManifest {
|
|
94
|
-
/** Lib version that produced the output. A lib upgrade invalidates the manifest. */
|
|
95
|
-
version: string;
|
|
96
|
-
/** Content hash over all generate inputs (source files + resolved config + version). */
|
|
97
|
-
hash: string;
|
|
98
|
-
/** Generated output files, relative to `outDir`, recorded after the last run. */
|
|
99
|
-
files: string[];
|
|
100
|
-
}
|
|
101
|
-
|
|
102
147
|
/**
|
|
103
148
|
* Renders the neutral {@link SchemaNode} IR to a TypeScript *type* expression
|
|
104
149
|
* (not a validation-lib schema). Used to synthesize the hoisted structural type
|
|
@@ -322,6 +367,6 @@ interface FastDiscoveryOptions {
|
|
|
322
367
|
}
|
|
323
368
|
declare function discoverContractsFast(opts: FastDiscoveryOptions): Promise<RouteDescriptor[]>;
|
|
324
369
|
|
|
325
|
-
declare const VERSION = "0.
|
|
370
|
+
declare const VERSION = "0.14.0";
|
|
326
371
|
|
|
327
|
-
export { type ChainModuleRendererOptions, CodegenError, type CodegenManifest, ConfigError, type FastDiscoveryOptions, type JsonSchema, type MocksEmitOptions, type OpenApiDocument, type OpenApiEmitOptions, type OpenApiInfo, RenderContext, RenderedModule, ResolvedConfig, RouteDescriptor, SchemaModule, SchemaNode, type TsTypeContext, UserConfig, VERSION, ValidationAdapter, type WatchOptions, type Watcher, acquireLock, buildMocksFile, buildOpenApiSpec, createChainModuleRenderer, defineConfig, discoverContractsFast, emitApi, emitForms, emitMocks, emitOpenApi, emitRoutes, extractSchemaFromDto, generate, loadConfig, renderTsType, resolveConfig, schemaModuleToJsonSchema, schemaNodeToJsonSchema, toObjectKey, typeNameFor, watch };
|
|
372
|
+
export { type ChainModuleRendererOptions, CodegenError, type CodegenManifest, ConfigError, DriftGuardError, type EntryPoint, type FastDiscoveryOptions, type JsonSchema, type MocksEmitOptions, type OpenApiDocument, type OpenApiEmitOptions, type OpenApiInfo, RenderContext, RenderedModule, ResolvedConfig, RouteDescriptor, SchemaModule, SchemaNode, type TsTypeContext, UserConfig, VERSION, ValidationAdapter, type WatchOptions, type Watcher, acquireLock, buildMocksFile, buildOpenApiSpec, createChainModuleRenderer, defineConfig, discoverContractsFast, emitApi, emitForms, emitMocks, emitOpenApi, emitRoutes, extractSchemaFromDto, generate, loadConfig, renderTsType, resolveConfig, schemaModuleToJsonSchema, schemaNodeToJsonSchema, toObjectKey, typeNameFor, watch };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as RenderContext, c as SchemaModule, d as RenderedModule, e as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext, f as SerializationMode } from './index-
|
|
2
|
-
export { A as AdapterUsage, g as ContractDescriptor, h as ContractSource, i as ControllerRef, N as NumberCheck, j as ScopeConfig, k as StringCheck, T as TypeRef, l as ValidationOption, r as resolveAdapter } from './index-
|
|
1
|
+
import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as RenderContext, c as SchemaModule, d as RenderedModule, e as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext, f as SerializationMode } from './index-DT8SgPxp.js';
|
|
2
|
+
export { A as AdapterUsage, g as ContractDescriptor, h as ContractSource, i as ControllerRef, N as NumberCheck, j as ScopeConfig, k as StringCheck, T as TypeRef, l as ValidationOption, r as resolveAdapter } from './index-DT8SgPxp.js';
|
|
3
3
|
import { ClassDeclaration, SourceFile, Project } from 'ts-morph';
|
|
4
4
|
|
|
5
5
|
declare function defineConfig(c: UserConfig): UserConfig;
|
|
@@ -31,6 +31,52 @@ declare class CodegenError extends Error {
|
|
|
31
31
|
constructor(message: string, options?: ErrorOptions);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Which entry point produced a generate pass: the one-shot/`--watch` CLI
|
|
36
|
+
* (reads `nestjs-codegen.config.ts` from disk), or the Nest module
|
|
37
|
+
* (`NestjsCodegenModule.forRoot()`, options passed in-process). Recorded in
|
|
38
|
+
* the manifest so {@link CodegenManifest.configHash} drift between the two can
|
|
39
|
+
* be detected — see {@link DriftGuardError}.
|
|
40
|
+
*/
|
|
41
|
+
type EntryPoint = 'cli' | 'module';
|
|
42
|
+
/**
|
|
43
|
+
* Thrown by `generate()` when the drift guard (see `driftGuard` in
|
|
44
|
+
* {@link import('./config/types.js').UserConfig}) detects that the CLI and the
|
|
45
|
+
* Nest module are writing the same `outDir` from two different resolved
|
|
46
|
+
* configs. Distinguished from a generic `Error` so callers (the watcher's
|
|
47
|
+
* initial-pass fallback) can single it out instead of treating it as a
|
|
48
|
+
* transient discovery failure to retry.
|
|
49
|
+
*/
|
|
50
|
+
declare class DriftGuardError extends Error {
|
|
51
|
+
constructor(message: string);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Persisted record of the last successful generate, written to
|
|
55
|
+
* `<outDir>/.codegen-manifest.json`. Used to skip regeneration when nothing
|
|
56
|
+
* relevant changed (see {@link isManifestFresh}), and to detect CLI↔module
|
|
57
|
+
* config drift (see {@link DriftGuardError}).
|
|
58
|
+
*/
|
|
59
|
+
interface CodegenManifest {
|
|
60
|
+
/** Lib version that produced the output. A lib upgrade invalidates the manifest. */
|
|
61
|
+
version: string;
|
|
62
|
+
/** Content hash over all generate inputs (source files + resolved config + version). */
|
|
63
|
+
hash: string;
|
|
64
|
+
/**
|
|
65
|
+
* Which entry point produced this manifest. Absent on manifests written before
|
|
66
|
+
* this field existed — treated as "unknown", which never trips the drift guard.
|
|
67
|
+
*/
|
|
68
|
+
entryPoint?: EntryPoint;
|
|
69
|
+
/**
|
|
70
|
+
* Hash of ONLY the serialized resolved config (not source files / version) —
|
|
71
|
+
* a narrower signal than {@link hash}, used to tell "same config, different
|
|
72
|
+
* entry point" (fine) apart from "different config" (drift) regardless of
|
|
73
|
+
* unrelated source-file changes. Absent on pre-drift-guard manifests.
|
|
74
|
+
*/
|
|
75
|
+
configHash?: string;
|
|
76
|
+
/** Generated output files, relative to `outDir`, recorded after the last run. */
|
|
77
|
+
files: string[];
|
|
78
|
+
}
|
|
79
|
+
|
|
34
80
|
/**
|
|
35
81
|
* Run one full codegen pass: discover pages, emit pages.d.ts, components.json, index.d.ts.
|
|
36
82
|
* Route discovery is deliberately skipped — it requires spawning a Nest app and is
|
|
@@ -39,8 +85,11 @@ declare class CodegenError extends Error {
|
|
|
39
85
|
* Optionally accepts pre-discovered routes (e.g. from a full generate + route-discovery pass).
|
|
40
86
|
* When routes are present, emits routes.ts.
|
|
41
87
|
* When routes with contracts are present, also emits api.ts.
|
|
88
|
+
*
|
|
89
|
+
* `entryPoint` identifies which caller is running — the CLI or the Nest module — and is
|
|
90
|
+
* recorded in the manifest for the {@link DriftGuardError} check below.
|
|
42
91
|
*/
|
|
43
|
-
declare function generate(config: ResolvedConfig, inputRoutes?: RouteDescriptor[]): Promise<void>;
|
|
92
|
+
declare function generate(config: ResolvedConfig, inputRoutes?: RouteDescriptor[], entryPoint?: EntryPoint): Promise<void>;
|
|
44
93
|
|
|
45
94
|
interface Watcher {
|
|
46
95
|
close(): Promise<void>;
|
|
@@ -56,6 +105,16 @@ interface WatchOptions {
|
|
|
56
105
|
* @default false
|
|
57
106
|
*/
|
|
58
107
|
deferInitialGenerate?: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Which entry point is running the watcher — threaded through to every
|
|
110
|
+
* `generate()` call so the drift guard (see `driftGuard` in
|
|
111
|
+
* {@link import('../config/types.js').UserConfig}) can tell the CLI and the
|
|
112
|
+
* Nest module apart. The one-shot/`--watch` CLI passes `'cli'` (the
|
|
113
|
+
* default); `NestjsCodegenModule` passes `'module'`.
|
|
114
|
+
*
|
|
115
|
+
* @default 'cli'
|
|
116
|
+
*/
|
|
117
|
+
entryPoint?: EntryPoint;
|
|
59
118
|
}
|
|
60
119
|
/**
|
|
61
120
|
* Start two chokidar watchers:
|
|
@@ -85,20 +144,6 @@ declare function acquireLock(outDir: string): Promise<{
|
|
|
85
144
|
release: () => Promise<void>;
|
|
86
145
|
} | null>;
|
|
87
146
|
|
|
88
|
-
/**
|
|
89
|
-
* Persisted record of the last successful generate, written to
|
|
90
|
-
* `<outDir>/.codegen-manifest.json`. Used to skip regeneration when nothing
|
|
91
|
-
* relevant changed (see {@link isManifestFresh}).
|
|
92
|
-
*/
|
|
93
|
-
interface CodegenManifest {
|
|
94
|
-
/** Lib version that produced the output. A lib upgrade invalidates the manifest. */
|
|
95
|
-
version: string;
|
|
96
|
-
/** Content hash over all generate inputs (source files + resolved config + version). */
|
|
97
|
-
hash: string;
|
|
98
|
-
/** Generated output files, relative to `outDir`, recorded after the last run. */
|
|
99
|
-
files: string[];
|
|
100
|
-
}
|
|
101
|
-
|
|
102
147
|
/**
|
|
103
148
|
* Renders the neutral {@link SchemaNode} IR to a TypeScript *type* expression
|
|
104
149
|
* (not a validation-lib schema). Used to synthesize the hoisted structural type
|
|
@@ -322,6 +367,6 @@ interface FastDiscoveryOptions {
|
|
|
322
367
|
}
|
|
323
368
|
declare function discoverContractsFast(opts: FastDiscoveryOptions): Promise<RouteDescriptor[]>;
|
|
324
369
|
|
|
325
|
-
declare const VERSION = "0.
|
|
370
|
+
declare const VERSION = "0.14.0";
|
|
326
371
|
|
|
327
|
-
export { type ChainModuleRendererOptions, CodegenError, type CodegenManifest, ConfigError, type FastDiscoveryOptions, type JsonSchema, type MocksEmitOptions, type OpenApiDocument, type OpenApiEmitOptions, type OpenApiInfo, RenderContext, RenderedModule, ResolvedConfig, RouteDescriptor, SchemaModule, SchemaNode, type TsTypeContext, UserConfig, VERSION, ValidationAdapter, type WatchOptions, type Watcher, acquireLock, buildMocksFile, buildOpenApiSpec, createChainModuleRenderer, defineConfig, discoverContractsFast, emitApi, emitForms, emitMocks, emitOpenApi, emitRoutes, extractSchemaFromDto, generate, loadConfig, renderTsType, resolveConfig, schemaModuleToJsonSchema, schemaNodeToJsonSchema, toObjectKey, typeNameFor, watch };
|
|
372
|
+
export { type ChainModuleRendererOptions, CodegenError, type CodegenManifest, ConfigError, DriftGuardError, type EntryPoint, type FastDiscoveryOptions, type JsonSchema, type MocksEmitOptions, type OpenApiDocument, type OpenApiEmitOptions, type OpenApiInfo, RenderContext, RenderedModule, ResolvedConfig, RouteDescriptor, SchemaModule, SchemaNode, type TsTypeContext, UserConfig, VERSION, ValidationAdapter, type WatchOptions, type Watcher, acquireLock, buildMocksFile, buildOpenApiSpec, createChainModuleRenderer, defineConfig, discoverContractsFast, emitApi, emitForms, emitMocks, emitOpenApi, emitRoutes, extractSchemaFromDto, generate, loadConfig, renderTsType, resolveConfig, schemaModuleToJsonSchema, schemaNodeToJsonSchema, toObjectKey, typeNameFor, watch };
|
package/dist/index.js
CHANGED
|
@@ -151,7 +151,8 @@ function applyDefaults(userConfig, cwd) {
|
|
|
151
151
|
fileName: userConfig.mocks?.fileName ?? "mocks.ts",
|
|
152
152
|
seed: userConfig.mocks?.seed ?? 1,
|
|
153
153
|
baseUrl: userConfig.mocks?.baseUrl ?? ""
|
|
154
|
-
}
|
|
154
|
+
},
|
|
155
|
+
driftGuard: userConfig.driftGuard ?? true
|
|
155
156
|
};
|
|
156
157
|
}
|
|
157
158
|
async function loadConfig(cwd) {
|
|
@@ -611,7 +612,7 @@ async function collectEmittedFiles(extensions, ctx) {
|
|
|
611
612
|
function requestShape(route) {
|
|
612
613
|
const cs = route.contract?.contractSource;
|
|
613
614
|
const isGet = route.method.toUpperCase() === "GET";
|
|
614
|
-
const isQuery = isGet || !!cs?.filterFields?.length;
|
|
615
|
+
const isQuery = isGet || !!cs?.filterFields?.length || !!cs?.asQuery;
|
|
615
616
|
const hasBody = !!cs?.bodyRef || cs?.body != null && cs.body !== "never";
|
|
616
617
|
const hasQuery = isGet || !!cs?.queryRef || cs?.query != null && cs.query !== "never";
|
|
617
618
|
return { isGet, isQuery, hasBody, hasQuery };
|
|
@@ -718,6 +719,7 @@ function emitFilterQueryType(c) {
|
|
|
718
719
|
return `import('@dudousxd/nestjs-filter-client').TypedFilterQuery<${emitFilterQueryTypeArgs(c)}>`;
|
|
719
720
|
}
|
|
720
721
|
function buildResponseType(c, outDir, serialization) {
|
|
722
|
+
if (c.contractSource.binaryResponse) return "RawResponse<Blob>";
|
|
721
723
|
const raw = rawResponseType(c, outDir);
|
|
722
724
|
return serialization === "json" ? `Jsonify<${raw}>` : raw;
|
|
723
725
|
}
|
|
@@ -772,8 +774,9 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
|
|
|
772
774
|
const safeUrl = JSON.stringify(c.path);
|
|
773
775
|
const filterFields = c.contractSource.filterFields?.length ? c.contractSource.filterFields.map((f) => JSON.stringify(f)).join(" | ") : "never";
|
|
774
776
|
const stream = c.contractSource.stream ? "true" : "false";
|
|
777
|
+
const binary = c.contractSource.binaryResponse ? "true" : "false";
|
|
775
778
|
lines.push(
|
|
776
|
-
`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; params: ${params}; query: ${query}; body: ${body}; response: ${response}; error: ${error}; filterFields: ${filterFields}; stream: ${stream} };`
|
|
779
|
+
`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; params: ${params}; query: ${query}; body: ${body}; response: ${response}; error: ${error}; filterFields: ${filterFields}; stream: ${stream}; binary: ${binary} };`
|
|
777
780
|
);
|
|
778
781
|
} else {
|
|
779
782
|
lines.push(`${pad}${objKey}: {`);
|
|
@@ -819,6 +822,9 @@ function buildRequestModel(c) {
|
|
|
819
822
|
if (hasQuery) optsParts.push("query: input?.query as Record<string, unknown> | undefined");
|
|
820
823
|
if (hasBody) optsParts.push("body: input?.body");
|
|
821
824
|
if (hasBody && c.contractSource.multipart) optsParts.push("multipart: true");
|
|
825
|
+
if (c.contractSource.binaryResponse && m !== "get") {
|
|
826
|
+
optsParts.unshift(`method: ${JSON.stringify(m.toUpperCase())}`);
|
|
827
|
+
}
|
|
822
828
|
const optsExpr = optsParts.length ? `{ ${optsParts.join(", ")} }` : "{}";
|
|
823
829
|
return {
|
|
824
830
|
routeName: c.name,
|
|
@@ -838,7 +844,8 @@ function buildRequestModel(c) {
|
|
|
838
844
|
queryKeyExpr: `(input === undefined ? [${flat}] as const : [${flat}, input] as const)`
|
|
839
845
|
};
|
|
840
846
|
}
|
|
841
|
-
function renderFetcherRequest(req) {
|
|
847
|
+
function renderFetcherRequest(req, binaryResponse) {
|
|
848
|
+
if (binaryResponse) return `fetcher.fetchBlob(${req.urlExpr}, ${req.optsExpr})`;
|
|
842
849
|
return `fetcher.${req.method}<${req.responseType}>(${req.urlExpr}, ${req.optsExpr})`;
|
|
843
850
|
}
|
|
844
851
|
function emitReqHelper() {
|
|
@@ -899,7 +906,7 @@ function emitApiObjectBlock(tree, indent, p) {
|
|
|
899
906
|
const leaf = {
|
|
900
907
|
route: node.route,
|
|
901
908
|
request: req,
|
|
902
|
-
requestExpr: renderFetcherRequest(req)
|
|
909
|
+
requestExpr: renderFetcherRequest(req, node.contractSource.binaryResponse === true)
|
|
903
910
|
};
|
|
904
911
|
const owned = /* @__PURE__ */ new Map();
|
|
905
912
|
if (p.layer) {
|
|
@@ -959,6 +966,8 @@ var ROUTE_NAMESPACE = [
|
|
|
959
966
|
' export type FilterFields<K extends string> = ResolveByName<K, "filterFields">;',
|
|
960
967
|
" /** The streamed element type of an `@Sse()`/streaming route \u2014 the type yielded by its `stream()` AsyncIterable. */",
|
|
961
968
|
' export type Stream<K extends string> = ResolveByName<K, "response">;',
|
|
969
|
+
" /** True for a binary/blob route (`StreamableFile`/`Buffer` handler return type). */",
|
|
970
|
+
' export type Binary<K extends string> = ResolveByName<K, "binary">;',
|
|
962
971
|
" export type Request<K extends string> = {",
|
|
963
972
|
" body: Body<K>;",
|
|
964
973
|
" query: Query<K>;",
|
|
@@ -976,6 +985,7 @@ var PATH_NAMESPACE = [
|
|
|
976
985
|
' export type Error<M extends string, U extends string> = ResolveByPath<M, U, "error">;',
|
|
977
986
|
' export type FilterFields<M extends string, U extends string> = ResolveByPath<M, U, "filterFields">;',
|
|
978
987
|
' export type Stream<M extends string, U extends string> = ResolveByPath<M, U, "response">;',
|
|
988
|
+
' export type Binary<M extends string, U extends string> = ResolveByPath<M, U, "binary">;',
|
|
979
989
|
"}",
|
|
980
990
|
""
|
|
981
991
|
];
|
|
@@ -988,6 +998,7 @@ var EMPTY_ROUTE_NAMESPACE = [
|
|
|
988
998
|
" export type Error<K extends string> = never;",
|
|
989
999
|
" export type FilterFields<K extends string> = never;",
|
|
990
1000
|
" export type Stream<K extends string> = never;",
|
|
1001
|
+
" export type Binary<K extends string> = never;",
|
|
991
1002
|
" export type Request<K extends string> = { body: never; query: never; params: never };",
|
|
992
1003
|
"}",
|
|
993
1004
|
""
|
|
@@ -1001,6 +1012,7 @@ var EMPTY_PATH_NAMESPACE = [
|
|
|
1001
1012
|
" export type Error<M extends string, U extends string> = never;",
|
|
1002
1013
|
" export type FilterFields<M extends string, U extends string> = never;",
|
|
1003
1014
|
" export type Stream<M extends string, U extends string> = never;",
|
|
1015
|
+
" export type Binary<M extends string, U extends string> = never;",
|
|
1004
1016
|
"}",
|
|
1005
1017
|
""
|
|
1006
1018
|
];
|
|
@@ -1066,6 +1078,9 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1066
1078
|
if (serialization === "json" && contracted.length > 0) {
|
|
1067
1079
|
lines.push(`import type { Jsonify } from '${runtimeImport}';`);
|
|
1068
1080
|
}
|
|
1081
|
+
if (contracted.some((r) => r.contract?.contractSource.binaryResponse)) {
|
|
1082
|
+
lines.push(`import type { RawResponse } from '${runtimeImport}';`);
|
|
1083
|
+
}
|
|
1069
1084
|
if (importsByFile.size > 0 && outDir) {
|
|
1070
1085
|
lines.push("");
|
|
1071
1086
|
const emittedNames = /* @__PURE__ */ new Set();
|
|
@@ -1102,6 +1117,12 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1102
1117
|
lines.push("");
|
|
1103
1118
|
lines.push(...EMPTY_ROUTE_NAMESPACE);
|
|
1104
1119
|
lines.push(...EMPTY_PATH_NAMESPACE);
|
|
1120
|
+
for (const ext of headerExts) {
|
|
1121
|
+
const statements = ext.apiHeader?.(ctx)?.statements;
|
|
1122
|
+
if (statements?.length) {
|
|
1123
|
+
lines.push(...statements, "");
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1105
1126
|
return lines.join("\n");
|
|
1106
1127
|
}
|
|
1107
1128
|
const tree = /* @__PURE__ */ new Map();
|
|
@@ -2122,11 +2143,22 @@ import { join as join12, relative as relative6 } from "path";
|
|
|
2122
2143
|
import fg2 from "fast-glob";
|
|
2123
2144
|
var MANIFEST_FILE = ".codegen-manifest.json";
|
|
2124
2145
|
var LOCK_FILE = ".watcher.lock";
|
|
2146
|
+
var DriftGuardError = class extends Error {
|
|
2147
|
+
constructor(message) {
|
|
2148
|
+
super(message);
|
|
2149
|
+
this.name = "DriftGuardError";
|
|
2150
|
+
}
|
|
2151
|
+
};
|
|
2152
|
+
function isEntryPoint(value) {
|
|
2153
|
+
return value === "cli" || value === "module";
|
|
2154
|
+
}
|
|
2125
2155
|
function isManifestShape(value) {
|
|
2126
2156
|
if (typeof value !== "object" || value === null) return false;
|
|
2127
2157
|
const candidate = value;
|
|
2128
2158
|
if (typeof candidate.version !== "string") return false;
|
|
2129
2159
|
if (typeof candidate.hash !== "string") return false;
|
|
2160
|
+
if (candidate.entryPoint !== void 0 && !isEntryPoint(candidate.entryPoint)) return false;
|
|
2161
|
+
if (candidate.configHash !== void 0 && typeof candidate.configHash !== "string") return false;
|
|
2130
2162
|
if (!Array.isArray(candidate.files)) return false;
|
|
2131
2163
|
return candidate.files.every((entry) => typeof entry === "string");
|
|
2132
2164
|
}
|
|
@@ -2169,11 +2201,20 @@ async function readManifest(outDir) {
|
|
|
2169
2201
|
const raw = await readFile2(join12(outDir, MANIFEST_FILE), "utf8");
|
|
2170
2202
|
const parsed = JSON.parse(raw);
|
|
2171
2203
|
if (!isManifestShape(parsed)) return null;
|
|
2172
|
-
return {
|
|
2204
|
+
return {
|
|
2205
|
+
version: parsed.version,
|
|
2206
|
+
hash: parsed.hash,
|
|
2207
|
+
...parsed.entryPoint ? { entryPoint: parsed.entryPoint } : {},
|
|
2208
|
+
...parsed.configHash ? { configHash: parsed.configHash } : {},
|
|
2209
|
+
files: parsed.files
|
|
2210
|
+
};
|
|
2173
2211
|
} catch {
|
|
2174
2212
|
return null;
|
|
2175
2213
|
}
|
|
2176
2214
|
}
|
|
2215
|
+
function computeConfigHash(config) {
|
|
2216
|
+
return createHash("sha256").update(serializeConfig(config)).digest("hex");
|
|
2217
|
+
}
|
|
2177
2218
|
async function writeManifest(outDir, manifest) {
|
|
2178
2219
|
await writeFile9(join12(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
|
|
2179
2220
|
`, "utf8");
|
|
@@ -2218,7 +2259,10 @@ function debugWarn(message) {
|
|
|
2218
2259
|
}
|
|
2219
2260
|
|
|
2220
2261
|
// src/generate.ts
|
|
2221
|
-
|
|
2262
|
+
function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint) {
|
|
2263
|
+
return `[nestjs-codegen] Config drift detected in "${outDir}": the last generate ran from the "${previousEntryPoint}" entry point, this run is from the "${currentEntryPoint}" entry point, and their resolved configs differ (e.g. \`serialization: "json"\` vs \`"superjson"\`). Both entry points must read the SAME config \u2014 export a shared config object (e.g. codegen.config.ts) and import it from BOTH nestjs-codegen.config.ts (CLI) and NestjsCodegenModule.forRoot() (Nest module), or set \`driftGuard: false\` on either config to opt out of this check.`;
|
|
2264
|
+
}
|
|
2265
|
+
async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
2222
2266
|
setCodegenDebug(config.debug);
|
|
2223
2267
|
const inputsHash = await computeInputsHash(config);
|
|
2224
2268
|
const manifest = await readManifest(config.codegen.outDir);
|
|
@@ -2226,6 +2270,12 @@ async function generate(config, inputRoutes = []) {
|
|
|
2226
2270
|
console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
|
|
2227
2271
|
return;
|
|
2228
2272
|
}
|
|
2273
|
+
const configHash = computeConfigHash(config);
|
|
2274
|
+
if (config.driftGuard && manifest?.entryPoint && manifest.entryPoint !== entryPoint && manifest.configHash && manifest.configHash !== configHash) {
|
|
2275
|
+
throw new DriftGuardError(
|
|
2276
|
+
driftGuardMessage(config.codegen.outDir, manifest.entryPoint, entryPoint)
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2229
2279
|
const extensions = config.extensions ?? [];
|
|
2230
2280
|
let routes = inputRoutes;
|
|
2231
2281
|
const ctx = createExtensionContext(config, () => routes);
|
|
@@ -2291,6 +2341,8 @@ async function generate(config, inputRoutes = []) {
|
|
|
2291
2341
|
await writeManifest(config.codegen.outDir, {
|
|
2292
2342
|
version: VERSION,
|
|
2293
2343
|
hash: inputsHash,
|
|
2344
|
+
entryPoint,
|
|
2345
|
+
configHash,
|
|
2294
2346
|
files: outputFiles
|
|
2295
2347
|
});
|
|
2296
2348
|
}
|
|
@@ -3595,6 +3647,19 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
|
|
|
3595
3647
|
dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
|
|
3596
3648
|
return "unknown";
|
|
3597
3649
|
}
|
|
3650
|
+
if (Node6.isTypeLiteral(typeNode)) {
|
|
3651
|
+
const members = [];
|
|
3652
|
+
for (const member of typeNode.getMembers()) {
|
|
3653
|
+
if (Node6.isPropertySignature(member)) {
|
|
3654
|
+
const memberTypeNode = member.getTypeNode();
|
|
3655
|
+
const memberType = memberTypeNode ? resolveTypeNodeToString(memberTypeNode, sourceFile, project, depth, subst) : "unknown";
|
|
3656
|
+
members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${memberType}`);
|
|
3657
|
+
} else {
|
|
3658
|
+
members.push(member.getText());
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3661
|
+
return members.length > 0 ? `{ ${members.join("; ")} }` : "{}";
|
|
3662
|
+
}
|
|
3598
3663
|
const kind = typeNode.getKind();
|
|
3599
3664
|
if (kind === SyntaxKind3.StringKeyword) return "string";
|
|
3600
3665
|
if (kind === SyntaxKind3.NumberKeyword) return "number";
|
|
@@ -3867,6 +3932,14 @@ function resolveBodyQueryResponseRef(typeNode, sourceFile, project) {
|
|
|
3867
3932
|
var STREAM_CONTAINERS = /* @__PURE__ */ new Set(["Observable", "AsyncIterable", "AsyncIterableIterator"]);
|
|
3868
3933
|
var STREAM_CONTAINERS_GENERATOR = /* @__PURE__ */ new Set(["AsyncGenerator"]);
|
|
3869
3934
|
var STREAM_ENVELOPES = /* @__PURE__ */ new Set(["MessageEvent", "MessageEventLike"]);
|
|
3935
|
+
var BINARY_RESPONSE_TYPES = /* @__PURE__ */ new Set(["StreamableFile", "Buffer"]);
|
|
3936
|
+
function detectBinaryResponse(method) {
|
|
3937
|
+
const node = unwrapNamedContainer(method.getReturnTypeNode(), /* @__PURE__ */ new Set(["Promise"]));
|
|
3938
|
+
if (!node || !Node6.isTypeReference(node)) return false;
|
|
3939
|
+
const typeName = node.getTypeName();
|
|
3940
|
+
const name = Node6.isIdentifier(typeName) ? typeName.getText() : "";
|
|
3941
|
+
return BINARY_RESPONSE_TYPES.has(name);
|
|
3942
|
+
}
|
|
3870
3943
|
function detectStreamElement(method) {
|
|
3871
3944
|
const hasSse = method.getDecorators().some((d) => d.getName() === "Sse");
|
|
3872
3945
|
let node = method.getReturnTypeNode();
|
|
@@ -3887,6 +3960,9 @@ function streamContainerElement(node) {
|
|
|
3887
3960
|
}
|
|
3888
3961
|
return null;
|
|
3889
3962
|
}
|
|
3963
|
+
function hasAsQueryDecorator(method) {
|
|
3964
|
+
return method.getDecorators().some((d) => d.getName() === "AsQuery");
|
|
3965
|
+
}
|
|
3890
3966
|
function unwrapNamedContainer(node, names) {
|
|
3891
3967
|
if (!node || !Node6.isTypeReference(node)) return node;
|
|
3892
3968
|
const typeName = node.getTypeName();
|
|
@@ -3904,6 +3980,8 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3904
3980
|
const multipartBody = uploads.fields ? `{ ${uploads.fields} }` : null;
|
|
3905
3981
|
const streamElement = detectStreamElement(method);
|
|
3906
3982
|
const isStream = streamElement !== null;
|
|
3983
|
+
const binaryResponse = detectBinaryResponse(method);
|
|
3984
|
+
const asQuery = hasAsQueryDecorator(method);
|
|
3907
3985
|
if (filterInfo && filterInfo.source === "body") {
|
|
3908
3986
|
const bodyType = "import('@dudousxd/nestjs-filter-client').FilterQueryResult";
|
|
3909
3987
|
body = body ?? bodyType;
|
|
@@ -3911,7 +3989,7 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3911
3989
|
const paramsType = extractParamsType(method, sourceFile, project);
|
|
3912
3990
|
const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
|
|
3913
3991
|
const errorInfo = extractErrorType(method, sourceFile, project);
|
|
3914
|
-
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart) {
|
|
3992
|
+
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
|
|
3915
3993
|
return null;
|
|
3916
3994
|
}
|
|
3917
3995
|
let bodyRef = null;
|
|
@@ -3986,7 +4064,9 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3986
4064
|
querySchema,
|
|
3987
4065
|
stream: isStream,
|
|
3988
4066
|
multipart: uploads.multipart,
|
|
3989
|
-
multipartBody
|
|
4067
|
+
multipartBody,
|
|
4068
|
+
binaryResponse,
|
|
4069
|
+
asQuery
|
|
3990
4070
|
};
|
|
3991
4071
|
}
|
|
3992
4072
|
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
@@ -4473,7 +4553,9 @@ function extractDtoRoute(args) {
|
|
|
4473
4553
|
querySchema: dtoContract?.querySchema ?? null,
|
|
4474
4554
|
stream: dtoContract?.stream ?? false,
|
|
4475
4555
|
multipart: dtoContract?.multipart ?? false,
|
|
4476
|
-
multipartBody: dtoContract?.multipartBody ?? null
|
|
4556
|
+
multipartBody: dtoContract?.multipartBody ?? null,
|
|
4557
|
+
binaryResponse: dtoContract?.binaryResponse ?? false,
|
|
4558
|
+
asQuery: dtoContract?.asQuery ?? false
|
|
4477
4559
|
}
|
|
4478
4560
|
});
|
|
4479
4561
|
}
|
|
@@ -4566,6 +4648,7 @@ var PAGES_DEBOUNCE_MS = 150;
|
|
|
4566
4648
|
var NO_OP_WATCHER = { close: async () => {
|
|
4567
4649
|
} };
|
|
4568
4650
|
async function watch(config, onChange, options = {}) {
|
|
4651
|
+
const entryPoint = options.entryPoint ?? "cli";
|
|
4569
4652
|
const lock = await acquireLock(config.codegen.outDir);
|
|
4570
4653
|
if (lock === null) {
|
|
4571
4654
|
let holderPid = "unknown";
|
|
@@ -4597,13 +4680,17 @@ async function watch(config, onChange, options = {}) {
|
|
|
4597
4680
|
try {
|
|
4598
4681
|
const initialRoutes = (await getDiscovery()).discover();
|
|
4599
4682
|
lastRoutes = initialRoutes;
|
|
4600
|
-
await generate(config, initialRoutes);
|
|
4683
|
+
await generate(config, initialRoutes, entryPoint);
|
|
4601
4684
|
} catch (err) {
|
|
4685
|
+
if (err instanceof DriftGuardError) {
|
|
4686
|
+
console.error(err.message);
|
|
4687
|
+
return;
|
|
4688
|
+
}
|
|
4602
4689
|
console.warn(
|
|
4603
4690
|
`[nestjs-codegen] Initial route discovery failed, falling back to pages-only: ${err instanceof Error ? err.message : String(err)}`
|
|
4604
4691
|
);
|
|
4605
4692
|
try {
|
|
4606
|
-
await generate(config, lastRoutes);
|
|
4693
|
+
await generate(config, lastRoutes, entryPoint);
|
|
4607
4694
|
} catch {
|
|
4608
4695
|
}
|
|
4609
4696
|
}
|
|
@@ -4631,7 +4718,7 @@ async function watch(config, onChange, options = {}) {
|
|
|
4631
4718
|
pagesDebounceTimer = setTimeout(async () => {
|
|
4632
4719
|
pagesDebounceTimer = void 0;
|
|
4633
4720
|
try {
|
|
4634
|
-
await generate(config, lastRoutes);
|
|
4721
|
+
await generate(config, lastRoutes, entryPoint);
|
|
4635
4722
|
} catch (err) {
|
|
4636
4723
|
console.error(
|
|
4637
4724
|
"[nestjs-codegen] Pages generation failed:",
|
|
@@ -4663,7 +4750,7 @@ async function watch(config, onChange, options = {}) {
|
|
|
4663
4750
|
try {
|
|
4664
4751
|
const routes = await (await getDiscovery()).rediscover(changed);
|
|
4665
4752
|
lastRoutes = routes;
|
|
4666
|
-
await generate(config, routes);
|
|
4753
|
+
await generate(config, routes, entryPoint);
|
|
4667
4754
|
} catch (err) {
|
|
4668
4755
|
console.error(
|
|
4669
4756
|
"[nestjs-codegen] Contracts generation failed:",
|
|
@@ -4786,10 +4873,11 @@ function createChainModuleRenderer(opts) {
|
|
|
4786
4873
|
}
|
|
4787
4874
|
|
|
4788
4875
|
// src/index.ts
|
|
4789
|
-
var VERSION = "0.
|
|
4876
|
+
var VERSION = "0.14.0";
|
|
4790
4877
|
export {
|
|
4791
4878
|
CodegenError,
|
|
4792
4879
|
ConfigError,
|
|
4880
|
+
DriftGuardError,
|
|
4793
4881
|
VERSION,
|
|
4794
4882
|
acquireLock,
|
|
4795
4883
|
buildMocksFile,
|