zod-nest 1.9.0 → 1.10.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
@@ -81,6 +81,7 @@ export class UserDto extends createZodDto(userSchema) {}
81
81
  // users.controller.ts
82
82
  import { Body, Controller, Get, Post } from '@nestjs/common';
83
83
  import { ZodResponse } from 'zod-nest';
84
+
84
85
  import { UserDto } from './user.dto';
85
86
 
86
87
  @Controller('users')
@@ -104,6 +105,7 @@ export class UsersController {
104
105
  // app.module.ts
105
106
  import { Module } from '@nestjs/common';
106
107
  import { ZodNestModule } from 'zod-nest';
108
+
107
109
  import { UsersController } from './users.controller';
108
110
 
109
111
  @Module({
@@ -118,6 +120,7 @@ export class AppModule {}
118
120
  import { NestFactory } from '@nestjs/core';
119
121
  import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
120
122
  import { applyZodNest } from 'zod-nest';
123
+
121
124
  import { AppModule } from './app.module';
122
125
 
123
126
  async function bootstrap() {
@@ -154,7 +157,7 @@ See [`docs/dto.md`](docs/dto.md) for the full surface.
154
157
 
155
158
  ### Schemas that don't fit a class
156
159
 
157
- A schema whose `z.infer<>` is a TypeScript union — `z.union`, `z.discriminatedUnion`, or `z.intersection(obj, union)` — can't be used as a class base because TS rejects unions as constructor return types (TS2509: *Base constructor return type ... is not an object type*). For these, skip `createZodDto` and pair the raw schema with parameter-level decorators that handle OpenAPI emission directly:
160
+ A schema whose `z.infer<>` is a TypeScript union — `z.union`, `z.discriminatedUnion`, or `z.intersection(obj, union)` — can't be used as a class base because TS rejects unions as constructor return types (TS2509: _Base constructor return type ... is not an object type_). For these, skip `createZodDto` and pair the raw schema with parameter-level decorators that handle OpenAPI emission directly:
158
161
 
159
162
  ```ts
160
163
  const IntersectionWithUnion = z
@@ -207,6 +210,18 @@ At request time, `ZodSerializerInterceptor` looks at `response.statusCode` and p
207
210
 
208
211
  See [`docs/responses.md`](docs/responses.md) for the precedence chain and `passthroughOnError`.
209
212
 
213
+ ### Streaming responses (SSE, NDJSON, binary)
214
+
215
+ Streamed bodies are written straight to the response buffer — they shouldn't be validated, and the OpenAPI media type isn't `application/json`. Pass `contentType` (or declare a stream-typed `@Header('Content-Type', …)`) and `@ZodResponse` documents the response under that media type and skips validation:
216
+
217
+ ```ts
218
+ @Sse('events')
219
+ @ZodResponse({ type: NotificationEventDto, contentType: 'text/event-stream' })
220
+ streamEvents(): Observable<MessageEvent> { /* … */ }
221
+ ```
222
+
223
+ `stream` is inferred `true` for known stream types (`text/event-stream`, `application/x-ndjson`, `application/octet-stream`, `application/pdf`, `image/*`, `audio/*`, `video/*`); override with `stream: false`, or extend the set globally via `ZodNestModule.forRoot({ streamContentTypes: [...] })`. See [`docs/recipes/streaming-responses.md`](docs/recipes/streaming-responses.md).
224
+
210
225
  ## Usage
211
226
 
212
227
  ### Creating DTOs
@@ -228,12 +243,18 @@ The id is what appears as the `components.schemas` key in the OpenAPI document a
228
243
 
229
244
  ```ts
230
245
  // Preferred — on the schema, via Zod's metadata
231
- const userSchema = z.object({ /* ... */ }).meta({ id: 'User' });
246
+ const userSchema = z
247
+ .object({
248
+ /* ... */
249
+ })
250
+ .meta({ id: 'User' });
232
251
  class UserDto extends createZodDto(userSchema) {}
233
252
 
234
253
  // Also valid — passed through createZodDto's options
235
254
  class UserDto extends createZodDto(
236
- z.object({ /* ... */ }),
255
+ z.object({
256
+ /* ... */
257
+ }),
237
258
  { id: 'User' },
238
259
  ) {}
239
260
  ```
@@ -245,8 +266,8 @@ If you pass neither, the class name is used as a fallback. Under minification
245
266
  ### Input validation
246
267
 
247
268
  ```ts
248
- import { ZodValidationPipe } from 'zod-nest';
249
269
  import { APP_PIPE } from '@nestjs/core';
270
+ import { ZodValidationPipe } from 'zod-nest';
250
271
 
251
272
  @Controller('things')
252
273
  class ThingsController {
@@ -310,14 +331,14 @@ Stack `@ZodResponse` per status code:
310
331
  import { Get, HttpStatus } from '@nestjs/common';
311
332
  import { ZodResponse } from 'zod-nest';
312
333
 
313
- class UserDto extends createZodDto(z.object({ id: z.string() }), { id: 'User' }) {}
314
- class ErrorDto extends createZodDto(z.object({ code: z.number() }), { id: 'Error' }) {}
334
+ class UserDto extends createZodDto(z.object({ id: z.string() }), { id: 'User' }) {}
335
+ class ErrorDto extends createZodDto(z.object({ code: z.number() }), { id: 'Error' }) {}
315
336
  class FatalDto extends createZodDto(z.object({ trace: z.string() }), { id: 'Fatal' }) {}
316
337
 
317
338
  class UsersController {
318
339
  @Get(':id')
319
- @ZodResponse({ type: UserDto }) // 200 inferred
320
- @ZodResponse({ status: HttpStatus.NOT_FOUND, type: ErrorDto })
340
+ @ZodResponse({ type: UserDto }) // 200 inferred
341
+ @ZodResponse({ status: HttpStatus.NOT_FOUND, type: ErrorDto })
321
342
  @ZodResponse({ status: HttpStatus.INTERNAL_SERVER_ERROR, type: FatalDto })
322
343
  getUser(): void {}
323
344
  }
@@ -372,8 +393,7 @@ import { ZodNestModule } from 'zod-nest';
372
393
  ZodNestModule.forRoot({
373
394
  validationLogs: { input: true, output: true },
374
395
  redactKeys: ['password', 'token', 'sessionId'],
375
- createSerializationException: (err, ctx) =>
376
- new MyCustomFiveHundred(err, ctx),
396
+ createSerializationException: (err, ctx) => new MyCustomFiveHundred(err, ctx),
377
397
  }),
378
398
  ],
379
399
  controllers: [UsersController],
@@ -385,14 +405,14 @@ class AppModule {}
385
405
 
386
406
  ## Module options
387
407
 
388
- | Option | Type | Default | What it does |
389
- |---|---|---|---|
390
- | `createValidationException` | `(err, argMetadata) => unknown` | uses `ZodValidationException` | Custom 400 exception for input failures |
408
+ | Option | Type | Default | What it does |
409
+ | ------------------------------ | ------------------------------------ | -------------------------------- | ----------------------------------------------------------- |
410
+ | `createValidationException` | `(err, argMetadata) => unknown` | uses `ZodValidationException` | Custom 400 exception for input failures |
391
411
  | `createSerializationException` | `(err, executionContext) => unknown` | uses `ZodSerializationException` | Custom 500 exception for output failures (strict mode only) |
392
- | `validationLogs` | `boolean \| { input?, output? }` | `false` | Opt-in failure-only logging |
393
- | `logger` | `LoggerService` | NestJS `Logger` | Replace the logger (pino, winston, …) |
394
- | `redactKeys` | `readonly string[]` | `DEFAULT_REDACT_KEYS` | Keys redacted in logs (replaces default list, no merge) |
395
- | `maxLoggedValueBytes` | `number` | `4096` | Truncate oversize logged values |
412
+ | `validationLogs` | `boolean \| { input?, output? }` | `false` | Opt-in failure-only logging |
413
+ | `logger` | `LoggerService` | NestJS `Logger` | Replace the logger (pino, winston, …) |
414
+ | `redactKeys` | `readonly string[]` | `DEFAULT_REDACT_KEYS` | Keys redacted in logs (replaces default list, no merge) |
415
+ | `maxLoggedValueBytes` | `number` | `4096` | Truncate oversize logged values |
396
416
 
397
417
  `DEFAULT_REDACT_KEYS` includes `password`, `secret`, `apiKey`, `authorization`, `bearer`, `token`, `accessToken`, `refreshToken`, `jwt`, `cookie`, `set-cookie`. Matching is case-insensitive and applied at any depth in the logged value.
398
418
 
@@ -412,7 +432,7 @@ A log entry carries:
412
432
 
413
433
  Redaction is **case-insensitive** at any depth — a key named `Password` deep in a nested object is replaced with `'[REDACTED]'` just like a top-level `password`. Truncation replaces values larger than `maxLoggedValueBytes` (UTF-8 bytes) with `{ _truncated: true, _originalBytes, _preview }` so you keep enough context to debug without flooding the logger.
414
434
 
415
- Supplying `redactKeys` **replaces** the default list — there is no merge. If you want to *add* keys, spread `DEFAULT_REDACT_KEYS`:
435
+ Supplying `redactKeys` **replaces** the default list — there is no merge. If you want to _add_ keys, spread `DEFAULT_REDACT_KEYS`:
416
436
 
417
437
  ```ts
418
438
  import { DEFAULT_REDACT_KEYS, ZodNestModule } from 'zod-nest';
@@ -450,27 +470,35 @@ See [`docs/composition.md`](docs/composition.md) for the full contract, current
450
470
  A compact, link-out index. Type signatures and detailed semantics live in the companion docs.
451
471
 
452
472
  **DTO** — [`docs/dto.md`](docs/dto.md)
473
+
453
474
  - `createZodDto(schema, options?)`, `isZodDto(value)`, `ZodDto<TSchema>`, `Io`
454
475
 
455
476
  **Validation** — [`docs/validation-pipe.md`](docs/validation-pipe.md)
477
+
456
478
  - `ZodValidationPipe`, `ZodValidationException`, `ZodValidationPipeOptions`, `CreateValidationException`
457
479
 
458
480
  **Response** — [`docs/responses.md`](docs/responses.md)
481
+
459
482
  - `@ZodResponse({ status?, type, description?, passthroughOnError? })`, `ZodSerializerInterceptor`, `ZodSerializationException`, `defaultStatusFor`, `resolveEffectiveStatus`, `ResponseStatusInput`, `ResponseStatusWildcard`, `ResponseVariant`, `ZOD_RESPONSES_METADATA_KEY`
460
483
 
461
484
  **Parameter decorators for raw schemas** — [`docs/recipes/intersection-with-union.md`](docs/recipes/intersection-with-union.md)
485
+
462
486
  - `@ZodBody(schema, options?)`, `@ZodQuery(schema, options?)`, `@ZodHeaders(schema, options?)`, `@ZodCookies(schema, options?)`, `ZodBodyOptions`, `ZodQueryOptions`, `ZodHeadersOptions`, `ZodCookiesOptions`
463
487
 
464
488
  **Document** — [`docs/swagger-integration.md`](docs/swagger-integration.md)
489
+
465
490
  - `applyZodNest(rawDoc, options)`, `ApplyZodNestOptions`, `ZodNestDocumentError`
466
491
 
467
492
  **Module** — [`docs/module-options.md`](docs/module-options.md) / [`docs/logging.md`](docs/logging.md)
493
+
468
494
  - `ZodNestModule.forRoot(options?)`, `ZodNestModuleOptions`, `DEFAULT_REDACT_KEYS`, `DEFAULT_MAX_LOGGED_VALUE_BYTES`, `ZOD_NEST_OPTIONS`
469
495
 
470
496
  **Schema engine** — single-schema mode and extension points
497
+
471
498
  - `toOpenApi(schema, opts)`, `createRegistry()`, `defaultRegistry`, `registerSchema(schema, registry?, options?)`, `ZodNestRegistry`, `RegisterSchemaOptions`, `Override`, `OverrideContext`, `overrideJSONSchema(schema, fragment | { input?, output? })`, `OverrideJSONSchemaArg`, `ZodNestError`, `ZodNestUnrepresentableError`, `extend`, `getLineage`, `LineageEntry`
472
499
 
473
500
  **Helpers** (subpath: `zod-nest/helpers`) — common JSON Schema fragments + presets for assembling overrides
501
+
474
502
  - **Fragment catalog** (frozen consts): `dateTimeFragment`, `dateFragment`, `timeFragment`, `uuidFragment`, `emailFragment`, `uriFragment`, `hostnameFragment`, `ipv4Fragment`, `ipv6Fragment`, `binaryFragment`, `byteFragment`, `int32Fragment`, `int64Fragment`, `floatFragment`, `doubleFragment`, `opaqueFragment`
475
503
  - **Sugar functions**: `binary(opts?)`, `opaque(opts?)`
476
504
  - **Type-strict composition**: `enrich(base, extras)` — extras are typed per fragment family
@@ -480,31 +508,31 @@ See [`docs/recipes/custom-openapi-overrides.md`](docs/recipes/custom-openapi-ove
480
508
 
481
509
  ## Documentation
482
510
 
483
- | Topic | Doc |
484
- |---|---|
485
- | Why this library exists | [`docs/why-this-exists.md`](docs/why-this-exists.md) |
486
- | `createZodDto` in depth | [`docs/dto.md`](docs/dto.md) |
487
- | Input validation | [`docs/validation-pipe.md`](docs/validation-pipe.md) |
488
- | Responses, multi-status, status resolution | [`docs/responses.md`](docs/responses.md) |
489
- | Module options reference | [`docs/module-options.md`](docs/module-options.md) |
490
- | Validation logging | [`docs/logging.md`](docs/logging.md) |
491
- | Swagger integration & custom emission | [`docs/swagger-integration.md`](docs/swagger-integration.md) |
492
- | Composition (experimental) | [`docs/composition.md`](docs/composition.md) |
493
- | Exception classes | [`docs/exceptions.md`](docs/exceptions.md) |
494
- | Recipes | [`docs/recipes/`](docs/recipes/) |
511
+ | Topic | Doc |
512
+ | ------------------------------------------ | ------------------------------------------------------------ |
513
+ | Why this library exists | [`docs/why-this-exists.md`](docs/why-this-exists.md) |
514
+ | `createZodDto` in depth | [`docs/dto.md`](docs/dto.md) |
515
+ | Input validation | [`docs/validation-pipe.md`](docs/validation-pipe.md) |
516
+ | Responses, multi-status, status resolution | [`docs/responses.md`](docs/responses.md) |
517
+ | Module options reference | [`docs/module-options.md`](docs/module-options.md) |
518
+ | Validation logging | [`docs/logging.md`](docs/logging.md) |
519
+ | Swagger integration & custom emission | [`docs/swagger-integration.md`](docs/swagger-integration.md) |
520
+ | Composition (experimental) | [`docs/composition.md`](docs/composition.md) |
521
+ | Exception classes | [`docs/exceptions.md`](docs/exceptions.md) |
522
+ | Recipes | [`docs/recipes/`](docs/recipes/) |
495
523
 
496
524
  ## Compatibility matrix
497
525
 
498
526
  `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.
499
527
 
500
- | Cell | `zod` | `@nestjs/common` | `@nestjs/core` | `@nestjs/swagger` | `rxjs` | `reflect-metadata` |
501
- |---|---|---|---|---|---|---|
502
- | `floor` | 4.4.0 | 11.0.1 | 11.0.1 | 11.0.0 | 7.6.0 | 0.2.0 |
503
- | `zod-latest` | latest | 11.0.1 | 11.0.1 | 11.0.0 | 7.6.0 | 0.2.0 |
504
- | `nest-latest` | 4.4.0 | latest | latest | latest | 7.6.0 | 0.2.0 |
505
- | `rxjs-latest` | 4.4.0 | 11.0.1 | 11.0.1 | 11.0.0 | latest | 0.2.0 |
506
- | `reflect-metadata-latest` | 4.4.0 | 11.0.1 | 11.0.1 | 11.0.0 | 7.6.0 | latest |
507
- | `all-latest` | latest | latest | latest | latest | latest | latest |
528
+ | Cell | `zod` | `@nestjs/common` | `@nestjs/core` | `@nestjs/swagger` | `rxjs` | `reflect-metadata` |
529
+ | ------------------------- | ------ | ---------------- | -------------- | ----------------- | ------ | ------------------ |
530
+ | `floor` | 4.4.0 | 11.0.1 | 11.0.1 | 11.0.0 | 7.6.0 | 0.2.0 |
531
+ | `zod-latest` | latest | 11.0.1 | 11.0.1 | 11.0.0 | 7.6.0 | 0.2.0 |
532
+ | `nest-latest` | 4.4.0 | latest | latest | latest | 7.6.0 | 0.2.0 |
533
+ | `rxjs-latest` | 4.4.0 | 11.0.1 | 11.0.1 | 11.0.0 | latest | 0.2.0 |
534
+ | `reflect-metadata-latest` | 4.4.0 | 11.0.1 | 11.0.1 | 11.0.0 | 7.6.0 | latest |
535
+ | `all-latest` | latest | latest | latest | latest | latest | latest |
508
536
 
509
537
  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`.
510
538
 
package/dist/index.d.mts CHANGED
@@ -292,6 +292,93 @@ interface ValidationLogContext {
292
292
  }
293
293
  type LogValidationFailure = (err: z.ZodError, value: unknown, ctx: ValidationLogContext) => void;
294
294
 
295
+ /**
296
+ * Public metadata key for the array of `ResponseVariant` records attached
297
+ * to handler methods by `@ZodResponse(...)`. Symbol.for() so external
298
+ * consumers (e.g. custom interceptors or doc builders) can read the same
299
+ * registry across realms.
300
+ */
301
+ declare const ZOD_RESPONSES_METADATA_KEY: unique symbol;
302
+ type ResponseVariantKind = 'single' | 'array' | 'tuple';
303
+ /**
304
+ * OpenAPI 3.1 range keys accepted by `@ZodResponse({ status })`. Matched
305
+ * by `ZodSerializerInterceptor` after exact numeric matches fail —
306
+ * `'2XX'` covers 200–299, `'4XX'` covers 400–499, and so on.
307
+ */
308
+ type ResponseStatusWildcard = '1XX' | '2XX' | '3XX' | '4XX' | '5XX';
309
+ /**
310
+ * Description payload accepted by `@ZodResponse(...)` and passed through to
311
+ * `applyZodNest`'s `@ApiResponse(...)` emitter. String form is shorthand for
312
+ * `{ description }`; the object form lets users declare OpenAPI response
313
+ * `headers` / `links` alongside the description.
314
+ */
315
+ type ZodResponseDescription = string | {
316
+ description: string;
317
+ headers?: Record<string, unknown>;
318
+ links?: Record<string, unknown>;
319
+ };
320
+ /**
321
+ * One variant record per `@ZodResponse(...)` call. `dto` is kept alongside
322
+ * `validationSchema` so `applyZodNest` can emit `@ApiResponse({ type })`
323
+ * without unwrapping the runtime-only `z.array(...)` / `z.tuple([...])` wrapper.
324
+ *
325
+ * `status` is `undefined` when the user didn't pass one explicitly OR when
326
+ * the user wrote `'default'` (the decorator collapses both to `undefined` —
327
+ * they mean the same thing: "resolve to the method's default status at
328
+ * request time"). The effective status is resolved lazily by
329
+ * `resolveEffectiveStatus(variant, handler)` because `@ZodResponse` runs
330
+ * *before* NestJS' route decorators (`@Get`, `@Post`, ...) under
331
+ * TypeScript's bottom-up application order, so `METHOD_METADATA` is not
332
+ * yet set when the decorator evaluates.
333
+ *
334
+ * A wildcard string (`'2XX'` / `'4XX'` / ...) is kept verbatim — the
335
+ * matcher in `ZodSerializerInterceptor` handles range comparison.
336
+ */
337
+ interface ResponseVariant {
338
+ status: number | ResponseStatusWildcard | undefined;
339
+ kind: ResponseVariantKind;
340
+ dto: ZodDto | readonly ZodDto[];
341
+ validationSchema: z.ZodType;
342
+ description?: ZodResponseDescription;
343
+ passthroughOnError: boolean;
344
+ /**
345
+ * Raw `contentType` option from `@ZodResponse`, if set. `undefined` means
346
+ * "resolve lazily" — `resolveContentType` falls back to a stream-typed
347
+ * `@Header('Content-Type', …)` or `application/json`. Drives the OpenAPI
348
+ * media-type key.
349
+ */
350
+ contentType?: string;
351
+ /**
352
+ * Raw `stream` option from `@ZodResponse`, if set. `undefined` means
353
+ * "infer" — `isStreamResponse` treats the response as a stream when the
354
+ * effective content type is a known stream type. Streams skip validation.
355
+ */
356
+ stream?: boolean;
357
+ }
358
+
359
+ /**
360
+ * Content types whose bodies zod-nest treats as streams by default: the
361
+ * handler writes them straight to the response buffer (SSE, NDJSON, raw
362
+ * binary, files), so `ZodSerializerInterceptor` skips validation and the
363
+ * OpenAPI media-type key becomes the stream type rather than `application/json`.
364
+ *
365
+ * A trailing `/*` marks a family prefix — `image/*` matches `image/png`,
366
+ * `image/jpeg`, … — everything else matches exactly. Consumers extend this set
367
+ * *additively* via `ZodNestModuleOptions.streamContentTypes`; the defaults are
368
+ * always retained so SSE / NDJSON detection can't be accidentally dropped.
369
+ */
370
+ declare const DEFAULT_STREAM_CONTENT_TYPES: readonly string[];
371
+ /**
372
+ * Normalised stream-content-type matcher: exact media types in a `Set` for
373
+ * O(1) lookup, family prefixes (`image/`, …) checked with `startsWith`. Built
374
+ * once by `normalizeStreamMatcher` from the default list (decoration time) or
375
+ * the default ∪ module-configured list (runtime).
376
+ */
377
+ interface StreamContentTypeMatcher {
378
+ exact: ReadonlySet<string>;
379
+ prefixes: readonly string[];
380
+ }
381
+
295
382
  /**
296
383
  * Module-scope factory for the exception thrown by `ZodValidationPipe` on
297
384
  * input validation failure. Mirrors the existing per-pipe option but lives
@@ -329,6 +416,14 @@ interface ZodNestModuleOptions {
329
416
  * values become `{ _truncated: true, _originalBytes, _preview }`.
330
417
  */
331
418
  maxLoggedValueBytes?: number;
419
+ /**
420
+ * Additional response content types treated as streams — written directly
421
+ * to the response buffer and never validated by `ZodSerializerInterceptor`.
422
+ * MERGED with the built-in `DEFAULT_STREAM_CONTENT_TYPES` (SSE, NDJSON,
423
+ * octet-stream, pdf, `image/*`, `audio/*`, `video/*`), so the defaults are
424
+ * always retained. A trailing `/*` entry matches a media-type family.
425
+ */
426
+ streamContentTypes?: readonly string[];
332
427
  }
333
428
  /**
334
429
  * Resolved options consumed by pipe + interceptor. `forRoot()` builds this
@@ -342,6 +437,8 @@ interface NormalizedZodNestOptions {
342
437
  logInputFailure: LogValidationFailure;
343
438
  /** No-op when `validationLogs.output` resolved to false. */
344
439
  logOutputFailure: LogValidationFailure;
440
+ /** Built-in defaults ∪ `streamContentTypes`, used by the interceptor's stream check. */
441
+ streamMatcher: StreamContentTypeMatcher;
345
442
  }
