@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# @dudousxd/nestjs-codegen
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- fc78a39: feat: binary (blob) response mode, `@AsQuery()` marker, CLI↔module config-drift guard, and a `handleQuery` TanStack helper.
|
|
8
|
+
|
|
9
|
+
- **Binary (blob) response mode.** A handler returning NestJS `StreamableFile` or Node `Buffer`
|
|
10
|
+
(including `Promise<StreamableFile>`) is now discovered as `binaryResponse: true` and emitted
|
|
11
|
+
with `response: RawResponse<Blob>` (never `Jsonify<...>`) — the leaf issues its request via
|
|
12
|
+
`fetcher.fetchBlob(...)` instead of the verb method, so callers get `{ data, status, headers }`
|
|
13
|
+
and can read `content-disposition` etc. Works on any HTTP method (`fetchBlob` already accepted
|
|
14
|
+
a `method` opt); a non-GET binary route passes it explicitly since `fetchBlob` defaults to GET.
|
|
15
|
+
`Observable`/`ReadableStream` handlers are unaffected — they stay on the existing SSE/stream
|
|
16
|
+
path. Each `ApiRouter` leaf now also carries a `binary` flag (`Route.Binary<K>` /
|
|
17
|
+
`Path.Binary<M, U>` type helpers), mirroring `stream`.
|
|
18
|
+
- **`@AsQuery()` marker** (new `@dudousxd/nestjs-codegen/markers` subpath — zero-import, runtime
|
|
19
|
+
no-op). Marks a non-GET route whose semantics are a read (e.g. a POST with a query-shaped
|
|
20
|
+
payload) so codegen emits `queryOptions` for it, exactly like a GET or a filter-search route.
|
|
21
|
+
- **CLI↔module config-drift guard.** The CLI (`nestjs-codegen.config.ts`) and the Nest module
|
|
22
|
+
(`NestjsCodegenModule.forRoot()`) can target the same `outDir` from independently-resolved
|
|
23
|
+
configs; if they genuinely differ (e.g. `serialization` `'json'` vs `'superjson'`), each run
|
|
24
|
+
used to silently overwrite the other's `api.ts` shape. `generate()` now throws a
|
|
25
|
+
`DriftGuardError` _before writing anything_ when the manifest's `entryPoint` differs from the
|
|
26
|
+
current run's AND the resolved configs' hashes differ — naming both entry points and
|
|
27
|
+
instructing how to fix it (share one config object, or set `driftGuard: false`). Same entry
|
|
28
|
+
point (a normal config edit) or same config across entry points both proceed as before.
|
|
29
|
+
- **TanStack: `handleQuery` helper**, emitted into `api.ts` whenever the TanStack layer is
|
|
30
|
+
active. Wraps any `{ queryKey, fetch }`-shaped handle (a POST-as-query handle, or a runtime
|
|
31
|
+
pick between two different handles) into a plain `{ queryKey, queryFn }` pair — solves the
|
|
32
|
+
useQuery-overload break from spreading a ternary of `queryOptions()` calls. Also: binary GET
|
|
33
|
+
routes get `queryOptions` but never `infiniteQueryOptions` (a download isn't paginated data).
|
|
34
|
+
|
|
35
|
+
## 0.13.2
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- 889af1f: Resolve the members of an inline object-literal type (`{ a: Foo; b: Bar }`) in response/stream types instead of emitting the node's raw text. A named type nested in an object literal — most commonly an SSE payload's `Observable<{ data: SomeType }>`, where `SomeType` is imported from another package — was previously copied verbatim, leaving a bare, unimported identifier that is undefined in the generated file. Each member's type is now resolved (expanded inline, or reduced to `unknown` when unresolvable) like any other named reference.
|
|
40
|
+
|
|
3
41
|
## 0.13.1
|
|
4
42
|
|
|
5
43
|
### Patch Changes
|
package/dist/cli/main.cjs
CHANGED
|
@@ -180,7 +180,8 @@ function applyDefaults(userConfig, cwd) {
|
|
|
180
180
|
fileName: userConfig.mocks?.fileName ?? "mocks.ts",
|
|
181
181
|
seed: userConfig.mocks?.seed ?? 1,
|
|
182
182
|
baseUrl: userConfig.mocks?.baseUrl ?? ""
|
|
183
|
-
}
|
|
183
|
+
},
|
|
184
|
+
driftGuard: userConfig.driftGuard ?? true
|
|
184
185
|
};
|
|
185
186
|
}
|
|
186
187
|
async function loadConfig(cwd) {
|
|
@@ -640,7 +641,7 @@ async function collectEmittedFiles(extensions, ctx) {
|
|
|
640
641
|
function requestShape(route) {
|
|
641
642
|
const cs = route.contract?.contractSource;
|
|
642
643
|
const isGet = route.method.toUpperCase() === "GET";
|
|
643
|
-
const isQuery = isGet || !!cs?.filterFields?.length;
|
|
644
|
+
const isQuery = isGet || !!cs?.filterFields?.length || !!cs?.asQuery;
|
|
644
645
|
const hasBody = !!cs?.bodyRef || cs?.body != null && cs.body !== "never";
|
|
645
646
|
const hasQuery = isGet || !!cs?.queryRef || cs?.query != null && cs.query !== "never";
|
|
646
647
|
return { isGet, isQuery, hasBody, hasQuery };
|
|
@@ -747,6 +748,7 @@ function emitFilterQueryType(c) {
|
|
|
747
748
|
return `import('@dudousxd/nestjs-filter-client').TypedFilterQuery<${emitFilterQueryTypeArgs(c)}>`;
|
|
748
749
|
}
|
|
749
750
|
function buildResponseType(c, outDir, serialization) {
|
|
751
|
+
if (c.contractSource.binaryResponse) return "RawResponse<Blob>";
|
|
750
752
|
const raw = rawResponseType(c, outDir);
|
|
751
753
|
return serialization === "json" ? `Jsonify<${raw}>` : raw;
|
|
752
754
|
}
|
|
@@ -801,8 +803,9 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
|
|
|
801
803
|
const safeUrl = JSON.stringify(c.path);
|
|
802
804
|
const filterFields = c.contractSource.filterFields?.length ? c.contractSource.filterFields.map((f) => JSON.stringify(f)).join(" | ") : "never";
|
|
803
805
|
const stream = c.contractSource.stream ? "true" : "false";
|
|
806
|
+
const binary = c.contractSource.binaryResponse ? "true" : "false";
|
|
804
807
|
lines.push(
|
|
805
|
-
`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; params: ${params}; query: ${query}; body: ${body}; response: ${response}; error: ${error}; filterFields: ${filterFields}; stream: ${stream} };`
|
|
808
|
+
`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; params: ${params}; query: ${query}; body: ${body}; response: ${response}; error: ${error}; filterFields: ${filterFields}; stream: ${stream}; binary: ${binary} };`
|
|
806
809
|
);
|
|
807
810
|
} else {
|
|
808
811
|
lines.push(`${pad}${objKey}: {`);
|
|
@@ -848,6 +851,9 @@ function buildRequestModel(c) {
|
|
|
848
851
|
if (hasQuery) optsParts.push("query: input?.query as Record<string, unknown> | undefined");
|
|
849
852
|
if (hasBody) optsParts.push("body: input?.body");
|
|
850
853
|
if (hasBody && c.contractSource.multipart) optsParts.push("multipart: true");
|
|
854
|
+
if (c.contractSource.binaryResponse && m !== "get") {
|
|
855
|
+
optsParts.unshift(`method: ${JSON.stringify(m.toUpperCase())}`);
|
|
856
|
+
}
|
|
851
857
|
const optsExpr = optsParts.length ? `{ ${optsParts.join(", ")} }` : "{}";
|
|
852
858
|
return {
|
|
853
859
|
routeName: c.name,
|
|
@@ -867,7 +873,8 @@ function buildRequestModel(c) {
|
|
|
867
873
|
queryKeyExpr: `(input === undefined ? [${flat}] as const : [${flat}, input] as const)`
|
|
868
874
|
};
|
|
869
875
|
}
|
|
870
|
-
function renderFetcherRequest(req) {
|
|
876
|
+
function renderFetcherRequest(req, binaryResponse) {
|
|
877
|
+
if (binaryResponse) return `fetcher.fetchBlob(${req.urlExpr}, ${req.optsExpr})`;
|
|
871
878
|
return `fetcher.${req.method}<${req.responseType}>(${req.urlExpr}, ${req.optsExpr})`;
|
|
872
879
|
}
|
|
873
880
|
function emitReqHelper() {
|
|
@@ -928,7 +935,7 @@ function emitApiObjectBlock(tree, indent, p) {
|
|
|
928
935
|
const leaf = {
|
|
929
936
|
route: node.route,
|
|
930
937
|
request: req,
|
|
931
|
-
requestExpr: renderFetcherRequest(req)
|
|
938
|
+
requestExpr: renderFetcherRequest(req, node.contractSource.binaryResponse === true)
|
|
932
939
|
};
|
|
933
940
|
const owned = /* @__PURE__ */ new Map();
|
|
934
941
|
if (p.layer) {
|
|
@@ -988,6 +995,8 @@ var ROUTE_NAMESPACE = [
|
|
|
988
995
|
' export type FilterFields<K extends string> = ResolveByName<K, "filterFields">;',
|
|
989
996
|
" /** The streamed element type of an `@Sse()`/streaming route \u2014 the type yielded by its `stream()` AsyncIterable. */",
|
|
990
997
|
' export type Stream<K extends string> = ResolveByName<K, "response">;',
|
|
998
|
+
" /** True for a binary/blob route (`StreamableFile`/`Buffer` handler return type). */",
|
|
999
|
+
' export type Binary<K extends string> = ResolveByName<K, "binary">;',
|
|
991
1000
|
" export type Request<K extends string> = {",
|
|
992
1001
|
" body: Body<K>;",
|
|
993
1002
|
" query: Query<K>;",
|
|
@@ -1005,6 +1014,7 @@ var PATH_NAMESPACE = [
|
|
|
1005
1014
|
' export type Error<M extends string, U extends string> = ResolveByPath<M, U, "error">;',
|
|
1006
1015
|
' export type FilterFields<M extends string, U extends string> = ResolveByPath<M, U, "filterFields">;',
|
|
1007
1016
|
' export type Stream<M extends string, U extends string> = ResolveByPath<M, U, "response">;',
|
|
1017
|
+
' export type Binary<M extends string, U extends string> = ResolveByPath<M, U, "binary">;',
|
|
1008
1018
|
"}",
|
|
1009
1019
|
""
|
|
1010
1020
|
];
|
|
@@ -1017,6 +1027,7 @@ var EMPTY_ROUTE_NAMESPACE = [
|
|
|
1017
1027
|
" export type Error<K extends string> = never;",
|
|
1018
1028
|
" export type FilterFields<K extends string> = never;",
|
|
1019
1029
|
" export type Stream<K extends string> = never;",
|
|
1030
|
+
" export type Binary<K extends string> = never;",
|
|
1020
1031
|
" export type Request<K extends string> = { body: never; query: never; params: never };",
|
|
1021
1032
|
"}",
|
|
1022
1033
|
""
|
|
@@ -1030,6 +1041,7 @@ var EMPTY_PATH_NAMESPACE = [
|
|
|
1030
1041
|
" export type Error<M extends string, U extends string> = never;",
|
|
1031
1042
|
" export type FilterFields<M extends string, U extends string> = never;",
|
|
1032
1043
|
" export type Stream<M extends string, U extends string> = never;",
|
|
1044
|
+
" export type Binary<M extends string, U extends string> = never;",
|
|
1033
1045
|
"}",
|
|
1034
1046
|
""
|
|
1035
1047
|
];
|
|
@@ -1095,6 +1107,9 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1095
1107
|
if (serialization === "json" && contracted.length > 0) {
|
|
1096
1108
|
lines.push(`import type { Jsonify } from '${runtimeImport}';`);
|
|
1097
1109
|
}
|
|
1110
|
+
if (contracted.some((r) => r.contract?.contractSource.binaryResponse)) {
|
|
1111
|
+
lines.push(`import type { RawResponse } from '${runtimeImport}';`);
|
|
1112
|
+
}
|
|
1098
1113
|
if (importsByFile.size > 0 && outDir) {
|
|
1099
1114
|
lines.push("");
|
|
1100
1115
|
const emittedNames = /* @__PURE__ */ new Set();
|
|
@@ -1131,6 +1146,12 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1131
1146
|
lines.push("");
|
|
1132
1147
|
lines.push(...EMPTY_ROUTE_NAMESPACE);
|
|
1133
1148
|
lines.push(...EMPTY_PATH_NAMESPACE);
|
|
1149
|
+
for (const ext of headerExts) {
|
|
1150
|
+
const statements = ext.apiHeader?.(ctx)?.statements;
|
|
1151
|
+
if (statements?.length) {
|
|
1152
|
+
lines.push(...statements, "");
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1134
1155
|
return lines.join("\n");
|
|
1135
1156
|
}
|
|
1136
1157
|
const tree = /* @__PURE__ */ new Map();
|
|
@@ -2148,11 +2169,22 @@ var import_node_path12 = require("path");
|
|
|
2148
2169
|
var import_fast_glob2 = __toESM(require("fast-glob"), 1);
|
|
2149
2170
|
var MANIFEST_FILE = ".codegen-manifest.json";
|
|
2150
2171
|
var LOCK_FILE = ".watcher.lock";
|
|
2172
|
+
var DriftGuardError = class extends Error {
|
|
2173
|
+
constructor(message) {
|
|
2174
|
+
super(message);
|
|
2175
|
+
this.name = "DriftGuardError";
|
|
2176
|
+
}
|
|
2177
|
+
};
|
|
2178
|
+
function isEntryPoint(value) {
|
|
2179
|
+
return value === "cli" || value === "module";
|
|
2180
|
+
}
|
|
2151
2181
|
function isManifestShape(value) {
|
|
2152
2182
|
if (typeof value !== "object" || value === null) return false;
|
|
2153
2183
|
const candidate = value;
|
|
2154
2184
|
if (typeof candidate.version !== "string") return false;
|
|
2155
2185
|
if (typeof candidate.hash !== "string") return false;
|
|
2186
|
+
if (candidate.entryPoint !== void 0 && !isEntryPoint(candidate.entryPoint)) return false;
|
|
2187
|
+
if (candidate.configHash !== void 0 && typeof candidate.configHash !== "string") return false;
|
|
2156
2188
|
if (!Array.isArray(candidate.files)) return false;
|
|
2157
2189
|
return candidate.files.every((entry) => typeof entry === "string");
|
|
2158
2190
|
}
|
|
@@ -2195,11 +2227,20 @@ async function readManifest(outDir) {
|
|
|
2195
2227
|
const raw = await (0, import_promises11.readFile)((0, import_node_path12.join)(outDir, MANIFEST_FILE), "utf8");
|
|
2196
2228
|
const parsed = JSON.parse(raw);
|
|
2197
2229
|
if (!isManifestShape(parsed)) return null;
|
|
2198
|
-
return {
|
|
2230
|
+
return {
|
|
2231
|
+
version: parsed.version,
|
|
2232
|
+
hash: parsed.hash,
|
|
2233
|
+
...parsed.entryPoint ? { entryPoint: parsed.entryPoint } : {},
|
|
2234
|
+
...parsed.configHash ? { configHash: parsed.configHash } : {},
|
|
2235
|
+
files: parsed.files
|
|
2236
|
+
};
|
|
2199
2237
|
} catch {
|
|
2200
2238
|
return null;
|
|
2201
2239
|
}
|
|
2202
2240
|
}
|
|
2241
|
+
function computeConfigHash(config) {
|
|
2242
|
+
return (0, import_node_crypto.createHash)("sha256").update(serializeConfig(config)).digest("hex");
|
|
2243
|
+
}
|
|
2203
2244
|
async function writeManifest(outDir, manifest) {
|
|
2204
2245
|
await (0, import_promises11.writeFile)((0, import_node_path12.join)(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
|
|
2205
2246
|
`, "utf8");
|
|
@@ -2244,7 +2285,10 @@ function debugWarn(message) {
|
|
|
2244
2285
|
}
|
|
2245
2286
|
|
|
2246
2287
|
// src/generate.ts
|
|
2247
|
-
|
|
2288
|
+
function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint) {
|
|
2289
|
+
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.`;
|
|
2290
|
+
}
|
|
2291
|
+
async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
2248
2292
|
setCodegenDebug(config.debug);
|
|
2249
2293
|
const inputsHash = await computeInputsHash(config);
|
|
2250
2294
|
const manifest = await readManifest(config.codegen.outDir);
|
|
@@ -2252,6 +2296,12 @@ async function generate(config, inputRoutes = []) {
|
|
|
2252
2296
|
console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
|
|
2253
2297
|
return;
|
|
2254
2298
|
}
|
|
2299
|
+
const configHash = computeConfigHash(config);
|
|
2300
|
+
if (config.driftGuard && manifest?.entryPoint && manifest.entryPoint !== entryPoint && manifest.configHash && manifest.configHash !== configHash) {
|
|
2301
|
+
throw new DriftGuardError(
|
|
2302
|
+
driftGuardMessage(config.codegen.outDir, manifest.entryPoint, entryPoint)
|
|
2303
|
+
);
|
|
2304
|
+
}
|
|
2255
2305
|
const extensions = config.extensions ?? [];
|
|
2256
2306
|
let routes = inputRoutes;
|
|
2257
2307
|
const ctx = createExtensionContext(config, () => routes);
|
|
@@ -2317,6 +2367,8 @@ async function generate(config, inputRoutes = []) {
|
|
|
2317
2367
|
await writeManifest(config.codegen.outDir, {
|
|
2318
2368
|
version: VERSION,
|
|
2319
2369
|
hash: inputsHash,
|
|
2370
|
+
entryPoint,
|
|
2371
|
+
configHash,
|
|
2320
2372
|
files: outputFiles
|
|
2321
2373
|
});
|
|
2322
2374
|
}
|
|
@@ -3606,6 +3658,19 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
|
|
|
3606
3658
|
dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
|
|
3607
3659
|
return "unknown";
|
|
3608
3660
|
}
|
|
3661
|
+
if (import_ts_morph7.Node.isTypeLiteral(typeNode)) {
|
|
3662
|
+
const members = [];
|
|
3663
|
+
for (const member of typeNode.getMembers()) {
|
|
3664
|
+
if (import_ts_morph7.Node.isPropertySignature(member)) {
|
|
3665
|
+
const memberTypeNode = member.getTypeNode();
|
|
3666
|
+
const memberType = memberTypeNode ? resolveTypeNodeToString(memberTypeNode, sourceFile, project, depth, subst) : "unknown";
|
|
3667
|
+
members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${memberType}`);
|
|
3668
|
+
} else {
|
|
3669
|
+
members.push(member.getText());
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
return members.length > 0 ? `{ ${members.join("; ")} }` : "{}";
|
|
3673
|
+
}
|
|
3609
3674
|
const kind = typeNode.getKind();
|
|
3610
3675
|
if (kind === import_ts_morph7.SyntaxKind.StringKeyword) return "string";
|
|
3611
3676
|
if (kind === import_ts_morph7.SyntaxKind.NumberKeyword) return "number";
|
|
@@ -3878,6 +3943,14 @@ function resolveBodyQueryResponseRef(typeNode, sourceFile, project) {
|
|
|
3878
3943
|
var STREAM_CONTAINERS = /* @__PURE__ */ new Set(["Observable", "AsyncIterable", "AsyncIterableIterator"]);
|
|
3879
3944
|
var STREAM_CONTAINERS_GENERATOR = /* @__PURE__ */ new Set(["AsyncGenerator"]);
|
|
3880
3945
|
var STREAM_ENVELOPES = /* @__PURE__ */ new Set(["MessageEvent", "MessageEventLike"]);
|
|
3946
|
+
var BINARY_RESPONSE_TYPES = /* @__PURE__ */ new Set(["StreamableFile", "Buffer"]);
|
|
3947
|
+
function detectBinaryResponse(method) {
|
|
3948
|
+
const node = unwrapNamedContainer(method.getReturnTypeNode(), /* @__PURE__ */ new Set(["Promise"]));
|
|
3949
|
+
if (!node || !import_ts_morph7.Node.isTypeReference(node)) return false;
|
|
3950
|
+
const typeName = node.getTypeName();
|
|
3951
|
+
const name = import_ts_morph7.Node.isIdentifier(typeName) ? typeName.getText() : "";
|
|
3952
|
+
return BINARY_RESPONSE_TYPES.has(name);
|
|
3953
|
+
}
|
|
3881
3954
|
function detectStreamElement(method) {
|
|
3882
3955
|
const hasSse = method.getDecorators().some((d) => d.getName() === "Sse");
|
|
3883
3956
|
let node = method.getReturnTypeNode();
|
|
@@ -3898,6 +3971,9 @@ function streamContainerElement(node) {
|
|
|
3898
3971
|
}
|
|
3899
3972
|
return null;
|
|
3900
3973
|
}
|
|
3974
|
+
function hasAsQueryDecorator(method) {
|
|
3975
|
+
return method.getDecorators().some((d) => d.getName() === "AsQuery");
|
|
3976
|
+
}
|
|
3901
3977
|
function unwrapNamedContainer(node, names) {
|
|
3902
3978
|
if (!node || !import_ts_morph7.Node.isTypeReference(node)) return node;
|
|
3903
3979
|
const typeName = node.getTypeName();
|
|
@@ -3915,6 +3991,8 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3915
3991
|
const multipartBody = uploads.fields ? `{ ${uploads.fields} }` : null;
|
|
3916
3992
|
const streamElement = detectStreamElement(method);
|
|
3917
3993
|
const isStream = streamElement !== null;
|
|
3994
|
+
const binaryResponse = detectBinaryResponse(method);
|
|
3995
|
+
const asQuery = hasAsQueryDecorator(method);
|
|
3918
3996
|
if (filterInfo && filterInfo.source === "body") {
|
|
3919
3997
|
const bodyType = "import('@dudousxd/nestjs-filter-client').FilterQueryResult";
|
|
3920
3998
|
body = body ?? bodyType;
|
|
@@ -3922,7 +4000,7 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3922
4000
|
const paramsType = extractParamsType(method, sourceFile, project);
|
|
3923
4001
|
const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
|
|
3924
4002
|
const errorInfo = extractErrorType(method, sourceFile, project);
|
|
3925
|
-
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart) {
|
|
4003
|
+
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
|
|
3926
4004
|
return null;
|
|
3927
4005
|
}
|
|
3928
4006
|
let bodyRef = null;
|
|
@@ -3997,7 +4075,9 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3997
4075
|
querySchema,
|
|
3998
4076
|
stream: isStream,
|
|
3999
4077
|
multipart: uploads.multipart,
|
|
4000
|
-
multipartBody
|
|
4078
|
+
multipartBody,
|
|
4079
|
+
binaryResponse,
|
|
4080
|
+
asQuery
|
|
4001
4081
|
};
|
|
4002
4082
|
}
|
|
4003
4083
|
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
@@ -4484,7 +4564,9 @@ function extractDtoRoute(args) {
|
|
|
4484
4564
|
querySchema: dtoContract?.querySchema ?? null,
|
|
4485
4565
|
stream: dtoContract?.stream ?? false,
|
|
4486
4566
|
multipart: dtoContract?.multipart ?? false,
|
|
4487
|
-
multipartBody: dtoContract?.multipartBody ?? null
|
|
4567
|
+
multipartBody: dtoContract?.multipartBody ?? null,
|
|
4568
|
+
binaryResponse: dtoContract?.binaryResponse ?? false,
|
|
4569
|
+
asQuery: dtoContract?.asQuery ?? false
|
|
4488
4570
|
}
|
|
4489
4571
|
});
|
|
4490
4572
|
}
|
|
@@ -4577,6 +4659,7 @@ var PAGES_DEBOUNCE_MS = 150;
|
|
|
4577
4659
|
var NO_OP_WATCHER = { close: async () => {
|
|
4578
4660
|
} };
|
|
4579
4661
|
async function watch(config, onChange, options = {}) {
|
|
4662
|
+
const entryPoint = options.entryPoint ?? "cli";
|
|
4580
4663
|
const lock = await acquireLock(config.codegen.outDir);
|
|
4581
4664
|
if (lock === null) {
|
|
4582
4665
|
let holderPid = "unknown";
|
|
@@ -4608,13 +4691,17 @@ async function watch(config, onChange, options = {}) {
|
|
|
4608
4691
|
try {
|
|
4609
4692
|
const initialRoutes = (await getDiscovery()).discover();
|
|
4610
4693
|
lastRoutes = initialRoutes;
|
|
4611
|
-
await generate(config, initialRoutes);
|
|
4694
|
+
await generate(config, initialRoutes, entryPoint);
|
|
4612
4695
|
} catch (err) {
|
|
4696
|
+
if (err instanceof DriftGuardError) {
|
|
4697
|
+
console.error(err.message);
|
|
4698
|
+
return;
|
|
4699
|
+
}
|
|
4613
4700
|
console.warn(
|
|
4614
4701
|
`[nestjs-codegen] Initial route discovery failed, falling back to pages-only: ${err instanceof Error ? err.message : String(err)}`
|
|
4615
4702
|
);
|
|
4616
4703
|
try {
|
|
4617
|
-
await generate(config, lastRoutes);
|
|
4704
|
+
await generate(config, lastRoutes, entryPoint);
|
|
4618
4705
|
} catch {
|
|
4619
4706
|
}
|
|
4620
4707
|
}
|
|
@@ -4642,7 +4729,7 @@ async function watch(config, onChange, options = {}) {
|
|
|
4642
4729
|
pagesDebounceTimer = setTimeout(async () => {
|
|
4643
4730
|
pagesDebounceTimer = void 0;
|
|
4644
4731
|
try {
|
|
4645
|
-
await generate(config, lastRoutes);
|
|
4732
|
+
await generate(config, lastRoutes, entryPoint);
|
|
4646
4733
|
} catch (err) {
|
|
4647
4734
|
console.error(
|
|
4648
4735
|
"[nestjs-codegen] Pages generation failed:",
|
|
@@ -4674,7 +4761,7 @@ async function watch(config, onChange, options = {}) {
|
|
|
4674
4761
|
try {
|
|
4675
4762
|
const routes = await (await getDiscovery()).rediscover(changed);
|
|
4676
4763
|
lastRoutes = routes;
|
|
4677
|
-
await generate(config, routes);
|
|
4764
|
+
await generate(config, routes, entryPoint);
|
|
4678
4765
|
} catch (err) {
|
|
4679
4766
|
console.error(
|
|
4680
4767
|
"[nestjs-codegen] Contracts generation failed:",
|
|
@@ -4714,14 +4801,14 @@ async function watch(config, onChange, options = {}) {
|
|
|
4714
4801
|
}
|
|
4715
4802
|
|
|
4716
4803
|
// src/index.ts
|
|
4717
|
-
var VERSION = "0.
|
|
4804
|
+
var VERSION = "0.14.0";
|
|
4718
4805
|
|
|
4719
4806
|
// src/cli/codegen.ts
|
|
4720
4807
|
async function runCodegen(opts = {}) {
|
|
4721
4808
|
const cwd = opts.cwd ?? process.cwd();
|
|
4722
4809
|
const config = await loadConfig(cwd);
|
|
4723
4810
|
if (opts.watch) {
|
|
4724
|
-
const watcher = await watch(config);
|
|
4811
|
+
const watcher = await watch(config, void 0, { entryPoint: "cli" });
|
|
4725
4812
|
await new Promise((resolve4) => {
|
|
4726
4813
|
function onSignal() {
|
|
4727
4814
|
watcher.close().then(resolve4).catch(resolve4);
|
|
@@ -4736,7 +4823,7 @@ async function runCodegen(opts = {}) {
|
|
|
4736
4823
|
glob: config.contracts.glob,
|
|
4737
4824
|
...config.app?.tsconfig ? { tsconfig: config.app.tsconfig } : {}
|
|
4738
4825
|
});
|
|
4739
|
-
await generate(config, routes);
|
|
4826
|
+
await generate(config, routes, "cli");
|
|
4740
4827
|
console.log("\u2713 Codegen generated artifacts in", config.codegen.outDir);
|
|
4741
4828
|
}
|
|
4742
4829
|
|