zod-nest 1.11.0 → 1.12.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 +2 -0
- package/dist/index.d.mts +60 -7
- package/dist/index.d.ts +60 -7
- package/dist/index.js +81 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +81 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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, { app, 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.
|
package/dist/index.d.mts
CHANGED
|
@@ -616,12 +616,34 @@ interface ZodQueryOptions {
|
|
|
616
616
|
readonly id?: string;
|
|
617
617
|
/** Registry to register into. Defaults to `defaultRegistry`. */
|
|
618
618
|
readonly registry?: ZodNestRegistry;
|
|
619
|
+
/**
|
|
620
|
+
* Override how this query DTO is represented in the OpenAPI doc, taking
|
|
621
|
+
* precedence over `applyZodNest`'s `queryParamStyle`:
|
|
622
|
+
*
|
|
623
|
+
* - `true` — one single schema-based query parameter that `$ref`s the DTO's
|
|
624
|
+
* `components.schemas` entry (`style: 'form'`, `explode: true`).
|
|
625
|
+
* - `false` — one parameter per top-level property.
|
|
626
|
+
* - unset — follow the global `queryParamStyle` preference (default `'expand'`).
|
|
627
|
+
*
|
|
628
|
+
* Ref mode needs a named schema to reference: `ref: true` on an anonymous
|
|
629
|
+
* schema (no `.meta({ id })` and no `id` option) throws `ZodNestError`.
|
|
630
|
+
*/
|
|
631
|
+
readonly ref?: boolean;
|
|
619
632
|
}
|
|
620
633
|
/**
|
|
621
|
-
* Method-level decorator
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
*
|
|
634
|
+
* Method-level decorator describing a `@Query()` object schema as OpenAPI
|
|
635
|
+
* query parameters.
|
|
636
|
+
*
|
|
637
|
+
* When the schema is named (`.meta({ id })` or the `id` option), the decorator
|
|
638
|
+
* registers it and emits a single deferred marker, leaving the expand-vs-`$ref`
|
|
639
|
+
* decision to `applyZodNest` — so an unset `ref` follows the global
|
|
640
|
+
* `queryParamStyle` preference, `ref: true` collapses to one `$ref` query
|
|
641
|
+
* parameter, and `ref: false` expands per property. This mirrors the
|
|
642
|
+
* `@Query() dto` (`createZodDto`) path, which flows through the same marker.
|
|
643
|
+
*
|
|
644
|
+
* When the schema is anonymous, there is no component to reference, so the
|
|
645
|
+
* decorator expands one `@ApiQuery` per property immediately (and rejects
|
|
646
|
+
* `ref: true`).
|
|
625
647
|
*
|
|
626
648
|
* The user remains responsible for binding the actual query values to the
|
|
627
649
|
* handler, typically via `@Query(new ZodValidationPipe(schema)) q: z.infer<typeof schema>`.
|
|
@@ -720,6 +742,21 @@ declare class ZodNestModule {
|
|
|
720
742
|
static forRoot(options?: ZodNestModuleOptions): DynamicModule;
|
|
721
743
|
}
|
|
722
744
|
|
|
745
|
+
/**
|
|
746
|
+
* How a named `@Query()` / `@ZodQuery` DTO is represented in the OpenAPI doc:
|
|
747
|
+
*
|
|
748
|
+
* - `'expand'` (default) — one parameter per top-level property of the DTO
|
|
749
|
+
* schema, each inlined or `$ref`'d independently.
|
|
750
|
+
* - `'ref'` — a single schema-based query parameter that references the DTO's
|
|
751
|
+
* `components.schemas` entry (`style: 'form'`, `explode: true`,
|
|
752
|
+
* `schema: { $ref }`). The wire format is identical to `'expand'`; only the
|
|
753
|
+
* document representation collapses to the shared component.
|
|
754
|
+
*
|
|
755
|
+
* Query-only: path / header / cookie markers always expand regardless of this
|
|
756
|
+
* setting, since the form-exploded-object pattern is a query serialization.
|
|
757
|
+
*/
|
|
758
|
+
type QueryParamStyle = 'expand' | 'ref';
|
|
759
|
+
|
|
723
760
|
interface ApplyZodNestOptions {
|
|
724
761
|
/**
|
|
725
762
|
* The NestJS app instance. Required so `applyZodNest` can walk controllers
|
|
@@ -741,6 +778,20 @@ interface ApplyZodNestOptions {
|
|
|
741
778
|
* Set to `false` to emit `{}` for those instead.
|
|
742
779
|
*/
|
|
743
780
|
strict?: boolean;
|
|
781
|
+
/**
|
|
782
|
+
* How named `@Query()` / `@ZodQuery` DTOs are represented in the document
|
|
783
|
+
* (default `'expand'`):
|
|
784
|
+
*
|
|
785
|
+
* - `'expand'` — one query parameter per top-level property of the DTO.
|
|
786
|
+
* - `'ref'` — a single schema-based query parameter referencing the DTO's
|
|
787
|
+
* `components.schemas` entry (`style: 'form'`, `explode: true`). The wire
|
|
788
|
+
* format is unchanged; only the spec representation collapses to the
|
|
789
|
+
* shared component. Note: Swagger UI renders the two forms differently.
|
|
790
|
+
*
|
|
791
|
+
* Query-only — path / header / cookie DTOs always expand. A per-handler
|
|
792
|
+
* `@ZodQuery({ ref })` override takes precedence over this preference.
|
|
793
|
+
*/
|
|
794
|
+
queryParamStyle?: QueryParamStyle;
|
|
744
795
|
}
|
|
745
796
|
/**
|
|
746
797
|
* Post-processor over the OpenAPI document emitted by
|
|
@@ -752,9 +803,11 @@ interface ApplyZodNestOptions {
|
|
|
752
803
|
* keyed by the marker's `dtoId` (renaming as needed).
|
|
753
804
|
* - Every `@Query()` / `@Param()` / `@Headers()` / `@Cookie()` marker
|
|
754
805
|
* parameter is expanded into one parameter per top-level property of the
|
|
755
|
-
* DTO's schema (`expandParamMarkers`)
|
|
756
|
-
*
|
|
757
|
-
*
|
|
806
|
+
* DTO's schema (`expandParamMarkers`) — except `@Query()` DTOs under
|
|
807
|
+
* `queryParamStyle: 'ref'` (or a `@ZodQuery({ ref: true })` override), which
|
|
808
|
+
* collapse to a single `$ref` schema-based query parameter. The synthetic
|
|
809
|
+
* `components.schemas.Object` that `@nestjs/swagger` materialises for the
|
|
810
|
+
* marker placeholder is pruned when it has no remaining referrers.
|
|
758
811
|
* - The I/O suffix truth table is applied — equal input/output bodies collapse
|
|
759
812
|
* to one `components.schemas[id]`; divergent bodies split as
|
|
760
813
|
* `id` (input) + `idOutput` (output), with response-side refs rewritten.
|
package/dist/index.d.ts
CHANGED
|
@@ -616,12 +616,34 @@ interface ZodQueryOptions {
|
|
|
616
616
|
readonly id?: string;
|
|
617
617
|
/** Registry to register into. Defaults to `defaultRegistry`. */
|
|
618
618
|
readonly registry?: ZodNestRegistry;
|
|
619
|
+
/**
|
|
620
|
+
* Override how this query DTO is represented in the OpenAPI doc, taking
|
|
621
|
+
* precedence over `applyZodNest`'s `queryParamStyle`:
|
|
622
|
+
*
|
|
623
|
+
* - `true` — one single schema-based query parameter that `$ref`s the DTO's
|
|
624
|
+
* `components.schemas` entry (`style: 'form'`, `explode: true`).
|
|
625
|
+
* - `false` — one parameter per top-level property.
|
|
626
|
+
* - unset — follow the global `queryParamStyle` preference (default `'expand'`).
|
|
627
|
+
*
|
|
628
|
+
* Ref mode needs a named schema to reference: `ref: true` on an anonymous
|
|
629
|
+
* schema (no `.meta({ id })` and no `id` option) throws `ZodNestError`.
|
|
630
|
+
*/
|
|
631
|
+
readonly ref?: boolean;
|
|
619
632
|
}
|
|
620
633
|
/**
|
|
621
|
-
* Method-level decorator
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
*
|
|
634
|
+
* Method-level decorator describing a `@Query()` object schema as OpenAPI
|
|
635
|
+
* query parameters.
|
|
636
|
+
*
|
|
637
|
+
* When the schema is named (`.meta({ id })` or the `id` option), the decorator
|
|
638
|
+
* registers it and emits a single deferred marker, leaving the expand-vs-`$ref`
|
|
639
|
+
* decision to `applyZodNest` — so an unset `ref` follows the global
|
|
640
|
+
* `queryParamStyle` preference, `ref: true` collapses to one `$ref` query
|
|
641
|
+
* parameter, and `ref: false` expands per property. This mirrors the
|
|
642
|
+
* `@Query() dto` (`createZodDto`) path, which flows through the same marker.
|
|
643
|
+
*
|
|
644
|
+
* When the schema is anonymous, there is no component to reference, so the
|
|
645
|
+
* decorator expands one `@ApiQuery` per property immediately (and rejects
|
|
646
|
+
* `ref: true`).
|
|
625
647
|
*
|
|
626
648
|
* The user remains responsible for binding the actual query values to the
|
|
627
649
|
* handler, typically via `@Query(new ZodValidationPipe(schema)) q: z.infer<typeof schema>`.
|
|
@@ -720,6 +742,21 @@ declare class ZodNestModule {
|
|
|
720
742
|
static forRoot(options?: ZodNestModuleOptions): DynamicModule;
|
|
721
743
|
}
|
|
722
744
|
|
|
745
|
+
/**
|
|
746
|
+
* How a named `@Query()` / `@ZodQuery` DTO is represented in the OpenAPI doc:
|
|
747
|
+
*
|
|
748
|
+
* - `'expand'` (default) — one parameter per top-level property of the DTO
|
|
749
|
+
* schema, each inlined or `$ref`'d independently.
|
|
750
|
+
* - `'ref'` — a single schema-based query parameter that references the DTO's
|
|
751
|
+
* `components.schemas` entry (`style: 'form'`, `explode: true`,
|
|
752
|
+
* `schema: { $ref }`). The wire format is identical to `'expand'`; only the
|
|
753
|
+
* document representation collapses to the shared component.
|
|
754
|
+
*
|
|
755
|
+
* Query-only: path / header / cookie markers always expand regardless of this
|
|
756
|
+
* setting, since the form-exploded-object pattern is a query serialization.
|
|
757
|
+
*/
|
|
758
|
+
type QueryParamStyle = 'expand' | 'ref';
|
|
759
|
+
|
|
723
760
|
interface ApplyZodNestOptions {
|
|
724
761
|
/**
|
|
725
762
|
* The NestJS app instance. Required so `applyZodNest` can walk controllers
|
|
@@ -741,6 +778,20 @@ interface ApplyZodNestOptions {
|
|
|
741
778
|
* Set to `false` to emit `{}` for those instead.
|
|
742
779
|
*/
|
|
743
780
|
strict?: boolean;
|
|
781
|
+
/**
|
|
782
|
+
* How named `@Query()` / `@ZodQuery` DTOs are represented in the document
|
|
783
|
+
* (default `'expand'`):
|
|
784
|
+
*
|
|
785
|
+
* - `'expand'` — one query parameter per top-level property of the DTO.
|
|
786
|
+
* - `'ref'` — a single schema-based query parameter referencing the DTO's
|
|
787
|
+
* `components.schemas` entry (`style: 'form'`, `explode: true`). The wire
|
|
788
|
+
* format is unchanged; only the spec representation collapses to the
|
|
789
|
+
* shared component. Note: Swagger UI renders the two forms differently.
|
|
790
|
+
*
|
|
791
|
+
* Query-only — path / header / cookie DTOs always expand. A per-handler
|
|
792
|
+
* `@ZodQuery({ ref })` override takes precedence over this preference.
|
|
793
|
+
*/
|
|
794
|
+
queryParamStyle?: QueryParamStyle;
|
|
744
795
|
}
|
|
745
796
|
/**
|
|
746
797
|
* Post-processor over the OpenAPI document emitted by
|
|
@@ -752,9 +803,11 @@ interface ApplyZodNestOptions {
|
|
|
752
803
|
* keyed by the marker's `dtoId` (renaming as needed).
|
|
753
804
|
* - Every `@Query()` / `@Param()` / `@Headers()` / `@Cookie()` marker
|
|
754
805
|
* parameter is expanded into one parameter per top-level property of the
|
|
755
|
-
* DTO's schema (`expandParamMarkers`)
|
|
756
|
-
*
|
|
757
|
-
*
|
|
806
|
+
* DTO's schema (`expandParamMarkers`) — except `@Query()` DTOs under
|
|
807
|
+
* `queryParamStyle: 'ref'` (or a `@ZodQuery({ ref: true })` override), which
|
|
808
|
+
* collapse to a single `$ref` schema-based query parameter. The synthetic
|
|
809
|
+
* `components.schemas.Object` that `@nestjs/swagger` materialises for the
|
|
810
|
+
* marker placeholder is pruned when it has no remaining referrers.
|
|
758
811
|
* - The I/O suffix truth table is applied — equal input/output bodies collapse
|
|
759
812
|
* to one `components.schemas[id]`; divergent bodies split as
|
|
760
813
|
* `id` (input) + `idOutput` (output), with response-side refs rewritten.
|
package/dist/index.js
CHANGED
|
@@ -1472,10 +1472,51 @@ var resolveBodySchema = /* @__PURE__ */ __name((schema, options) => {
|
|
|
1472
1472
|
});
|
|
1473
1473
|
return resolution.kind === "ref" ? resolution.ref : resolution.schema;
|
|
1474
1474
|
}, "resolveBodySchema");
|
|
1475
|
+
|
|
1476
|
+
// src/decorators/internal/query-marker.ts
|
|
1477
|
+
var API_PARAMETERS_METADATA_KEY = "swagger/apiParameters";
|
|
1478
|
+
var appendQueryMarker = /* @__PURE__ */ __name((dtoId, ref) => (_target, _propertyKey, descriptor) => {
|
|
1479
|
+
const method = descriptor.value;
|
|
1480
|
+
if (typeof method !== "function") {
|
|
1481
|
+
return descriptor;
|
|
1482
|
+
}
|
|
1483
|
+
const marker = {
|
|
1484
|
+
name: dtoId,
|
|
1485
|
+
in: "query",
|
|
1486
|
+
__zodNestDto: true,
|
|
1487
|
+
dtoId,
|
|
1488
|
+
io: "input"
|
|
1489
|
+
};
|
|
1490
|
+
if (ref !== void 0) {
|
|
1491
|
+
marker.ref = ref;
|
|
1492
|
+
}
|
|
1493
|
+
const existing = Reflect.getMetadata(API_PARAMETERS_METADATA_KEY, method);
|
|
1494
|
+
const params = Array.isArray(existing) ? existing : [];
|
|
1495
|
+
Reflect.defineMetadata(API_PARAMETERS_METADATA_KEY, [
|
|
1496
|
+
...params,
|
|
1497
|
+
marker
|
|
1498
|
+
], method);
|
|
1499
|
+
return descriptor;
|
|
1500
|
+
}, "appendQueryMarker");
|
|
1501
|
+
|
|
1502
|
+
// src/decorators/zod-query.decorator.ts
|
|
1475
1503
|
var ZodQuery = /* @__PURE__ */ __name((schema, options) => {
|
|
1504
|
+
if (!isZodObject(schema)) {
|
|
1505
|
+
throw new ZodNestError(`@ZodQuery requires a \`z.object({...})\` schema (got \`${schema._zod.def.type}\`). Each property of the object becomes one OpenAPI parameter. For non-object body shapes (intersections, unions, primitives), use \`@ZodBody\` instead.`);
|
|
1506
|
+
}
|
|
1507
|
+
const registry = options?.registry ?? defaultRegistry;
|
|
1508
|
+
const id = registerSchema(schema, registry, {
|
|
1509
|
+
id: options?.id
|
|
1510
|
+
});
|
|
1511
|
+
if (id !== void 0) {
|
|
1512
|
+
return appendQueryMarker(id, options?.ref);
|
|
1513
|
+
}
|
|
1514
|
+
if (options?.ref === true) {
|
|
1515
|
+
throw new ZodNestError(`@ZodQuery({ ref: true }) requires a named schema \u2014 there is no component to \`$ref\`. Name the schema via \`.meta({ id: 'Foo' })\` or pass the \`id\` option, or drop \`ref\` to expand per property.`);
|
|
1516
|
+
}
|
|
1476
1517
|
const expanded = expandObjectSchema(schema, {
|
|
1477
1518
|
id: options?.id,
|
|
1478
|
-
registry
|
|
1519
|
+
registry,
|
|
1479
1520
|
decoratorName: "@ZodQuery"
|
|
1480
1521
|
});
|
|
1481
1522
|
return common.applyDecorators(...expanded.map(({ name, required, resolution }) => swagger.ApiQuery({
|
|
@@ -1981,13 +2022,21 @@ var hintFor = /* @__PURE__ */ __name((ref, collected) => {
|
|
|
1981
2022
|
var isPlainRecord3 = /* @__PURE__ */ __name((value) => value !== null && typeof value === "object" && !Array.isArray(value), "isPlainRecord");
|
|
1982
2023
|
var expandParamMarkers = /* @__PURE__ */ __name((params) => {
|
|
1983
2024
|
const { doc, inputSchemas, outputSchemas } = params;
|
|
2025
|
+
const queryParamStyle = params.queryParamStyle ?? "expand";
|
|
2026
|
+
const schemas = doc.components?.schemas;
|
|
2027
|
+
const componentIds = schemas !== null && typeof schemas === "object" ? new Set(Object.keys(schemas)) : /* @__PURE__ */ new Set();
|
|
1984
2028
|
let expandedAny = false;
|
|
1985
2029
|
forEachOperation(doc, (op) => {
|
|
1986
2030
|
const parameters = op.parameters;
|
|
1987
2031
|
if (!Array.isArray(parameters)) {
|
|
1988
2032
|
return;
|
|
1989
2033
|
}
|
|
1990
|
-
const next = expandParameterList(parameters,
|
|
2034
|
+
const next = expandParameterList(parameters, {
|
|
2035
|
+
inputSchemas,
|
|
2036
|
+
outputSchemas,
|
|
2037
|
+
queryParamStyle,
|
|
2038
|
+
componentIds
|
|
2039
|
+
});
|
|
1991
2040
|
if (next !== parameters) {
|
|
1992
2041
|
op.parameters = next;
|
|
1993
2042
|
expandedAny = true;
|
|
@@ -1997,7 +2046,7 @@ var expandParamMarkers = /* @__PURE__ */ __name((params) => {
|
|
|
1997
2046
|
pruneOrphanObjectSchema(doc);
|
|
1998
2047
|
}
|
|
1999
2048
|
}, "expandParamMarkers");
|
|
2000
|
-
var expandParameterList = /* @__PURE__ */ __name((parameters,
|
|
2049
|
+
var expandParameterList = /* @__PURE__ */ __name((parameters, context) => {
|
|
2001
2050
|
let result;
|
|
2002
2051
|
for (let i = 0; i < parameters.length; i++) {
|
|
2003
2052
|
const param = parameters[i];
|
|
@@ -2009,12 +2058,34 @@ var expandParameterList = /* @__PURE__ */ __name((parameters, inputSchemas, outp
|
|
|
2009
2058
|
if (result === void 0) {
|
|
2010
2059
|
result = parameters.slice(0, i);
|
|
2011
2060
|
}
|
|
2012
|
-
const map = marker.io === "output" ? outputSchemas : inputSchemas;
|
|
2061
|
+
const map = marker.io === "output" ? context.outputSchemas : context.inputSchemas;
|
|
2013
2062
|
const body = map.get(marker.dtoId);
|
|
2014
|
-
result.push(...
|
|
2063
|
+
result.push(...resolveMarker(marker, body, context));
|
|
2015
2064
|
}
|
|
2016
2065
|
return result ?? parameters;
|
|
2017
2066
|
}, "expandParameterList");
|
|
2067
|
+
var resolveMarker = /* @__PURE__ */ __name((marker, body, context) => {
|
|
2068
|
+
const useRef = marker.ref ?? context.queryParamStyle === "ref";
|
|
2069
|
+
if (marker.in === "query" && useRef && context.componentIds.has(marker.dtoId)) {
|
|
2070
|
+
return [
|
|
2071
|
+
buildRefQueryParam(marker, body)
|
|
2072
|
+
];
|
|
2073
|
+
}
|
|
2074
|
+
return expandOne(marker, body);
|
|
2075
|
+
}, "resolveMarker");
|
|
2076
|
+
var buildRefQueryParam = /* @__PURE__ */ __name((marker, body) => {
|
|
2077
|
+
const required = isPlainRecord3(body) && Array.isArray(body.required) && body.required.length > 0;
|
|
2078
|
+
return {
|
|
2079
|
+
name: marker.dtoId,
|
|
2080
|
+
in: "query",
|
|
2081
|
+
required,
|
|
2082
|
+
style: "form",
|
|
2083
|
+
explode: true,
|
|
2084
|
+
schema: {
|
|
2085
|
+
$ref: `${COMPONENTS_SCHEMAS_PREFIX}${marker.dtoId}`
|
|
2086
|
+
}
|
|
2087
|
+
};
|
|
2088
|
+
}, "buildRefQueryParam");
|
|
2018
2089
|
var readMarker2 = /* @__PURE__ */ __name((value) => {
|
|
2019
2090
|
if (!isPlainRecord3(value)) {
|
|
2020
2091
|
return void 0;
|
|
@@ -2031,6 +2102,9 @@ var readMarker2 = /* @__PURE__ */ __name((value) => {
|
|
|
2031
2102
|
if (typeof value.in !== "string" || value.in === "") {
|
|
2032
2103
|
return void 0;
|
|
2033
2104
|
}
|
|
2105
|
+
if (value.ref !== void 0 && typeof value.ref !== "boolean") {
|
|
2106
|
+
return void 0;
|
|
2107
|
+
}
|
|
2034
2108
|
return value;
|
|
2035
2109
|
}, "readMarker");
|
|
2036
2110
|
var expandOne = /* @__PURE__ */ __name((marker, body) => {
|
|
@@ -2369,7 +2443,8 @@ var applyZodNest = /* @__PURE__ */ __name((doc, opts) => {
|
|
|
2369
2443
|
expandParamMarkers({
|
|
2370
2444
|
doc,
|
|
2371
2445
|
inputSchemas,
|
|
2372
|
-
outputSchemas
|
|
2446
|
+
outputSchemas,
|
|
2447
|
+
queryParamStyle: opts.queryParamStyle
|
|
2373
2448
|
});
|
|
2374
2449
|
rewriteRefs2({
|
|
2375
2450
|
doc,
|