346
443
  declare const DEFAULT_REDACT_KEYS: readonly string[];
347
444
  declare const DEFAULT_MAX_LOGGED_VALUE_BYTES = 4096;
@@ -379,57 +476,6 @@ declare class ZodValidationPipe implements PipeTransform {
379
476
  private static parseArg;
380
477
  }
381
478
 
382
- /**
383
- * Public metadata key for the array of `ResponseVariant` records attached
384
- * to handler methods by `@ZodResponse(...)`. Symbol.for() so external
385
- * consumers (e.g. custom interceptors or doc builders) can read the same
386
- * registry across realms.
387
- */
388
- declare const ZOD_RESPONSES_METADATA_KEY: unique symbol;
389
- type ResponseVariantKind = 'single' | 'array' | 'tuple';
390
- /**
391
- * OpenAPI 3.1 range keys accepted by `@ZodResponse({ status })`. Matched
392
- * by `ZodSerializerInterceptor` after exact numeric matches fail —
393
- * `'2XX'` covers 200–299, `'4XX'` covers 400–499, and so on.
394
- */
395
- type ResponseStatusWildcard = '1XX' | '2XX' | '3XX' | '4XX' | '5XX';
396
- /**
397
- * Description payload accepted by `@ZodResponse(...)` and passed through to
398
- * `applyZodNest`'s `@ApiResponse(...)` emitter. String form is shorthand for
399
- * `{ description }`; the object form lets users declare OpenAPI response
400
- * `headers` / `links` alongside the description.
401
- */
402
- type ZodResponseDescription = string | {
403
- description: string;
404
- headers?: Record<string, unknown>;
405
- links?: Record<string, unknown>;
406
- };
407
- /**
408
- * One variant record per `@ZodResponse(...)` call. `dto` is kept alongside
409
- * `validationSchema` so `applyZodNest` can emit `@ApiResponse({ type })`
410
- * without unwrapping the runtime-only `z.array(...)` / `z.tuple([...])` wrapper.
411
- *
412
- * `status` is `undefined` when the user didn't pass one explicitly OR when
413
- * the user wrote `'default'` (the decorator collapses both to `undefined` —
414
- * they mean the same thing: "resolve to the method's default status at
415
- * request time"). The effective status is resolved lazily by
416
- * `resolveEffectiveStatus(variant, handler)` because `@ZodResponse` runs
417
- * *before* NestJS' route decorators (`@Get`, `@Post`, ...) under
418
- * TypeScript's bottom-up application order, so `METHOD_METADATA` is not
419
- * yet set when the decorator evaluates.
420
- *
421
- * A wildcard string (`'2XX'` / `'4XX'` / ...) is kept verbatim — the
422
- * matcher in `ZodSerializerInterceptor` handles range comparison.
423
- */
424
- interface ResponseVariant {
425
- status: number | ResponseStatusWildcard | undefined;
426
- kind: ResponseVariantKind;
427
- dto: ZodDto | readonly ZodDto[];
428
- validationSchema: z.ZodType;
429
- description?: ZodResponseDescription;
430
- passthroughOnError: boolean;
431
- }
432
-
433
479
  /**
434
480
  * Accepted shapes for `@ZodResponse({ type })`:
435
481
  * - `Dto` → validates as `Dto.schema` (single-DTO response).
@@ -462,6 +508,20 @@ interface ZodResponseOptions {
462
508
  type: ZodResponseType;
463
509
  description?: ZodResponseDescription;
464
510
  passthroughOnError?: boolean;
511
+ /**
512
+ * Response content type, used as the OpenAPI media-type key (`@ApiResponse`
513
+ * `content`). Defaults to `'application/json'`. Set a stream type
514
+ * (`'text/event-stream'`, `'application/x-ndjson'`, …) to document a streamed
515
+ * body; combined with `stream` it also turns off response validation.
516
+ */
517
+ contentType?: string;
518
+ /**
519
+ * When `true`, the response is written directly to the buffer and
520
+ * `ZodSerializerInterceptor` skips validation. When unset, it is inferred:
521
+ * a stream-typed `contentType` (or a stream-typed `@Header('Content-Type', …)`)
522
+ * implies `true`. See `DEFAULT_STREAM_CONTENT_TYPES`.
523
+ */
524
+ stream?: boolean;
465
525
  }
