zod-nest 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +19 -6
- package/dist/index.d.ts +19 -6
- package/dist/index.js +147 -76
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +147 -76
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -446,13 +446,26 @@ interface ZodResponseOptions {
|
|
|
446
446
|
passthroughOnError?: boolean;
|
|
447
447
|
}
|
|
448
448
|
/**
|
|
449
|
-
* Method-only decorator. Declares a typed response variant for the handler
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
* `
|
|
449
|
+
* Method-only decorator. Declares a typed response variant for the handler
|
|
450
|
+
* AND applies the equivalent `@ApiResponse(...)` from `@nestjs/swagger` so
|
|
451
|
+
* the OpenAPI document carries the response shape — no need for consumers
|
|
452
|
+
* to hand-write `@ApiResponse` alongside `@ZodResponse`.
|
|
453
453
|
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
454
|
+
* Stack multiple decorations to declare per-status types; runtime lookup
|
|
455
|
+
* is by `ZodSerializerInterceptor`'s two-pass matcher (exact numeric, then
|
|
456
|
+
* `'NXX'` wildcard). The wrapped Zod schema (array / tuple) is built once
|
|
457
|
+
* at decoration time — no per-request schema construction.
|
|
458
|
+
*
|
|
459
|
+
* **Decorator-ordering note (implicit `status` only).** TypeScript decorators
|
|
460
|
+
* apply bottom-up, so `@ZodResponse` (typically written above `@Get` /
|
|
461
|
+
* `@HttpCode`) executes its factory body *first* — before sibling
|
|
462
|
+
* decorators have written their `HTTP_CODE_METADATA` / `METHOD_METADATA`.
|
|
463
|
+
* When `opts.status` is explicit, the `@ApiResponse(...)` call is applied
|
|
464
|
+
* synchronously — there's nothing to wait for. When `opts.status` is
|
|
465
|
+
* implicit (resolves to `@HttpCode` or the HTTP-method default), the
|
|
466
|
+
* `@ApiResponse(...)` call is deferred via `queueMicrotask` so the sibling
|
|
467
|
+
* metadata has settled by the time we read it. See `docs/responses.md →
|
|
468
|
+
* "Decorator ordering & the microtask trick"`.
|
|
456
469
|
*/
|
|
457
470
|
declare const ZodResponse: (opts: ZodResponseOptions) => MethodDecorator;
|
|
458
471
|
|
package/dist/index.d.ts
CHANGED
|
@@ -446,13 +446,26 @@ interface ZodResponseOptions {
|
|
|
446
446
|
passthroughOnError?: boolean;
|
|
447
447
|
}
|
|
448
448
|
/**
|
|
449
|
-
* Method-only decorator. Declares a typed response variant for the handler
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
* `
|
|
449
|
+
* Method-only decorator. Declares a typed response variant for the handler
|
|
450
|
+
* AND applies the equivalent `@ApiResponse(...)` from `@nestjs/swagger` so
|
|
451
|
+
* the OpenAPI document carries the response shape — no need for consumers
|
|
452
|
+
* to hand-write `@ApiResponse` alongside `@ZodResponse`.
|
|
453
453
|
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
454
|
+
* Stack multiple decorations to declare per-status types; runtime lookup
|
|
455
|
+
* is by `ZodSerializerInterceptor`'s two-pass matcher (exact numeric, then
|
|
456
|
+
* `'NXX'` wildcard). The wrapped Zod schema (array / tuple) is built once
|
|
457
|
+
* at decoration time — no per-request schema construction.
|
|
458
|
+
*
|
|
459
|
+
* **Decorator-ordering note (implicit `status` only).** TypeScript decorators
|
|
460
|
+
* apply bottom-up, so `@ZodResponse` (typically written above `@Get` /
|
|
461
|
+
* `@HttpCode`) executes its factory body *first* — before sibling
|
|
462
|
+
* decorators have written their `HTTP_CODE_METADATA` / `METHOD_METADATA`.
|
|
463
|
+
* When `opts.status` is explicit, the `@ApiResponse(...)` call is applied
|
|
464
|
+
* synchronously — there's nothing to wait for. When `opts.status` is
|
|
465
|
+
* implicit (resolves to `@HttpCode` or the HTTP-method default), the
|
|
466
|
+
* `@ApiResponse(...)` call is deferred via `queueMicrotask` so the sibling
|
|
467
|
+
* metadata has settled by the time we read it. See `docs/responses.md →
|
|
468
|
+
* "Decorator ordering & the microtask trick"`.
|
|
456
469
|
*/
|
|
457
470
|
declare const ZodResponse: (opts: ZodResponseOptions) => MethodDecorator;
|
|
458
471
|
|
package/dist/index.js
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
var zod = require('zod');
|
|
4
4
|
var common = require('@nestjs/common');
|
|
5
|
+
var constants_js = require('@nestjs/common/constants.js');
|
|
6
|
+
var swagger = require('@nestjs/swagger');
|
|
5
7
|
var core = require('@nestjs/core');
|
|
6
8
|
var rxjs = require('rxjs');
|
|
7
9
|
var operators = require('rxjs/operators');
|
|
8
|
-
var constants_js = require('@nestjs/common/constants.js');
|
|
9
10
|
var stringify = require('fast-json-stable-stringify');
|
|
10
11
|
|
|
11
12
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -924,6 +925,25 @@ exports.ZodValidationPipe = _ts_decorate([
|
|
|
924
925
|
typeof NormalizedZodNestOptions === "undefined" ? Object : NormalizedZodNestOptions
|
|
925
926
|
])
|
|
926
927
|
], exports.ZodValidationPipe);
|
|
928
|
+
var POST_DEFAULT_STATUS = 201;
|
|
929
|
+
var GENERIC_DEFAULT_STATUS = 200;
|
|
930
|
+
var defaultStatusFor = /* @__PURE__ */ __name((handler) => {
|
|
931
|
+
const httpCode = Reflect.getMetadata(constants_js.HTTP_CODE_METADATA, handler);
|
|
932
|
+
if (typeof httpCode === "number") {
|
|
933
|
+
return httpCode;
|
|
934
|
+
}
|
|
935
|
+
const method = Reflect.getMetadata(constants_js.METHOD_METADATA, handler);
|
|
936
|
+
if (method === common.RequestMethod.POST) {
|
|
937
|
+
return POST_DEFAULT_STATUS;
|
|
938
|
+
}
|
|
939
|
+
return GENERIC_DEFAULT_STATUS;
|
|
940
|
+
}, "defaultStatusFor");
|
|
941
|
+
var resolveEffectiveStatus = /* @__PURE__ */ __name((variant, handler) => {
|
|
942
|
+
if (variant.status !== void 0) {
|
|
943
|
+
return variant.status;
|
|
944
|
+
}
|
|
945
|
+
return defaultStatusFor(handler);
|
|
946
|
+
}, "resolveEffectiveStatus");
|
|
927
947
|
|
|
928
948
|
// src/response/metadata.ts
|
|
929
949
|
var ZOD_RESPONSES_METADATA_KEY = /* @__PURE__ */ Symbol.for("zod-nest.responses");
|
|
@@ -938,6 +958,67 @@ var appendResponseVariant = /* @__PURE__ */ __name((handler, variant) => {
|
|
|
938
958
|
...existing
|
|
939
959
|
], handler);
|
|
940
960
|
}, "appendResponseVariant");
|
|
961
|
+
var extractDescriptionFields = /* @__PURE__ */ __name((desc) => {
|
|
962
|
+
if (desc === void 0) {
|
|
963
|
+
return {};
|
|
964
|
+
}
|
|
965
|
+
if (typeof desc === "string") {
|
|
966
|
+
return {
|
|
967
|
+
description: desc
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
const out = {
|
|
971
|
+
description: desc.description
|
|
972
|
+
};
|
|
973
|
+
if (desc.headers !== void 0) {
|
|
974
|
+
out.headers = desc.headers;
|
|
975
|
+
}
|
|
976
|
+
if (desc.links !== void 0) {
|
|
977
|
+
out.links = desc.links;
|
|
978
|
+
}
|
|
979
|
+
return out;
|
|
980
|
+
}, "extractDescriptionFields");
|
|
981
|
+
var asDtoFunction = /* @__PURE__ */ __name((dto) => dto, "asDtoFunction");
|
|
982
|
+
var buildApiResponseOptions = /* @__PURE__ */ __name((base, body) => {
|
|
983
|
+
return {
|
|
984
|
+
...base,
|
|
985
|
+
...body
|
|
986
|
+
};
|
|
987
|
+
}, "buildApiResponseOptions");
|
|
988
|
+
var applySwaggerResponseDecorator = /* @__PURE__ */ __name((variant, effectiveStatus, target, propertyKey, descriptor) => {
|
|
989
|
+
const base = {
|
|
990
|
+
status: effectiveStatus,
|
|
991
|
+
...extractDescriptionFields(variant.description)
|
|
992
|
+
};
|
|
993
|
+
if (variant.kind === "single") {
|
|
994
|
+
const dto = variant.dto;
|
|
995
|
+
swagger.ApiResponse(buildApiResponseOptions(base, {
|
|
996
|
+
type: asDtoFunction(dto)
|
|
997
|
+
}))(target, propertyKey, descriptor);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
if (variant.kind === "array") {
|
|
1001
|
+
const dtos2 = variant.dto;
|
|
1002
|
+
const dto = dtos2[0];
|
|
1003
|
+
swagger.ApiResponse(buildApiResponseOptions(base, {
|
|
1004
|
+
type: asDtoFunction(dto),
|
|
1005
|
+
isArray: true
|
|
1006
|
+
}))(target, propertyKey, descriptor);
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
const dtos = variant.dto;
|
|
1010
|
+
swagger.ApiExtraModels(...dtos.map(asDtoFunction))(target, propertyKey, descriptor);
|
|
1011
|
+
const schema = {
|
|
1012
|
+
type: "array",
|
|
1013
|
+
prefixItems: dtos.map((d) => ({
|
|
1014
|
+
$ref: swagger.getSchemaPath(asDtoFunction(d))
|
|
1015
|
+
})),
|
|
1016
|
+
items: false
|
|
1017
|
+
};
|
|
1018
|
+
swagger.ApiResponse(buildApiResponseOptions(base, {
|
|
1019
|
+
schema
|
|
1020
|
+
}))(target, propertyKey, descriptor);
|
|
1021
|
+
}, "applySwaggerResponseDecorator");
|
|
941
1022
|
|
|
942
1023
|
// src/decorators/zod-response.decorator.ts
|
|
943
1024
|
var buildArrayKind = /* @__PURE__ */ __name((dtos) => {
|
|
@@ -991,7 +1072,8 @@ var normaliseStatus = /* @__PURE__ */ __name((status) => {
|
|
|
991
1072
|
var ZodResponse = /* @__PURE__ */ __name((opts) => {
|
|
992
1073
|
const built = buildKind(opts.type);
|
|
993
1074
|
const status = normaliseStatus(opts.status);
|
|
994
|
-
|
|
1075
|
+
const statusExplicit = status !== void 0;
|
|
1076
|
+
return (target, propertyKey, descriptor) => {
|
|
995
1077
|
const handler = descriptor.value;
|
|
996
1078
|
if (typeof handler !== "function") {
|
|
997
1079
|
throw new TypeError("[zod-nest] @ZodResponse can only be applied to methods.");
|
|
@@ -1005,29 +1087,17 @@ var ZodResponse = /* @__PURE__ */ __name((opts) => {
|
|
|
1005
1087
|
passthroughOnError: opts.passthroughOnError ?? false
|
|
1006
1088
|
};
|
|
1007
1089
|
appendResponseVariant(handler, variant);
|
|
1090
|
+
const swaggerDescriptor = descriptor;
|
|
1091
|
+
if (statusExplicit) {
|
|
1092
|
+
applySwaggerResponseDecorator(variant, status, target, propertyKey, swaggerDescriptor);
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
queueMicrotask(() => {
|
|
1096
|
+
const effective = resolveEffectiveStatus(variant, handler);
|
|
1097
|
+
applySwaggerResponseDecorator(variant, effective, target, propertyKey, swaggerDescriptor);
|
|
1098
|
+
});
|
|
1008
1099
|
};
|
|
1009
1100
|
}, "ZodResponse");
|
|
1010
|
-
var POST_DEFAULT_STATUS = 201;
|
|
1011
|
-
var GENERIC_DEFAULT_STATUS = 200;
|
|
1012
|
-
var defaultStatusFor = /* @__PURE__ */ __name((handler) => {
|
|
1013
|
-
const httpCode = Reflect.getMetadata(constants_js.HTTP_CODE_METADATA, handler);
|
|
1014
|
-
if (typeof httpCode === "number") {
|
|
1015
|
-
return httpCode;
|
|
1016
|
-
}
|
|
1017
|
-
const method = Reflect.getMetadata(constants_js.METHOD_METADATA, handler);
|
|
1018
|
-
if (method === common.RequestMethod.POST) {
|
|
1019
|
-
return POST_DEFAULT_STATUS;
|
|
1020
|
-
}
|
|
1021
|
-
return GENERIC_DEFAULT_STATUS;
|
|
1022
|
-
}, "defaultStatusFor");
|
|
1023
|
-
var resolveEffectiveStatus = /* @__PURE__ */ __name((variant, handler) => {
|
|
1024
|
-
if (variant.status !== void 0) {
|
|
1025
|
-
return variant.status;
|
|
1026
|
-
}
|
|
1027
|
-
return defaultStatusFor(handler);
|
|
1028
|
-
}, "resolveEffectiveStatus");
|
|
1029
|
-
|
|
1030
|
-
// src/interceptors/serializer.interceptor.ts
|
|
1031
1101
|
function _ts_decorate2(decorators, target, key, desc) {
|
|
1032
1102
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1033
1103
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -1199,6 +1269,25 @@ var HTTP_METHODS = [
|
|
|
1199
1269
|
"patch",
|
|
1200
1270
|
"trace"
|
|
1201
1271
|
];
|
|
1272
|
+
var forEachOperation = /* @__PURE__ */ __name((doc, fn) => {
|
|
1273
|
+
const paths = doc.paths;
|
|
1274
|
+
if (paths === null || typeof paths !== "object") {
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
for (const pathItem of Object.values(paths)) {
|
|
1278
|
+
if (pathItem === null || typeof pathItem !== "object") {
|
|
1279
|
+
continue;
|
|
1280
|
+
}
|
|
1281
|
+
const pathRecord = pathItem;
|
|
1282
|
+
for (const method of HTTP_METHODS) {
|
|
1283
|
+
const op = pathRecord[method];
|
|
1284
|
+
if (op === null || typeof op !== "object") {
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
fn(op);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}, "forEachOperation");
|
|
1202
1291
|
|
|
1203
1292
|
// src/document/collect-usage.ts
|
|
1204
1293
|
var isPlainRecord2 = /* @__PURE__ */ __name((value) => value !== null && typeof value === "object" && !Array.isArray(value), "isPlainRecord");
|
|
@@ -1441,41 +1530,39 @@ var hintFor = /* @__PURE__ */ __name((ref, collected) => {
|
|
|
1441
1530
|
var isPlainRecord3 = /* @__PURE__ */ __name((value) => value !== null && typeof value === "object" && !Array.isArray(value), "isPlainRecord");
|
|
1442
1531
|
var expandParamMarkers = /* @__PURE__ */ __name((params) => {
|
|
1443
1532
|
const { doc, inputSchemas, outputSchemas } = params;
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
if (!isPlainRecord3(pathItem)) {
|
|
1450
|
-
continue;
|
|
1533
|
+
let expandedAny = false;
|
|
1534
|
+
forEachOperation(doc, (op) => {
|
|
1535
|
+
const parameters = op.parameters;
|
|
1536
|
+
if (!Array.isArray(parameters)) {
|
|
1537
|
+
return;
|
|
1451
1538
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
}
|
|
1457
|
-
const parameters = op.parameters;
|
|
1458
|
-
if (!Array.isArray(parameters)) {
|
|
1459
|
-
continue;
|
|
1460
|
-
}
|
|
1461
|
-
op.parameters = expandParameterList(parameters, inputSchemas, outputSchemas);
|
|
1539
|
+
const next = expandParameterList(parameters, inputSchemas, outputSchemas);
|
|
1540
|
+
if (next !== parameters) {
|
|
1541
|
+
op.parameters = next;
|
|
1542
|
+
expandedAny = true;
|
|
1462
1543
|
}
|
|
1544
|
+
});
|
|
1545
|
+
if (expandedAny) {
|
|
1546
|
+
pruneOrphanObjectSchema(doc);
|
|
1463
1547
|
}
|
|
1464
|
-
pruneOrphanObjectSchema(doc);
|
|
1465
1548
|
}, "expandParamMarkers");
|
|
1466
1549
|
var expandParameterList = /* @__PURE__ */ __name((parameters, inputSchemas, outputSchemas) => {
|
|
1467
|
-
|
|
1468
|
-
for (
|
|
1550
|
+
let result;
|
|
1551
|
+
for (let i = 0; i < parameters.length; i++) {
|
|
1552
|
+
const param = parameters[i];
|
|
1469
1553
|
const marker = readMarker2(param);
|
|
1470
1554
|
if (marker === void 0) {
|
|
1471
|
-
result
|
|
1555
|
+
result?.push(param);
|
|
1472
1556
|
continue;
|
|
1473
1557
|
}
|
|
1558
|
+
if (result === void 0) {
|
|
1559
|
+
result = parameters.slice(0, i);
|
|
1560
|
+
}
|
|
1474
1561
|
const map = marker.io === "output" ? outputSchemas : inputSchemas;
|
|
1475
1562
|
const body = map.get(marker.dtoId);
|
|
1476
1563
|
result.push(...expandOne(marker, body));
|
|
1477
1564
|
}
|
|
1478
|
-
return result;
|
|
1565
|
+
return result ?? parameters;
|
|
1479
1566
|
}, "expandParameterList");
|
|
1480
1567
|
var readMarker2 = /* @__PURE__ */ __name((value) => {
|
|
1481
1568
|
if (!isPlainRecord3(value)) {
|
|
@@ -1532,16 +1619,12 @@ var buildParameter = /* @__PURE__ */ __name((marker, name, schema, required) =>
|
|
|
1532
1619
|
console.warn(`[zod-nest] Path parameter \`${name}\` on DTO \`${marker.dtoId}\` is marked optional in the Zod schema; OpenAPI 3.1 requires path parameters to be required. Coercing \`required: true\` so the emitted document is spec-valid. Fix by removing \`.optional()\` / \`.nullish()\` from the field, or by switching the decorator to @Query() / @Headers() if the field is genuinely optional.`);
|
|
1533
1620
|
effectiveRequired = true;
|
|
1534
1621
|
}
|
|
1535
|
-
|
|
1622
|
+
return {
|
|
1536
1623
|
name,
|
|
1537
1624
|
in: marker.in,
|
|
1538
1625
|
required: effectiveRequired,
|
|
1539
1626
|
schema
|
|
1540
1627
|
};
|
|
1541
|
-
if (typeof schema.description === "string") {
|
|
1542
|
-
entry.description = schema.description;
|
|
1543
|
-
}
|
|
1544
|
-
return entry;
|
|
1545
1628
|
}, "buildParameter");
|
|
1546
1629
|
var capitalize = /* @__PURE__ */ __name((value) => value.charAt(0).toUpperCase() + value.slice(1), "capitalize");
|
|
1547
1630
|
var pruneOrphanObjectSchema = /* @__PURE__ */ __name((doc) => {
|
|
@@ -1755,40 +1838,28 @@ var stripMarkers = /* @__PURE__ */ __name((doc) => {
|
|
|
1755
1838
|
if (schemas !== void 0) {
|
|
1756
1839
|
for (const schema of Object.values(schemas)) {
|
|
1757
1840
|
stripMarkerFromSchema(schema);
|
|
1841
|
+
dropJsonSchemaMetadata(schema);
|
|
1758
1842
|
}
|
|
1759
1843
|
}
|
|
1760
1844
|
stripMarkerParameters(doc);
|
|
1761
1845
|
}, "stripMarkers");
|
|
1762
|
-
var
|
|
1763
|
-
|
|
1764
|
-
if (paths === null || typeof paths !== "object") {
|
|
1846
|
+
var dropJsonSchemaMetadata = /* @__PURE__ */ __name((schema) => {
|
|
1847
|
+
if (schema === null || typeof schema !== "object") {
|
|
1765
1848
|
return;
|
|
1766
1849
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
}
|
|
1777
|
-
const opRecord = op;
|
|
1778
|
-
const parameters = opRecord.parameters;
|
|
1779
|
-
if (!Array.isArray(parameters)) {
|
|
1780
|
-
continue;
|
|
1781
|
-
}
|
|
1782
|
-
opRecord.parameters = parameters.filter((param) => !isMarkerParam(param));
|
|
1850
|
+
const body = schema;
|
|
1851
|
+
delete body.$schema;
|
|
1852
|
+
delete body.$id;
|
|
1853
|
+
}, "dropJsonSchemaMetadata");
|
|
1854
|
+
var stripMarkerParameters = /* @__PURE__ */ __name((doc) => {
|
|
1855
|
+
forEachOperation(doc, (op) => {
|
|
1856
|
+
const parameters = op.parameters;
|
|
1857
|
+
if (!Array.isArray(parameters)) {
|
|
1858
|
+
return;
|
|
1783
1859
|
}
|
|
1784
|
-
|
|
1860
|
+
op.parameters = parameters.filter((param) => !isZodDtoMarker(param));
|
|
1861
|
+
});
|
|
1785
1862
|
}, "stripMarkerParameters");
|
|
1786
|
-
var isMarkerParam = /* @__PURE__ */ __name((value) => {
|
|
1787
|
-
if (value === null || typeof value !== "object") {
|
|
1788
|
-
return false;
|
|
1789
|
-
}
|
|
1790
|
-
return value.__zodNestDto === true;
|
|
1791
|
-
}, "isMarkerParam");
|
|
1792
1863
|
var stripMarkerFromSchema = /* @__PURE__ */ __name((schema) => {
|
|
1793
1864
|
if (schema === null || typeof schema !== "object") {
|
|
1794
1865
|
return;
|