zod-nest 0.3.0 → 0.5.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/dist/index.d.mts CHANGED
@@ -1,6 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  import { JSONSchema, $ZodTypes } from 'zod/v4/core';
3
- import { BadRequestException, ArgumentMetadata, PipeTransform } from '@nestjs/common';
3
+ import { InternalServerErrorException, ExecutionContext, BadRequestException, ArgumentMetadata, LoggerService, PipeTransform, NestInterceptor, CallHandler, DynamicModule, INestApplication } from '@nestjs/common';
4
+ import { Reflector } from '@nestjs/core';
5
+ import { Observable } from 'rxjs';
6
+ import { OpenAPIObject } from '@nestjs/swagger';
4
7
 
5
8
  /** OpenAPI 3.1 `$ref` prefix for entries in `components.schemas`. */
6
9
  declare const COMPONENTS_SCHEMAS_PREFIX = "#/components/schemas/";
@@ -30,6 +33,13 @@ interface ZodNestRegistry {
30
33
  register(schema: z.ZodType, id: string): void;
31
34
  hasCollision(id: string): boolean;
32
35
  getCollisions(): ReadonlyMap<string, ReadonlySet<z.ZodType>>;
36
+ /**
37
+ * Snapshot of every id registered through this `ZodNestRegistry`. Phase 2e's
38
+ * bulk-mode emitter uses it to filter `z.toJSONSchema(registry.zodRegistry,
39
+ * ...)` output to zod-nest-known ids only (the underlying Zod registry is
40
+ * `z.globalRegistry`, which can hold third-party entries).
41
+ */
42
+ ids(): readonly string[];
33
43
  }
34
44
  declare const createRegistry: () => ZodNestRegistry;
35
45
  /**
@@ -126,6 +136,30 @@ declare const isZodDtoMarker: (value: unknown) => value is ZodDtoMarker;
126
136
  */
127
137
  declare const isZodDto: (value: unknown) => value is ZodDto;
128
138
 