466
526
  /**
467
527
  * Method-only decorator. Declares a typed response variant for the handler
@@ -474,16 +534,16 @@ interface ZodResponseOptions {
474
534
  * `'NXX'` wildcard). The wrapped Zod schema (array / tuple) is built once
475
535
  * at decoration time — no per-request schema construction.
476
536
  *
477
- * **Decorator-ordering note (implicit `status` only).** TypeScript decorators
478
- * apply bottom-up, so `@ZodResponse` (typically written above `@Get` /
479
- * `@HttpCode`) executes its factory body *first* before sibling
480
- * decorators have written their `HTTP_CODE_METADATA` / `METHOD_METADATA`.
481
- * When `opts.status` is explicit, the `@ApiResponse(...)` call is applied
482
- * synchronously there's nothing to wait for. When `opts.status` is
483
- * implicit (resolves to `@HttpCode` or the HTTP-method default), the
484
- * `@ApiResponse(...)` call is deferred via `queueMicrotask` so the sibling
485
- * metadata has settled by the time we read it. See `docs/responses.md →
486
- * "Decorator ordering & the microtask trick"`.
537
+ * **Decorator-ordering note.** TypeScript decorators apply bottom-up, so
538
+ * `@ZodResponse` (typically written above `@Get` / `@HttpCode` /
539
+ * `@Header(...)`) runs *before* those siblings have written their
540
+ * `HTTP_CODE_METADATA` / `METHOD_METADATA` / `HEADERS_METADATA`. The
541
+ * `@ApiResponse(...)` call is therefore always deferred via `queueMicrotask`,
542
+ * so by the time it reads sibling metadata — the effective status *and* the
543
+ * effective content type (including `@Header('Content-Type', )` inference) —
544
+ * everything has settled. The document is built much later
545
+ * (`SwaggerModule.createDocument`), so the deferral is invisible to output.
546
+ * See `docs/responses.md → "Decorator ordering & the microtask trick"`.
487
547
  */
