zod-nest 1.11.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -130,7 +130,7 @@ async function bootstrap() {
130
130
  app,
131
131
  new DocumentBuilder().setTitle('Users').setVersion('1').build(),
132
132
  );
133
- const doc = applyZodNest(raw, { app });
133
+ const doc = applyZodNest(raw);
134
134
  SwaggerModule.setup('docs', app, doc);
135
135
 
136
136
  await app.listen(3000);
@@ -151,7 +151,7 @@ Every DTO is one Zod schema wrapped in a class. The class exists so NestJS' intr
151
151
 
152
152
  The class returned by `createZodDto(schema)` carries `schema`, `id`, `io: 'input'`, and a lazy `Output` sibling. `parse` / `safeParse` are static methods on the class. The class is tagged with `Symbol.for('zod-nest.dto')` so `ZodValidationPipe` and `ZodSerializerInterceptor` can discriminate it from plain constructors. The id comes from `schema.meta({ id })` when present (preferred) or from the second-argument options.
153
153
 
154
- **Naming is exposing.** Every schema put through `registerSchema()` — directly, or transitively via `createZodDto` / `@ZodBody` / `extend` / descendant discovery lands in `components.schemas` when `applyZodNest` runs, regardless of whether any `$ref` in the doc points at it. If you give a schema `.meta({ id })`, you're declaring it documented. Anonymous schemas without an id stay inlined where used.
154
+ **Exposure is reachability-scoped.** A schema lands in `components.schemas` when an endpoint in the document references it — directly or transitively through a `$ref` (response inner deps included), or as a query/param/header/cookie DTO captured via its marker. A registered schema that no endpoint reaches is pruned; force it in with `{ expose: true }`. Anonymous schemas without an id stay inlined where used. This keeps several Swagger documents that share one registry from each carrying the whole catalog.
155
155
 
156
156
  See [`docs/dto.md`](docs/dto.md) for the full surface.
157
157
 
@@ -186,6 +186,8 @@ The decorator set: `@ZodBody`, `@ZodQuery`, `@ZodHeaders`, `@ZodCookies`. All ar
186
186
 
187
187
  See [`docs/recipes/intersection-with-union.md`](docs/recipes/intersection-with-union.md) for the full pattern.
188
188
 
