zod-nest 2.1.5 → 3.0.1
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/README.md +7 -13
- package/dist/index.d.mts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +65 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +65 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +33 -31
package/README.md
CHANGED
|
@@ -245,20 +245,11 @@ The id is what appears as the `components.schemas` key in the OpenAPI document a
|
|
|
245
245
|
|
|
246
246
|
```ts
|
|
247
247
|
// Preferred — on the schema, via Zod's metadata
|
|
248
|
-
const userSchema = z
|
|
249
|
-
.object({
|
|
250
|
-
/* ... */
|
|
251
|
-
})
|
|
252
|
-
.meta({ id: 'User' });
|
|
248
|
+
const userSchema = z.object({/* ... */}).meta({ id: 'User' });
|
|
253
249
|
class UserDto extends createZodDto(userSchema) {}
|
|
254
250
|
|
|
255
251
|
// Also valid — passed through createZodDto's options
|
|
256
|
-
class UserDto extends createZodDto(
|
|
257
|
-
z.object({
|
|
258
|
-
/* ... */
|
|
259
|
-
}),
|
|
260
|
-
{ id: 'User' },
|
|
261
|
-
) {}
|
|
252
|
+
class UserDto extends createZodDto(z.object({/* ... */}), { id: 'User' }) {}
|
|
262
253
|
```
|
|
263
254
|
|
|
264
255
|
Both produce the same OpenAPI output. `.meta({ id })` is preferred when the schema is hoisted into its own `const`, because the id stays with the schema — composition (`extend(parent, ...)`), shared input/output via `.meta({ id })` on the same schema reference, and any non-DTO use of the schema all pick up the same id without an extra hop through `createZodDto`'s options. Use the `createZodDto(schema, { id })` form when you don't own the schema (e.g. it comes from a third-party module) or when defining a small DTO with an inline schema, where chaining `.meta()` on the inline expression hurts readability.
|
|
@@ -525,7 +516,9 @@ See [`docs/recipes/custom-openapi-overrides.md`](docs/recipes/custom-openapi-ove
|
|
|
525
516
|
|
|
526
517
|
## Compatibility matrix
|
|
527
518
|
|
|
528
|
-
`zod-nest` declares explicit min + max peer-dep ranges in `package.json`: `zod >=4.4.0 <5.0.0`, `@nestjs/common >=11.0.1 <
|
|
519
|
+
`zod-nest` declares explicit min + max peer-dep ranges in `package.json`: `zod >=4.4.0 <5.0.0`, `@nestjs/common >=11.0.1 <13.0.0`, `@nestjs/core >=11.0.1 <13.0.0`, `@nestjs/swagger >=11.0.0 <13.0.0`, `rxjs >=7.6.0 <8.0.0`, `reflect-metadata >=0.2.0 <0.3.0`, Node `>=22.12`. CI validates those claims by running the full test suite against the cells below; a red cell is a real blocker. Upper bounds are deliberate — a new peer major has to land in a real PR with a `/check-upstream-updates` audit before consumers can install it against this library.
|
|
520
|
+
|
|
521
|
+
NestJS 11 and 12 are both supported. The Node floor is `>=22.12` rather than `>=22` because NestJS 12 ships as ESM: a CommonJS app doing `require('zod-nest')` reaches `require('@nestjs/common')`, and unflagged `require(esm)` only landed in Node 22.12.0.
|
|
529
522
|
|
|
530
523
|
| Cell | `zod` | `@nestjs/common` | `@nestjs/core` | `@nestjs/swagger` | `rxjs` | `reflect-metadata` |
|
|
531
524
|
| ------------------------- | ------ | ---------------- | -------------- | ----------------- | ------ | ------------------ |
|
|
@@ -533,13 +526,14 @@ See [`docs/recipes/custom-openapi-overrides.md`](docs/recipes/custom-openapi-ove
|
|
|
533
526
|
| `zod-latest` | latest | 11.0.1 | 11.0.1 | 11.0.0 | 7.6.0 | 0.2.0 |
|
|
534
527
|
| `zod-4.5` | 4.5.4 | 11.0.1 | 11.0.1 | 11.0.0 | 7.6.0 | 0.2.0 |
|
|
535
528
|
| `nest-latest` | 4.4.0 | latest | latest | latest | 7.6.0 | 0.2.0 |
|
|
529
|
+
| `nest12` | 4.4.0 | 12.0.1 | 12.0.1 | 12.0.1 | 7.6.0 | 0.2.0 |
|
|
536
530
|
| `rxjs-latest` | 4.4.0 | 11.0.1 | 11.0.1 | 11.0.0 | latest | 0.2.0 |
|
|
537
531
|
| `reflect-metadata-latest` | 4.4.0 | 11.0.1 | 11.0.1 | 11.0.0 | 7.6.0 | latest |
|
|
538
532
|
| `all-latest` | latest | latest | latest | latest | latest | latest |
|
|
539
533
|
|
|
540
534
|
One emission difference is visible across the supported `zod` range: from 4.5, a union of primitives — including `.nullable()` — emits `{ "type": ["string", "null"] }` where 4.4 emitted `{ "anyOf": [{ "type": "string" }, { "type": "null" }] }`. Both are valid OpenAPI 3.1 and validate identically; only the spelling changes. Everything else — `$ref` layout, composition `allOf`, input/output siblings, `paths` — is byte-identical across 4.4 and 4.5.
|
|
541
535
|
|
|
542
|
-
Cell definitions live in [`.github/compat-matrix.json`](.github/compat-matrix.json). The CI workflow ([`.github/workflows/compat-matrix.yml`](.github/workflows/compat-matrix.yml)) runs on every push to `main` and weekly on Monday — when a cell fails, the workflow opens (or comments on) a GitHub issue labelled `compat-matrix-failure` so the regression is tracked outside the Actions UI. Editing the JSON is the formal way to extend or shrink supported ranges. Node is not matrixed — the `>=22` floor is enforced by `engines`.
|
|
536
|
+
Cell definitions live in [`.github/compat-matrix.json`](.github/compat-matrix.json). The CI workflow ([`.github/workflows/compat-matrix.yml`](.github/workflows/compat-matrix.yml)) runs on every push to `main` and weekly on Monday — when a cell fails, the workflow opens (or comments on) a GitHub issue labelled `compat-matrix-failure` so the regression is tracked outside the Actions UI. Editing the JSON is the formal way to extend or shrink supported ranges. Node is not matrixed — the `>=22.12` floor is enforced by `engines`. Cells that move NestJS also pin `@nestjs/platform-express` and `@nestjs/testing` to the same major (omitted from the table above for width) — both peer-require matching `@nestjs/common` + `@nestjs/core` majors, so moving the others without them just produces a peer conflict.
|
|
543
537
|
|
|
544
538
|
## Migration from `nestjs-zod`
|
|
545
539
|
|
package/dist/index.d.mts
CHANGED
|
@@ -895,9 +895,11 @@ type ZodNestDocumentErrorCode = 'AMBIGUOUS_RENAME' | 'DANGLING_REF' | 'UNEXPANDA
|
|
|
895
895
|
* Thrown by `applyZodNest` when the doc cannot be processed cleanly. Surfaces
|
|
896
896
|
* at doc-build time so typos / mis-registrations fail in CI, not at runtime.
|
|
897
897
|
*
|
|
898
|
-
* `AMBIGUOUS_RENAME`: two distinct
|
|
899
|
-
*
|
|
900
|
-
*
|
|
898
|
+
* `AMBIGUOUS_RENAME`: two distinct bodies target one `components.schemas[id]`.
|
|
899
|
+
* `details.preexisting` distinguishes the causes — `true` means the key was
|
|
900
|
+
* already populated before zod-nest emitted anything (on NestJS 12+, usually
|
|
901
|
+
* the native Standard Schema path emitting the component itself); `false`
|
|
902
|
+
* means two registered ids collided on one key.
|
|
901
903
|
*
|
|
902
904
|
* `DANGLING_REF`: a `$ref` in the doc points at a `components.schemas` key
|
|
903
905
|
* that no longer exists after `applyZodNest`. Usually means a marker was
|
package/dist/index.d.ts
CHANGED
|
@@ -895,9 +895,11 @@ type ZodNestDocumentErrorCode = 'AMBIGUOUS_RENAME' | 'DANGLING_REF' | 'UNEXPANDA
|
|
|
895
895
|
* Thrown by `applyZodNest` when the doc cannot be processed cleanly. Surfaces
|
|
896
896
|
* at doc-build time so typos / mis-registrations fail in CI, not at runtime.
|
|
897
897
|
*
|
|
898
|
-
* `AMBIGUOUS_RENAME`: two distinct
|
|
899
|
-
*
|
|
900
|
-
*
|
|
898
|
+
* `AMBIGUOUS_RENAME`: two distinct bodies target one `components.schemas[id]`.
|
|
899
|
+
* `details.preexisting` distinguishes the causes — `true` means the key was
|
|
900
|
+
* already populated before zod-nest emitted anything (on NestJS 12+, usually
|
|
901
|
+
* the native Standard Schema path emitting the component itself); `false`
|
|
902
|
+
* means two registered ids collided on one key.
|
|
901
903
|
*
|
|
902
904
|
* `DANGLING_REF`: a `$ref` in the doc points at a `components.schemas` key
|
|
903
905
|
* that no longer exists after `applyZodNest`. Usually means a marker was
|
package/dist/index.js
CHANGED
|
@@ -1063,13 +1063,22 @@ var normalizeZodNestOptions = /* @__PURE__ */ __name((opts) => {
|
|
|
1063
1063
|
// src/pipes/validation.pipe.ts
|
|
1064
1064
|
function _ts_decorate(decorators, target, key, desc) {
|
|
1065
1065
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1066
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
|
|
1067
|
-
|
|
1066
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
1067
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
1068
|
+
} else {
|
|
1069
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
1070
|
+
if (d = decorators[i]) {
|
|
1071
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1068
1075
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1069
1076
|
}
|
|
1070
1077
|
__name(_ts_decorate, "_ts_decorate");
|
|
1071
|
-
function _ts_metadata(
|
|
1072
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
|
|
1078
|
+
function _ts_metadata(metadataKey, metadataValue) {
|
|
1079
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
1080
|
+
return Reflect.metadata(metadataKey, metadataValue);
|
|
1081
|
+
}
|
|
1073
1082
|
}
|
|
1074
1083
|
__name(_ts_metadata, "_ts_metadata");
|
|
1075
1084
|
function _ts_param(paramIndex, decorator) {
|
|
@@ -1728,13 +1737,22 @@ var ZodCookies = /* @__PURE__ */ __name((schema, options) => {
|
|
|
1728
1737
|
}, "ZodCookies");
|
|
1729
1738
|
function _ts_decorate2(decorators, target, key, desc) {
|
|
1730
1739
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1731
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
|
|
1732
|
-
|
|
1740
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
1741
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
1742
|
+
} else {
|
|
1743
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
1744
|
+
if (d = decorators[i]) {
|
|
1745
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1733
1749
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1734
1750
|
}
|
|
1735
1751
|
__name(_ts_decorate2, "_ts_decorate");
|
|
1736
|
-
function _ts_metadata2(
|
|
1737
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
|
|
1752
|
+
function _ts_metadata2(metadataKey, metadataValue) {
|
|
1753
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
1754
|
+
return Reflect.metadata(metadataKey, metadataValue);
|
|
1755
|
+
}
|
|
1738
1756
|
}
|
|
1739
1757
|
__name(_ts_metadata2, "_ts_metadata");
|
|
1740
1758
|
function _ts_param2(paramIndex, decorator) {
|
|
@@ -2410,8 +2428,18 @@ var mergeSchemas = /* @__PURE__ */ __name((params) => {
|
|
|
2410
2428
|
...collected.inputExposedIds,
|
|
2411
2429
|
...collected.outputExposedIds
|
|
2412
2430
|
]);
|
|
2431
|
+
const foreignKeys = collectForeignKeys(schemas);
|
|
2413
2432
|
for (const id of exposedIds) {
|
|
2414
|
-
applyTruthTable(
|
|
2433
|
+
applyTruthTable({
|
|
2434
|
+
schemas,
|
|
2435
|
+
id,
|
|
2436
|
+
inputExposed: collected.inputExposedIds.has(id),
|
|
2437
|
+
outputExposed: collected.outputExposedIds.has(id),
|
|
2438
|
+
inputBody: inputSchemas.get(id),
|
|
2439
|
+
outputBody: outputSchemas.get(id),
|
|
2440
|
+
divergentOutputIds,
|
|
2441
|
+
foreignKeys
|
|
2442
|
+
});
|
|
2415
2443
|
}
|
|
2416
2444
|
for (const [className, dtoId] of collected.classToDtoId) {
|
|
2417
2445
|
if (className === dtoId) {
|
|
@@ -2433,24 +2461,35 @@ var ensureComponentsSchemas = /* @__PURE__ */ __name((doc) => {
|
|
|
2433
2461
|
components.schemas = schemas;
|
|
2434
2462
|
return schemas;
|
|
2435
2463
|
}, "ensureComponentsSchemas");
|
|
2436
|
-
var applyTruthTable = /* @__PURE__ */ __name((
|
|
2464
|
+
var applyTruthTable = /* @__PURE__ */ __name((params) => {
|
|
2465
|
+
const { schemas, id, inputExposed, outputExposed, inputBody, outputBody } = params;
|
|
2466
|
+
const { divergentOutputIds, foreignKeys } = params;
|
|
2437
2467
|
if (inputExposed && !outputExposed) {
|
|
2438
|
-
writeOrThrowAmbiguous(schemas, id, inputBody);
|
|
2468
|
+
writeOrThrowAmbiguous(schemas, id, inputBody, foreignKeys);
|
|
2439
2469
|
return;
|
|
2440
2470
|
}
|
|
2441
2471
|
if (!inputExposed && outputExposed) {
|
|
2442
|
-
writeOrThrowAmbiguous(schemas, id, outputBody);
|
|
2472
|
+
writeOrThrowAmbiguous(schemas, id, outputBody, foreignKeys);
|
|
2443
2473
|
return;
|
|
2444
2474
|
}
|
|
2445
2475
|
if (canonicalEqual(inputBody, outputBody)) {
|
|
2446
|
-
writeOrThrowAmbiguous(schemas, id, inputBody);
|
|
2476
|
+
writeOrThrowAmbiguous(schemas, id, inputBody, foreignKeys);
|
|
2447
2477
|
return;
|
|
2448
2478
|
}
|
|
2449
|
-
writeOrThrowAmbiguous(schemas, id, inputBody);
|
|
2450
|
-
writeOrThrowAmbiguous(schemas, `${id}${OUTPUT_SUFFIX}`, outputBody);
|
|
2479
|
+
writeOrThrowAmbiguous(schemas, id, inputBody, foreignKeys);
|
|
2480
|
+
writeOrThrowAmbiguous(schemas, `${id}${OUTPUT_SUFFIX}`, outputBody, foreignKeys);
|
|
2451
2481
|
divergentOutputIds.add(id);
|
|
2452
2482
|
}, "applyTruthTable");
|
|
2453
|
-
var
|
|
2483
|
+
var collectForeignKeys = /* @__PURE__ */ __name((schemas) => {
|
|
2484
|
+
const foreign = /* @__PURE__ */ new Set();
|
|
2485
|
+
for (const [key, body] of Object.entries(schemas)) {
|
|
2486
|
+
if (!isMarkerPlaceholder(body)) {
|
|
2487
|
+
foreign.add(key);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
return foreign;
|
|
2491
|
+
}, "collectForeignKeys");
|
|
2492
|
+
var writeOrThrowAmbiguous = /* @__PURE__ */ __name((schemas, key, body, foreignKeys) => {
|
|
2454
2493
|
if (body === void 0) {
|
|
2455
2494
|
return;
|
|
2456
2495
|
}
|
|
@@ -2459,10 +2498,18 @@ var writeOrThrowAmbiguous = /* @__PURE__ */ __name((schemas, key, body) => {
|
|
|
2459
2498
|
schemas[key] = body;
|
|
2460
2499
|
return;
|
|
2461
2500
|
}
|
|
2462
|
-
|
|
2463
|
-
|
|
2501
|
+
const preexisting = foreignKeys.has(key);
|
|
2502
|
+
throw new ZodNestDocumentError("AMBIGUOUS_RENAME", `Two distinct schemas target \`components.schemas[${key}]\` with differing bodies \u2014 ` + ambiguousRenameHint(key, preexisting), {
|
|
2503
|
+
key,
|
|
2504
|
+
preexisting
|
|
2464
2505
|
});
|
|
2465
2506
|
}, "writeOrThrowAmbiguous");
|
|
2507
|
+
var ambiguousRenameHint = /* @__PURE__ */ __name((key, preexisting) => {
|
|
2508
|
+
if (preexisting) {
|
|
2509
|
+
return `\`${key}\` was already populated before zod-nest emitted anything. On NestJS 12+ the usual cause is the native Standard Schema path \u2014 \`@Body({ schema })\`, \`@Query({ schema })\` or \`@ApiResponse({ standardSchema })\` \u2014 which makes @nestjs/swagger emit the component itself, in its OpenAPI 3.0 shape. Route the schema through \`createZodDto\` / \`@ZodBody\` / \`@ZodResponse\` instead, or drop \`applyZodNest\` and let Nest own the whole document. A hand-authored component or a doc pre-pass under the same name does this too.`;
|
|
2510
|
+
}
|
|
2511
|
+
return "Two registered ids resolved to the same key \u2014 most often an `<Id>Output` sibling from a diverging input/output schema colliding with a DTO explicitly registered as `<Id>Output`. Rename one of them, or set a distinct `options.id`.";
|
|
2512
|
+
}, "ambiguousRenameHint");
|
|
2466
2513
|
var isMarkerPlaceholder = /* @__PURE__ */ __name((value) => {
|
|
2467
2514
|
if (value === null || typeof value !== "object") {
|
|
2468
2515
|
return false;
|