zod-nest 1.7.0 → 1.8.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
@@ -493,15 +493,15 @@ See [`docs/recipes/custom-openapi-overrides.md`](docs/recipes/custom-openapi-ove
493
493
 
494
494
  ## Compatibility matrix
495
495
 
496
- `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.1.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.
496
+ `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.
497
497
 
498
498
  | Cell | `zod` | `@nestjs/common` | `@nestjs/core` | `@nestjs/swagger` | `rxjs` | `reflect-metadata` |
499
499
  |---|---|---|---|---|---|---|
500
- | `floor` | 4.4.0 | 11.0.1 | 11.0.1 | 11.0.0 | 7.1.0 | 0.2.0 |
501
- | `zod-latest` | latest | 11.0.1 | 11.0.1 | 11.0.0 | 7.1.0 | 0.2.0 |
502
- | `nest-latest` | 4.4.0 | latest | latest | latest | 7.1.0 | 0.2.0 |
500
+ | `floor` | 4.4.0 | 11.0.1 | 11.0.1 | 11.0.0 | 7.6.0 | 0.2.0 |
501
+ | `zod-latest` | latest | 11.0.1 | 11.0.1 | 11.0.0 | 7.6.0 | 0.2.0 |
502
+ | `nest-latest` | 4.4.0 | latest | latest | latest | 7.6.0 | 0.2.0 |
503
503
  | `rxjs-latest` | 4.4.0 | 11.0.1 | 11.0.1 | 11.0.0 | latest | 0.2.0 |
504
- | `reflect-metadata-latest` | 4.4.0 | 11.0.1 | 11.0.1 | 11.0.0 | 7.1.0 | latest |
504
+ | `reflect-metadata-latest` | 4.4.0 | 11.0.1 | 11.0.1 | 11.0.0 | 7.6.0 | latest |
505
505
  | `all-latest` | latest | latest | latest | latest | latest | latest |
506
506
 
507
507
  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`.
package/dist/index.d.mts CHANGED
@@ -496,6 +496,15 @@ interface ZodBodyOptions {
496
496
  readonly description?: string;
497
497
  /** Whether the body is required. Defaults to `true`. */
498
498
  readonly required?: boolean;
499
+ /**
500
+ * Merge an intersection of `z.object` arms into a single flat object body
501
+ * emitted inline (no `$ref`, no `components.schemas` entry). Use when
502
+ * Swagger UI's `multipart/form-data` `try-it-out` form needs to render the
503
+ * body — the UI doesn't follow `$ref` or unwrap `allOf`. Throws
504
+ * `ZodNestError` if the schema isn't an object or an intersection of
505
+ * objects. No-op for a bare `z.object`. Defaults to `false`.
506
+ */
507
+ readonly flatten?: boolean;
499
508
  }
500
509
  /**
501
510
  * Method-level decorator that wires the OpenAPI `requestBody` for a handler
package/dist/index.d.ts CHANGED
@@ -496,6 +496,15 @@ interface ZodBodyOptions {
496
496
  readonly description?: string;
497
497
  /** Whether the body is required. Defaults to `true`. */
498
498
  readonly required?: boolean;
499
+ /**
500
+ * Merge an intersection of `z.object` arms into a single flat object body
501
+ * emitted inline (no `$ref`, no `components.schemas` entry). Use when
502
+ * Swagger UI's `multipart/form-data` `try-it-out` form needs to render the
503
+ * body — the UI doesn't follow `$ref` or unwrap `allOf`. Throws
504
+ * `ZodNestError` if the schema isn't an object or an intersection of
505
+ * objects. No-op for a bare `z.object`. Defaults to `false`.
506
+ */
507
+ readonly flatten?: boolean;
499
508
  }
500
509
  /**
501
510
  * Method-level decorator that wires the OpenAPI `requestBody` for a handler
package/dist/index.js CHANGED
@@ -1157,22 +1157,6 @@ var resolveSchemaRef = /* @__PURE__ */ __name((schema, options) => {
1157
1157
  };
1158
1158
  }, "resolveSchemaRef");
1159
1159
 
1160
- // src/decorators/zod-body.decorator.ts
1161
- var ZodBody = /* @__PURE__ */ __name((schema, options) => {
1162
- const resolution = resolveSchemaRef(schema, {
1163
- id: options?.id,
1164
- registry: options?.registry
1165
- });
1166
- const apiBodySchema = resolution.kind === "ref" ? resolution.ref : resolution.schema;
1167
- return swagger.ApiBody({
1168
- schema: apiBodySchema,
1169
- ...options?.description !== void 0 ? {
1170
- description: options.description
1171
- } : {},
1172
- required: options?.required ?? true
1173
- });
1174
- }, "ZodBody");
1175
-
1176
1160
  // src/decorators/internal/zod-param-expand.ts
1177
1161
  var isZodObject = /* @__PURE__ */ __name((schema) => schema._zod.def.type === "object", "isZodObject");
1178
1162
  var expandObjectSchema = /* @__PURE__ */ __name((schema, options) => {
@@ -1199,7 +1183,117 @@ var expandObjectSchema = /* @__PURE__ */ __name((schema, options) => {
1199
1183
  }, "expandObjectSchema");
1200
1184
  var paramSchemaBody = /* @__PURE__ */ __name((resolution) => resolution.kind === "ref" ? resolution.ref : resolution.schema, "paramSchemaBody");
1201
1185
 
1202
- // src/decorators/zod-query.decorator.ts
1186
+ // src/decorators/internal/flatten-intersection.ts
1187
+ var intersectionArms = /* @__PURE__ */ __name((schema) => {
1188
+ if (schema._zod.def.type !== "intersection") {
1189
+ return null;
1190
+ }
1191
+ const def = schema._zod.def;
1192
+ return {
1193
+ left: def.left,
1194
+ right: def.right
1195
+ };
1196
+ }, "intersectionArms");
1197
+ var unionOptions = /* @__PURE__ */ __name((schema) => {
1198
+ if (schema._zod.def.type !== "union") {
1199
+ return null;
1200
+ }
1201
+ const def = schema._zod.def;
1202
+ return def.options;
1203
+ }, "unionOptions");
1204
+ var collectObjectShapes = /* @__PURE__ */ __name((schema) => {
1205
+ if (isZodObject(schema)) {
1206
+ return {
1207
+ shapes: [
1208
+ schema.shape
1209
+ ],
1210
+ unionCrossed: false
1211
+ };
1212
+ }
1213
+ const options = unionOptions(schema);
1214
+ if (options !== null) {
1215
+ const allShapes = [];
1216
+ for (const variant of options) {
1217
+ const variantResult = collectObjectShapes(variant);
1218
+ if (variantResult === null) {
1219
+ return null;
1220
+ }
1221
+ allShapes.push(...variantResult.shapes);
1222
+ }
1223
+ return {
1224
+ shapes: allShapes,
1225
+ unionCrossed: true
1226
+ };
1227
+ }
1228
+ const arms = intersectionArms(schema);
1229
+ if (arms === null) {
1230
+ return null;
1231
+ }
1232
+ const left = collectObjectShapes(arms.left);
1233
+ if (left === null) {
1234
+ return null;
1235
+ }
1236
+ const right = collectObjectShapes(arms.right);
1237
+ if (right === null) {
1238
+ return null;
1239
+ }
1240
+ return {
1241
+ shapes: [
1242
+ ...left.shapes,
1243
+ ...right.shapes
1244
+ ],
1245
+ unionCrossed: left.unionCrossed || right.unionCrossed
1246
+ };
1247
+ }, "collectObjectShapes");
1248
+ var flattenObjectIntersection = /* @__PURE__ */ __name((schema, registry, decoratorName) => {
1249
+ const collected = collectObjectShapes(schema);
1250
+ if (collected === null) {
1251
+ throw new ZodNestError(`${decoratorName} \`flatten: true\` requires every leaf of the schema to be a \`z.object({...})\` \u2014 intersections and unions of objects are supported, but primitives, tuples, transforms, and other non-object leaves are not. Drop \`flatten: true\` to emit the original schema with its composition intact.`);
1252
+ }
1253
+ const mergedShape = {};
1254
+ for (const shape of collected.shapes) {
1255
+ Object.assign(mergedShape, shape);
1256
+ }
1257
+ if (collected.unionCrossed) {
1258
+ for (const key of Object.keys(mergedShape)) {
1259
+ const value = mergedShape[key];
1260
+ if (value !== void 0) {
1261
+ mergedShape[key] = value.optional();
1262
+ }
1263
+ }
1264
+ }
1265
+ const merged = zod.z.object(mergedShape);
1266
+ for (const [child, childId] of discoverDependents(schema)) {
1267
+ registry.register(child, childId);
1268
+ }
1269
+ const { schema: body } = toOpenApi(merged, {
1270
+ io: "input",
1271
+ registry
1272
+ });
1273
+ return body;
1274
+ }, "flattenObjectIntersection");
1275
+
1276
+ // src/decorators/zod-body.decorator.ts
1277
+ var ZodBody = /* @__PURE__ */ __name((schema, options) => {
1278
+ const apiBodySchema = resolveBodySchema(schema, options);
1279
+ return swagger.ApiBody({
1280
+ schema: apiBodySchema,
1281
+ ...options?.description !== void 0 ? {
1282
+ description: options.description
1283
+ } : {},
1284
+ required: options?.required ?? true
1285
+ });
1286
+ }, "ZodBody");
1287
+ var resolveBodySchema = /* @__PURE__ */ __name((schema, options) => {
1288
+ if (options?.flatten === true) {
1289
+ return flattenObjectIntersection(schema, options.registry ?? defaultRegistry, "@ZodBody");
1290
+ }
1291
+ const resolution = resolveSchemaRef(schema, {
1292
+ id: options?.id,
1293
+ registry: options?.registry
1294
+ });
1295
+ return resolution.kind === "ref" ? resolution.ref : resolution.schema;
1296
+ }, "resolveBodySchema");
1203
1297
  var ZodQuery = /* @__PURE__ */ __name((schema, options) => {
1204
1298
  const expanded = expandObjectSchema(schema, {
1205
1299
  id: options?.id,
@@ -1419,9 +1513,9 @@ var runPass = /* @__PURE__ */ __name((opts, io, knownIds) => {
1419
1513
  const raw = zod.z.toJSONSchema(opts.registry.zodRegistry, built.options);
1420
1514
  built.consumeUnrepresentable();
1421
1515
  const filtered = /* @__PURE__ */ new Map();
1422
- for (const id of knownIds) {
1423
- if (Object.prototype.hasOwnProperty.call(raw.schemas, id)) {
1424
- filtered.set(id, raw.schemas[id]);
1516
+ for (const [id, schema] of Object.entries(raw.schemas)) {
1517
+ if (knownIds.has(id)) {
1518
+ filtered.set(id, schema);
1425
1519
  }
1426
1520
  }
1427
1521
  return filtered;