zod-openapi 2.17.0 → 2.18.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
@@ -31,7 +31,9 @@ pnpm install zod zod-openapi
31
31
 
32
32
  ### Extend Zod
33
33
 
34
- This mutates Zod to add an extra `.openapi()` method. Call this at the top of your entry point(s).
34
+ This mutates Zod to add an extra `.openapi()` method. Call this at the top of your entry point(s). You can achieve this in two differernt ways, depending on your preference.
35
+
36
+ #### Subpath Import
35
37
 
36
38
  ```ts
37
39
  import 'zod-openapi/extend';
@@ -40,12 +42,12 @@ import { z } from 'zod';
40
42
  z.string().openapi({ description: 'hello world!', example: 'hello world' });
41
43
  ```
42
44
 
43
- #### Manual
45
+ #### Manual Extension
44
46
 
45
- This is useful if you have a different instance of Zod that you would like to extend.
47
+ This is useful if you have a specific instance of Zod or a Zod instance from another library that you would like to target.
46
48
 
47
49
  ```typescript
48
- import { z } from 'another-lib';
50
+ import { z } from 'zod';
49
51
  import { extendZodWithOpenApi } from 'zod-openapi';
50
52
 
51
53
  extendZodWithOpenApi(z);
@@ -63,7 +65,7 @@ Use the `.openapi()` method to add metadata to a specific Zod type. The `.openap
63
65
  | `effectType` | Use to override the creation type for a [Zod Effect](#zod-effects) |
64
66
  | `header` | Use to provide metadata for [response headers](#response-headers) |
65
67
  | `param` | Use to provide metadata for [request parameters](#parameters) |
66
- | `ref` | Use this to [auto register a schema](#creating-components) |
68
+ | `ref` | Use this to [auto register a schema as a re-usable component](#creating-components) |
67
69
  | `refType` | Use this to set the creation type for a component which is not referenced in the document. |
68
70
  | `type` | Use this to override the generated type. If this is provided no metadata will be generated. |
69
71
  | `unionOneOf` | Set to `true` to force a ZodUnion to output `oneOf` instead of `allOf` |
@@ -73,8 +75,9 @@ Use the `.openapi()` method to add metadata to a specific Zod type. The `.openap
73
75
  Creates an OpenAPI documentation object
74
76
 
75
77
  ```typescript
78
+ import 'zod-openapi/extend';
76
79
  import { z } from 'zod';
77
- import { createDocument, extendZodWithOpenApi } from 'zod-openapi';
80
+ import { createDocument } from 'zod-openapi';
78
81
 
79
82
  const jobId = z.string().openapi({
80
83
  description: 'A unique identifier for a job',
@@ -116,82 +119,84 @@ const document = createDocument({
116
119
  });
117
120
  ```
118
121
 
119
- Generates the following object:
120
-
121
- ```json
122
- {
123
- "openapi": "3.1.0",
124
- "info": {
125
- "title": "My API",
126
- "version": "1.0.0"
127
- },
128
- "paths": {
129
- "/jobs/{jobId}": {
130
- "put": {
131
- "parameters": [
132
- {
133
- "in": "path",
134
- "name": "jobId",
135
- "description": "A unique identifier for a job",
136
- "schema": {
137
- "$ref": "#/components/schemas/jobId"
138
- }
139
- }
140
- ],
141
- "requestBody": {
142
- "content": {
143
- "application/json": {
122
+ <details>
123
+ <summary>Creates the following object:</summary>
124
+
125
+ ```json
126
+ {
127
+ "openapi": "3.1.0",
128
+ "info": {
129
+ "title": "My API",
130
+ "version": "1.0.0"
131
+ },
132
+ "paths": {
133
+ "/jobs/{jobId}": {
134
+ "put": {
135
+ "parameters": [
136
+ {
137
+ "in": "path",
138
+ "name": "jobId",
139
+ "description": "A unique identifier for a job",
144
140
  "schema": {
145
- "type": "object",
146
- "properties": {
147
- "title": {
148
- "type": "string",
149
- "description": "Job title",
150
- "example": "My job"
151
- }
152
- },
153
- "required": ["title"]
141
+ "$ref": "#/components/schemas/jobId"
154
142
  }
155
143
  }
156
- }
157
- },
158
- "responses": {
159
- "200": {
160
- "description": "200 OK",
144
+ ],
145
+ "requestBody": {
161
146
  "content": {
162
147
  "application/json": {
163
148
  "schema": {
164
149
  "type": "object",
165
150
  "properties": {
166
- "jobId": {
167
- "$ref": "#/components/schemas/jobId"
168
- },
169
151
  "title": {
170
152
  "type": "string",
171
153
  "description": "Job title",
172
154
  "example": "My job"
173
155
  }
174
156
  },
175
- "required": ["jobId", "title"]
157
+ "required": ["title"]
158
+ }
159
+ }
160
+ }
161
+ },
162
+ "responses": {
163
+ "200": {
164
+ "description": "200 OK",
165
+ "content": {
166
+ "application/json": {
167
+ "schema": {
168
+ "type": "object",
169
+ "properties": {
170
+ "jobId": {
171
+ "$ref": "#/components/schemas/jobId"
172
+ },
173
+ "title": {
174
+ "type": "string",
175
+ "description": "Job title",
176
+ "example": "My job"
177
+ }
178
+ },
179
+ "required": ["jobId", "title"]
180
+ }
176
181
  }
177
182
  }
178
183
  }
179
184
  }
180
185
  }
181
186
  }
182
- }
183
- },
184
- "components": {
185
- "schemas": {
186
- "jobId": {
187
- "type": "string",
188
- "description": "A unique identifier for a job",
189
- "example": "12345"
187
+ },
188
+ "components": {
189
+ "schemas": {
190
+ "jobId": {
191
+ "type": "string",
192
+ "description": "A unique identifier for a job",
193
+ "example": "12345"
194
+ }
190
195
  }
191
196
  }
192
197
  }
193
- }
194
- ```
198
+ ```
199
+ </details>
195
200
 
196
201
  ### Request Parameters
197
202
 
@@ -283,6 +288,42 @@ createDocument({
283
288
  });
284
289
  ```
285
290
 
291
+ ### Callbacks
292
+
293
+ ```typescript
294
+ createDocument({
295
+ paths: {
296
+ '/jobs': {
297
+ get: {
298
+ callbacks: {
299
+ onData: {
300
+ '{$request.query.callbackUrl}/data': {
301
+ post: {
302
+ requestBody: {
303
+ content: {
304
+ 'application/json': { schema: z.object({ a: z.string() }) },
305
+ },
306
+ },
307
+ responses: {
308
+ 200: {
309
+ description: '200 OK',
310
+ content: {
311
+ 'application/json': {
312
+ schema: z.object({ a: z.string() }),
313
+ },
314
+ },
315
+ },
316
+ },
317
+ },
318
+ },
319
+ },
320
+ },
321
+ },
322
+ },
323
+ },
324
+ });
325
+ ```
326
+
286
327
  ### Creating Components
287
328
 
288
329
  OpenAPI allows you to define reusable [components](https://swagger.io/docs/specification/components/) and this library allows you to replicate that in two separate ways.
@@ -326,7 +367,7 @@ Wherever `title` is used in schemas across the document, it will instead be crea
326
367
  }
327
368
  ```
328
369
 
329
- This can be an extremely powerful way to generate better Open API documentation. There are some Open API features like [discriminator mapping](https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/) which require all schemas in the union to contain a ref.
370
+ This can be an extremely powerful way to create less repetitive Open API documentation. There are some Open API features like [discriminator mapping](https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/) which require all schemas in the union to contain a ref.
330
371
 
331
372
  ##### Manually Registering Schema
332
373
 
@@ -479,6 +520,53 @@ createDocument({
479
520
  });
480
521
  ```
481
522
 
523
+ #### Callbacks
524
+
525
+ Callbacks can also be registered
526
+
527
+ ```typescript
528
+ const callback: ZodOpenApiCallbackObject = {
529
+ ref: 'some-callback'
530
+ post: {
531
+ responses: {
532
+ 200: {
533
+ description: '200 OK',
534
+ content: {
535
+ 'application/json': {
536
+ schema: z.object({ a: z.string() }),
537
+ },
538
+ },
539
+ },
540
+ },
541
+ },
542
+ };
543
+
544
+ //or
545
+
546
+ const callback: ZodOpenApiCallbackObject = {
547
+ post: {
548
+ responses: {
549
+ 200: {
550
+ description: '200 OK',
551
+ content: {
552
+ 'application/json': {
553
+ schema: z.object({ a: z.string() }),
554
+ },
555
+ },
556
+ },
557
+ },
558
+ },
559
+ };
560
+
561
+ createDocument({
562
+ components: {
563
+ callbacks: {
564
+ 'some-callback': callback,
565
+ },
566
+ },
567
+ });
568
+ ```
569
+
482
570
  ## Supported OpenAPI Versions
483
571
 
484
572
  Currently the following versions of OpenAPI are supported
@@ -525,7 +613,7 @@ For example in `z.string().nullable()` will be rendered differently
525
613
  - ZodBranded
526
614
  - ZodCatch
527
615
  - ZodDate
528
- - `string` `type` mapping by default
616
+ - `type` is mapped as `string` by default
529
617
  - ZodDefault
530
618
  - ZodDiscriminatedUnion
531
619
  - `discriminator` mapping when all schemas in the union are [registered](#creating-components). The discriminator must be a `ZodLiteral`, `ZodEnum` or `ZodNativeEnum` with string values. Only values wrapped in `ZodBranded`, `ZodReadOnly` and `ZodCatch` are supported.
@@ -555,7 +643,7 @@ For example in `z.string().nullable()` will be rendered differently
555
643
  - ZodReadonly
556
644
  - ZodRecord
557
645
  - ZodSet
558
- - Treated as an array with `uniqueItems` (you may need to add a pre-process)
646
+ - Treated as an array with `uniqueItems` (you may need to add a pre-process to convert it to a set)
559
647
  - ZodString
560
648
  - `format` mapping for `.url()`, `.uuid()`, `.email()`, `.datetime()`, `.date()`, `.time()`, `.duration()`
561
649
  - `minLength`/`maxLength` mapping for `.length()`, `.min()`, `.max()`
@@ -1431,6 +1431,53 @@ var createSchema = (zodSchema, state, subpath) => {
1431
1431
  return schema.schema;
1432
1432
  };
1433
1433
 
1434
+ // src/create/content.ts
1435
+ var createMediaTypeSchema = (schemaObject, components, type, subpath) => {
1436
+ if (!schemaObject) {
1437
+ return void 0;
1438
+ }
1439
+ if (!isAnyZodType(schemaObject)) {
1440
+ return schemaObject;
1441
+ }
1442
+ return createSchema(
1443
+ schemaObject,
1444
+ {
1445
+ components,
1446
+ type,
1447
+ path: [],
1448
+ visited: /* @__PURE__ */ new Set()
1449
+ },
1450
+ subpath
1451
+ );
1452
+ };
1453
+ var createMediaTypeObject = (mediaTypeObject, components, type, subpath) => {
1454
+ if (!mediaTypeObject) {
1455
+ return void 0;
1456
+ }
1457
+ return {
1458
+ ...mediaTypeObject,
1459
+ schema: createMediaTypeSchema(mediaTypeObject.schema, components, type, [
1460
+ ...subpath,
1461
+ "schema"
1462
+ ])
1463
+ };
1464
+ };
1465
+ var createContent = (contentObject, components, type, subpath) => Object.entries(contentObject).reduce(
1466
+ (acc, [mediaType, zodOpenApiMediaTypeObject]) => {
1467
+ const mediaTypeObject = createMediaTypeObject(
1468
+ zodOpenApiMediaTypeObject,
1469
+ components,
1470
+ type,
1471
+ [...subpath, mediaType]
1472
+ );
1473
+ if (mediaTypeObject) {
1474
+ acc[mediaType] = mediaTypeObject;
1475
+ }
1476
+ return acc;
1477
+ },
1478
+ {}
1479
+ );
1480
+
1434
1481
  // src/create/parameters.ts
1435
1482
  var createComponentParamRef = (ref) => `#/components/parameters/${ref}`;
1436
1483
  var createBaseParameter = (schema, components, subpath) => {
@@ -1574,53 +1621,6 @@ var getZodObject = (schema, type) => {
1574
1621
  throw new Error("failed to find ZodObject in schema");
1575
1622
  };
1576
1623
 
1577
- // src/create/content.ts
1578
- var createMediaTypeSchema = (schemaObject, components, type, subpath) => {
1579
- if (!schemaObject) {
1580
- return void 0;
1581
- }
1582
- if (!isAnyZodType(schemaObject)) {
1583
- return schemaObject;
1584
- }
1585
- return createSchema(
1586
- schemaObject,
1587
- {
1588
- components,
1589
- type,
1590
- path: [],
1591
- visited: /* @__PURE__ */ new Set()
1592
- },
1593
- subpath
1594
- );
1595
- };
1596
- var createMediaTypeObject = (mediaTypeObject, components, type, subpath) => {
1597
- if (!mediaTypeObject) {
1598
- return void 0;
1599
- }
1600
- return {
1601
- ...mediaTypeObject,
1602
- schema: createMediaTypeSchema(mediaTypeObject.schema, components, type, [
1603
- ...subpath,
1604
- "schema"
1605
- ])
1606
- };
1607
- };
1608
- var createContent = (contentObject, components, type, subpath) => Object.entries(contentObject).reduce(
1609
- (acc, [mediaType, zodOpenApiMediaTypeObject]) => {
1610
- const mediaTypeObject = createMediaTypeObject(
1611
- zodOpenApiMediaTypeObject,
1612
- components,
1613
- type,
1614
- [...subpath, mediaType]
1615
- );
1616
- if (mediaTypeObject) {
1617
- acc[mediaType] = mediaTypeObject;
1618
- }
1619
- return acc;
1620
- },
1621
- {}
1622
- );
1623
-
1624
1624
  // src/create/specificationExtension.ts
1625
1625
  var isISpecificationExtension = (key) => key.startsWith("x-");
1626
1626
 
@@ -1775,11 +1775,17 @@ var createOperation = (operationObject, components, subpath) => {
1775
1775
  components,
1776
1776
  [...subpath, "responses"]
1777
1777
  );
1778
+ const maybeCallbacks = createCallbacks(
1779
+ operationObject.callbacks,
1780
+ components,
1781
+ [...subpath, "callbacks"]
1782
+ );
1778
1783
  return {
1779
1784
  ...rest,
1780
1785
  ...maybeParameters && { parameters: maybeParameters },
1781
1786
  ...maybeRequestBody && { requestBody: maybeRequestBody },
1782
- ...maybeResponses && { responses: maybeResponses }
1787
+ ...maybeResponses && { responses: maybeResponses },
1788
+ ...maybeCallbacks && { callbacks: maybeCallbacks }
1783
1789
  };
1784
1790
  };
1785
1791
  var createPathItem = (pathObject, components, path) => Object.entries(pathObject).reduce(
@@ -1791,7 +1797,7 @@ var createPathItem = (pathObject, components, path) => Object.entries(pathObject
1791
1797
  acc[key] = createOperation(
1792
1798
  value,
1793
1799
  components,
1794
- [path, key]
1800
+ [...path, key]
1795
1801
  );
1796
1802
  return acc;
1797
1803
  }
@@ -1810,7 +1816,55 @@ var createPaths = (pathsObject, components) => {
1810
1816
  acc[path] = pathItemObject;
1811
1817
  return acc;
1812
1818
  }
1813
- acc[path] = createPathItem(pathItemObject, components, path);
1819
+ acc[path] = createPathItem(pathItemObject, components, [path]);
1820
+ return acc;
1821
+ },
1822
+ {}
1823
+ );
1824
+ };
1825
+
1826
+ // src/create/callbacks.ts
1827
+ var createCallback = (callbackObject, components, subpath) => {
1828
+ const { ref, ...callbacks } = callbackObject;
1829
+ const callback = Object.entries(
1830
+ callbacks
1831
+ ).reduce((acc, [callbackName, pathItemObject]) => {
1832
+ if (isISpecificationExtension(callbackName)) {
1833
+ acc[callbackName] = pathItemObject;
1834
+ return acc;
1835
+ }
1836
+ acc[callbackName] = createPathItem(pathItemObject, components, [
1837
+ ...subpath,
1838
+ callbackName
1839
+ ]);
1840
+ return acc;
1841
+ }, {});
1842
+ if (ref) {
1843
+ components.callbacks.set(callbackObject, {
1844
+ type: "complete",
1845
+ ref,
1846
+ callbackObject: callback
1847
+ });
1848
+ return {
1849
+ $ref: createComponentCallbackRef(ref)
1850
+ };
1851
+ }
1852
+ return callback;
1853
+ };
1854
+ var createCallbacks = (callbacksObject, components, subpath) => {
1855
+ if (!callbacksObject) {
1856
+ return void 0;
1857
+ }
1858
+ return Object.entries(callbacksObject).reduce(
1859
+ (acc, [callbackName, callbackObject]) => {
1860
+ if (isISpecificationExtension(callbackName)) {
1861
+ acc[callbackName] = callbackObject;
1862
+ return acc;
1863
+ }
1864
+ acc[callbackName] = createCallback(callbackObject, components, [
1865
+ ...subpath,
1866
+ callbackName
1867
+ ]);
1814
1868
  return acc;
1815
1869
  },
1816
1870
  {}
@@ -1825,6 +1879,7 @@ var getDefaultComponents = (componentsObject, openapi = "3.1.0") => {
1825
1879
  headers: /* @__PURE__ */ new Map(),
1826
1880
  requestBodies: /* @__PURE__ */ new Map(),
1827
1881
  responses: /* @__PURE__ */ new Map(),
1882
+ callbacks: /* @__PURE__ */ new Map(),
1828
1883
  openapi
1829
1884
  };
1830
1885
  if (!componentsObject) {
@@ -1835,6 +1890,7 @@ var getDefaultComponents = (componentsObject, openapi = "3.1.0") => {
1835
1890
  getRequestBodies(componentsObject.requestBodies, defaultComponents);
1836
1891
  getHeaders(componentsObject.headers, defaultComponents);
1837
1892
  getResponses(componentsObject.responses, defaultComponents);
1893
+ getCallbacks(componentsObject.callbacks, defaultComponents);
1838
1894
  return defaultComponents;
1839
1895
  };
1840
1896
  var getSchemas = (schemas, components) => {
@@ -1935,9 +1991,27 @@ var getRequestBodies = (requestBodies, components) => {
1935
1991
  });
1936
1992
  });
1937
1993
  };
1994
+ var getCallbacks = (callbacks, components) => {
1995
+ if (!callbacks) {
1996
+ return;
1997
+ }
1998
+ Object.entries(callbacks).forEach(([key, callback]) => {
1999
+ if (components.callbacks.has(callback)) {
2000
+ throw new Error(
2001
+ `Callback ${JSON.stringify(callback)} is already registered`
2002
+ );
2003
+ }
2004
+ const ref = callback?.ref ?? key;
2005
+ components.callbacks.set(callback, {
2006
+ type: "manual",
2007
+ ref
2008
+ });
2009
+ });
2010
+ };
1938
2011
  var createComponentSchemaRef = (schemaRef) => `#/components/schemas/${schemaRef}`;
1939
2012
  var createComponentResponseRef = (responseRef) => `#/components/responses/${responseRef}`;
1940
2013
  var createComponentRequestBodyRef = (requestBodyRef) => `#/components/requestBodies/${requestBodyRef}`;
2014
+ var createComponentCallbackRef = (callbackRef) => `#/components/callbacks/${callbackRef}`;
1941
2015
  var createComponents = (componentsObject, components) => {
1942
2016
  const combinedSchemas = createSchemaComponents(componentsObject, components);
1943
2017
  const combinedParameters = createParamComponents(
@@ -1947,6 +2021,7 @@ var createComponents = (componentsObject, components) => {
1947
2021
  const combinedHeaders = createHeaderComponents(componentsObject, components);
1948
2022
  const combinedResponses = createResponseComponents(components);
1949
2023
  const combinedRequestBodies = createRequestBodiesComponents(components);
2024
+ const combinedCallbacks = createCallbackComponents(components);
1950
2025
  const { schemas, parameters, headers, responses, requestBodies, ...rest } = componentsObject;
1951
2026
  const finalComponents = {
1952
2027
  ...rest,
@@ -1954,7 +2029,8 @@ var createComponents = (componentsObject, components) => {
1954
2029
  ...combinedParameters && { parameters: combinedParameters },
1955
2030
  ...combinedRequestBodies && { requestBodies: combinedRequestBodies },
1956
2031
  ...combinedHeaders && { headers: combinedHeaders },
1957
- ...combinedResponses && { responses: combinedResponses }
2032
+ ...combinedResponses && { responses: combinedResponses },
2033
+ ...combinedCallbacks && { callbacks: combinedCallbacks }
1958
2034
  };
1959
2035
  return Object.keys(finalComponents).length ? finalComponents : void 0;
1960
2036
  };
@@ -2096,6 +2172,23 @@ var createRequestBodiesComponents = (components) => {
2096
2172
  }, {});
2097
2173
  return Object.keys(finalComponents).length ? finalComponents : void 0;
2098
2174
  };
2175
+ var createCallbackComponents = (components) => {
2176
+ Array.from(components.callbacks).forEach(([schema, component], index) => {
2177
+ if (component.type === "manual") {
2178
+ createCallback(schema, components, [`component callback ${index}`]);
2179
+ }
2180
+ });
2181
+ const finalComponents = Array.from(components.callbacks).reduce((acc, [_zodType, component]) => {
2182
+ if (component.type === "complete") {
2183
+ if (acc[component.ref]) {
2184
+ throw new Error(`Callback "${component.ref}" is already registered`);
2185
+ }
2186
+ acc[component.ref] = component.callbackObject;
2187
+ }
2188
+ return acc;
2189
+ }, {});
2190
+ return Object.keys(finalComponents).length ? finalComponents : void 0;
2191
+ };
2099
2192
 
2100
2193
  // src/create/document.ts
2101
2194
  var createDocument = (zodOpenApiObject) => {
package/lib-esm/index.mjs CHANGED
@@ -1407,6 +1407,53 @@ var createSchema = (zodSchema, state, subpath) => {
1407
1407
  return schema.schema;
1408
1408
  };
1409
1409
 
1410
+ // src/create/content.ts
1411
+ var createMediaTypeSchema = (schemaObject, components, type, subpath) => {
1412
+ if (!schemaObject) {
1413
+ return void 0;
1414
+ }
1415
+ if (!isAnyZodType(schemaObject)) {
1416
+ return schemaObject;
1417
+ }
1418
+ return createSchema(
1419
+ schemaObject,
1420
+ {
1421
+ components,
1422
+ type,
1423
+ path: [],
1424
+ visited: /* @__PURE__ */ new Set()
1425
+ },
1426
+ subpath
1427
+ );
1428
+ };
1429
+ var createMediaTypeObject = (mediaTypeObject, components, type, subpath) => {
1430
+ if (!mediaTypeObject) {
1431
+ return void 0;
1432
+ }
1433
+ return {
1434
+ ...mediaTypeObject,
1435
+ schema: createMediaTypeSchema(mediaTypeObject.schema, components, type, [
1436
+ ...subpath,
1437
+ "schema"
1438
+ ])
1439
+ };
1440
+ };
1441
+ var createContent = (contentObject, components, type, subpath) => Object.entries(contentObject).reduce(
1442
+ (acc, [mediaType, zodOpenApiMediaTypeObject]) => {
1443
+ const mediaTypeObject = createMediaTypeObject(
1444
+ zodOpenApiMediaTypeObject,
1445
+ components,
1446
+ type,
1447
+ [...subpath, mediaType]
1448
+ );
1449
+ if (mediaTypeObject) {
1450
+ acc[mediaType] = mediaTypeObject;
1451
+ }
1452
+ return acc;
1453
+ },
1454
+ {}
1455
+ );
1456
+
1410
1457
  // src/create/parameters.ts
1411
1458
  var createComponentParamRef = (ref) => `#/components/parameters/${ref}`;
1412
1459
  var createBaseParameter = (schema, components, subpath) => {
@@ -1550,53 +1597,6 @@ var getZodObject = (schema, type) => {
1550
1597
  throw new Error("failed to find ZodObject in schema");
1551
1598
  };
1552
1599
 
1553
- // src/create/content.ts
1554
- var createMediaTypeSchema = (schemaObject, components, type, subpath) => {
1555
- if (!schemaObject) {
1556
- return void 0;
1557
- }
1558
- if (!isAnyZodType(schemaObject)) {
1559
- return schemaObject;
1560
- }
1561
- return createSchema(
1562
- schemaObject,
1563
- {
1564
- components,
1565
- type,
1566
- path: [],
1567
- visited: /* @__PURE__ */ new Set()
1568
- },
1569
- subpath
1570
- );
1571
- };
1572
- var createMediaTypeObject = (mediaTypeObject, components, type, subpath) => {
1573
- if (!mediaTypeObject) {
1574
- return void 0;
1575
- }
1576
- return {
1577
- ...mediaTypeObject,
1578
- schema: createMediaTypeSchema(mediaTypeObject.schema, components, type, [
1579
- ...subpath,
1580
- "schema"
1581
- ])
1582
- };
1583
- };
1584
- var createContent = (contentObject, components, type, subpath) => Object.entries(contentObject).reduce(
1585
- (acc, [mediaType, zodOpenApiMediaTypeObject]) => {
1586
- const mediaTypeObject = createMediaTypeObject(
1587
- zodOpenApiMediaTypeObject,
1588
- components,
1589
- type,
1590
- [...subpath, mediaType]
1591
- );
1592
- if (mediaTypeObject) {
1593
- acc[mediaType] = mediaTypeObject;
1594
- }
1595
- return acc;
1596
- },
1597
- {}
1598
- );
1599
-
1600
1600
  // src/create/specificationExtension.ts
1601
1601
  var isISpecificationExtension = (key) => key.startsWith("x-");
1602
1602
 
@@ -1751,11 +1751,17 @@ var createOperation = (operationObject, components, subpath) => {
1751
1751
  components,
1752
1752
  [...subpath, "responses"]
1753
1753
  );
1754
+ const maybeCallbacks = createCallbacks(
1755
+ operationObject.callbacks,
1756
+ components,
1757
+ [...subpath, "callbacks"]
1758
+ );
1754
1759
  return {
1755
1760
  ...rest,
1756
1761
  ...maybeParameters && { parameters: maybeParameters },
1757
1762
  ...maybeRequestBody && { requestBody: maybeRequestBody },
1758
- ...maybeResponses && { responses: maybeResponses }
1763
+ ...maybeResponses && { responses: maybeResponses },
1764
+ ...maybeCallbacks && { callbacks: maybeCallbacks }
1759
1765
  };
1760
1766
  };
1761
1767
  var createPathItem = (pathObject, components, path) => Object.entries(pathObject).reduce(
@@ -1767,7 +1773,7 @@ var createPathItem = (pathObject, components, path) => Object.entries(pathObject
1767
1773
  acc[key] = createOperation(
1768
1774
  value,
1769
1775
  components,
1770
- [path, key]
1776
+ [...path, key]
1771
1777
  );
1772
1778
  return acc;
1773
1779
  }
@@ -1786,7 +1792,55 @@ var createPaths = (pathsObject, components) => {
1786
1792
  acc[path] = pathItemObject;
1787
1793
  return acc;
1788
1794
  }
1789
- acc[path] = createPathItem(pathItemObject, components, path);
1795
+ acc[path] = createPathItem(pathItemObject, components, [path]);
1796
+ return acc;
1797
+ },
1798
+ {}
1799
+ );
1800
+ };
1801
+
1802
+ // src/create/callbacks.ts
1803
+ var createCallback = (callbackObject, components, subpath) => {
1804
+ const { ref, ...callbacks } = callbackObject;
1805
+ const callback = Object.entries(
1806
+ callbacks
1807
+ ).reduce((acc, [callbackName, pathItemObject]) => {
1808
+ if (isISpecificationExtension(callbackName)) {
1809
+ acc[callbackName] = pathItemObject;
1810
+ return acc;
1811
+ }
1812
+ acc[callbackName] = createPathItem(pathItemObject, components, [
1813
+ ...subpath,
1814
+ callbackName
1815
+ ]);
1816
+ return acc;
1817
+ }, {});
1818
+ if (ref) {
1819
+ components.callbacks.set(callbackObject, {
1820
+ type: "complete",
1821
+ ref,
1822
+ callbackObject: callback
1823
+ });
1824
+ return {
1825
+ $ref: createComponentCallbackRef(ref)
1826
+ };
1827
+ }
1828
+ return callback;
1829
+ };
1830
+ var createCallbacks = (callbacksObject, components, subpath) => {
1831
+ if (!callbacksObject) {
1832
+ return void 0;
1833
+ }
1834
+ return Object.entries(callbacksObject).reduce(
1835
+ (acc, [callbackName, callbackObject]) => {
1836
+ if (isISpecificationExtension(callbackName)) {
1837
+ acc[callbackName] = callbackObject;
1838
+ return acc;
1839
+ }
1840
+ acc[callbackName] = createCallback(callbackObject, components, [
1841
+ ...subpath,
1842
+ callbackName
1843
+ ]);
1790
1844
  return acc;
1791
1845
  },
1792
1846
  {}
@@ -1801,6 +1855,7 @@ var getDefaultComponents = (componentsObject, openapi = "3.1.0") => {
1801
1855
  headers: /* @__PURE__ */ new Map(),
1802
1856
  requestBodies: /* @__PURE__ */ new Map(),
1803
1857
  responses: /* @__PURE__ */ new Map(),
1858
+ callbacks: /* @__PURE__ */ new Map(),
1804
1859
  openapi
1805
1860
  };
1806
1861
  if (!componentsObject) {
@@ -1811,6 +1866,7 @@ var getDefaultComponents = (componentsObject, openapi = "3.1.0") => {
1811
1866
  getRequestBodies(componentsObject.requestBodies, defaultComponents);
1812
1867
  getHeaders(componentsObject.headers, defaultComponents);
1813
1868
  getResponses(componentsObject.responses, defaultComponents);
1869
+ getCallbacks(componentsObject.callbacks, defaultComponents);
1814
1870
  return defaultComponents;
1815
1871
  };
1816
1872
  var getSchemas = (schemas, components) => {
@@ -1911,9 +1967,27 @@ var getRequestBodies = (requestBodies, components) => {
1911
1967
  });
1912
1968
  });
1913
1969
  };
1970
+ var getCallbacks = (callbacks, components) => {
1971
+ if (!callbacks) {
1972
+ return;
1973
+ }
1974
+ Object.entries(callbacks).forEach(([key, callback]) => {
1975
+ if (components.callbacks.has(callback)) {
1976
+ throw new Error(
1977
+ `Callback ${JSON.stringify(callback)} is already registered`
1978
+ );
1979
+ }
1980
+ const ref = callback?.ref ?? key;
1981
+ components.callbacks.set(callback, {
1982
+ type: "manual",
1983
+ ref
1984
+ });
1985
+ });
1986
+ };
1914
1987
  var createComponentSchemaRef = (schemaRef) => `#/components/schemas/${schemaRef}`;
1915
1988
  var createComponentResponseRef = (responseRef) => `#/components/responses/${responseRef}`;
1916
1989
  var createComponentRequestBodyRef = (requestBodyRef) => `#/components/requestBodies/${requestBodyRef}`;
1990
+ var createComponentCallbackRef = (callbackRef) => `#/components/callbacks/${callbackRef}`;
1917
1991
  var createComponents = (componentsObject, components) => {
1918
1992
  const combinedSchemas = createSchemaComponents(componentsObject, components);
1919
1993
  const combinedParameters = createParamComponents(
@@ -1923,6 +1997,7 @@ var createComponents = (componentsObject, components) => {
1923
1997
  const combinedHeaders = createHeaderComponents(componentsObject, components);
1924
1998
  const combinedResponses = createResponseComponents(components);
1925
1999
  const combinedRequestBodies = createRequestBodiesComponents(components);
2000
+ const combinedCallbacks = createCallbackComponents(components);
1926
2001
  const { schemas, parameters, headers, responses, requestBodies, ...rest } = componentsObject;
1927
2002
  const finalComponents = {
1928
2003
  ...rest,
@@ -1930,7 +2005,8 @@ var createComponents = (componentsObject, components) => {
1930
2005
  ...combinedParameters && { parameters: combinedParameters },
1931
2006
  ...combinedRequestBodies && { requestBodies: combinedRequestBodies },
1932
2007
  ...combinedHeaders && { headers: combinedHeaders },
1933
- ...combinedResponses && { responses: combinedResponses }
2008
+ ...combinedResponses && { responses: combinedResponses },
2009
+ ...combinedCallbacks && { callbacks: combinedCallbacks }
1934
2010
  };
1935
2011
  return Object.keys(finalComponents).length ? finalComponents : void 0;
1936
2012
  };
@@ -2072,6 +2148,23 @@ var createRequestBodiesComponents = (components) => {
2072
2148
  }, {});
2073
2149
  return Object.keys(finalComponents).length ? finalComponents : void 0;
2074
2150
  };
2151
+ var createCallbackComponents = (components) => {
2152
+ Array.from(components.callbacks).forEach(([schema, component], index) => {
2153
+ if (component.type === "manual") {
2154
+ createCallback(schema, components, [`component callback ${index}`]);
2155
+ }
2156
+ });
2157
+ const finalComponents = Array.from(components.callbacks).reduce((acc, [_zodType, component]) => {
2158
+ if (component.type === "complete") {
2159
+ if (acc[component.ref]) {
2160
+ throw new Error(`Callback "${component.ref}" is already registered`);
2161
+ }
2162
+ acc[component.ref] = component.callbackObject;
2163
+ }
2164
+ return acc;
2165
+ }, {});
2166
+ return Object.keys(finalComponents).length ? finalComponents : void 0;
2167
+ };
2075
2168
 
2076
2169
  // src/create/document.ts
2077
2170
  var createDocument = (zodOpenApiObject) => {
@@ -0,0 +1,5 @@
1
+ import type { oas31 } from '../openapi3-ts/dist';
2
+ import { type ComponentsObject } from './components';
3
+ import type { ZodOpenApiCallbackObject } from './document';
4
+ export declare const createCallback: (callbackObject: ZodOpenApiCallbackObject, components: ComponentsObject, subpath: string[]) => oas31.CallbackObject;
5
+ export declare const createCallbacks: (callbacksObject: oas31.CallbackObject | undefined, components: ComponentsObject, subpath: string[]) => oas31.CallbackObject | undefined;
@@ -1,6 +1,6 @@
1
1
  import type { ZodType } from 'zod';
2
2
  import type { oas30, oas31 } from '../openapi3-ts/dist';
3
- import type { ZodOpenApiComponentsObject, ZodOpenApiRequestBodyObject, ZodOpenApiResponseObject, ZodOpenApiVersion } from './document';
3
+ import type { ZodOpenApiCallbackObject, ZodOpenApiComponentsObject, ZodOpenApiRequestBodyObject, ZodOpenApiResponseObject, ZodOpenApiVersion } from './document';
4
4
  export type CreationType = 'input' | 'output';
5
5
  type BaseEffect = {
6
6
  zodType: ZodType;
@@ -95,17 +95,31 @@ export interface PartialRequestBodyComponent extends BaseRequestBodyComponent {
95
95
  }
96
96
  export type RequestBodyComponent = CompleteRequestBodyComponent | PartialRequestBodyComponent;
97
97
  export type RequestBodyComponentMap = Map<ZodOpenApiRequestBodyObject, RequestBodyComponent>;
98
+ export interface BaseCallbackComponent {
99
+ ref: string;
100
+ }
101
+ export interface CompleteCallbackComponent extends BaseCallbackComponent {
102
+ type: 'complete';
103
+ callbackObject: ZodOpenApiCallbackObject | oas31.CallbackObject | oas30.CallbackObject;
104
+ }
105
+ export interface PartialCallbackComponent extends BaseCallbackComponent {
106
+ type: 'manual';
107
+ }
108
+ export type CallbackComponent = CompleteCallbackComponent | PartialCallbackComponent;
109
+ export type CallbackComponentMap = Map<ZodOpenApiCallbackObject, CallbackComponent>;
98
110
  export interface ComponentsObject {
99
111
  schemas: SchemaComponentMap;
100
112
  parameters: ParameterComponentMap;
101
113
  headers: HeaderComponentMap;
102
114
  requestBodies: RequestBodyComponentMap;
103
115
  responses: ResponseComponentMap;
116
+ callbacks: CallbackComponentMap;
104
117
  openapi: ZodOpenApiVersion;
105
118
  }
106
119
  export declare const getDefaultComponents: (componentsObject?: ZodOpenApiComponentsObject, openapi?: ZodOpenApiVersion) => ComponentsObject;
107
120
  export declare const createComponentSchemaRef: (schemaRef: string) => string;
108
121
  export declare const createComponentResponseRef: (responseRef: string) => string;
109
122
  export declare const createComponentRequestBodyRef: (requestBodyRef: string) => string;
123
+ export declare const createComponentCallbackRef: (callbackRef: string) => string;
110
124
  export declare const createComponents: (componentsObject: ZodOpenApiComponentsObject, components: ComponentsObject) => oas31.ComponentsObject | undefined;
111
125
  export {};
@@ -26,11 +26,20 @@ export interface ZodOpenApiResponsesObject extends oas31.ISpecificationExtension
26
26
  export type ZodOpenApiParameters = {
27
27
  [type in oas31.ParameterLocation & oas30.ParameterLocation]?: ZodObjectInputType;
28
28
  };
29
- export interface ZodOpenApiOperationObject extends Omit<oas31.OperationObject & oas30.OperationObject, 'requestBody' | 'responses' | 'parameters'> {
29
+ export interface ZodOpenApiCallbacksObject extends oas31.ISpecificationExtension {
30
+ [name: string]: ZodOpenApiCallbackObject;
31
+ }
32
+ export interface ZodOpenApiCallbackObject extends oas31.ISpecificationExtension {
33
+ /** Use this field to auto register this callback object as a component */
34
+ ref?: string;
35
+ [name: string]: ZodOpenApiPathItemObject | string | undefined;
36
+ }
37
+ export interface ZodOpenApiOperationObject extends Omit<oas31.OperationObject & oas30.OperationObject, 'requestBody' | 'responses' | 'parameters' | 'callbacks'> {
30
38
  parameters?: Array<ZodType | oas31.ParameterObject | oas30.ParameterObject | oas31.ReferenceObject | oas30.ReferenceObject>;
31
39
  requestBody?: ZodOpenApiRequestBodyObject;
32
40
  requestParams?: ZodOpenApiParameters;
33
41
  responses: ZodOpenApiResponsesObject;
42
+ callbacks?: ZodOpenApiCallbacksObject;
34
43
  }
35
44
  export interface ZodOpenApiPathItemObject extends Omit<oas31.PathItemObject & oas30.PathItemObject, 'get' | 'put' | 'post' | 'delete' | 'options' | 'head' | 'patch' | 'trace'> {
36
45
  get?: ZodOpenApiOperationObject;
@@ -51,6 +60,7 @@ export interface ZodOpenApiComponentsObject extends Omit<oas31.ComponentsObject
51
60
  requestBodies?: Record<string, ZodOpenApiRequestBodyObject>;
52
61
  headers?: Record<string, ZodType | oas31.HeaderObject | oas30.HeaderObject | oas31.ReferenceObject | oas30.ReferenceObject>;
53
62
  responses?: Record<string, ZodOpenApiResponseObject>;
63
+ callbacks?: Record<string, ZodOpenApiCallbackObject>;
54
64
  }
55
65
  export type ZodOpenApiVersion = OpenApiVersion;
56
66
  export interface ZodOpenApiObject extends Omit<oas31.OpenAPIObject, 'openapi' | 'paths' | 'webhooks' | 'components'> {
@@ -1,5 +1,6 @@
1
1
  import type { oas31 } from '../openapi3-ts/dist';
2
2
  import { type ComponentsObject } from './components';
3
- import type { ZodOpenApiPathsObject, ZodOpenApiRequestBodyObject } from './document';
3
+ import type { ZodOpenApiPathItemObject, ZodOpenApiPathsObject, ZodOpenApiRequestBodyObject } from './document';
4
4
  export declare const createRequestBody: (requestBodyObject: ZodOpenApiRequestBodyObject | undefined, components: ComponentsObject, subpath: string[]) => oas31.ReferenceObject | oas31.RequestBodyObject | undefined;
5
+ export declare const createPathItem: (pathObject: ZodOpenApiPathItemObject, components: ComponentsObject, path: string[]) => oas31.PathItemObject;
5
6
  export declare const createPaths: (pathsObject: ZodOpenApiPathsObject | undefined, components: ComponentsObject) => oas31.PathsObject | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zod-openapi",
3
- "version": "2.17.0",
3
+ "version": "2.18.0",
4
4
  "description": "Convert Zod Schemas to OpenAPI v3.x documentation",
5
5
  "keywords": [
6
6
  "typescript",
@@ -67,17 +67,18 @@
67
67
  "test:watch": "skuba test --watch"
68
68
  },
69
69
  "devDependencies": {
70
- "@redocly/cli": "1.11.0",
70
+ "@redocly/cli": "1.14.0",
71
71
  "@types/node": "^20.3.0",
72
72
  "eslint-plugin-zod-openapi": "^0.1.0",
73
- "openapi3-ts": "4.3.1",
73
+ "openapi3-ts": "4.3.2",
74
74
  "skuba": "8.0.1",
75
- "yaml": "2.4.1",
76
- "zod": "3.23.3"
75
+ "yaml": "2.4.2",
76
+ "zod": "3.23.8"
77
77
  },
78
78
  "peerDependencies": {
79
79
  "zod": "^3.21.4"
80
80
  },
81
+ "packageManager": "pnpm@9.0.5",
81
82
  "engines": {
82
83
  "node": ">=16.11"
83
84
  },
@@ -1 +0,0 @@
1
- export * from './dist/oas30';
@@ -1 +0,0 @@
1
- export * from './dist/oas31';