189
+ Named query objects (both `@Query() dto` and `@ZodQuery`) expand to one parameter per field by default. Pass `applyZodNest(raw, { queryParamStyle: 'ref' })` — or `@ZodQuery(schema, { ref: true })` per handler — to instead emit a single schema-based query parameter that `$ref`s the shared component. Same wire format; see [`docs/swagger-integration.md → Query parameter style`](docs/swagger-integration.md#query-parameter-style).
190
+
189
191
  ### I/O suffix rules
190
192
 
191
193
  If a schema's input and output JSON Schemas are byte-equal (the common case), the OpenAPI doc emits a single `components.schemas[Id]`. If they differ (e.g. a `transform`, a `pipe`, an `.optional().default(x)` field), the doc emits two: `Id` for input, `IdOutput` for output. Response refs are rewritten to `IdOutput` automatically.
@@ -375,7 +377,7 @@ Use this sparingly — it bypasses the contract you declared. Logging at `warn`
375
377
  import { applyZodNest } from 'zod-nest';
376
378
 
377
379
  const raw = SwaggerModule.createDocument(app, config);
378
- const doc = applyZodNest(raw, { app });
380
+ const doc = applyZodNest(raw);
379
381
  SwaggerModule.setup('docs', app, doc);
380
382
  ```
381
383
 
@@ -540,7 +542,7 @@ Cell definitions live in [`.github/compat-matrix.json`](.github/compat-matrix.js
540
542
 
541
543
  If you're coming from `nestjs-zod`, the headline changes are:
542
544
 
543
- - Replace `cleanupOpenApiDoc(SwaggerModule.createDocument(app, config))` with `applyZodNest(SwaggerModule.createDocument(app, config), { app })`.
545
+ - Replace `cleanupOpenApiDoc(SwaggerModule.createDocument(app, config))` with `applyZodNest(SwaggerModule.createDocument(app, config))`.
544
546
  - Replace `@ApiOkResponse({ type: Dto }) + @ZodSerializerDto(Dto)` pairs with `@ZodResponse({ type: Dto })`.
545
547
  - Drop `class-validator` / `class-transformer` if they were installed only for `nestjs-zod` interop.
546
548
  - Check any `MyDto.isZodDto` reflection — the discriminator is now `Symbol.for('zod-nest.dto') in MyDto`.
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { S as SchemaObject } from './openapi.types-CFBG3Zz9.mjs';
3
3
  import { $ZodTypes } from 'zod/v4/core';
4
- import { InternalServerErrorException, ExecutionContext, BadRequestException, ArgumentMetadata, LoggerService, PipeTransform, NestInterceptor, CallHandler, DynamicModule, INestApplication } from '@nestjs/common';
4
+ import { InternalServerErrorException, ExecutionContext, BadRequestException, ArgumentMetadata, LoggerService, PipeTransform, NestInterceptor, CallHandler, DynamicModule } from '@nestjs/common';
5
5
  import { Reflector } from '@nestjs/core';
6
6
  import { Observable } from 'rxjs';
7
7
  import { OpenAPIObject } from '@nestjs/swagger';
@@ -27,9 +27,31 @@ interface OverrideContext {
27
27
  }
28
28
  type Override = (ctx: OverrideContext) => void;
29
29
 
30
+ /**
31
+ * Per-registration flags carried alongside the id.
32
+ *
33
+ * - `expose` — force the id into the emitted document even when no endpoint
34
+ * references it. The author deliberately wants it in `components.schemas`
35
+ * (e.g. for out-of-band client codegen). Default exposure is otherwise
36
+ * reachability-scoped, so an unreferenced schema is pruned unless flagged.
37
+ * - `anonymous` — the id is a synthetic placeholder for a schema with no
38
+ * resolvable id (passed inline to `@ZodResponse` / `@ZodBody`). It exists
39
+ * only to carry the body through bulk emission under the document's
40
+ * `strict` / `override` options; `inlineAnonymousBodies` later inlines the
41
+ * body at each `$ref` site and prunes the component, so the synthetic id
42
+ * never reaches the final document.
43
+ *
44
+ * Both flags are sticky — once set for an id they stay set, so a later plain
45
+ * `register` of the same id (e.g. the idempotent re-register inside
46
+ * `createZodDto`) doesn't clear them.
47
+ */
48
+ interface RegisterFlags {
49
+ readonly expose?: boolean;
50
+ readonly anonymous?: boolean;
51
+ }
30
52
  interface ZodNestRegistry {
31
53
  readonly zodRegistry: typeof z.globalRegistry;
32
- register(schema: z.ZodType, id: string): void;
54
+ register(schema: z.ZodType, id: string, flags?: RegisterFlags): void;
33
55
  hasCollision(id: string): boolean;
34
56
  getCollisions(): ReadonlyMap<string, ReadonlySet<z.ZodType>>;
35
57
  /**
@@ -42,6 +64,10 @@ interface ZodNestRegistry {
42
64
  * of explicitly-registered schemas.
43
65
  */
44
66
  ids(): readonly string[];
67
+ /** Ids registered with `{ expose: true }` — exposed regardless of usage. */
68
+ forceExposedIds(): readonly string[];
69
+ /** Ids registered with `{ anonymous: true }` — inlined + pruned by `applyZodNest`. */
70
+ anonymousIds(): readonly string[];
45
71
  }
46
72
  declare const createRegistry: () => ZodNestRegistry;
47
73
  /** Process-wide default registry, used when no explicit `options.registry` is passed. */
@@ -49,6 +75,17 @@ declare const defaultRegistry: ZodNestRegistry;
49
75
  interface RegisterSchemaOptions {
50
76
  /** Forces this id, overriding any `.meta({ id })` already on the schema. */
51
77
  readonly id?: string;
78
+ /**
79
+ * Force the schema into the emitted document even when no endpoint
80
+ * references it. Default exposure is reachability-scoped — see
81
+ * {@link RegisterFlags.expose}.
82
+ */
83
+ readonly expose?: boolean;
84
+ /**
85
+ * Mark the resolved id as a synthetic anonymous placeholder — inlined and
86
+ * pruned by `applyZodNest`. See {@link RegisterFlags.anonymous}.
87
+ */
88
+ readonly anonymous?: boolean;
52
89
  }
53
90
  /**
54
91
  * Register a schema with the given registry, resolving its id from (in order):
@@ -175,6 +212,14 @@ interface CreateZodDtoOptions {
175
212
  id?: string;
176
213
  /** Registry to register this DTO's schema into. Defaults to `defaultRegistry`. */
177
214
  registry?: ZodNestRegistry;
215
+ /**
216
+ * Force this DTO's schema into the emitted document even when no endpoint
217
+ * references it. Exposure is otherwise reachability-scoped — a registered
218
+ * schema that no exposed endpoint reaches is pruned. Set `true` to keep an
219
+ * intentionally-documented schema (e.g. for out-of-band client codegen).
220
+ * Defaults to `false`.
221
+ */
222
+ expose?: boolean;
178
223
  }
179
224
  /**
180
225
  * The class type returned by `createZodDto`. Instance type infers to the
@@ -616,12 +661,34 @@ interface ZodQueryOptions {
616
661
  readonly id?: string;
617
662
  /** Registry to register into. Defaults to `defaultRegistry`. */
618
663
  readonly registry?: ZodNestRegistry;
664
+ /**
665
+ * Override how this query DTO is represented in the OpenAPI doc, taking
666
+ * precedence over `applyZodNest`'s `queryParamStyle`:
667
+ *
668
+ * - `true` — one single schema-based query parameter that `$ref`s the DTO's
669
+ * `components.schemas` entry (`style: 'form'`, `explode: true`).
670
+ * - `false` — one parameter per top-level property.
671
+ * - unset — follow the global `queryParamStyle` preference (default `'expand'`).
672
+ *
673
+ * Ref mode needs a named schema to reference: `ref: true` on an anonymous
674
+ * schema (no `.meta({ id })` and no `id` option) throws `ZodNestError`.
675
+ */
676
+ readonly ref?: boolean;
619
677
  }
620
678
  /**
621
- * Method-level decorator that expands a `z.object` schema into one
622
- * `@ApiQuery` entry per property. Each property's schema is independently
623
- * resolved — named properties (`.meta({ id })`) become `$ref`s,
624
- * anonymous properties inline.
679
+ * Method-level decorator describing a `@Query()` object schema as OpenAPI
680
+ * query parameters.
681
+ *
682
+ * When the schema is named (`.meta({ id })` or the `id` option), the decorator
683
+ * registers it and emits a single deferred marker, leaving the expand-vs-`$ref`
684
+ * decision to `applyZodNest` — so an unset `ref` follows the global
685
+ * `queryParamStyle` preference, `ref: true` collapses to one `$ref` query
686
+ * parameter, and `ref: false` expands per property. This mirrors the
687
+ * `@Query() dto` (`createZodDto`) path, which flows through the same marker.
688
+ *
689
+ * When the schema is anonymous, there is no component to reference, so the
690
+ * decorator expands one `@ApiQuery` per property immediately (and rejects
691
+ * `ref: true`).
625
692
  *
626
693
  * The user remains responsible for binding the actual query values to the
627
694
  * handler, typically via `@Query(new ZodValidationPipe(schema)) q: z.infer<typeof schema>`.
@@ -720,13 +787,22 @@ declare class ZodNestModule {
720
787
  static forRoot(options?: ZodNestModuleOptions): DynamicModule;
721
788
  }
722
789
 
790
+ /**
791
+ * How a named `@Query()` / `@ZodQuery` DTO is represented in the OpenAPI doc:
792
+ *
793
+ * - `'expand'` (default) — one parameter per top-level property of the DTO
794
+ * schema, each inlined or `$ref`'d independently.
795
+ * - `'ref'` — a single schema-based query parameter that references the DTO's
796
+ * `components.schemas` entry (`style: 'form'`, `explode: true`,
797
+ * `schema: { $ref }`). The wire format is identical to `'expand'`; only the
798
+ * document representation collapses to the shared component.
799
+ *
800
+ * Query-only: path / header / cookie markers always expand regardless of this
801
+ * setting, since the form-exploded-object pattern is a query serialization.
802
+ */
803
+ type QueryParamStyle = 'expand' | 'ref';
804
+
723
805
  interface ApplyZodNestOptions {
724
- /**
725
- * The NestJS app instance. Required so `applyZodNest` can walk controllers
726
- * via `DiscoveryService` to pick up `@ZodResponse` output-side DTO usage —
727
- * `@nestjs/swagger` is currently anemic on response shapes.
728
- */
729
- app: INestApplication;
730
806
  /**
731
807
  * `ZodNestRegistry` instance that holds the zod-nest DTOs. Defaults to
732
808
  * `defaultRegistry` (the process-wide singleton populated by `createZodDto`).
@@ -741,6 +817,20 @@ interface ApplyZodNestOptions {
741
817
  * Set to `false` to emit `{}` for those instead.
742
818
  */
743
819
  strict?: boolean;
820
+ /**
821
+ * How named `@Query()` / `@ZodQuery` DTOs are represented in the document
822
+ * (default `'expand'`):
823
+ *
824
+ * - `'expand'` — one query parameter per top-level property of the DTO.
825
+ * - `'ref'` — a single schema-based query parameter referencing the DTO's
826
+ * `components.schemas` entry (`style: 'form'`, `explode: true`). The wire
827
+ * format is unchanged; only the spec representation collapses to the
828
+ * shared component. Note: Swagger UI renders the two forms differently.
829
+ *
830
+ * Query-only — path / header / cookie DTOs always expand. A per-handler
831
+ * `@ZodQuery({ ref })` override takes precedence over this preference.
832
+ */
833
+ queryParamStyle?: QueryParamStyle;
744
834
  }
745
835
  /**
746
836
  * Post-processor over the OpenAPI document emitted by
@@ -752,23 +842,29 @@ interface ApplyZodNestOptions {
752
842
  * keyed by the marker's `dtoId` (renaming as needed).
753
843
  * - Every `@Query()` / `@Param()` / `@Headers()` / `@Cookie()` marker
754
844
  * parameter is expanded into one parameter per top-level property of the
755
- * DTO's schema (`expandParamMarkers`). The synthetic `components.schemas.Object`
756
- * that `@nestjs/swagger` materialises for the marker placeholder is pruned
757
- * when it has no remaining referrers.
845
+ * DTO's schema (`expandParamMarkers`) except `@Query()` DTOs under
846
+ * `queryParamStyle: 'ref'` (or a `@ZodQuery({ ref: true })` override), which
847
+ * collapse to a single `$ref` schema-based query parameter. The synthetic
848
+ * `components.schemas.Object` that `@nestjs/swagger` materialises for the
849
+ * marker placeholder is pruned when it has no remaining referrers.
758
850
  * - The I/O suffix truth table is applied — equal input/output bodies collapse
759
851
  * to one `components.schemas[id]`; divergent bodies split as
760
852
  * `id` (input) + `idOutput` (output), with response-side refs rewritten.
853
+ * - Anonymous body/response schemas (no `.meta({ id })`) are inlined at their
854
+ * `$ref` sites and their synthetic components pruned (`inlineAnonymousBodies`).
855
+ * - Only schemas reachable from this document's endpoints (plus their
856
+ * transitive `$ref` deps, plus any `{ expose: true }` opt-ins) are kept —
857
+ * unreferenced registered schemas are pruned. Exposure is document-scoped, so
858
+ * several documents sharing one registry each carry only what they use.
761
859
  * - Every `$ref` whose target is missing throws `ZodNestDocumentError(DANGLING_REF)`.
762
860
  * - `doc.openapi` is set to `'3.1.0'` — zod-nest emits OpenAPI 3.1 only; this
763
861
  * guarantees the version string matches the emitted body regardless of the
764
862
  * `DocumentBuilder` configuration on the caller side.
765
863
  *
766
864
  * Composable with other doc-transform passes — apply other mutations before
767
- * or after this function. The `app` argument is required because the
768
- * output-side DTO usage lives on controller-method metadata that the doc
769
- * doesn't surface; `DiscoveryService` resolves it.
865
+ * or after this function.
770
866
  */
771
- declare const applyZodNest: (doc: OpenAPIObject, opts: ApplyZodNestOptions) => OpenAPIObject;
867
+ declare const applyZodNest: (doc: OpenAPIObject, opts?: ApplyZodNestOptions) => OpenAPIObject;
772
868
 
773
869
  type ZodNestDocumentErrorCode = 'AMBIGUOUS_RENAME' | 'DANGLING_REF' | 'UNEXPANDABLE_PARAM_DTO';
774
870
  /**
@@ -795,4 +891,4 @@ declare class ZodNestDocumentError extends ZodNestError {
795
891
  constructor(code: ZodNestDocumentErrorCode, message: string, details?: Record<string, unknown>);
796
892
  }
797
893
 
798
- 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 ZodResponseEntry, type ZodResponseOptions, type ZodResponseType, ZodSerializationException, ZodSerializerInterceptor, ZodValidationException, ZodValidationPipe, type ZodValidationPipeArg, type ZodValidationPipeOptions, applyZodNest, createRegistry, createZodDto, defaultRegistry, defaultStatusFor, extend, getLineage, isZodDto, isZodDtoMarker, isZodSchema, makeZodDtoMarker, overrideJSONSchema, registerSchema, resolveEffectiveStatus, toOpenApi };
894
+ 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 RegisterFlags, 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 ZodResponseEntry, type ZodResponseOptions, type ZodResponseType, ZodSerializationException, ZodSerializerInterceptor, ZodValidationException, ZodValidationPipe, type ZodValidationPipeArg, type ZodValidationPipeOptions, applyZodNest, createRegistry, createZodDto, defaultRegistry, defaultStatusFor, extend, getLineage, isZodDto, isZodDtoMarker, isZodSchema, makeZodDtoMarker, overrideJSONSchema, registerSchema, resolveEffectiveStatus, toOpenApi };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { S as SchemaObject } from './openapi.types-CFBG3Zz9.js';
3
3
  import { $ZodTypes } from 'zod/v4/core';
4
- import { InternalServerErrorException, ExecutionContext, BadRequestException, ArgumentMetadata, LoggerService, PipeTransform, NestInterceptor, CallHandler, DynamicModule, INestApplication } from '@nestjs/common';
4
+ import { InternalServerErrorException, ExecutionContext, BadRequestException, ArgumentMetadata, LoggerService, PipeTransform, NestInterceptor, CallHandler, DynamicModule } from '@nestjs/common';
5
5
  import { Reflector } from '@nestjs/core';
6
6
  import { Observable } from 'rxjs';
7
7
  import { OpenAPIObject } from '@nestjs/swagger';
@@ -27,9 +27,31 @@ interface OverrideContext {
27
27
  }
28
28
  type Override = (ctx: OverrideContext) => void;
29
29
 
30
+ /**
31
+ * Per-registration flags carried alongside the id.
32
+ *
33
+ * - `expose` — force the id into the emitted document even when no endpoint
34
+ * references it. The author deliberately wants it in `components.schemas`
35
+ * (e.g. for out-of-band client codegen). Default exposure is otherwise
36
+ * reachability-scoped, so an unreferenced schema is pruned unless flagged.
37
+ * - `anonymous` — the id is a synthetic placeholder for a schema with no
38
+ * resolvable id (passed inline to `@ZodResponse` / `@ZodBody`). It exists
39
+ * only to carry the body through bulk emission under the document's
40
+ * `strict` / `override` options; `inlineAnonymousBodies` later inlines the
41
+ * body at each `$ref` site and prunes the component, so the synthetic id
42
+ * never reaches the final document.
43
+ *
44
+ * Both flags are sticky — once set for an id they stay set, so a later plain
45
+ * `register` of the same id (e.g. the idempotent re-register inside
46
+ * `createZodDto`) doesn't clear them.
47
+ */
48
+ interface RegisterFlags {
49
+ readonly expose?: boolean;
50
+ readonly anonymous?: boolean;
51
+ }
30
52
  interface ZodNestRegistry {
31
53
  readonly zodRegistry: typeof z.globalRegistry;
32
- register(schema: z.ZodType, id: string): void;
54
+ register(schema: z.ZodType, id: string, flags?: RegisterFlags): void;
33
55
  hasCollision(id: string): boolean;
34
56
  getCollisions(): ReadonlyMap<string, ReadonlySet<z.ZodType>>;
35
57
  /**
@@ -42,6 +64,10 @@ interface ZodNestRegistry {
42
64
  * of explicitly-registered schemas.
43
65
  */
44
66
  ids(): readonly string[];
67
+ /** Ids registered with `{ expose: true }` — exposed regardless of usage. */
68
+ forceExposedIds(): readonly string[];
69
+ /** Ids registered with `{ anonymous: true }` — inlined + pruned by `applyZodNest`. */
70
+ anonymousIds(): readonly string[];
45
71
  }
46
72
  declare const createRegistry: () => ZodNestRegistry;
47
73
  /** Process-wide default registry, used when no explicit `options.registry` is passed. */
@@ -49,6 +75,17 @@ declare const defaultRegistry: ZodNestRegistry;
49
75
  interface RegisterSchemaOptions {
50
76
  /** Forces this id, overriding any `.meta({ id })` already on the schema. */
51
77
  readonly id?: string;
78
+ /**
79
+ * Force the schema into the emitted document even when no endpoint
80
+ * references it. Default exposure is reachability-scoped — see
81
+ * {@link RegisterFlags.expose}.
82
+ */
83
+ readonly expose?: boolean;
84
+ /**
85
+ * Mark the resolved id as a synthetic anonymous placeholder — inlined and
86
+ * pruned by `applyZodNest`. See {@link RegisterFlags.anonymous}.
87
+ */
88
+ readonly anonymous?: boolean;
52
89
  }
53
90
  /**
54
91
  * Register a schema with the given registry, resolving its id from (in order):
@@ -175,6 +212,14 @@ interface CreateZodDtoOptions {
175
212
  id?: string;
176
213
  /** Registry to register this DTO's schema into. Defaults to `defaultRegistry`. */
177
214
  registry?: ZodNestRegistry;
215
+ /**
216
+ * Force this DTO's schema into the emitted document even when no endpoint
217
+ * references it. Exposure is otherwise reachability-scoped — a registered
218
+ * schema that no exposed endpoint reaches is pruned. Set `true` to keep an
219
+ * intentionally-documented schema (e.g. for out-of-band client codegen).
220
+ * Defaults to `false`.
221
+ */
222
+ expose?: boolean;
178
223
  }
179
224
  /**
180
225
  * The class type returned by `createZodDto`. Instance type infers to the
@@ -616,12 +661,34 @@ interface ZodQueryOptions {
616
661
  readonly id?: string;
617
662
  /** Registry to register into. Defaults to `defaultRegistry`. */
618
663
  readonly registry?: ZodNestRegistry;
664
+ /**
665
+ * Override how this query DTO is represented in the OpenAPI doc, taking
666
+ * precedence over `applyZodNest`'s `queryParamStyle`:
667
+ *
668
+ * - `true` — one single schema-based query parameter that `$ref`s the DTO's
669
+ * `components.schemas` entry (`style: 'form'`, `explode: true`).
670
+ * - `false` — one parameter per top-level property.
671
+ * - unset — follow the global `queryParamStyle` preference (default `'expand'`).
672
+ *
673
+ * Ref mode needs a named schema to reference: `ref: true` on an anonymous
674
+ * schema (no `.meta({ id })` and no `id` option) throws `ZodNestError`.
675
+ */
676
+ readonly ref?: boolean;
619
677
  }
620
678
  /**
621
- * Method-level decorator that expands a `z.object` schema into one
622
- * `@ApiQuery` entry per property. Each property's schema is independently
623
- * resolved — named properties (`.meta({ id })`) become `$ref`s,
624
- * anonymous properties inline.
679
+ * Method-level decorator describing a `@Query()` object schema as OpenAPI
680
+ * query parameters.
681
+ *
682
+ * When the schema is named (`.meta({ id })` or the `id` option), the decorator
683
+ * registers it and emits a single deferred marker, leaving the expand-vs-`$ref`
684
+ * decision to `applyZodNest` — so an unset `ref` follows the global
685
+ * `queryParamStyle` preference, `ref: true` collapses to one `$ref` query
686
+ * parameter, and `ref: false` expands per property. This mirrors the
687
+ * `@Query() dto` (`createZodDto`) path, which flows through the same marker.
688
+ *
689
+ * When the schema is anonymous, there is no component to reference, so the
690
+ * decorator expands one `@ApiQuery` per property immediately (and rejects
691
+ * `ref: true`).
625
692
  *
626
693
  * The user remains responsible for binding the actual query values to the
627
694
  * handler, typically via `@Query(new ZodValidationPipe(schema)) q: z.infer<typeof schema>`.
@@ -720,13 +787,22 @@ declare class ZodNestModule {
720
787
  static forRoot(options?: ZodNestModuleOptions): DynamicModule;
721
788
  }
722
789
 
790
+ /**
791
+ * How a named `@Query()` / `@ZodQuery` DTO is represented in the OpenAPI doc:
792
+ *
793
+ * - `'expand'` (default) — one parameter per top-level property of the DTO
794
+ * schema, each inlined or `$ref`'d independently.
795
+ * - `'ref'` — a single schema-based query parameter that references the DTO's
796
+ * `components.schemas` entry (`style: 'form'`, `explode: true`,
797
+ * `schema: { $ref }`). The wire format is identical to `'expand'`; only the
798
+ * document representation collapses to the shared component.
799
+ *
800
+ * Query-only: path / header / cookie markers always expand regardless of this
801
+ * setting, since the form-exploded-object pattern is a query serialization.
802
+ */
803
+ type QueryParamStyle = 'expand' | 'ref';
804
+
723
805
  interface ApplyZodNestOptions {
724
- /**
725
- * The NestJS app instance. Required so `applyZodNest` can walk controllers
726
- * via `DiscoveryService` to pick up `@ZodResponse` output-side DTO usage —
727
- * `@nestjs/swagger` is currently anemic on response shapes.
728
- */
729
- app: INestApplication;
730
806
  /**
731
807
  * `ZodNestRegistry` instance that holds the zod-nest DTOs. Defaults to
732
808
  * `defaultRegistry` (the process-wide singleton populated by `createZodDto`).
@@ -741,6 +817,20 @@ interface ApplyZodNestOptions {
741
817
  * Set to `false` to emit `{}` for those instead.
742
818
  */
743
819
  strict?: boolean;
820
+ /**
821
+ * How named `@Query()` / `@ZodQuery` DTOs are represented in the document
822
+ * (default `'expand'`):
823
+ *
824
+ * - `'expand'` — one query parameter per top-level property of the DTO.
825
+ * - `'ref'` — a single schema-based query parameter referencing the DTO's
826
+ * `components.schemas` entry (`style: 'form'`, `explode: true`). The wire
827
+ * format is unchanged; only the spec representation collapses to the
828
+ * shared component. Note: Swagger UI renders the two forms differently.
829
+ *
830
+ * Query-only — path / header / cookie DTOs always expand. A per-handler
831
+ * `@ZodQuery({ ref })` override takes precedence over this preference.
832
+ */
833
+ queryParamStyle?: QueryParamStyle;
744
834
  }
745
835
  /**
746
836
  * Post-processor over the OpenAPI document emitted by
@@ -752,23 +842,29 @@ interface ApplyZodNestOptions {
752
842
  * keyed by the marker's `dtoId` (renaming as needed).
753
843
  * - Every `@Query()` / `@Param()` / `@Headers()` / `@Cookie()` marker
754
844
  * parameter is expanded into one parameter per top-level property of the
755
- * DTO's schema (`expandParamMarkers`). The synthetic `components.schemas.Object`
756
- * that `@nestjs/swagger` materialises for the marker placeholder is pruned
757
- * when it has no remaining referrers.
845
+ * DTO's schema (`expandParamMarkers`) except `@Query()` DTOs under
846
+ * `queryParamStyle: 'ref'` (or a `@ZodQuery({ ref: true })` override), which
847
+ * collapse to a single `$ref` schema-based query parameter. The synthetic
848
+ * `components.schemas.Object` that `@nestjs/swagger` materialises for the
849
+ * marker placeholder is pruned when it has no remaining referrers.
758
850
  * - The I/O suffix truth table is applied — equal input/output bodies collapse
759
851
  * to one `components.schemas[id]`; divergent bodies split as
760
852
  * `id` (input) + `idOutput` (output), with response-side refs rewritten.
853
+ * - Anonymous body/response schemas (no `.meta({ id })`) are inlined at their
854
+ * `$ref` sites and their synthetic components pruned (`inlineAnonymousBodies`).
855
+ * - Only schemas reachable from this document's endpoints (plus their
856
+ * transitive `$ref` deps, plus any `{ expose: true }` opt-ins) are kept —
857
+ * unreferenced registered schemas are pruned. Exposure is document-scoped, so
858
+ * several documents sharing one registry each carry only what they use.
761
859
  * - Every `$ref` whose target is missing throws `ZodNestDocumentError(DANGLING_REF)`.
762
860
  * - `doc.openapi` is set to `'3.1.0'` — zod-nest emits OpenAPI 3.1 only; this
763
861
  * guarantees the version string matches the emitted body regardless of the
764
862
  * `DocumentBuilder` configuration on the caller side.
765
863
  *
766
864
  * Composable with other doc-transform passes — apply other mutations before
767
- * or after this function. The `app` argument is required because the
768
- * output-side DTO usage lives on controller-method metadata that the doc
769
- * doesn't surface; `DiscoveryService` resolves it.
865
+ * or after this function.
770
866
  */
771
- declare const applyZodNest: (doc: OpenAPIObject, opts: ApplyZodNestOptions) => OpenAPIObject;
867
+ declare const applyZodNest: (doc: OpenAPIObject, opts?: ApplyZodNestOptions) => OpenAPIObject;
772
868
 
773
869
  type ZodNestDocumentErrorCode = 'AMBIGUOUS_RENAME' | 'DANGLING_REF' | 'UNEXPANDABLE_PARAM_DTO';
774
870
  /**
@@ -795,4 +891,4 @@ declare class ZodNestDocumentError extends ZodNestError {
795
891
  constructor(code: ZodNestDocumentErrorCode, message: string, details?: Record<string, unknown>);
796
892
  }
797
893
 
798
- 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 ZodResponseEntry, type ZodResponseOptions, type ZodResponseType, ZodSerializationException, ZodSerializerInterceptor, ZodValidationException, ZodValidationPipe, type ZodValidationPipeArg, type ZodValidationPipeOptions, applyZodNest, createRegistry, createZodDto, defaultRegistry, defaultStatusFor, extend, getLineage, isZodDto, isZodDtoMarker, isZodSchema, makeZodDtoMarker, overrideJSONSchema, registerSchema, resolveEffectiveStatus, toOpenApi };
894
+ 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 RegisterFlags, 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 ZodResponseEntry, type ZodResponseOptions, type ZodResponseType, ZodSerializationException, ZodSerializerInterceptor, ZodValidationException, ZodValidationPipe, type ZodValidationPipeArg, type ZodValidationPipeOptions, applyZodNest, createRegistry, createZodDto, defaultRegistry, defaultStatusFor, extend, getLineage, isZodDto, isZodDtoMarker, isZodSchema, makeZodDtoMarker, overrideJSONSchema, registerSchema, resolveEffectiveStatus, toOpenApi };