zod-nest 2.1.5 → 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 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 <12.0.0`, `@nestjs/core >=11.0.1 <12.0.0`, `@nestjs/swagger >=11.0.0 <12.0.0`, `rxjs >=7.6.0 <8.0.0`, `reflect-metadata >=0.2.0 <0.3.0`, Node `>=22`. 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.
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 DTO classes target the same registry id
899
- * with differing bodiesthe rename pass can't write `components.schemas[id]`
900
- * unambiguously.
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 DTO classes target the same registry id
899
- * with differing bodiesthe rename pass can't write `components.schemas[id]`
900
- * unambiguously.
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
@@ -2410,8 +2410,18 @@ var mergeSchemas = /* @__PURE__ */ __name((params) => {
2410
2410
  ...collected.inputExposedIds,
2411
2411
  ...collected.outputExposedIds
2412
2412
  ]);
2413
+ const foreignKeys = collectForeignKeys(schemas);
2413
2414
  for (const id of exposedIds) {
2414
- applyTruthTable(schemas, id, collected.inputExposedIds.has(id), collected.outputExposedIds.has(id), inputSchemas.get(id), outputSchemas.get(id), divergentOutputIds);
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
+ });
2415
2425
  }
2416
2426
  for (const [className, dtoId] of collected.classToDtoId) {
2417
2427
  if (className === dtoId) {
@@ -2433,24 +2443,35 @@ var ensureComponentsSchemas = /* @__PURE__ */ __name((doc) => {
2433
2443
  components.schemas = schemas;
2434
2444
  return schemas;
2435
2445
  }, "ensureComponentsSchemas");
2436
- var applyTruthTable = /* @__PURE__ */ __name((schemas, id, inputExposed, outputExposed, inputBody, outputBody, divergentOutputIds) => {
2446
+ var applyTruthTable = /* @__PURE__ */ __name((params) => {
2447
+ const { schemas, id, inputExposed, outputExposed, inputBody, outputBody } = params;
2448
+ const { divergentOutputIds, foreignKeys } = params;
2437
2449
  if (inputExposed && !outputExposed) {
2438
- writeOrThrowAmbiguous(schemas, id, inputBody);
2450
+ writeOrThrowAmbiguous(schemas, id, inputBody, foreignKeys);
2439
2451
  return;
2440
2452
  }
2441
2453
  if (!inputExposed && outputExposed) {
2442
- writeOrThrowAmbiguous(schemas, id, outputBody);
2454
+ writeOrThrowAmbiguous(schemas, id, outputBody, foreignKeys);
2443
2455
  return;
2444
2456
  }
2445
2457
  if (canonicalEqual(inputBody, outputBody)) {
2446
- writeOrThrowAmbiguous(schemas, id, inputBody);
2458
+ writeOrThrowAmbiguous(schemas, id, inputBody, foreignKeys);
2447
2459
  return;
2448
2460
  }
2449
- writeOrThrowAmbiguous(schemas, id, inputBody);
2450
- writeOrThrowAmbiguous(schemas, `${id}${OUTPUT_SUFFIX}`, outputBody);
2461
+ writeOrThrowAmbiguous(schemas, id, inputBody, foreignKeys);
2462
+ writeOrThrowAmbiguous(schemas, `${id}${OUTPUT_SUFFIX}`, outputBody, foreignKeys);
2451
2463
  divergentOutputIds.add(id);
2452
2464
  }, "applyTruthTable");
2453
- var writeOrThrowAmbiguous = /* @__PURE__ */ __name((schemas, key, body) => {
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) => {
2454
2475
  if (body === void 0) {
2455
2476
  return;
2456
2477
  }
@@ -2459,10 +2480,18 @@ var writeOrThrowAmbiguous = /* @__PURE__ */ __name((schemas, key, body) => {
2459
2480
  schemas[key] = body;
2460
2481
  return;
2461
2482
  }
2462
- throw new ZodNestDocumentError("AMBIGUOUS_RENAME", `Two distinct schemas target \`components.schemas[${key}]\` with differing bodies \u2014 multiple createZodDto classes likely share the same dtoId. Set distinct \`options.id\` values, or align the class names so renames are unambiguous.`, {
2463
- key
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
2464
2487
  });
2465
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");
2466
2495
  var isMarkerPlaceholder = /* @__PURE__ */ __name((value) => {
2467
2496
  if (value === null || typeof value !== "object") {
2468
2497
  return false;