@dudousxd/nestjs-codegen 0.13.2 → 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 +32 -0
- package/dist/cli/main.cjs +91 -17
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +91 -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 +91 -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 +90 -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 +4027 -3946
- 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 +4055 -3974
- 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
|
}
|
|
@@ -3880,6 +3932,14 @@ function resolveBodyQueryResponseRef(typeNode, sourceFile, project) {
|
|
|
3880
3932
|
var STREAM_CONTAINERS = /* @__PURE__ */ new Set(["Observable", "AsyncIterable", "AsyncIterableIterator"]);
|
|
3881
3933
|
var STREAM_CONTAINERS_GENERATOR = /* @__PURE__ */ new Set(["AsyncGenerator"]);
|
|
3882
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
|
+
}
|
|
3883
3943
|
function detectStreamElement(method) {
|
|
3884
3944
|
const hasSse = method.getDecorators().some((d) => d.getName() === "Sse");
|
|
3885
3945
|
let node = method.getReturnTypeNode();
|
|
@@ -3900,6 +3960,9 @@ function streamContainerElement(node) {
|
|
|
3900
3960
|
}
|
|
3901
3961
|
return null;
|
|
3902
3962
|
}
|
|
3963
|
+
function hasAsQueryDecorator(method) {
|
|
3964
|
+
return method.getDecorators().some((d) => d.getName() === "AsQuery");
|
|
3965
|
+
}
|
|
3903
3966
|
function unwrapNamedContainer(node, names) {
|
|
3904
3967
|
if (!node || !Node6.isTypeReference(node)) return node;
|
|
3905
3968
|
const typeName = node.getTypeName();
|
|
@@ -3917,6 +3980,8 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3917
3980
|
const multipartBody = uploads.fields ? `{ ${uploads.fields} }` : null;
|
|
3918
3981
|
const streamElement = detectStreamElement(method);
|
|
3919
3982
|
const isStream = streamElement !== null;
|
|
3983
|
+
const binaryResponse = detectBinaryResponse(method);
|
|
3984
|
+
const asQuery = hasAsQueryDecorator(method);
|
|
3920
3985
|
if (filterInfo && filterInfo.source === "body") {
|
|
3921
3986
|
const bodyType = "import('@dudousxd/nestjs-filter-client').FilterQueryResult";
|
|
3922
3987
|
body = body ?? bodyType;
|
|
@@ -3924,7 +3989,7 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3924
3989
|
const paramsType = extractParamsType(method, sourceFile, project);
|
|
3925
3990
|
const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
|
|
3926
3991
|
const errorInfo = extractErrorType(method, sourceFile, project);
|
|
3927
|
-
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) {
|
|
3928
3993
|
return null;
|
|
3929
3994
|
}
|
|
3930
3995
|
let bodyRef = null;
|
|
@@ -3999,7 +4064,9 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3999
4064
|
querySchema,
|
|
4000
4065
|
stream: isStream,
|
|
4001
4066
|
multipart: uploads.multipart,
|
|
4002
|
-
multipartBody
|
|
4067
|
+
multipartBody,
|
|
4068
|
+
binaryResponse,
|
|
4069
|
+
asQuery
|
|
4003
4070
|
};
|
|
4004
4071
|
}
|
|
4005
4072
|
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
@@ -4486,7 +4553,9 @@ function extractDtoRoute(args) {
|
|
|
4486
4553
|
querySchema: dtoContract?.querySchema ?? null,
|
|
4487
4554
|
stream: dtoContract?.stream ?? false,
|
|
4488
4555
|
multipart: dtoContract?.multipart ?? false,
|
|
4489
|
-
multipartBody: dtoContract?.multipartBody ?? null
|
|
4556
|
+
multipartBody: dtoContract?.multipartBody ?? null,
|
|
4557
|
+
binaryResponse: dtoContract?.binaryResponse ?? false,
|
|
4558
|
+
asQuery: dtoContract?.asQuery ?? false
|
|
4490
4559
|
}
|
|
4491
4560
|
});
|
|
4492
4561
|
}
|
|
@@ -4579,6 +4648,7 @@ var PAGES_DEBOUNCE_MS = 150;
|
|
|
4579
4648
|
var NO_OP_WATCHER = { close: async () => {
|
|
4580
4649
|
} };
|
|
4581
4650
|
async function watch(config, onChange, options = {}) {
|
|
4651
|
+
const entryPoint = options.entryPoint ?? "cli";
|
|
4582
4652
|
const lock = await acquireLock(config.codegen.outDir);
|
|
4583
4653
|
if (lock === null) {
|
|
4584
4654
|
let holderPid = "unknown";
|
|
@@ -4610,13 +4680,17 @@ async function watch(config, onChange, options = {}) {
|
|
|
4610
4680
|
try {
|
|
4611
4681
|
const initialRoutes = (await getDiscovery()).discover();
|
|
4612
4682
|
lastRoutes = initialRoutes;
|
|
4613
|
-
await generate(config, initialRoutes);
|
|
4683
|
+
await generate(config, initialRoutes, entryPoint);
|
|
4614
4684
|
} catch (err) {
|
|
4685
|
+
if (err instanceof DriftGuardError) {
|
|
4686
|
+
console.error(err.message);
|
|
4687
|
+
return;
|
|
4688
|
+
}
|
|
4615
4689
|
console.warn(
|
|
4616
4690
|
`[nestjs-codegen] Initial route discovery failed, falling back to pages-only: ${err instanceof Error ? err.message : String(err)}`
|
|
4617
4691
|
);
|
|
4618
4692
|
try {
|
|
4619
|
-
await generate(config, lastRoutes);
|
|
4693
|
+
await generate(config, lastRoutes, entryPoint);
|
|
4620
4694
|
} catch {
|
|
4621
4695
|
}
|
|
4622
4696
|
}
|
|
@@ -4644,7 +4718,7 @@ async function watch(config, onChange, options = {}) {
|
|
|
4644
4718
|
pagesDebounceTimer = setTimeout(async () => {
|
|
4645
4719
|
pagesDebounceTimer = void 0;
|
|
4646
4720
|
try {
|
|
4647
|
-
await generate(config, lastRoutes);
|
|
4721
|
+
await generate(config, lastRoutes, entryPoint);
|
|
4648
4722
|
} catch (err) {
|
|
4649
4723
|
console.error(
|
|
4650
4724
|
"[nestjs-codegen] Pages generation failed:",
|
|
@@ -4676,7 +4750,7 @@ async function watch(config, onChange, options = {}) {
|
|
|
4676
4750
|
try {
|
|
4677
4751
|
const routes = await (await getDiscovery()).rediscover(changed);
|
|
4678
4752
|
lastRoutes = routes;
|
|
4679
|
-
await generate(config, routes);
|
|
4753
|
+
await generate(config, routes, entryPoint);
|
|
4680
4754
|
} catch (err) {
|
|
4681
4755
|
console.error(
|
|
4682
4756
|
"[nestjs-codegen] Contracts generation failed:",
|
|
@@ -4799,10 +4873,11 @@ function createChainModuleRenderer(opts) {
|
|
|
4799
4873
|
}
|
|
4800
4874
|
|
|
4801
4875
|
// src/index.ts
|
|
4802
|
-
var VERSION = "0.
|
|
4876
|
+
var VERSION = "0.14.0";
|
|
4803
4877
|
export {
|
|
4804
4878
|
CodegenError,
|
|
4805
4879
|
ConfigError,
|
|
4880
|
+
DriftGuardError,
|
|
4806
4881
|
VERSION,
|
|
4807
4882
|
acquireLock,
|
|
4808
4883
|
buildMocksFile,
|