139
+ /**
140
+ * Default exception thrown by `ZodSerializerInterceptor` in strict mode
141
+ * (i.e. when `@ZodResponse({ passthroughOnError: true })` is NOT set) on
142
+ * response-validation failure.
143
+ *
144
+ * Body shape (returned by `getResponse()`):
145
+ * ```
146
+ * {
147
+ * statusCode: 500,
148
+ * message: 'Response validation failed',
149
+ * errors: z.treeifyError(zodError),
150
+ * }
151
+ * ```
152
+ *
153
+ * Carries `zodError` and `executionContext` so custom exception filters
154
+ * can introspect the original validation failure and the request that
155
+ * produced it.
156
+ */
157
+ declare class ZodSerializationException extends InternalServerErrorException {
158
+ readonly zodError: z.ZodError;
159
+ readonly executionContext?: ExecutionContext;
160
+ constructor(zodError: z.ZodError, executionContext?: ExecutionContext);
161
+ }
162
+
129
163
  /**
130
164
  * Default exception thrown by `ZodValidationPipe` when input fails to parse.
131
165
  *
@@ -147,6 +181,78 @@ declare class ZodValidationException extends BadRequestException {
147
181
  constructor(zodError: z.ZodError, argMetadata?: ArgumentMetadata);
148
182
  }
149
183
 
184
+ interface ValidationLogContext {
185
+ /** Which side of the request emitted the failure. */
186
+ side: 'input' | 'output';
187
+ /** Severity to log at; the formatter does not decide this. */
188
+ severity: 'warn' | 'error';
189
+ /** Pre-formatted DTO label, e.g. `'UserDto'`, `'[UserDto]'`, `'[A, B]'`. */
190
+ dto: string;
191
+ /** Output side only — HTTP response status code. */
192
+ status?: number;
193
+ /** Output side only — `Controller.method` best-effort. */
194
+ handler?: string;
195
+ /** Input side only — `body` / `query` / `param` / `custom`. */
196
+ argType?: string;
197
+ }
198
+ type LogValidationFailure = (err: z.ZodError, value: unknown, ctx: ValidationLogContext) => void;
199
+
200
+ /**
201
+ * Module-scope factory for the exception thrown by `ZodValidationPipe` on
202
+ * input validation failure. Mirrors the existing per-pipe option but lives
203
+ * at module scope; per-instance constructor arg wins.
204
+ */
205
+ type CreateValidationException$1 = (err: z.ZodError, argMetadata: ArgumentMetadata) => unknown;
206
+ /**
207
+ * Module-scope factory for the exception thrown by `ZodSerializerInterceptor`
208
+ * on output validation failure (strict mode only). Soft mode never calls
209
+ * this factory.
210
+ */
211
+ type CreateSerializationException = (err: z.ZodError, executionContext: ExecutionContext) => unknown;
212
+ /** Public options accepted by `ZodNestModule.forRoot()`. */
213
+ interface ZodNestModuleOptions {
214
+ createValidationException?: CreateValidationException$1;
215
+ createSerializationException?: CreateSerializationException;
216
+ /**
217
+ * Failure-only validation logging. `true` enables both input and output;
218
+ * the granular form lets each side be toggled independently. Default: off.
219
+ */
220
+ validationLogs?: boolean | {
221
+ input?: boolean;
222
+ output?: boolean;
223
+ };
224
+ /** Override Nest's built-in `Logger` (e.g. pino/winston adapter). */
225
+ logger?: LoggerService;
226
+ /**
227
+ * Keys whose values get scrubbed from logged input/response objects.
228
+ * Matched case-insensitively at any depth. Supplying this option
229
+ * REPLACES the default list (no merge).
230
+ */
231
+ redactKeys?: readonly string[];
232
+ /**
233
+ * Maximum size in bytes (UTF-8) for any single logged value. Oversized
234
+ * values become `{ _truncated: true, _originalBytes, _preview }`.
235
+ */
236
+ maxLoggedValueBytes?: number;
237
+ }
238
+ /**
239
+ * Resolved options consumed by pipe + interceptor. `forRoot()` builds this
240
+ * once and stuffs it into a provider for the `ZOD_NEST_OPTIONS` injection
241
+ * token; downstream code never re-checks the raw `validationLogs` flag.
242
+ */
243
+ interface NormalizedZodNestOptions {
244
+ createValidationException: CreateValidationException$1 | undefined;
245
+ createSerializationException: CreateSerializationException | undefined;
246
+ /** No-op when `validationLogs.input` resolved to false. */
247
+ logInputFailure: LogValidationFailure;
248
+ /** No-op when `validationLogs.output` resolved to false. */
249
+ logOutputFailure: LogValidationFailure;
250
+ }
251
+ declare const DEFAULT_REDACT_KEYS: readonly string[];
252
+ declare const DEFAULT_MAX_LOGGED_VALUE_BYTES = 4096;
253
+ /** DI token for `NormalizedZodNestOptions`. */
254
+ declare const ZOD_NEST_OPTIONS: unique symbol;
255
+
150
256
  /**
151
257
  * Build the exception thrown by `ZodValidationPipe` on validation failure.
152
258
  * Receives Zod's error and the NestJS argument metadata; returns anything
@@ -168,11 +274,200 @@ type ZodValidationPipeArg = z.ZodType | ZodDto | ZodValidationPipeOptions;
168
274
 
169
275
  declare class ZodValidationPipe implements PipeTransform {
170
276
  private readonly explicitSchema;
277
+ private readonly explicitDtoName;
171
278
  private readonly createValidationException;
172
- constructor(arg?: ZodValidationPipeArg);
279
+ private readonly logInputFailure;
280
+ constructor(arg?: ZodValidationPipeArg, moduleOptions?: NormalizedZodNestOptions);
173
281
  transform(value: unknown, metadata: ArgumentMetadata): Promise<unknown>;
174
282
  private resolveSchema;
283
+ private resolveDtoLabel;
175
284
  private static parseArg;
176
285
  }
177
286
 
178
- export { COMPONENTS_SCHEMAS_PREFIX, type CreateValidationException, type CreateZodDtoOptions, type Io, type Override, type OverrideContext, type SchemaObject, type ToOpenApiOptions, type ToOpenApiResult, ZOD_DTO_SYMBOL, ZOD_NEST_DTO_EXTENSION, ZOD_NEST_ERROR_DUPLICATE_ID, ZOD_NEST_ERROR_EXTENSION, type ZodDto, type ZodDtoMarker, ZodNestError, type ZodNestRegistry, ZodNestUnrepresentableError, ZodValidationException, ZodValidationPipe, type ZodValidationPipeArg, type ZodValidationPipeOptions, createRegistry, createZodDto, defaultRegistry, isZodDto, isZodDtoMarker, makeZodDtoMarker, toOpenApi };
287
+ /**
288
+ * Public metadata key for the array of `ResponseVariant` records attached
289
+ * to handler methods by `@ZodResponse(...)`. Symbol.for() so external
290
+ * consumers (e.g. custom interceptors or doc builders) can read the same
291
+ * registry across realms.
292
+ */
293
+ declare const ZOD_RESPONSES_METADATA_KEY: unique symbol;
294
+ type ResponseVariantKind = 'single' | 'array' | 'tuple';
295
+ /**
296
+ * Description payload accepted by `@ZodResponse(...)` and passed through to
297
+ * Phase 2e's `@ApiResponse(...)` emitter. String form is shorthand for
298
+ * `{ description }`; the object form lets users declare OpenAPI response
299
+ * `headers` / `links` alongside the description.
300
+ */
301
+ type ZodResponseDescription = string | {
302
+ description: string;
303
+ headers?: Record<string, unknown>;
304
+ links?: Record<string, unknown>;
305
+ };
306
+ /**
307
+ * One variant record per `@ZodResponse(...)` call. `dto` is kept alongside
308
+ * `validationSchema` so Phase 2e can emit `@ApiResponse({ type })` without
309
+ * unwrapping the runtime-only `z.array(...)` / `z.tuple([...])` wrapper.
310
+ *
311
+ * `status` is `undefined` when the user didn't pass one explicitly — the
312
+ * effective status is resolved lazily by `resolveEffectiveStatus(variant,
313
+ * handler)` because `@ZodResponse` runs *before* NestJS' route decorators
314
+ * (`@Get`, `@Post`, ...) under TypeScript's bottom-up application order,
315
+ * so `METHOD_METADATA` is not yet set when the decorator evaluates.
316
+ */
317
+ interface ResponseVariant {
318
+ status: number | undefined;
319
+ kind: ResponseVariantKind;
320
+ dto: ZodDto | readonly ZodDto[];
321
+ validationSchema: z.ZodType;
322
+ description?: ZodResponseDescription;
323
+ passthroughOnError: boolean;
324
+ }
325
+
326
+ /**
327
+ * Accepted shapes for `@ZodResponse({ type })`:
328
+ * - `Dto` → validates as `Dto.schema` (single-DTO response).
329
+ * - `[Dto]` (length 1) → validates as `z.array(Dto.schema)`; matches Nest's
330
+ * `@ApiResponse({ isArray: true })` convention without minting a separate
331
+ * `*sDto` id.
332
+ * - `[A, B, ...]` (length ≥ 2) → validates as `z.tuple([A.schema, B.schema, ...])`;
333
+ * surfaces as an OpenAPI 3.1 `prefixItems` tuple in Phase 2e.
334
+ *
335
+ * Empty arrays and non-DTO elements throw `TypeError` at decoration time
336
+ * so typos surface at module load, not the first request.
337
+ */
338
+ type ZodResponseType = ZodDto | readonly [ZodDto, ...ZodDto[]];
339
+ interface ZodResponseOptions {
340
+ status?: number;
341
+ type: ZodResponseType;
342
+ description?: ZodResponseDescription;
343
+ passthroughOnError?: boolean;
344
+ }
345
+ /**
346
+ * Method-only decorator. Declares a typed response variant for the handler.
347
+ * Stack multiple decorations to declare per-status types; lookup at runtime
348
+ * is by `response.statusCode === variant.status`.
349
+ *
350
+ * The wrapped Zod schema (array / tuple) is built once at decoration time
351
+ * and stored on the variant record — no per-request schema construction.
352
+ */
353
+ declare const ZodResponse: (opts: ZodResponseOptions) => MethodDecorator;
354
+
355
+ declare class ZodSerializerInterceptor implements NestInterceptor {
356
+ private readonly reflector;
357
+ private readonly logOutputFailure;
358
+ private readonly createSerializationException;
359
+ constructor(reflector: Reflector, options?: NormalizedZodNestOptions);
360
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
361
+ private transform;
362
+ }
363
+
364
+ /**
365
+ * Compute the default HTTP status code for a handler when `@ZodResponse(...)`
366
+ * is invoked without an explicit `status`. Lookup order (highest → lowest):
367
+ *
368
+ * 1. `@HttpCode(n)` on the handler — explicit per-handler status override.
369
+ * NestJS sets `HTTP_CODE_METADATA` to the numeric status when present.
370
+ * 2. HTTP method default — `POST` → `201`, everything else → `200`.
371
+ *
372
+ * The method default itself falls back to `200` when `METHOD_METADATA` is
373
+ * absent — pinned by tests so a future NestJS rename surfaces as a test
374
+ * failure rather than a silent regression.
375
+ */
376
+ declare const defaultStatusFor: (handler: object) => number;
377
+ /**
378
+ * Resolve the effective status code for a variant. Precedence chain:
379
+ *
380
+ * 1. Explicit `@ZodResponse({ status })` on the decorator call — `variant.status`.
381
+ * 2. `@HttpCode(n)` on the handler — read via `defaultStatusFor()` at runtime.
382
+ * 3. HTTP method default (POST → 201, others → 200) — also via `defaultStatusFor()`.
383
+ *
384
+ * Resolution is deferred to request time because `@ZodResponse` runs before
385
+ * NestJS' route + `@HttpCode` decorators — none of their metadata is set
386
+ * when the decorator evaluates.
387
+ */
388
+ declare const resolveEffectiveStatus: (variant: ResponseVariant, handler: object) => number;
389
+
390
+ /**
391
+ * Central wiring for the zod-nest pipe + interceptor. Call
392
+ * `ZodNestModule.forRoot(options?)` from your root `AppModule` to register
393
+ * `APP_PIPE` (`ZodValidationPipe`) and `APP_INTERCEPTOR`
394
+ * (`ZodSerializerInterceptor`) globally, with shared options for
395
+ * validation logging, redaction, and exception factories.
396
+ *
397
+ * `forRoot()` is optional — the pipe and interceptor also work as
398
+ * regular `APP_PIPE` / `APP_INTERCEPTOR` providers if you prefer to
399
+ * wire them manually; `@Optional()` injection of `ZOD_NEST_OPTIONS`
400
+ * falls through to safe defaults.
401
+ *
402
+ * Marked `global` so the `ZOD_NEST_OPTIONS` token is injectable from
403
+ * feature modules (e.g. a custom pipe that wants the same logger /
404
+ * redact list as the module-wired one).
405
+ */
406
+ declare class ZodNestModule {
407
+ static forRoot(options?: ZodNestModuleOptions): DynamicModule;
408
+ }
409
+
410
+ interface ApplyZodNestOptions {
411
+ /**
412
+ * The NestJS app instance. Required so `applyZodNest` can walk controllers
413
+ * via `DiscoveryService` to pick up `@ZodResponse` output-side DTO usage —
414
+ * `@nestjs/swagger` is currently anemic on response shapes.
415
+ */
416
+ app: INestApplication;
417
+ /**
418
+ * `ZodNestRegistry` instance that holds the zod-nest DTOs. Defaults to
419
+ * `defaultRegistry` (the process-wide singleton populated by `createZodDto`).
420
+ * Pass an explicit registry for multi-app isolation (planned formalization
421
+ * in v0.2).
422
+ */
423
+ registry?: ZodNestRegistry;
424
+ /** User override pipe applied on top of the built-in override during emission. */
425
+ override?: Override;
426
+ /**
427
+ * Strict mode (default `true`) throws `ZodNestUnrepresentableError` on
428
+ * unrepresentable Zod constructs (bigint / date / symbol / transform / ...).
429
+ * Set to `false` to emit `{}` for those instead.
430
+ */
431
+ strict?: boolean;
432
+ }
433
+ /**
434
+ * Post-processor over the OpenAPI document emitted by
435
+ * `SwaggerModule.createDocument`. Mutates the doc in place AND returns it for
436
+ * compositional convenience. After this runs:
437
+ *
438
+ * - Every `components.schemas[<DtoClassName>]` placeholder with an
439
+ * `x-zod-nest-dto` marker is replaced by the Zod-derived JSON Schema body,
440
+ * keyed by the marker's `dtoId` (renaming as needed).
441
+ * - The I/O suffix truth table is applied — equal input/output bodies collapse
442
+ * to one `components.schemas[id]`; divergent bodies split as
443
+ * `id` (input) + `idOutput` (output), with response-side refs rewritten.
444
+ * - Every `$ref` whose target is missing throws `ZodNestDocumentError(DANGLING_REF)`.
445
+ *
446
+ * Composable with other doc-transform passes — apply other mutations before
447
+ * or after this function. The `app` argument is required because the
448
+ * output-side DTO usage lives on controller-method metadata that the doc
449
+ * doesn't surface; `DiscoveryService` resolves it.
450
+ */
451
+ declare const applyZodNest: (doc: OpenAPIObject, opts: ApplyZodNestOptions) => OpenAPIObject;
452
+
453
+ type ZodNestDocumentErrorCode = 'AMBIGUOUS_RENAME' | 'DANGLING_REF';
454
+ /**
455
+ * Thrown by `applyZodNest` when the doc cannot be processed cleanly. Surfaces
456
+ * at doc-build time so typos / mis-registrations fail in CI, not at runtime.
457
+ *
458
+ * `AMBIGUOUS_RENAME`: two distinct DTO classes target the same registry id
459
+ * with differing bodies — the rename pass can't write `components.schemas[id]`
460
+ * unambiguously.
461
+ *
462
+ * `DANGLING_REF`: a `$ref` in the doc points at a `components.schemas` key
463
+ * that no longer exists after `applyZodNest`. Usually means a marker was
464
+ * stripped but its rename target wasn't populated, or a user-supplied pre-pass
465
+ * left a stale ref.
466
+ */
467
+ declare class ZodNestDocumentError extends ZodNestError {
468
+ readonly code: ZodNestDocumentErrorCode;
469
+ readonly details: Readonly<Record<string, unknown>>;
470
+ constructor(code: ZodNestDocumentErrorCode, message: string, details?: Record<string, unknown>);
471
+ }
472
+
473
+ export { type ApplyZodNestOptions, COMPONENTS_SCHEMAS_PREFIX, type CreateSerializationException, type CreateValidationException, type CreateZodDtoOptions, DEFAULT_MAX_LOGGED_VALUE_BYTES, DEFAULT_REDACT_KEYS, type Io, type NormalizedZodNestOptions, type Override, type OverrideContext, type ResponseVariant, type ResponseVariantKind, type 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, type ZodDto, type ZodDtoMarker, ZodNestDocumentError, type ZodNestDocumentErrorCode, ZodNestError, ZodNestModule, type ZodNestModuleOptions, type ZodNestRegistry, ZodNestUnrepresentableError, ZodResponse, type ZodResponseDescription, type ZodResponseOptions, type ZodResponseType, ZodSerializationException, ZodSerializerInterceptor, ZodValidationException, ZodValidationPipe, type ZodValidationPipeArg, type ZodValidationPipeOptions, applyZodNest, createRegistry, createZodDto, defaultRegistry, defaultStatusFor, isZodDto, isZodDtoMarker, makeZodDtoMarker, resolveEffectiveStatus, toOpenApi };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  import { JSONSchema, $ZodTypes } from 'zod/v4/core';
3
- import { BadRequestException, ArgumentMetadata, PipeTransform } from '@nestjs/common';
3
+ import { InternalServerErrorException, ExecutionContext, BadRequestException, ArgumentMetadata, LoggerService, PipeTransform, NestInterceptor, CallHandler, DynamicModule, INestApplication } from '@nestjs/common';
4
+ import { Reflector } from '@nestjs/core';
5
+ import { Observable } from 'rxjs';
6
+ import { OpenAPIObject } from '@nestjs/swagger';
4
7
 
