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 +66 -38
- package/dist/index.d.mts +123 -62
- package/dist/index.d.ts +123 -62
- package/dist/index.js +158 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +159 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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:
|
|
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
|
|
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
|
|
314
|
-
class ErrorDto extends createZodDto(z.object({ code: z.number() }),
|
|
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({
|
|
320
|
-
@ZodResponse({ status: HttpStatus.NOT_FOUND,
|
|
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
|
|
389
|
-
|
|
390
|
-
| `createValidationException`
|
|
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`
|
|
393
|
-
| `logger`
|
|
394
|
-
| `redactKeys`
|
|
395
|
-
| `maxLoggedValueBytes`
|
|
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
|
|
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
|
|
484
|
-
|
|
485
|
-
| Why this library exists
|
|
486
|
-
| `createZodDto` in depth
|
|
487
|
-
| Input validation
|
|
488
|
-
| Responses, multi-status, status resolution | [`docs/responses.md`](docs/responses.md)
|
|
489
|
-
| Module options reference
|
|
490
|
-
| Validation logging
|
|
491
|
-
| Swagger integration & custom emission
|
|
492
|
-
| Composition (experimental)
|
|
493
|
-
| Exception classes
|
|
494
|
-
| 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
|
|
501
|
-
|
|
502
|
-
| `floor`
|
|
503
|
-
| `zod-latest`
|
|
504
|
-
| `nest-latest`
|
|
505
|
-
| `rxjs-latest`
|
|
506
|
-
| `reflect-metadata-latest` | 4.4.0
|
|
507
|
-
| `all-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
|
|
478
|
-
*
|
|
479
|
-
* `@
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
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 };
|