488
548
  declare const ZodResponse: (opts: ZodResponseOptions) => MethodDecorator;
489
549
 
@@ -586,6 +646,7 @@ declare class ZodSerializerInterceptor implements NestInterceptor {
586
646
  private readonly reflector;
587
647
  private readonly logOutputFailure;
588
648
  private readonly createSerializationException;
649
+ private readonly streamMatcher;
589
650
  constructor(reflector: Reflector, options?: NormalizedZodNestOptions);
590
651
  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
591
652
  private transform;
@@ -714,4 +775,4 @@ declare class ZodNestDocumentError extends ZodNestError {
714
775
  constructor(code: ZodNestDocumentErrorCode, message: string, details?: Record<string, unknown>);
715
776
  }
716
777
 
717
- export { type ApplyZodNestOptions, COMPONENTS_SCHEMAS_PREFIX, type CreateSerializationException, type CreateValidationException, type CreateZodDtoOptions, DEFAULT_MAX_LOGGED_VALUE_BYTES, DEFAULT_REDACT_KEYS, type Io, type LineageEntry, type NormalizedZodNestOptions, type Override, type OverrideContext, type OverrideJSONSchemaArg, type RegisterSchemaOptions, type ResponseStatusInput, type ResponseStatusWildcard, type ResponseVariant, type ResponseVariantKind, SchemaObject, type ToOpenApiOptions, type ToOpenApiResult, ZOD_DTO_SYMBOL, ZOD_NEST_DTO_EXTENSION, ZOD_NEST_ERROR_DUPLICATE_ID, ZOD_NEST_ERROR_EXTENSION, ZOD_NEST_OPTIONS, ZOD_RESPONSES_METADATA_KEY, ZodBody, type ZodBodyOptions, ZodCookies, type ZodCookiesOptions, type ZodDto, type ZodDtoMarker, ZodHeaders, type ZodHeadersOptions, ZodNestDocumentError, type ZodNestDocumentErrorCode, ZodNestError, ZodNestModule, type ZodNestModuleOptions, type ZodNestRegistry, ZodNestUnrepresentableError, ZodQuery, type ZodQueryOptions, ZodResponse, type ZodResponseDescription, type ZodResponseOptions, type ZodResponseType, ZodSerializationException, ZodSerializerInterceptor, ZodValidationException, ZodValidationPipe, type ZodValidationPipeArg, type ZodValidationPipeOptions, applyZodNest, createRegistry, createZodDto, defaultRegistry, defaultStatusFor, extend, getLineage, isZodDto, isZodDtoMarker, makeZodDtoMarker, overrideJSONSchema, registerSchema, resolveEffectiveStatus, toOpenApi };
778
+ export { type ApplyZodNestOptions, COMPONENTS_SCHEMAS_PREFIX, type CreateSerializationException, type CreateValidationException, type CreateZodDtoOptions, DEFAULT_MAX_LOGGED_VALUE_BYTES, DEFAULT_REDACT_KEYS, DEFAULT_STREAM_CONTENT_TYPES, type Io, type LineageEntry, type NormalizedZodNestOptions, type Override, type OverrideContext, type OverrideJSONSchemaArg, type RegisterSchemaOptions, type ResponseStatusInput, type ResponseStatusWildcard, type ResponseVariant, type ResponseVariantKind, SchemaObject, type ToOpenApiOptions, type ToOpenApiResult, ZOD_DTO_SYMBOL, ZOD_NEST_DTO_EXTENSION, ZOD_NEST_ERROR_DUPLICATE_ID, ZOD_NEST_ERROR_EXTENSION, ZOD_NEST_OPTIONS, ZOD_RESPONSES_METADATA_KEY, ZodBody, type ZodBodyOptions, ZodCookies, type ZodCookiesOptions, type ZodDto, type ZodDtoMarker, ZodHeaders, type ZodHeadersOptions, ZodNestDocumentError, type ZodNestDocumentErrorCode, ZodNestError, ZodNestModule, type ZodNestModuleOptions, type ZodNestRegistry, ZodNestUnrepresentableError, ZodQuery, type ZodQueryOptions, ZodResponse, type ZodResponseDescription, type ZodResponseOptions, type ZodResponseType, ZodSerializationException, ZodSerializerInterceptor, ZodValidationException, ZodValidationPipe, type ZodValidationPipeArg, type ZodValidationPipeOptions, applyZodNest, createRegistry, createZodDto, defaultRegistry, defaultStatusFor, extend, getLineage, isZodDto, isZodDtoMarker, makeZodDtoMarker, overrideJSONSchema, registerSchema, resolveEffectiveStatus, toOpenApi };