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 +298 -3
- package/dist/index.d.ts +298 -3
- package/dist/index.js +837 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +824 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|