zod-nest 2.1.4 → 3.0.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/README.md +5 -2
- package/dist/index.d.mts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +52 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +52 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -12
package/README.md
CHANGED
|
@@ -525,7 +525,9 @@ See [`docs/recipes/custom-openapi-overrides.md`](docs/recipes/custom-openapi-ove
|
|
|
525
525
|
|
|
526
526
|
## Compatibility matrix
|
|
527
527
|
|
|
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 <
|
|
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 <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.
|
|
529
|
+
|
|
530
|
+
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
531
|
|
|
530
532
|
| Cell | `zod` | `@nestjs/common` | `@nestjs/core` | `@nestjs/swagger` | `rxjs` | `reflect-metadata` |
|
|
531
533
|
| ------------------------- | ------ | ---------------- | -------------- | ----------------- | ------ | ------------------ |
|
|
@@ -533,13 +535,14 @@ See [`docs/recipes/custom-openapi-overrides.md`](docs/recipes/custom-openapi-ove
|
|
|
533
535
|
| `zod-latest` | latest | 11.0.1 | 11.0.1 | 11.0.0 | 7.6.0 | 0.2.0 |
|
|
534
536
|
| `zod-4.5` | 4.5.4 | 11.0.1 | 11.0.1 | 11.0.0 | 7.6.0 | 0.2.0 |
|
|
535
537
|
| `nest-latest` | 4.4.0 | latest | latest | latest | 7.6.0 | 0.2.0 |
|
|
538
|
+
| `nest12` | 4.4.0 | 12.0.1 | 12.0.1 | 12.0.1 | 7.6.0 | 0.2.0 |
|
|
536
539
|
| `rxjs-latest` | 4.4.0 | 11.0.1 | 11.0.1 | 11.0.0 | latest | 0.2.0 |
|
|
537
540
|
| `reflect-metadata-latest` | 4.4.0 | 11.0.1 | 11.0.1 | 11.0.0 | 7.6.0 | latest |
|
|
538
541
|
| `all-latest` | latest | latest | latest | latest | latest | latest |
|
|
539
542
|
|
|
540
543
|
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
544
|
|
|
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`.
|
|
545
|
+
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
546
|
|
|
544
547
|
## Migration from `nestjs-zod`
|
|
545
548
|
|
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
|
@@ -2357,11 +2357,24 @@ var inlineAnonymousBodies = /* @__PURE__ */ __name(({ doc, registry }) => {
|
|
|
2357
2357
|
if (bodyByRef.size === 0) {
|
|
2358
2358
|
return;
|
|
2359
2359
|
}
|
|
2360
|
+
for (const ref of refsWithin(schemas, bodyByRef)) {
|
|
2361
|
+
bodyByRef.delete(ref);
|
|
2362
|
+
}
|
|
2360
2363
|
inlineRefs(doc.paths, bodyByRef);
|
|
2361
2364
|
for (const ref of bodyByRef.keys()) {
|
|
2362
2365
|
delete schemas[ref.slice(COMPONENTS_SCHEMAS_PREFIX.length)];
|
|
2363
2366
|
}
|
|
2364
2367
|
}, "inlineAnonymousBodies");
|
|
2368
|
+
var refsWithin = /* @__PURE__ */ __name((schemas, bodyByRef) => {
|
|
2369
|
+
const found = /* @__PURE__ */ new Set();
|
|
2370
|
+
walkRefs(schemas, (ref) => {
|
|
2371
|
+
if (bodyByRef.has(ref)) {
|
|
2372
|
+
found.add(ref);
|
|
2373
|
+
}
|
|
2374
|
+
return void 0;
|
|
2375
|
+
});
|
|
2376
|
+
return found;
|
|
2377
|
+
}, "refsWithin");
|
|
2365
2378
|
var isPlainRecord4 = /* @__PURE__ */ __name((value) => value !== null && typeof value === "object" && !Array.isArray(value), "isPlainRecord");
|
|
2366
2379
|
var inlineRefs = /* @__PURE__ */ __name((node, bodyByRef) => {
|
|
2367
2380
|
if (Array.isArray(node)) {
|
|
@@ -2397,8 +2410,18 @@ var mergeSchemas = /* @__PURE__ */ __name((params) => {
|
|
|
2397
2410
|
...collected.inputExposedIds,
|
|
2398
2411
|
...collected.outputExposedIds
|
|
2399
2412
|
]);
|
|
2413
|
+
const foreignKeys = collectForeignKeys(schemas);
|
|
2400
2414
|
for (const id of exposedIds) {
|
|
2401
|
-
applyTruthTable(
|
|
2415
|
+
applyTruthTable({
|
|
2416
|
+
schemas,
|
|
2417
|
+
id,
|
|
2418
|
+
inputExposed: collected.inputExposedIds.has(id),
|
|
2419
|
+
outputExposed: collected.outputExposedIds.has(id),
|
|
2420
|
+
inputBody: inputSchemas.get(id),
|
|
2421
|
+
outputBody: outputSchemas.get(id),
|
|
2422
|
+
divergentOutputIds,
|
|
2423
|
+
foreignKeys
|
|
2424
|
+
});
|
|
2402
2425
|
}
|
|
2403
2426
|
for (const [className, dtoId] of collected.classToDtoId) {
|
|
2404
2427
|
if (className === dtoId) {
|
|
@@ -2420,24 +2443,35 @@ var ensureComponentsSchemas = /* @__PURE__ */ __name((doc) => {
|
|
|
2420
2443
|
components.schemas = schemas;
|
|
2421
2444
|
return schemas;
|
|
2422
2445
|
}, "ensureComponentsSchemas");
|
|
2423
|
-
var applyTruthTable = /* @__PURE__ */ __name((
|
|
2446
|
+
var applyTruthTable = /* @__PURE__ */ __name((params) => {
|
|
2447
|
+
const { schemas, id, inputExposed, outputExposed, inputBody, outputBody } = params;
|
|
2448
|
+
const { divergentOutputIds, foreignKeys } = params;
|
|
2424
2449
|
if (inputExposed && !outputExposed) {
|
|
2425
|
-
writeOrThrowAmbiguous(schemas, id, inputBody);
|
|
2450
|
+
writeOrThrowAmbiguous(schemas, id, inputBody, foreignKeys);
|
|
2426
2451
|
return;
|
|
2427
2452
|
}
|
|
2428
2453
|
if (!inputExposed && outputExposed) {
|
|
2429
|
-
writeOrThrowAmbiguous(schemas, id, outputBody);
|
|
2454
|
+
writeOrThrowAmbiguous(schemas, id, outputBody, foreignKeys);
|
|
2430
2455
|
return;
|
|
2431
2456
|
}
|
|
2432
2457
|
if (canonicalEqual(inputBody, outputBody)) {
|
|
2433
|
-
writeOrThrowAmbiguous(schemas, id, inputBody);
|
|
2458
|
+
writeOrThrowAmbiguous(schemas, id, inputBody, foreignKeys);
|
|
2434
2459
|
return;
|
|
2435
2460
|
}
|
|
2436
|
-
writeOrThrowAmbiguous(schemas, id, inputBody);
|
|
2437
|
-
writeOrThrowAmbiguous(schemas, `${id}${OUTPUT_SUFFIX}`, outputBody);
|
|
2461
|
+
writeOrThrowAmbiguous(schemas, id, inputBody, foreignKeys);
|
|
2462
|
+
writeOrThrowAmbiguous(schemas, `${id}${OUTPUT_SUFFIX}`, outputBody, foreignKeys);
|
|
2438
2463
|
divergentOutputIds.add(id);
|
|
2439
2464
|
}, "applyTruthTable");
|
|
2440
|
-
var
|
|
2465
|
+
var collectForeignKeys = /* @__PURE__ */ __name((schemas) => {
|
|
2466
|
+
const foreign = /* @__PURE__ */ new Set();
|
|
2467
|
+
for (const [key, body] of Object.entries(schemas)) {
|
|
2468
|
+
if (!isMarkerPlaceholder(body)) {
|
|
2469
|
+
foreign.add(key);
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
return foreign;
|
|
2473
|
+
}, "collectForeignKeys");
|
|
2474
|
+
var writeOrThrowAmbiguous = /* @__PURE__ */ __name((schemas, key, body, foreignKeys) => {
|
|
2441
2475
|
if (body === void 0) {
|
|
2442
2476
|
return;
|
|
2443
2477
|
}
|
|
@@ -2446,10 +2480,18 @@ var writeOrThrowAmbiguous = /* @__PURE__ */ __name((schemas, key, body) => {
|
|
|
2446
2480
|
schemas[key] = body;
|
|
2447
2481
|
return;
|
|
2448
2482
|
}
|
|
2449
|
-
|
|
2450
|
-
|
|
2483
|
+
const preexisting = foreignKeys.has(key);
|
|
2484
|
+
throw new ZodNestDocumentError("AMBIGUOUS_RENAME", `Two distinct schemas target \`components.schemas[${key}]\` with differing bodies \u2014 ` + ambiguousRenameHint(key, preexisting), {
|
|
2485
|
+
key,
|
|
2486
|
+
preexisting
|
|
2451
2487
|
});
|
|
2452
2488
|
}, "writeOrThrowAmbiguous");
|
|
2489
|
+
var ambiguousRenameHint = /* @__PURE__ */ __name((key, preexisting) => {
|
|
2490
|
+
if (preexisting) {
|
|
2491
|
+
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.`;
|
|
2492
|
+
}
|
|
2493
|
+
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`.";
|
|
2494
|
+
}, "ambiguousRenameHint");
|
|
2453
2495
|
var isMarkerPlaceholder = /* @__PURE__ */ __name((value) => {
|
|
2454
2496
|
if (value === null || typeof value !== "object") {
|
|
2455
2497
|
return false;
|