zod-nest 1.2.0 → 1.3.1
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 +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +179 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +179 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -543,10 +543,18 @@ interface ApplyZodNestOptions {
|
|
|
543
543
|
* - Every `components.schemas[<DtoClassName>]` placeholder with an
|
|
544
544
|
* `x-zod-nest-dto` marker is replaced by the Zod-derived JSON Schema body,
|
|
545
545
|
* keyed by the marker's `dtoId` (renaming as needed).
|
|
546
|
+
* - Every `@Query()` / `@Param()` / `@Headers()` / `@Cookie()` marker
|
|
547
|
+
* parameter is expanded into one parameter per top-level property of the
|
|
548
|
+
* DTO's schema (`expandParamMarkers`). The synthetic `components.schemas.Object`
|
|
549
|
+
* that `@nestjs/swagger` materialises for the marker placeholder is pruned
|
|
550
|
+
* when it has no remaining referrers.
|
|
546
551
|
* - The I/O suffix truth table is applied — equal input/output bodies collapse
|
|
547
552
|
* to one `components.schemas[id]`; divergent bodies split as
|
|
548
553
|
* `id` (input) + `idOutput` (output), with response-side refs rewritten.
|
|
549
554
|
* - Every `$ref` whose target is missing throws `ZodNestDocumentError(DANGLING_REF)`.
|
|
555
|
+
* - `doc.openapi` is set to `'3.1.0'` — zod-nest emits OpenAPI 3.1 only; this
|
|
556
|
+
* guarantees the version string matches the emitted body regardless of the
|
|
557
|
+
* `DocumentBuilder` configuration on the caller side.
|
|
550
558
|
*
|
|
551
559
|
* Composable with other doc-transform passes — apply other mutations before
|
|
552
560
|
* or after this function. The `app` argument is required because the
|
|
@@ -555,7 +563,7 @@ interface ApplyZodNestOptions {
|
|
|
555
563
|
*/
|
|
556
564
|
declare const applyZodNest: (doc: OpenAPIObject, opts: ApplyZodNestOptions) => OpenAPIObject;
|
|
557
565
|
|
|
558
|
-
type ZodNestDocumentErrorCode = 'AMBIGUOUS_RENAME' | 'DANGLING_REF';
|
|
566
|
+
type ZodNestDocumentErrorCode = 'AMBIGUOUS_RENAME' | 'DANGLING_REF' | 'UNEXPANDABLE_PARAM_DTO';
|
|
559
567
|
/**
|
|
560
568
|
* Thrown by `applyZodNest` when the doc cannot be processed cleanly. Surfaces
|
|
561
569
|
* at doc-build time so typos / mis-registrations fail in CI, not at runtime.
|
|
@@ -568,6 +576,11 @@ type ZodNestDocumentErrorCode = 'AMBIGUOUS_RENAME' | 'DANGLING_REF';
|
|
|
568
576
|
* that no longer exists after `applyZodNest`. Usually means a marker was
|
|
569
577
|
* stripped but its rename target wasn't populated, or a user-supplied pre-pass
|
|
570
578
|
* left a stale ref.
|
|
579
|
+
*
|
|
580
|
+
* `UNEXPANDABLE_PARAM_DTO`: a `@Query()` / `@Param()` / `@Headers()` /
|
|
581
|
+
* `@Cookie()` handler argument resolved to a `createZodDto` whose schema is
|
|
582
|
+
* not an object — the marker parameter can't be expanded into individual
|
|
583
|
+
* parameters because there's no top-level `properties` record to iterate.
|
|
571
584
|
*/
|
|
572
585
|
declare class ZodNestDocumentError extends ZodNestError {
|
|
573
586
|
readonly code: ZodNestDocumentErrorCode;
|
package/dist/index.d.ts
CHANGED
|
@@ -543,10 +543,18 @@ interface ApplyZodNestOptions {
|
|
|
543
543
|
* - Every `components.schemas[<DtoClassName>]` placeholder with an
|
|
544
544
|
* `x-zod-nest-dto` marker is replaced by the Zod-derived JSON Schema body,
|
|
545
545
|
* keyed by the marker's `dtoId` (renaming as needed).
|
|
546
|
+
* - Every `@Query()` / `@Param()` / `@Headers()` / `@Cookie()` marker
|
|
547
|
+
* parameter is expanded into one parameter per top-level property of the
|
|
548
|
+
* DTO's schema (`expandParamMarkers`). The synthetic `components.schemas.Object`
|
|
549
|
+
* that `@nestjs/swagger` materialises for the marker placeholder is pruned
|
|
550
|
+
* when it has no remaining referrers.
|
|
546
551
|
* - The I/O suffix truth table is applied — equal input/output bodies collapse
|
|
547
552
|
* to one `components.schemas[id]`; divergent bodies split as
|
|
548
553
|
* `id` (input) + `idOutput` (output), with response-side refs rewritten.
|
|
549
554
|
* - Every `$ref` whose target is missing throws `ZodNestDocumentError(DANGLING_REF)`.
|
|
555
|
+
* - `doc.openapi` is set to `'3.1.0'` — zod-nest emits OpenAPI 3.1 only; this
|
|
556
|
+
* guarantees the version string matches the emitted body regardless of the
|
|
557
|
+
* `DocumentBuilder` configuration on the caller side.
|
|
550
558
|
*
|
|
551
559
|
* Composable with other doc-transform passes — apply other mutations before
|
|
552
560
|
* or after this function. The `app` argument is required because the
|
|
@@ -555,7 +563,7 @@ interface ApplyZodNestOptions {
|
|
|
555
563
|
*/
|
|
556
564
|
declare const applyZodNest: (doc: OpenAPIObject, opts: ApplyZodNestOptions) => OpenAPIObject;
|
|
557
565
|
|
|
558
|
-
type ZodNestDocumentErrorCode = 'AMBIGUOUS_RENAME' | 'DANGLING_REF';
|
|
566
|
+
type ZodNestDocumentErrorCode = 'AMBIGUOUS_RENAME' | 'DANGLING_REF' | 'UNEXPANDABLE_PARAM_DTO';
|
|
559
567
|
/**
|
|
560
568
|
* Thrown by `applyZodNest` when the doc cannot be processed cleanly. Surfaces
|
|
561
569
|
* at doc-build time so typos / mis-registrations fail in CI, not at runtime.
|
|
@@ -568,6 +576,11 @@ type ZodNestDocumentErrorCode = 'AMBIGUOUS_RENAME' | 'DANGLING_REF';
|
|
|
568
576
|
* that no longer exists after `applyZodNest`. Usually means a marker was
|
|
569
577
|
* stripped but its rename target wasn't populated, or a user-supplied pre-pass
|
|
570
578
|
* left a stale ref.
|
|
579
|
+
*
|
|
580
|
+
* `UNEXPANDABLE_PARAM_DTO`: a `@Query()` / `@Param()` / `@Headers()` /
|
|
581
|
+
* `@Cookie()` handler argument resolved to a `createZodDto` whose schema is
|
|
582
|
+
* not an object — the marker parameter can't be expanded into individual
|
|
583
|
+
* parameters because there's no top-level `properties` record to iterate.
|
|
571
584
|
*/
|
|
572
585
|
declare class ZodNestDocumentError extends ZodNestError {
|
|
573
586
|
readonly code: ZodNestDocumentErrorCode;
|
package/dist/index.js
CHANGED
|
@@ -1199,6 +1199,25 @@ var HTTP_METHODS = [
|
|
|
1199
1199
|
"patch",
|
|
1200
1200
|
"trace"
|
|
1201
1201
|
];
|
|
1202
|
+
var forEachOperation = /* @__PURE__ */ __name((doc, fn) => {
|
|
1203
|
+
const paths = doc.paths;
|
|
1204
|
+
if (paths === null || typeof paths !== "object") {
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
for (const pathItem of Object.values(paths)) {
|
|
1208
|
+
if (pathItem === null || typeof pathItem !== "object") {
|
|
1209
|
+
continue;
|
|
1210
|
+
}
|
|
1211
|
+
const pathRecord = pathItem;
|
|
1212
|
+
for (const method of HTTP_METHODS) {
|
|
1213
|
+
const op = pathRecord[method];
|
|
1214
|
+
if (op === null || typeof op !== "object") {
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
fn(op);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}, "forEachOperation");
|
|
1202
1221
|
|
|
1203
1222
|
// src/document/collect-usage.ts
|
|
1204
1223
|
var isPlainRecord2 = /* @__PURE__ */ __name((value) => value !== null && typeof value === "object" && !Array.isArray(value), "isPlainRecord");
|
|
@@ -1273,9 +1292,20 @@ var collectRefsFromOperation = /* @__PURE__ */ __name((operation, classToDtoId,
|
|
|
1273
1292
|
continue;
|
|
1274
1293
|
}
|
|
1275
1294
|
collectRefFromSchema(param.schema, classToDtoId, ids);
|
|
1295
|
+
collectIdFromMarkerParam(param, ids);
|
|
1276
1296
|
}
|
|
1277
1297
|
}
|
|
1278
1298
|
}, "collectRefsFromOperation");
|
|
1299
|
+
var collectIdFromMarkerParam = /* @__PURE__ */ __name((param, ids) => {
|
|
1300
|
+
if (param.__zodNestDto !== true) {
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
const dtoId = param.dtoId;
|
|
1304
|
+
if (typeof dtoId !== "string" || dtoId === "") {
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
ids.add(dtoId);
|
|
1308
|
+
}, "collectIdFromMarkerParam");
|
|
1279
1309
|
var collectRefsFromContent = /* @__PURE__ */ __name((content, classToDtoId, ids) => {
|
|
1280
1310
|
if (!isPlainRecord2(content)) {
|
|
1281
1311
|
return;
|
|
@@ -1426,6 +1456,125 @@ var hintFor = /* @__PURE__ */ __name((ref, collected) => {
|
|
|
1426
1456
|
return "no DTO with this id was registered \u2014 check for a meta.id typo or a DTO used without createZodDto";
|
|
1427
1457
|
}, "hintFor");
|
|
1428
1458
|
|
|
1459
|
+
// src/document/expand-param-markers.ts
|
|
1460
|
+
var isPlainRecord3 = /* @__PURE__ */ __name((value) => value !== null && typeof value === "object" && !Array.isArray(value), "isPlainRecord");
|
|
1461
|
+
var expandParamMarkers = /* @__PURE__ */ __name((params) => {
|
|
1462
|
+
const { doc, inputSchemas, outputSchemas } = params;
|
|
1463
|
+
let expandedAny = false;
|
|
1464
|
+
forEachOperation(doc, (op) => {
|
|
1465
|
+
const parameters = op.parameters;
|
|
1466
|
+
if (!Array.isArray(parameters)) {
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
const next = expandParameterList(parameters, inputSchemas, outputSchemas);
|
|
1470
|
+
if (next !== parameters) {
|
|
1471
|
+
op.parameters = next;
|
|
1472
|
+
expandedAny = true;
|
|
1473
|
+
}
|
|
1474
|
+
});
|
|
1475
|
+
if (expandedAny) {
|
|
1476
|
+
pruneOrphanObjectSchema(doc);
|
|
1477
|
+
}
|
|
1478
|
+
}, "expandParamMarkers");
|
|
1479
|
+
var expandParameterList = /* @__PURE__ */ __name((parameters, inputSchemas, outputSchemas) => {
|
|
1480
|
+
let result;
|
|
1481
|
+
for (let i = 0; i < parameters.length; i++) {
|
|
1482
|
+
const param = parameters[i];
|
|
1483
|
+
const marker = readMarker2(param);
|
|
1484
|
+
if (marker === void 0) {
|
|
1485
|
+
result?.push(param);
|
|
1486
|
+
continue;
|
|
1487
|
+
}
|
|
1488
|
+
if (result === void 0) {
|
|
1489
|
+
result = parameters.slice(0, i);
|
|
1490
|
+
}
|
|
1491
|
+
const map = marker.io === "output" ? outputSchemas : inputSchemas;
|
|
1492
|
+
const body = map.get(marker.dtoId);
|
|
1493
|
+
result.push(...expandOne(marker, body));
|
|
1494
|
+
}
|
|
1495
|
+
return result ?? parameters;
|
|
1496
|
+
}, "expandParameterList");
|
|
1497
|
+
var readMarker2 = /* @__PURE__ */ __name((value) => {
|
|
1498
|
+
if (!isPlainRecord3(value)) {
|
|
1499
|
+
return void 0;
|
|
1500
|
+
}
|
|
1501
|
+
if (value.__zodNestDto !== true) {
|
|
1502
|
+
return void 0;
|
|
1503
|
+
}
|
|
1504
|
+
if (typeof value.dtoId !== "string" || value.dtoId === "") {
|
|
1505
|
+
return void 0;
|
|
1506
|
+
}
|
|
1507
|
+
if (value.io !== "input" && value.io !== "output") {
|
|
1508
|
+
return void 0;
|
|
1509
|
+
}
|
|
1510
|
+
if (typeof value.in !== "string" || value.in === "") {
|
|
1511
|
+
return void 0;
|
|
1512
|
+
}
|
|
1513
|
+
return value;
|
|
1514
|
+
}, "readMarker");
|
|
1515
|
+
var expandOne = /* @__PURE__ */ __name((marker, body) => {
|
|
1516
|
+
if (!isPlainRecord3(body) || !isPlainRecord3(body.properties)) {
|
|
1517
|
+
throw new ZodNestDocumentError("UNEXPANDABLE_PARAM_DTO", `Cannot expand \`@${capitalize(marker.in)}() x: ${marker.dtoId}\` \u2014 the DTO's schema is not an object with \`properties\`. Non-body parameter DTOs must be object schemas; arrays, unions, primitives, etc. cannot be split into individual parameters. Use \`@Body()\` for non-object DTOs, or restructure the schema as an object whose fields become the params.`, {
|
|
1518
|
+
dtoId: marker.dtoId,
|
|
1519
|
+
in: marker.in,
|
|
1520
|
+
io: marker.io
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
const properties = body.properties;
|
|
1524
|
+
const requiredSet = collectRequired(body.required);
|
|
1525
|
+
const out = [];
|
|
1526
|
+
for (const [propName, propSchemaRaw] of Object.entries(properties)) {
|
|
1527
|
+
if (!isPlainRecord3(propSchemaRaw)) {
|
|
1528
|
+
continue;
|
|
1529
|
+
}
|
|
1530
|
+
out.push(buildParameter(marker, propName, propSchemaRaw, requiredSet.has(propName)));
|
|
1531
|
+
}
|
|
1532
|
+
return out;
|
|
1533
|
+
}, "expandOne");
|
|
1534
|
+
var collectRequired = /* @__PURE__ */ __name((value) => {
|
|
1535
|
+
if (!Array.isArray(value)) {
|
|
1536
|
+
return /* @__PURE__ */ new Set();
|
|
1537
|
+
}
|
|
1538
|
+
const out = /* @__PURE__ */ new Set();
|
|
1539
|
+
for (const item of value) {
|
|
1540
|
+
if (typeof item === "string") {
|
|
1541
|
+
out.add(item);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
return out;
|
|
1545
|
+
}, "collectRequired");
|
|
1546
|
+
var buildParameter = /* @__PURE__ */ __name((marker, name, schema, required) => {
|
|
1547
|
+
let effectiveRequired = required;
|
|
1548
|
+
if (marker.in === "path" && !effectiveRequired) {
|
|
1549
|
+
console.warn(`[zod-nest] Path parameter \`${name}\` on DTO \`${marker.dtoId}\` is marked optional in the Zod schema; OpenAPI 3.1 requires path parameters to be required. Coercing \`required: true\` so the emitted document is spec-valid. Fix by removing \`.optional()\` / \`.nullish()\` from the field, or by switching the decorator to @Query() / @Headers() if the field is genuinely optional.`);
|
|
1550
|
+
effectiveRequired = true;
|
|
1551
|
+
}
|
|
1552
|
+
return {
|
|
1553
|
+
name,
|
|
1554
|
+
in: marker.in,
|
|
1555
|
+
required: effectiveRequired,
|
|
1556
|
+
schema
|
|
1557
|
+
};
|
|
1558
|
+
}, "buildParameter");
|
|
1559
|
+
var capitalize = /* @__PURE__ */ __name((value) => value.charAt(0).toUpperCase() + value.slice(1), "capitalize");
|
|
1560
|
+
var pruneOrphanObjectSchema = /* @__PURE__ */ __name((doc) => {
|
|
1561
|
+
const schemas = doc.components?.schemas;
|
|
1562
|
+
if (schemas === void 0 || !Object.prototype.hasOwnProperty.call(schemas, "Object")) {
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1565
|
+
let referenced = false;
|
|
1566
|
+
const targetRef = `${COMPONENTS_SCHEMAS_PREFIX}Object`;
|
|
1567
|
+
walkRefs(doc, (ref) => {
|
|
1568
|
+
if (ref === targetRef) {
|
|
1569
|
+
referenced = true;
|
|
1570
|
+
}
|
|
1571
|
+
return void 0;
|
|
1572
|
+
});
|
|
1573
|
+
if (!referenced) {
|
|
1574
|
+
delete schemas.Object;
|
|
1575
|
+
}
|
|
1576
|
+
}, "pruneOrphanObjectSchema");
|
|
1577
|
+
|
|
1429
1578
|
// src/document/expose-closure.ts
|
|
1430
1579
|
var extendExposureViaRefs = /* @__PURE__ */ __name((collected, inputSchemas, outputSchemas) => ({
|
|
1431
1580
|
...collected,
|
|
@@ -1616,13 +1765,31 @@ var rewriteResponseSubtree = /* @__PURE__ */ __name((pathItem, divergentOutputId
|
|
|
1616
1765
|
// src/document/strip-markers.ts
|
|
1617
1766
|
var stripMarkers = /* @__PURE__ */ __name((doc) => {
|
|
1618
1767
|
const schemas = doc.components?.schemas;
|
|
1619
|
-
if (schemas
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1768
|
+
if (schemas !== void 0) {
|
|
1769
|
+
for (const schema of Object.values(schemas)) {
|
|
1770
|
+
stripMarkerFromSchema(schema);
|
|
1771
|
+
dropJsonSchemaMetadata(schema);
|
|
1772
|
+
}
|
|
1624
1773
|
}
|
|
1774
|
+
stripMarkerParameters(doc);
|
|
1625
1775
|
}, "stripMarkers");
|
|
1776
|
+
var dropJsonSchemaMetadata = /* @__PURE__ */ __name((schema) => {
|
|
1777
|
+
if (schema === null || typeof schema !== "object") {
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
const body = schema;
|
|
1781
|
+
delete body.$schema;
|
|
1782
|
+
delete body.$id;
|
|
1783
|
+
}, "dropJsonSchemaMetadata");
|
|
1784
|
+
var stripMarkerParameters = /* @__PURE__ */ __name((doc) => {
|
|
1785
|
+
forEachOperation(doc, (op) => {
|
|
1786
|
+
const parameters = op.parameters;
|
|
1787
|
+
if (!Array.isArray(parameters)) {
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
op.parameters = parameters.filter((param) => !isZodDtoMarker(param));
|
|
1791
|
+
});
|
|
1792
|
+
}, "stripMarkerParameters");
|
|
1626
1793
|
var stripMarkerFromSchema = /* @__PURE__ */ __name((schema) => {
|
|
1627
1794
|
if (schema === null || typeof schema !== "object") {
|
|
1628
1795
|
return;
|
|
@@ -1642,6 +1809,7 @@ var stripMarkerFromSchema = /* @__PURE__ */ __name((schema) => {
|
|
|
1642
1809
|
}, "stripMarkerFromSchema");
|
|
1643
1810
|
|
|
1644
1811
|
// src/document/apply-zod-nest.ts
|
|
1812
|
+
var OPENAPI_VERSION = "3.1.0";
|
|
1645
1813
|
var applyZodNest = /* @__PURE__ */ __name((doc, opts) => {
|
|
1646
1814
|
const registry = opts.registry ?? defaultRegistry;
|
|
1647
1815
|
const collected = collectUsage(doc, opts.app);
|
|
@@ -1658,6 +1826,11 @@ var applyZodNest = /* @__PURE__ */ __name((doc, opts) => {
|
|
|
1658
1826
|
collected: extended,
|
|
1659
1827
|
collisions: registry.getCollisions()
|
|
1660
1828
|
});
|
|
1829
|
+
expandParamMarkers({
|
|
1830
|
+
doc,
|
|
1831
|
+
inputSchemas,
|
|
1832
|
+
outputSchemas
|
|
1833
|
+
});
|
|
1661
1834
|
rewriteRefs2({
|
|
1662
1835
|
doc,
|
|
1663
1836
|
renames,
|
|
@@ -1668,6 +1841,7 @@ var applyZodNest = /* @__PURE__ */ __name((doc, opts) => {
|
|
|
1668
1841
|
doc,
|
|
1669
1842
|
collected: extended
|
|
1670
1843
|
});
|
|
1844
|
+
doc.openapi = OPENAPI_VERSION;
|
|
1671
1845
|
return doc;
|
|
1672
1846
|
}, "applyZodNest");
|
|
1673
1847
|
|