5
8
  /** OpenAPI 3.1 `$ref` prefix for entries in `components.schemas`. */
6
9
  declare const COMPONENTS_SCHEMAS_PREFIX = "#/components/schemas/";
@@ -30,6 +33,13 @@ interface ZodNestRegistry {
30
33
  register(schema: z.ZodType, id: string): void;
31
34
  hasCollision(id: string): boolean;
32
35
  getCollisions(): ReadonlyMap<string, ReadonlySet<z.ZodType>>;
36
+ /**
37
+ * Snapshot of every id registered through this `ZodNestRegistry`. Phase 2e's
38
+ * bulk-mode emitter uses it to filter `z.toJSONSchema(registry.zodRegistry,
39
+ * ...)` output to zod-nest-known ids only (the underlying Zod registry is
40
+ * `z.globalRegistry`, which can hold third-party entries).
41
+ */
42
+ ids(): readonly string[];
33
43
  }
34
44
  declare const createRegistry: () => ZodNestRegistry;
35
45
  /**
@@ -126,6 +136,30 @@ declare const isZodDtoMarker: (value: unknown) => value is ZodDtoMarker;
126
136
  */
127
137
  declare const isZodDto: (value: unknown) => value is ZodDto;
128
138
 
139
+ /**
140
+ * Default exception thrown by `ZodSerializerInterceptor` in strict mode
141
+ * (i.e. when `@ZodResponse({ passthroughOnError: true })` is NOT set) on
142
+ * response-validation failure.
143
+ *
144
+ * Body shape (returned by `getResponse()`):
145
+ * ```
146
+ * {
147
+ * statusCode: 500,
148
+ * message: 'Response validation failed',
149
+ * errors: z.treeifyError(zodError),
150
+ * }
151
+ * ```
152
+ *
153
+ * Carries `zodError` and `executionContext` so custom exception filters
154
+ * can introspect the original validation failure and the request that
155
+ * produced it.
156
+ */
157
+ declare class ZodSerializationException extends InternalServerErrorException {
158
+ readonly zodError: z.ZodError;
159
+ readonly executionContext?: ExecutionContext;
160
+ constructor(zodError: z.ZodError, executionContext?: ExecutionContext);
161
+ }
162
+
129
163
  /**
130
164
  * Default exception thrown by `ZodValidationPipe` when input fails to parse.
131
165
  *
@@ -147,6 +181,78 @@ declare class ZodValidationException extends BadRequestException {
147
181
  constructor(zodError: z.ZodError, argMetadata?: ArgumentMetadata);
148
182
  }
149
183
 
184
+ interface ValidationLogContext {
185
+ /** Which side of the request emitted the failure. */
186
+ side: 'input' | 'output';
187
+ /** Severity to log at; the formatter does not decide this. */
188
+ severity: 'warn' | 'error';
189
+ /** Pre-formatted DTO label, e.g. `'UserDto'`, `'[UserDto]'`, `'[A, B]'`. */
190
+ dto: string;
191
+ /** Output side only — HTTP response status code. */
192
+ status?: number;
193
+ /** Output side only — `Controller.method` best-effort. */
194
+ handler?: string;
195
+ /** Input side only — `body` / `query` / `param` / `custom`. */
196
+ argType?: string;
197
+ }
198
+ type LogValidationFailure = (err: z.ZodError, value: unknown, ctx: ValidationLogContext) => void;
199
+
200
+ /**
201
+ * Module-scope factory for the exception thrown by `ZodValidationPipe` on
202
+ * input validation failure. Mirrors the existing per-pipe option but lives
203
+ * at module scope; per-instance constructor arg wins.
204
+ */
205
+ type CreateValidationException$1 = (err: z.ZodError, argMetadata: ArgumentMetadata) => unknown;
206
+ /**
207
+ * Module-scope factory for the exception thrown by `ZodSerializerInterceptor`
208
+ * on output validation failure (strict mode only). Soft mode never calls
209
+ * this factory.
210
+ */
211
+ type CreateSerializationException = (err: z.ZodError, executionContext: ExecutionContext) => unknown;
212
+ /** Public options accepted by `ZodNestModule.forRoot()`. */
213
+ interface ZodNestModuleOptions {
214
+ createValidationException?: CreateValidationException$1;
215
+ createSerializationException?: CreateSerializationException;
216
+ /**
217
+ * Failure-only validation logging. `true` enables both input and output;
218
+ * the granular form lets each side be toggled independently. Default: off.
219
+ */
220
+ validationLogs?: boolean | {
221
+ input?: boolean;
222
+ output?: boolean;
223
+ };
224
+ /** Override Nest's built-in `Logger` (e.g. pino/winston adapter). */
225
+ logger?: LoggerService;
226
+ /**
227
+ * Keys whose values get scrubbed from logged input/response objects.
228
+ * Matched case-insensitively at any depth. Supplying this option
229
+ * REPLACES the default list (no merge).
230
+ */
231
+ redactKeys?: readonly string[];
232
+ /**
233
+ * Maximum size in bytes (UTF-8) for any single logged value. Oversized
234
+ * values become `{ _truncated: true, _originalBytes, _preview }`.
235
+ */
236
+ maxLoggedValueBytes?: number;
237
+ }
238
+ /**
239
+ * Resolved options consumed by pipe + interceptor. `forRoot()` builds this
240
+ * once and stuffs it into a provider for the `ZOD_NEST_OPTIONS` injection
241
+ * token; downstream code never re-checks the raw `validationLogs` flag.
242
+ */
243
+ interface NormalizedZodNestOptions {
244
+ createValidationException: CreateValidationException$1 | undefined;
245
+ createSerializationException: CreateSerializationException | undefined;
246
+ /** No-op when `validationLogs.input` resolved to false. */
247
+ logInputFailure: LogValidationFailure;
248
+ /** No-op when `validationLogs.output` resolved to false. */
249
+ logOutputFailure: LogValidationFailure;
250
+ }
251
+ declare const DEFAULT_REDACT_KEYS: readonly string[];
252
+ declare const DEFAULT_MAX_LOGGED_VALUE_BYTES = 4096;
253
+ /** DI token for `NormalizedZodNestOptions`. */
254
+ declare const ZOD_NEST_OPTIONS: unique symbol;
255
+
150
256
  /**
151
257
  * Build the exception thrown by `ZodValidationPipe` on validation failure.
152
258
  * Receives Zod's error and the NestJS argument metadata; returns anything
@@ -168,11 +274,200 @@ type ZodValidationPipeArg = z.ZodType | ZodDto | ZodValidationPipeOptions;
168
274
 
169
275
  declare class ZodValidationPipe implements PipeTransform {
170
276
  private readonly explicitSchema;
277
+ private readonly explicitDtoName;
171
278
  private readonly createValidationException;
172
- constructor(arg?: ZodValidationPipeArg);
279
+ private readonly logInputFailure;
280
+ constructor(arg?: ZodValidationPipeArg, moduleOptions?: NormalizedZodNestOptions);
173
281
  transform(value: unknown, metadata: ArgumentMetadata): Promise<unknown>;
174
282
  private resolveSchema;
283
+ private resolveDtoLabel;
175
284
  private static parseArg;
176
285
  }
177
286
 
178
- export { COMPONENTS_SCHEMAS_PREFIX, type CreateValidationException, type CreateZodDtoOptions, type Io, type Override, type OverrideContext, type SchemaObject, type ToOpenApiOptions, type ToOpenApiResult, ZOD_DTO_SYMBOL, ZOD_NEST_DTO_EXTENSION, ZOD_NEST_ERROR_DUPLICATE_ID, ZOD_NEST_ERROR_EXTENSION, type ZodDto, type ZodDtoMarker, ZodNestError, type ZodNestRegistry, ZodNestUnrepresentableError, ZodValidationException, ZodValidationPipe, type ZodValidationPipeArg, type ZodValidationPipeOptions, createRegistry, createZodDto, defaultRegistry, isZodDto, isZodDtoMarker, makeZodDtoMarker, toOpenApi };
287
+ /**
288
+ * Public metadata key for the array of `ResponseVariant` records attached
289
+ * to handler methods by `@ZodResponse(...)`. Symbol.for() so external
290
+ * consumers (e.g. custom interceptors or doc builders) can read the same
291
+ * registry across realms.
292
+ */
293
+ declare const ZOD_RESPONSES_METADATA_KEY: unique symbol;
294
+ type ResponseVariantKind = 'single' | 'array' | 'tuple';
295
+ /**
296
+ * Description payload accepted by `@ZodResponse(...)` and passed through to
297
+ * Phase 2e's `@ApiResponse(...)` emitter. String form is shorthand for
298
+ * `{ description }`; the object form lets users declare OpenAPI response
299
+ * `headers` / `links` alongside the description.
300
+ */
301
+ type ZodResponseDescription = string | {
302
+ description: string;
303
+ headers?: Record<string, unknown>;
304
+ links?: Record<string, unknown>;
305
+ };
306
+ /**
307
+ * One variant record per `@ZodResponse(...)` call. `dto` is kept alongside
308
+ * `validationSchema` so Phase 2e can emit `@ApiResponse({ type })` without
309
+ * unwrapping the runtime-only `z.array(...)` / `z.tuple([...])` wrapper.
310
+ *
311
+ * `status` is `undefined` when the user didn't pass one explicitly — the
312
+ * effective status is resolved lazily by `resolveEffectiveStatus(variant,
313
+ * handler)` because `@ZodResponse` runs *before* NestJS' route decorators
314
+ * (`@Get`, `@Post`, ...) under TypeScript's bottom-up application order,
315
+ * so `METHOD_METADATA` is not yet set when the decorator evaluates.
316
+ */
317
+ interface ResponseVariant {
318
+ status: number | undefined;
319
+ kind: ResponseVariantKind;
320
+ dto: ZodDto | readonly ZodDto[];
321
+ validationSchema: z.ZodType;
322
+ description?: ZodResponseDescription;
323
+ passthroughOnError: boolean;
324
+ }
325
+
326
+ /**
327
+ * Accepted shapes for `@ZodResponse({ type })`:
328
+ * - `Dto` → validates as `Dto.schema` (single-DTO response).
329
+ * - `[Dto]` (length 1) → validates as `z.array(Dto.schema)`; matches Nest's
330
+ * `@ApiResponse({ isArray: true })` convention without minting a separate
331
+ * `*sDto` id.
332
+ * - `[A, B, ...]` (length ≥ 2) → validates as `z.tuple([A.schema, B.schema, ...])`;
333
+ * surfaces as an OpenAPI 3.1 `prefixItems` tuple in Phase 2e.
334
+ *
335
+ * Empty arrays and non-DTO elements throw `TypeError` at decoration time
336
+ * so typos surface at module load, not the first request.
337
+ */
338
+ type ZodResponseType = ZodDto | readonly [ZodDto, ...ZodDto[]];
339
+ interface ZodResponseOptions {
340
+ status?: number;
341
+ type: ZodResponseType;
342
+ description?: ZodResponseDescription;
343
+ passthroughOnError?: boolean;
344
+ }
345
+ /**
346
+ * Method-only decorator. Declares a typed response variant for the handler.
347
+ * Stack multiple decorations to declare per-status types; lookup at runtime
348
+ * is by `response.statusCode === variant.status`.
349
+ *
350
+ * The wrapped Zod schema (array / tuple) is built once at decoration time
351
+ * and stored on the variant record — no per-request schema construction.
352
+ */
353
+ declare const ZodResponse: (opts: ZodResponseOptions) => MethodDecorator;
354
+
355
+ declare class ZodSerializerInterceptor implements NestInterceptor {
356
+ private readonly reflector;
357
+ private readonly logOutputFailure;
358
+ private readonly createSerializationException;
359
+ constructor(reflector: Reflector, options?: NormalizedZodNestOptions);
360
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
361
+ private transform;
362
+ }
363
+
364
+ /**
365
+ * Compute the default HTTP status code for a handler when `@ZodResponse(...)`
366
+ * is invoked without an explicit `status`. Lookup order (highest → lowest):
367
+ *
368
+ * 1. `@HttpCode(n)` on the handler — explicit per-handler status override.
369
+ * NestJS sets `HTTP_CODE_METADATA` to the numeric status when present.
370
+ * 2. HTTP method default — `POST` → `201`, everything else → `200`.
371
+ *
372
+ * The method default itself falls back to `200` when `METHOD_METADATA` is
373
+ * absent — pinned by tests so a future NestJS rename surfaces as a test
374
+ * failure rather than a silent regression.
375
+ */
376
+ declare const defaultStatusFor: (handler: object) => number;
377
+ /**
378
+ * Resolve the effective status code for a variant. Precedence chain:
379
+ *
380
+ * 1. Explicit `@ZodResponse({ status })` on the decorator call — `variant.status`.
381
+ * 2. `@HttpCode(n)` on the handler — read via `defaultStatusFor()` at runtime.
382
+ * 3. HTTP method default (POST → 201, others → 200) — also via `defaultStatusFor()`.
383
+ *
384
+ * Resolution is deferred to request time because `@ZodResponse` runs before
385
+ * NestJS' route + `@HttpCode` decorators — none of their metadata is set
386
+ * when the decorator evaluates.
387
+ */
388
+ declare const resolveEffectiveStatus: (variant: ResponseVariant, handler: object) => number;
389
+
390
+ /**
391
+ * Central wiring for the zod-nest pipe + interceptor. Call
392
+ * `ZodNestModule.forRoot(options?)` from your root `AppModule` to register
393
+ * `APP_PIPE` (`ZodValidationPipe`) and `APP_INTERCEPTOR`
394
+ * (`ZodSerializerInterceptor`) globally, with shared options for
395
+ * validation logging, redaction, and exception factories.
396
+ *
397
+ * `forRoot()` is optional — the pipe and interceptor also work as
398
+ * regular `APP_PIPE` / `APP_INTERCEPTOR` providers if you prefer to
399
+ * wire them manually; `@Optional()` injection of `ZOD_NEST_OPTIONS`
400
+ * falls through to safe defaults.
401
+ *
402
+ * Marked `global` so the `ZOD_NEST_OPTIONS` token is injectable from
403
+ * feature modules (e.g. a custom pipe that wants the same logger /
404
+ * redact list as the module-wired one).
405
+ */
406
+ declare class ZodNestModule {
407
+ static forRoot(options?: ZodNestModuleOptions): DynamicModule;
408
+ }
409
+
410
+ interface ApplyZodNestOptions {
411
+ /**
412
+ * The NestJS app instance. Required so `applyZodNest` can walk controllers
413
+ * via `DiscoveryService` to pick up `@ZodResponse` output-side DTO usage —
414
+ * `@nestjs/swagger` is currently anemic on response shapes.
415
+ */
416
+ app: INestApplication;
417
+ /**
418
+ * `ZodNestRegistry` instance that holds the zod-nest DTOs. Defaults to
419
+ * `defaultRegistry` (the process-wide singleton populated by `createZodDto`).
420
+ * Pass an explicit registry for multi-app isolation (planned formalization
421
+ * in v0.2).
422
+ */
423
+ registry?: ZodNestRegistry;
424
+ /** User override pipe applied on top of the built-in override during emission. */
425
+ override?: Override;
426
+ /**
427
+ * Strict mode (default `true`) throws `ZodNestUnrepresentableError` on
428
+ * unrepresentable Zod constructs (bigint / date / symbol / transform / ...).
429
+ * Set to `false` to emit `{}` for those instead.
430
+ */
431
+ strict?: boolean;
432
+ }
433
+ /**
434
+ * Post-processor over the OpenAPI document emitted by
435
+ * `SwaggerModule.createDocument`. Mutates the doc in place AND returns it for
436
+ * compositional convenience. After this runs:
437
+ *
438
+ * - Every `components.schemas[<DtoClassName>]` placeholder with an
439
+ * `x-zod-nest-dto` marker is replaced by the Zod-derived JSON Schema body,
440
+ * keyed by the marker's `dtoId` (renaming as needed).
441
+ * - The I/O suffix truth table is applied — equal input/output bodies collapse
442
+ * to one `components.schemas[id]`; divergent bodies split as
443
+ * `id` (input) + `idOutput` (output), with response-side refs rewritten.
444
+ * - Every `$ref` whose target is missing throws `ZodNestDocumentError(DANGLING_REF)`.
445
+ *
446
+ * Composable with other doc-transform passes — apply other mutations before
447
+ * or after this function. The `app` argument is required because the
448
+ * output-side DTO usage lives on controller-method metadata that the doc
449
+ * doesn't surface; `DiscoveryService` resolves it.
450
+ */
451
+ declare const applyZodNest: (doc: OpenAPIObject, opts: ApplyZodNestOptions) => OpenAPIObject;
452
+
453
+ type ZodNestDocumentErrorCode = 'AMBIGUOUS_RENAME' | 'DANGLING_REF';
454
+ /**
455
+ * Thrown by `applyZodNest` when the doc cannot be processed cleanly. Surfaces
456
+ * at doc-build time so typos / mis-registrations fail in CI, not at runtime.
457
+ *
458
+ * `AMBIGUOUS_RENAME`: two distinct DTO classes target the same registry id
459
+ * with differing bodies — the rename pass can't write `components.schemas[id]`
460
+ * unambiguously.
461
+ *
462
+ * `DANGLING_REF`: a `$ref` in the doc points at a `components.schemas` key
463
+ * that no longer exists after `applyZodNest`. Usually means a marker was
464
+ * stripped but its rename target wasn't populated, or a user-supplied pre-pass
465
+ * left a stale ref.
466
+ */
467
+ declare class ZodNestDocumentError extends ZodNestError {
468
+ readonly code: ZodNestDocumentErrorCode;
469
+ readonly details: Readonly<Record<string, unknown>>;
470
+ constructor(code: ZodNestDocumentErrorCode, message: string, details?: Record<string, unknown>);
471
+ }
472
+
473
+ export { type ApplyZodNestOptions, COMPONENTS_SCHEMAS_PREFIX, type CreateSerializationException, type CreateValidationException, type CreateZodDtoOptions, DEFAULT_MAX_LOGGED_VALUE_BYTES, DEFAULT_REDACT_KEYS, type Io, type NormalizedZodNestOptions, type Override, type OverrideContext, type ResponseVariant, type ResponseVariantKind, type 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, type ZodDto, type ZodDtoMarker, ZodNestDocumentError, type ZodNestDocumentErrorCode, ZodNestError, ZodNestModule, type ZodNestModuleOptions, type ZodNestRegistry, ZodNestUnrepresentableError, ZodResponse, type ZodResponseDescription, type ZodResponseOptions, type ZodResponseType, ZodSerializationException, ZodSerializerInterceptor, ZodValidationException, ZodValidationPipe, type ZodValidationPipeArg, type ZodValidationPipeOptions, applyZodNest, createRegistry, createZodDto, defaultRegistry, defaultStatusFor, isZodDto, isZodDtoMarker, makeZodDtoMarker, resolveEffectiveStatus, toOpenApi };