express-zod-api 28.7.5 → 29.0.0-beta.3
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/CHANGELOG.md +68 -39
- package/README.md +36 -33
- package/SECURITY.md +2 -1
- package/dist/documentation-helpers-BLgXFiWr.d.ts +30 -0
- package/dist/documentation.d.ts +75 -0
- package/dist/documentation.js +1 -0
- package/dist/index.d.ts +48 -870
- package/dist/index.js +6 -20
- package/dist/integration.d.ts +85 -0
- package/dist/integration.js +4 -0
- package/dist/json-schema-helpers-Bhz0qPT7.js +1 -0
- package/dist/peer-helpers-eiBZCqrL.js +1 -0
- package/dist/routing-C0jFxoT6.d.ts +742 -0
- package/dist/routing-walker--qYUjhGi.js +1 -0
- package/package.json +18 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Version 29
|
|
4
|
+
|
|
5
|
+
### v29.0.0
|
|
6
|
+
|
|
7
|
+
- Supported Node.js versions: `^22.19.0 || ^24.11.0 || ^26.0.0`;
|
|
8
|
+
- Breaking change to the `cors` config option:
|
|
9
|
+
- Changed from `boolean | HeadersProvider` to `boolean | RequestHandler`;
|
|
10
|
+
- You can now use the well-known `cors` package or pass a conventional `RequestHandler` directly.
|
|
11
|
+
- Body parsers are now applied globally rather than per-endpoint (`createServer` experience only):
|
|
12
|
+
- Endpoints can accept requests in different content types (e.g., either JSON or URL-encoded body);
|
|
13
|
+
- The `Documentation` generator continues to guess the desired request type from the input schema;
|
|
14
|
+
- Parsers retain the ability for configuration via `jsonParser`, `formParser`, and `rawParser` config options;
|
|
15
|
+
- If using `attachRouting()` in a DIY server, parsers have to be installed manually.
|
|
16
|
+
- Potentially breaking changes to the server lifecycle hooks:
|
|
17
|
+
- The hooks (`beforeRouting` and `afterRouting` config options) are no longer async;
|
|
18
|
+
- `beforeRouting` now runs after the installation of globally enabled parsers;
|
|
19
|
+
- Added new `beforeParsers` hook that runs before all parsers are installed.
|
|
20
|
+
- The `createServer()` function is now synchronous — that should simplify the daily routines for beginners;
|
|
21
|
+
- Added HTTP QUERY method support (RFC 10008):
|
|
22
|
+
- The QUERY method is like GET but with a body — safe, idempotent, and cacheable;
|
|
23
|
+
- Default input sources for QUERY: `["query", "body", "params"]` (from the lowest priority to highest);
|
|
24
|
+
- Supported by `Integration` and `Documentation` generators.
|
|
25
|
+
- Changes to `Documentation`:
|
|
26
|
+
- Moved to the dedicated subpath `express-zod-api/documentation` along with `Depicter` type;
|
|
27
|
+
- `serverUrl` constructor option renamed to `server` and now also accepts OpenAPI's ServerObject;
|
|
28
|
+
- `title` option and `version` must be wrapped into `info`, assignable with OpenAPI's InfoObject;
|
|
29
|
+
- Now produces OpenAPI 3.2.0 with better SSE support and other features.
|
|
30
|
+
- Changes to `Integration`:
|
|
31
|
+
- Moved to the dedicated subpath `express-zod-api/integration` along with `Producer` type;
|
|
32
|
+
- The static async method `create()` removed — use `new Integration()` instead;
|
|
33
|
+
- Removed `typescript` option from constructor — now imported statically.
|
|
34
|
+
- Consider using [the automated migration](https://www.npmjs.com/package/@express-zod-api/migration).
|
|
35
|
+
|
|
36
|
+
```diff
|
|
37
|
+
- import { Integration, Documentation, type Producer, type Depicter } from "express-zod-api";
|
|
38
|
+
+ import { Integration, type Producer } from "express-zod-api/integration";
|
|
39
|
+
+ import { Documentation, type Depicter } from "express-zod-api/documentation";
|
|
40
|
+
|
|
41
|
+
const config = createConfig({
|
|
42
|
+
- cors: () => ({ origin: "https://example.com" }),
|
|
43
|
+
+ cors: cors({ origin: "https://example.com" }), // import cors from "cors"
|
|
44
|
+
});
|
|
45
|
+
- await Integration.create({});
|
|
46
|
+
+ new Integration({});
|
|
47
|
+
- const {} = await createServer({});
|
|
48
|
+
+ const {} = createServer({});
|
|
49
|
+
new Documentation({
|
|
50
|
+
+ info: {
|
|
51
|
+
title: "Sample API",
|
|
52
|
+
version: "1.2.3",
|
|
53
|
+
+ },
|
|
54
|
+
- serverUrl: "https://example.com",
|
|
55
|
+
+ server: "https://example.com",
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
3
59
|
## Version 28
|
|
4
60
|
|
|
5
61
|
### v28.7.5
|
|
@@ -4242,9 +4298,7 @@ import { z } from "express-zod-api";
|
|
|
4242
4298
|
|
|
4243
4299
|
const endpoint = endpointsFactory.build({
|
|
4244
4300
|
input: z
|
|
4245
|
-
.object({
|
|
4246
|
-
/* ... */
|
|
4247
|
-
})
|
|
4301
|
+
.object({/* ... */})
|
|
4248
4302
|
.refine(() => true)
|
|
4249
4303
|
.refine(() => true)
|
|
4250
4304
|
.refine(() => true),
|
|
@@ -4503,9 +4557,7 @@ const myMiddleware = createMiddleware({
|
|
|
4503
4557
|
},
|
|
4504
4558
|
},
|
|
4505
4559
|
input: z.object({}),
|
|
4506
|
-
middleware: async () => ({
|
|
4507
|
-
/* ... */
|
|
4508
|
-
}),
|
|
4560
|
+
middleware: async () => ({/* ... */}),
|
|
4509
4561
|
});
|
|
4510
4562
|
|
|
4511
4563
|
// example endpoint
|
|
@@ -4514,9 +4566,7 @@ const myEndpoint = defaultEndpointsFactory.addMiddleware(myMiddleware).build({
|
|
|
4514
4566
|
method: "post",
|
|
4515
4567
|
input: z.object({}),
|
|
4516
4568
|
output: z.object({}),
|
|
4517
|
-
handler: async () => ({
|
|
4518
|
-
/* ... */
|
|
4519
|
-
}),
|
|
4569
|
+
handler: async () => ({/* ... */}),
|
|
4520
4570
|
});
|
|
4521
4571
|
```
|
|
4522
4572
|
|
|
@@ -5448,15 +5498,11 @@ const fileUploadEndpoint = defaultEndpointsFactory.build({
|
|
|
5448
5498
|
input: z.object({
|
|
5449
5499
|
avatar: z.upload(),
|
|
5450
5500
|
}),
|
|
5451
|
-
output: z.object({
|
|
5452
|
-
/* ... */
|
|
5453
|
-
}),
|
|
5501
|
+
output: z.object({/* ... */}),
|
|
5454
5502
|
handler: async ({ input: { avatar } }) => {
|
|
5455
5503
|
// avatar: {name, mv(), mimetype, encoding, data, truncated, size, etc}
|
|
5456
5504
|
// avatar.truncated is true on failure
|
|
5457
|
-
return {
|
|
5458
|
-
/* ... */
|
|
5459
|
-
};
|
|
5505
|
+
return {/* ... */};
|
|
5460
5506
|
},
|
|
5461
5507
|
});
|
|
5462
5508
|
```
|
|
@@ -5690,15 +5736,11 @@ import { EndpointOutput } from "express-zod-api";
|
|
|
5690
5736
|
|
|
5691
5737
|
const myEndpointV1 = endpointsFactory.build({
|
|
5692
5738
|
method: "get",
|
|
5693
|
-
input: z.object({
|
|
5694
|
-
/* ... */
|
|
5695
|
-
}),
|
|
5739
|
+
input: z.object({/* ... */}),
|
|
5696
5740
|
output: z.object({
|
|
5697
5741
|
name: z.string(),
|
|
5698
5742
|
}),
|
|
5699
|
-
handler: async () => ({
|
|
5700
|
-
/* ... */
|
|
5701
|
-
}),
|
|
5743
|
+
handler: async () => ({/* ... */}),
|
|
5702
5744
|
});
|
|
5703
5745
|
type MyEndpointOutput = EndpointOutput<typeof myEndpointV1>; // => { name: string }
|
|
5704
5746
|
|
|
@@ -5707,15 +5749,11 @@ import { defaultEndpointsFactory, EndpointResponse } from "express-zod-api";
|
|
|
5707
5749
|
|
|
5708
5750
|
const myEndpointV2 = defaultEndpointsFactory.build({
|
|
5709
5751
|
method: "get",
|
|
5710
|
-
input: z.object({
|
|
5711
|
-
/* ... */
|
|
5712
|
-
}),
|
|
5752
|
+
input: z.object({/* ... */}),
|
|
5713
5753
|
output: z.object({
|
|
5714
5754
|
name: z.string(),
|
|
5715
5755
|
}),
|
|
5716
|
-
handler: async () => ({
|
|
5717
|
-
/* ... */
|
|
5718
|
-
}),
|
|
5756
|
+
handler: async () => ({/* ... */}),
|
|
5719
5757
|
});
|
|
5720
5758
|
type MyEndpointResponse = EndpointResponse<typeof myEndpointV2>; // => the following type:
|
|
5721
5759
|
// {
|
|
@@ -5729,13 +5767,9 @@ type MyEndpointResponse = EndpointResponse<typeof myEndpointV2>; // => the follo
|
|
|
5729
5767
|
|
|
5730
5768
|
```ts
|
|
5731
5769
|
// before
|
|
5732
|
-
new OpenAPI({
|
|
5733
|
-
/* ... */
|
|
5734
|
-
}).builder.getSpecAsYaml();
|
|
5770
|
+
new OpenAPI({/* ... */}).builder.getSpecAsYaml();
|
|
5735
5771
|
// after
|
|
5736
|
-
new OpenAPI({
|
|
5737
|
-
/* ... */
|
|
5738
|
-
}).getSpecAsYaml();
|
|
5772
|
+
new OpenAPI({/* ... */}).getSpecAsYaml();
|
|
5739
5773
|
```
|
|
5740
5774
|
|
|
5741
5775
|
```ts
|
|
@@ -5760,12 +5794,7 @@ const myResultHandlerV2 = createResultHandler({
|
|
|
5760
5794
|
}),
|
|
5761
5795
|
["mime/type1", "mime/type2"], // optional, default: application/json
|
|
5762
5796
|
),
|
|
5763
|
-
getNegativeResponse: () =>
|
|
5764
|
-
createApiResponse(
|
|
5765
|
-
z.object({
|
|
5766
|
-
/* ... */
|
|
5767
|
-
}),
|
|
5768
|
-
),
|
|
5797
|
+
getNegativeResponse: () => createApiResponse(z.object({/* ... */})),
|
|
5769
5798
|
handler: ({ error, input, output, request, response, logger }) => {
|
|
5770
5799
|
/* ... */
|
|
5771
5800
|
},
|
package/README.md
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-

|
|
6
|
+

|
|
7
|
+
[](https://coveralls.io/github/RobinTail/express-zod-api)
|
|
8
8
|
|
|
9
9
|

|
|
10
|
-

|
|
11
10
|

|
|
12
11
|

|
|
13
12
|
|
|
@@ -83,7 +82,7 @@ Therefore, many basic tasks can be achieved faster and easier, in particular:
|
|
|
83
82
|
- All of your endpoints can respond consistently.
|
|
84
83
|
- The expected endpoint input and response types can be exported to the frontend, giving you end-to-end type safety
|
|
85
84
|
so you don't get confused about the field names when you implement the client for your API.
|
|
86
|
-
- You can generate your API documentation in OpenAPI 3.
|
|
85
|
+
- You can generate your API documentation in OpenAPI 3.2 and JSON Schema compatible format.
|
|
87
86
|
|
|
88
87
|
## Contributors
|
|
89
88
|
|
|
@@ -171,7 +170,7 @@ Much can be customized to fit your needs.
|
|
|
171
170
|
- Supports any logger having `info()`, `debug()`, `error()` and `warn()` methods;
|
|
172
171
|
- Built-in console logger with colorful and pretty inspections by default.
|
|
173
172
|
- Generators:
|
|
174
|
-
- Documentation — [OpenAPI 3.
|
|
173
|
+
- Documentation — [OpenAPI 3.2](https://github.com/metadevpro/openapi3-ts) (former Swagger);
|
|
175
174
|
- Client side types — inspired by [zod-to-ts](https://github.com/sachinraja/zod-to-ts).
|
|
176
175
|
- File uploads — [Express-FileUpload](https://github.com/richardgirges/express-fileupload)
|
|
177
176
|
(based on [Busboy](https://github.com/mscdex/busboy)).
|
|
@@ -424,9 +423,9 @@ const resultHandlerWithCleanup = new ResultHandler({
|
|
|
424
423
|
|
|
425
424
|
There are two ways of connecting the native express middlewares depending on their nature and your objective.
|
|
426
425
|
|
|
427
|
-
In case it's
|
|
428
|
-
|
|
429
|
-
|
|
426
|
+
In case it's middleware establishing and serving its own routes, or somehow globally modifying the behavior, use the
|
|
427
|
+
`beforeRouting` or `beforeParsers` hooks. Note that [CORS](#cross-origin-resource-sharing), cookies, compression, and
|
|
428
|
+
body parsing are already available as separate [config options](#set-up-config).
|
|
430
429
|
|
|
431
430
|
```ts
|
|
432
431
|
import { createConfig } from "express-zod-api";
|
|
@@ -647,18 +646,27 @@ const listUsers = defaultEndpointsFactory.build({
|
|
|
647
646
|
## Cross-Origin Resource Sharing
|
|
648
647
|
|
|
649
648
|
You can enable your API for other domains using the corresponding configuration option `cors`. The value is required to
|
|
650
|
-
ensure you explicitly choose the correct setting
|
|
651
|
-
|
|
649
|
+
ensure you explicitly choose the correct setting: `false | true` — disables/enables CORS for any origin, setting
|
|
650
|
+
`Access-Control-Allow-Origin: *` and `Access-Control-Allow-Headers: content-type`. You can also pass standard Express
|
|
651
|
+
middleware for full control, consider using the well-known [cors](https://www.npmjs.com/package/cors) package.
|
|
652
652
|
|
|
653
653
|
```ts
|
|
654
|
+
import cors from "cors";
|
|
654
655
|
import { createConfig } from "express-zod-api";
|
|
655
656
|
|
|
656
|
-
const
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
657
|
+
const configWithCorsPackage = createConfig({
|
|
658
|
+
cors: cors({ origin: "https://example.com" }),
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
const configWithCustomRequestHandler = createConfig({
|
|
662
|
+
cors: (req, res, next) => {
|
|
663
|
+
res.set({
|
|
664
|
+
"Access-Control-Allow-Origin": "https://example.com",
|
|
665
|
+
"Access-Control-Allow-Headers": "content-type",
|
|
666
|
+
"Access-Control-Max-Age": "5000",
|
|
667
|
+
});
|
|
668
|
+
next();
|
|
669
|
+
},
|
|
662
670
|
});
|
|
663
671
|
```
|
|
664
672
|
|
|
@@ -683,9 +691,7 @@ const config = createConfig({
|
|
|
683
691
|
}, // ... cors, logger, etc
|
|
684
692
|
});
|
|
685
693
|
|
|
686
|
-
|
|
687
|
-
// For top level CJS you can wrap you code with (async () => { ... })()
|
|
688
|
-
const { app, servers, logger } = await createServer(config, routing);
|
|
694
|
+
const { app, servers, logger } = createServer(config, routing);
|
|
689
695
|
```
|
|
690
696
|
|
|
691
697
|
Ensure having `@types/node` package installed. At least you need to specify the port (usually it is 443) or UNIX socket,
|
|
@@ -787,6 +793,7 @@ createConfig({
|
|
|
787
793
|
put: ["body", "params"],
|
|
788
794
|
patch: ["body", "params"],
|
|
789
795
|
delete: ["query", "params"],
|
|
796
|
+
query: ["query", "body", "params"],
|
|
790
797
|
}, // ...
|
|
791
798
|
});
|
|
792
799
|
```
|
|
@@ -1124,7 +1131,7 @@ errors yourself. In this regard `attachRouting()` provides you with `notFoundHan
|
|
|
1124
1131
|
to your custom express app.
|
|
1125
1132
|
|
|
1126
1133
|
Besides that, if you're looking to include additional request parsers, or a middleware that establishes its own routes,
|
|
1127
|
-
|
|
1134
|
+
install them on your custom `app` before calling `attachRouting()`.
|
|
1128
1135
|
|
|
1129
1136
|
## Testing endpoints
|
|
1130
1137
|
|
|
@@ -1206,7 +1213,7 @@ safety between your API and frontend. Make sure you have `typescript` installed.
|
|
|
1206
1213
|
and using the async `printFormatted()` method.
|
|
1207
1214
|
|
|
1208
1215
|
```ts
|
|
1209
|
-
import { Integration } from "express-zod-api";
|
|
1216
|
+
import { Integration } from "express-zod-api/integration";
|
|
1210
1217
|
|
|
1211
1218
|
const client = new Integration({
|
|
1212
1219
|
routing,
|
|
@@ -1236,14 +1243,13 @@ new Subscription("get /v1/events/stream", {}).on("time", (time) => {}); // Serve
|
|
|
1236
1243
|
You can generate the specification of your API and write it to a `.yaml` file, that can be used as the documentation:
|
|
1237
1244
|
|
|
1238
1245
|
```ts
|
|
1239
|
-
import { Documentation } from "express-zod-api";
|
|
1246
|
+
import { Documentation } from "express-zod-api/documentation";
|
|
1240
1247
|
|
|
1241
1248
|
const yamlString = new Documentation({
|
|
1242
1249
|
routing, // the same routing and config that you use to start the server
|
|
1243
1250
|
config,
|
|
1244
|
-
version: "1.2.3",
|
|
1245
|
-
|
|
1246
|
-
serverUrl: "https://example.com",
|
|
1251
|
+
info: { version: "1.2.3", title: "Example API" },
|
|
1252
|
+
server: "https://example.com",
|
|
1247
1253
|
composition: "inline", // optional, or "components" for keeping schemas in a separate dedicated section using refs
|
|
1248
1254
|
// descriptions: { positiveResponse, negativeResponse, requestParameter, requestBody }, // check out these features
|
|
1249
1255
|
}).getSpecAsYaml();
|
|
@@ -1281,7 +1287,8 @@ endpoints is available for that purpose. In order to establish the constraints o
|
|
|
1281
1287
|
should be declared as keys of `TagOverrides` interface. Consider the following example:
|
|
1282
1288
|
|
|
1283
1289
|
```ts
|
|
1284
|
-
import { defaultEndpointsFactory
|
|
1290
|
+
import { defaultEndpointsFactory } from "express-zod-api";
|
|
1291
|
+
import { Documentation } from "express-zod-api/documentation";
|
|
1285
1292
|
|
|
1286
1293
|
// Add similar declaration once, somewhere in your code, preferably near config
|
|
1287
1294
|
declare module "express-zod-api" {
|
|
@@ -1340,12 +1347,8 @@ handling rule for multiple brands, use the exposed types `Depicter` and `Produce
|
|
|
1340
1347
|
```ts
|
|
1341
1348
|
import ts from "typescript";
|
|
1342
1349
|
import { z } from "zod";
|
|
1343
|
-
import {
|
|
1344
|
-
|
|
1345
|
-
Integration,
|
|
1346
|
-
Depicter,
|
|
1347
|
-
Producer,
|
|
1348
|
-
} from "express-zod-api";
|
|
1350
|
+
import { Documentation, type Depicter } from "express-zod-api/documentation";
|
|
1351
|
+
import { Integration, type Producer } from "express-zod-api/integration";
|
|
1349
1352
|
|
|
1350
1353
|
const myBrand = Symbol("MamaToldMeImSpecial"); // I recommend to use symbols for this purpose
|
|
1351
1354
|
const myBrandedSchema = z.string().xBrand(myBrand); // requires Zod Plugin, or .meta({ "x-brand": myBrand })
|
package/SECURITY.md
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
| Version | Code name | Release | Supported |
|
|
6
6
|
| ------: | :------------ | :------ | :----------------: |
|
|
7
|
+
| 29.x.x | Angie | 08.2026 | :white_check_mark: |
|
|
7
8
|
| 28.x.x | Koko | 05.2026 | :white_check_mark: |
|
|
8
9
|
| 27.x.x | Nikki | 02.2026 | :white_check_mark: |
|
|
9
10
|
| 26.x.x | Lia | 12.2025 | :white_check_mark: |
|
|
10
|
-
| 25.x.x | Sara | 08.2025 |
|
|
11
|
+
| 25.x.x | Sara | 08.2025 | :x: |
|
|
11
12
|
| 24.x.x | Ashley | 06.2025 | :x: |
|
|
12
13
|
| 23.x.x | Sonia | 04.2025 | :x: |
|
|
13
14
|
| 22.x.x | Tai | 01.2025 | :x: |
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { P as ClientMethod, y as Tag } from "./routing-C0jFxoT6.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { ReferenceObject, SchemaObjectValue, TagObject } from "openapi3-ts/oas32";
|
|
4
|
+
interface ReqResCommons {
|
|
5
|
+
makeRef: (key: object | string, value: SchemaObjectValue | ReferenceObject, proposedName?: string) => ReferenceObject;
|
|
6
|
+
path: string;
|
|
7
|
+
method: ClientMethod;
|
|
8
|
+
}
|
|
9
|
+
interface OpenAPIContext extends ReqResCommons {
|
|
10
|
+
isResponse: boolean;
|
|
11
|
+
}
|
|
12
|
+
type Depicter = (
|
|
13
|
+
zodCtx: {
|
|
14
|
+
zodSchema: z.core.$ZodType;
|
|
15
|
+
jsonSchema: z.core.JSONSchema.BaseSchema;
|
|
16
|
+
},
|
|
17
|
+
oasCtx: OpenAPIContext,
|
|
18
|
+
) => z.core.JSONSchema.BaseSchema | SchemaObjectValue;
|
|
19
|
+
/** @desc Using defaultIsHeader when returns null or undefined */
|
|
20
|
+
type IsHeader = (name: string, method: ClientMethod, path: string) => boolean | null | undefined;
|
|
21
|
+
type BrandHandling = Record<string | symbol, Depicter>;
|
|
22
|
+
interface TagDetails extends Pick<TagObject, "summary" | "description" | "externalDocs" | "kind"> {
|
|
23
|
+
/** @desc shorthand for externalDocs.url */
|
|
24
|
+
url?: string;
|
|
25
|
+
parent?: Tag;
|
|
26
|
+
}
|
|
27
|
+
declare const depictTags: (tags: Partial<Record<Tag, string | TagDetails>>) => TagObject[];
|
|
28
|
+
/** @desc Ensures the summary string does not exceed the limit */
|
|
29
|
+
declare const trimSummary: (summary?: string, limit?: number) => string | undefined;
|
|
30
|
+
export { depictTags as a, OpenAPIContext as i, Depicter as n, trimSummary as o, IsHeader as r, BrandHandling as t };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { B as ResponseVariant, C as CommonConfig, t as Routing } from "./routing-C0jFxoT6.js";
|
|
2
|
+
import {
|
|
3
|
+
a as depictTags,
|
|
4
|
+
n as Depicter,
|
|
5
|
+
o as trimSummary,
|
|
6
|
+
r as IsHeader,
|
|
7
|
+
t as BrandHandling,
|
|
8
|
+
} from "./documentation-helpers-BLgXFiWr.js";
|
|
9
|
+
import { InfoObject, OpenApiBuilder, ServerObject } from "openapi3-ts/oas32";
|
|
10
|
+
type Component = `${ResponseVariant}Response` | "requestParameter" | "requestBody";
|
|
11
|
+
/** @desc user defined function that creates a component description from its properties */
|
|
12
|
+
type Descriptor = (
|
|
13
|
+
props: Record<"method" | "path" | "operationId", string> & {
|
|
14
|
+
statusCode?: number;
|
|
15
|
+
},
|
|
16
|
+
) => string;
|
|
17
|
+
type Summarizer = (params: { summary?: string; description?: string; trim: typeof trimSummary }) => string | undefined;
|
|
18
|
+
interface DocumentationParams {
|
|
19
|
+
/**
|
|
20
|
+
* @desc The metadata about the API
|
|
21
|
+
* @default { title: "Generated by Express Zod API", version: "0.0.0" }
|
|
22
|
+
* */
|
|
23
|
+
info?: InfoObject;
|
|
24
|
+
/**
|
|
25
|
+
* @desc Server URL(s) or their complete definitions
|
|
26
|
+
* @default []
|
|
27
|
+
* */
|
|
28
|
+
server?: string | ServerObject | Array<string | ServerObject>;
|
|
29
|
+
routing: Routing;
|
|
30
|
+
config: CommonConfig;
|
|
31
|
+
/**
|
|
32
|
+
* @desc Descriptions of various components based on their properties (method, path, operationId).
|
|
33
|
+
* @desc When composition set to "components", component name is generated from this description
|
|
34
|
+
* @default () => `${method} ${path} ${component}`
|
|
35
|
+
* */
|
|
36
|
+
descriptions?: Partial<Record<Component, Descriptor>>;
|
|
37
|
+
/**
|
|
38
|
+
* @desc The function that ensures the maximum length for summary fields. Can optionally make them from descriptions.
|
|
39
|
+
* @see defaultSummarizer
|
|
40
|
+
* @see trimSummary
|
|
41
|
+
* */
|
|
42
|
+
summarizer?: Summarizer;
|
|
43
|
+
/**
|
|
44
|
+
* @desc Depict the HEAD method for each Endpoint supporting the GET method (feature of Express)
|
|
45
|
+
* @default true
|
|
46
|
+
* */
|
|
47
|
+
hasHeadMethod?: boolean;
|
|
48
|
+
/** @default inline */
|
|
49
|
+
composition?: "inline" | "components";
|
|
50
|
+
/**
|
|
51
|
+
* @desc Handling rules for your own schemas branded with `x-brand` metadata.
|
|
52
|
+
* @desc Keys: brands (recommended to use unique symbols).
|
|
53
|
+
* @desc Values: functions having Zod context as first argument, second one is the framework context.
|
|
54
|
+
* @example { MyBrand: ({ zodSchema, jsonSchema }) => ({ type: "object" })
|
|
55
|
+
* @link https://www.npmjs.com/package/@express-zod-api/zod-plugin
|
|
56
|
+
*/
|
|
57
|
+
brandHandling?: BrandHandling;
|
|
58
|
+
/**
|
|
59
|
+
* @desc Ability to configure recognition of headers among other input data
|
|
60
|
+
* @desc Only applicable when "headers" is present within inputSources config option
|
|
61
|
+
* @see defaultIsHeader
|
|
62
|
+
* @link https://www.iana.org/assignments/http-fields/http-fields.xhtml
|
|
63
|
+
* */
|
|
64
|
+
isHeader?: IsHeader;
|
|
65
|
+
/**
|
|
66
|
+
* @desc Extended description of tags used in endpoints. For enforcing constraints:
|
|
67
|
+
* @see TagOverrides
|
|
68
|
+
* @example { users: "About users", files: { description: "About files", url: "https://example.com" } }
|
|
69
|
+
* */
|
|
70
|
+
tags?: Parameters<typeof depictTags>[0];
|
|
71
|
+
}
|
|
72
|
+
declare class Documentation extends OpenApiBuilder {
|
|
73
|
+
constructor({ hasHeadMethod, ...rest }: DocumentationParams);
|
|
74
|
+
}
|
|
75
|
+
export { type Depicter, Documentation };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{B as e,D as t,G as n,H as r,J as i,K as a,M as o,P as s,R as c,V as l,W as u,Z as d,_ as f,a as p,b as m,h,j as g,k as _,n as v,q as y,t as b}from"./routing-walker--qYUjhGi.js";import{t as x}from"./json-schema-helpers-Bhz0qPT7.js";import{z as S}from"zod";import*as C from"ramda";import{OpenApiBuilder as w,isReferenceObject as T,isSchemaObject as E}from"openapi3-ts/oas32";const D=e=>r(e)&&`or`in e,O=e=>r(e)&&`and`in e,k=e=>!O(e)&&!D(e),A=e=>{let t=C.filter(k,e),n=C.chain(C.prop(`and`),C.filter(O,e)),[r,i]=C.partition(k,n),a=C.concat(t,r),o=C.filter(D,e);return C.map(C.prop(`or`),C.concat(o,i)).reduce((e,t)=>s(e,C.map(e=>k(e)?[e]:e.and,t),([e,t])=>C.concat(e,t)),C.reject(C.isEmpty,[a]))},j=(e,t)=>O(e)?C.chain(e=>j(e,t),e.and):D(e)?C.chain(e=>j(e,t),e.or):e.type===t?[e.name]:[],M=(e,t)=>new Set(C.chain(e=>j(e,t),e));let N;const P=()=>N??=new Set(`a-im.accept.accept-additions.accept-charset.accept-datetime.accept-encoding.accept-features.accept-language.accept-signature.access-control.access-control-request-headers.access-control-request-method.alpn.alt-used.amp-cache-transform.apply-to-redirect-ref.authorization.available-dictionary.c-ext.c-man.c-opt.c-pep.c-pep-info.cache-control.cal-managed-id.caldav-timezones.capsule-protocol.cdn-loop.cert-not-after.cert-not-before.client-cert.client-cert-chain.close.cmcd-object.cmcd-request.cmcd-session.cmcd-status.cmsd-dynamic.cmsd-static.concealed-auth-export.configuration-context.connection.content-digest.content-disposition.content-encoding.content-id.content-language.content-length.content-location.content-md5.content-range.content-script-type.content-type.cookie.cookie2.cta-common-access-token.dasl.date.dav.default-style.delta-base.deprecation.depth.derived-from.destination.detached-jws.dictionary-id.differential-id.digest.dpop.early-data.ediint-features.expect.expect-ct.ext.forwarded.from.getprofile.hobareg.host.http2-settings.if.if-match.if-modified-since.if-none-match.if-range.if-schedule-tag-match.if-unmodified-since.im.include-referred-token-binding-id.incremental.isolation.keep-alive.label.last-event-id.link.link-template.lock-token.man.max-forwards.memento-datetime.meter.method-check.method-check-expires.mime-version.negotiate.odata-entityid.odata-isolation.odata-maxversion.odata-version.opt.ordering-type.origin.origin-agent-cluster.oscore.oslc-core-version.overwrite.pep.pep-info.permissions-policy.pics-label.ping-from.ping-to.position.pragma.prefer.preference-applied.priority.profileobject.protocol.protocol-info.protocol-query.protocol-request.proxy-authorization.proxy-features.proxy-instruction.range.redirect-ref.referer.referer-root.referrer-policy.repeatability-client-id.repeatability-first-sent.repeatability-request-id.repeatability-result.replay-nonce.reporting-endpoints.repr-digest.safe.schedule-reply.schedule-tag.sec-fetch-storage-access.sec-gpc.sec-purpose.sec-token-binding.sec-websocket-extensions.sec-websocket-key.sec-websocket-protocol.sec-websocket-version.security-scheme.signature.signature-input.slug.soapaction.status-uri.sunset.surrogate-capability.tcn.te.timeout.topic.traceparent.tracestate.trailer.transfer-encoding.ttl.upgrade.urgency.uri.user-agent.variant-vary.via.want-content-digest.want-digest.want-repr-digest.want-unencoded-digest.warning.x-content-type-options.x-frame-options`.split(`.`)),ee={integer:0,number:0,string:``,boolean:!1,object:{},null:null,array:[]},te=e=>e.replace(a,e=>`{${e.slice(1)}}`),ne=({},e)=>{if(e.isResponse)throw new m(`Please use ez.upload() only for input.`,e);return{type:`string`,format:`binary`}},re=({jsonSchema:e})=>({...e,externalDocs:{description:`raw binary data`,url:`https://swagger.io/specification/#working-with-binary-data`}}),ie=({zodSchema:e,jsonSchema:t})=>{if(!u(e,`union`)||!(`discriminator`in e._zod.def))return t;let n=e._zod.def.discriminator;return{...t,discriminator:t.discriminator??{propertyName:n}}},F=C.tryCatch(({jsonSchema:e})=>{if(!e.allOf)throw`no allOf`;return x(e,`throw`)},(e,{jsonSchema:t})=>t),I=({jsonSchema:e})=>{if(!e.anyOf||!e.anyOf.length)return e;let t=e.anyOf[0];return Object.assign(t,{type:U(t.type)})},L=e=>e,R=({jsonSchema:e},t)=>{if(t.isResponse)throw new m(`Please use ez.dateOut() for output.`,t);return e},z=({jsonSchema:e},t)=>{if(!t.isResponse)throw new m(`Please use ez.dateIn() for input.`,t);return e},B=()=>({type:`string`,format:`bigint`,pattern:`^-?\\d+$`}),V=({zodSchema:e,jsonSchema:t})=>e._zod.def.rest===null?{...t,items:{not:{}}}:t,H=e=>{let t=Array.isArray(e.type)?e.type[0]:e.type;return ee?.[t]},U=e=>e===`null`?e:typeof e==`string`?[e,`null`]:e&&[...new Set(e).add(`null`)],W=({zodSchema:e,jsonSchema:t},n)=>{let r=e._zod.def[n.isResponse?`out`:`in`],i=e._zod.def[n.isResponse?`in`:`out`];if(!u(r,`transform`))return t;let a=L(X(i,{ctx:n}));if(E(a))if(n.isResponse){let e=l(r,H(a));if(e&&[`number`,`string`,`boolean`].includes(e))return{...t,type:e}}else{let{type:e,...t}=a;return{...t,format:`${t.format||e} (preprocessed)`}}return t},G=({jsonSchema:e})=>{if(e.type!==`object`)return e;let t=e;return!t.properties||!(`raw`in t.properties)||!r(t.properties.raw)?e:t.properties.raw},K=e=>e.length?C.fromPairs(C.zip(C.times(e=>`example${e+1}`,e.length),C.map(C.objOf(`dataValue`),e))):void 0,q=(e,t)=>t?.has(e)||e.startsWith(`x-`)||P().has(e),J=({path:t,method:i,request:a,inputSources:o,makeRef:s,composition:c,isHeader:l,securityHeaders:u,securityCookies:d,description:f=`${i.toUpperCase()} ${t} Parameter`})=>{let p=x(a),m=e(t),h=o.includes(`query`),g=o.includes(`params`),_=o.includes(`headers`),v=o.includes(`cookies`)||o.includes(`signedCookies`),y=e=>{if(g&&m.includes(e))return`path`;if(v&&d?.has(e))return`cookie`;if(_&&(l?.(e,i,t)??q(e,u)))return`header`;if(h&&i!==`query`)return`query`};return Object.entries(p.properties).reduce((e,[t,i])=>{if(!r(i))return e;let a=y(t);if(!a)return e;let o=L(i),l=c===`components`?s(i.id||JSON.stringify(i),o,i.id||n(f,t)):o;return e.concat({name:t,in:a,deprecated:i.deprecated,required:p.required?.includes(t)||!1,description:o.description||f,schema:l,examples:K(E(o)&&o.examples?.length?o.examples:C.pluck(t,p.examples?.filter(C.both(r,C.has(t)))||[]))})},[])},Y={nullable:I,union:ie,bigint:B,intersection:F,tuple:V,pipe:W,[_]:R,[t]:z,[f]:ne,[h]:G,[g]:re},ae=(e,t,n)=>{let r=[e,t],i=e=>/schema\d+$/.test(e)?void 0:e;for(let e=0;e<r.length;e++){let a=r[e];if(C.is(Object,a)){if(T(a)&&!a.$ref.startsWith(`#/components`)){let e=a.$ref.split(`/`).pop(),r=t[e];r&&(a.$ref=n.makeRef(r.id||r,L(r),r.id||i(e)).$ref);continue}r.push(...C.values(a))}C.is(Array,a)&&r.push(...C.values(a))}return e},X=(e,{ctx:t,rules:n=Y})=>{let{$defs:i={},properties:a={}}=S.toJSONSchema(S.object({subject:e}),{unrepresentable:`any`,io:t.isResponse?`output`:`input`,override:e=>{let r=o(e.zodSchema),i=n[r&&r in n?r:e.zodSchema._zod.def.type];if(i){let n={...i(e,t)};for(let t in e.jsonSchema)delete e.jsonSchema[t];Object.assign(e.jsonSchema,n)}}});return ae(r(a.subject)?a.subject:{},i,t)},Z=(e,t)=>{if(T(e))return[e,!1];let n=!1,r=C.map(e=>{let[r,i]=Z(e,t);return n||=i,r}),i=C.omit(t),a={properties:i,examples:C.map(i),required:C.without(t),allOf:r,oneOf:r,anyOf:r},o=C.evolve(a,e);return[o,n||!!o.required?.length]},oe=({method:e,path:t,schema:r,mimeTypes:a,variant:o,makeRef:s,composition:c,hasMultipleStatusCodes:l,statusCode:u,brandHandling:f,description:p=`${e.toUpperCase()} ${t} ${i(o)} response ${l?u:``}`.trim()})=>{if(!y(e,a))return{description:p};let m=L(X(r,{rules:{...f,...Y},ctx:{isResponse:!0,makeRef:s,path:t,method:e}})),h=[];E(m)&&m.examples&&(h.push(...m.examples),delete m.examples);let g=c===`components`?s(r,m,n(p)):m;return{description:p,content:C.fromPairs(a.map(e=>[e,{[e===d.sse?`itemSchema`:`schema`]:g,examples:K(h)}]))}},se=({format:e})=>{let t={type:`http`,scheme:`bearer`};return e&&(t.bearerFormat=e),t},ce=({name:e},t)=>{let n={type:`apiKey`,in:`query`,name:e};return t?.includes(`body`)&&(t?.includes(`query`)?(n[`x-in-alternative`]=`body`,n.description=`${e} CAN also be supplied within the request body`):(n[`x-in-actual`]=`body`,n.description=`${e} MUST be supplied within the request body instead of query`)),n},le=({name:e})=>({type:`apiKey`,in:`header`,name:e}),ue=({name:e})=>({type:`apiKey`,in:`cookie`,name:e}),de=({url:e})=>({type:`openIdConnect`,openIdConnectUrl:e}),fe=({flows:e={},oauth2MetadataUrl:t})=>({type:`oauth2`,flows:C.map(e=>({...e,scopes:e.scopes||{}}),C.reject(C.isNil,e)),oauth2MetadataUrl:t}),pe=(e,t=[])=>{let n=e=>e.type===`basic`?{type:`http`,scheme:`basic`}:e.type===`bearer`?se(e):e.type===`input`?ce(e,t):e.type===`header`?le(e):e.type===`cookie`?ue(e):e.type===`openid`?de(e):fe(e);return e.map(e=>e.map(({deprecated:e,...t})=>({...n(t),deprecated:e})))},me=(e,t,n)=>e.map(e=>e.reduce((e,r)=>{let i=n(r),a=[`oauth2`,`openIdConnect`].includes(r.type);return Object.assign(e,{[i]:a?t:[]})},{})),Q=({schema:e,brandHandling:t,makeRef:n,path:r,method:i})=>X(e,{rules:{...t,...Y},ctx:{isResponse:!1,makeRef:n,path:r,method:i}}),he=({method:e,path:t,schema:i,request:a,mimeType:o,makeRef:s,composition:c,paramNames:l,description:u=`${e.toUpperCase()} ${t} Request body`})=>{let[f,p]=Z(a,l),m=L(f),h=[];E(m)&&m.examples&&(h.push(...m.examples),delete m.examples);let g={schema:c===`components`?s(i,m,n(u)):m,examples:K(h.length?h:x(a).examples?.filter(e=>r(e)&&!Array.isArray(e)).map(C.omit(l))||[])},_={description:u,content:{[o]:g}};return(p||o===d.raw)&&(_.required=!0),_},ge=e=>Object.entries(e).reduce((e,[t,n])=>{if(!n)return e;if(typeof n==`string`)return e.concat({name:t,description:n});let{url:r,...i}=n,a={...i,name:t};return r&&(a.externalDocs={...a.externalDocs,url:r}),e.concat(a)},[]),_e=(e,t=50)=>!e||e.length<=t?e:e.slice(0,Math.max(1,t||0)-1)+`…`,$=e=>e.length?e.slice():void 0,ve=({description:e,summary:t=e,trim:n})=>n(t);var ye=class extends w{#e=new Map;#t=new Map;#n=new Map;#r(e,t,n){let r=this.#n.get(e);if(!r){let t=+!n;do r=`${n??`Schema`}${t?this.#n.size+t:``}`,t++;while(this.rootDoc.components?.schemas?.[r]);this.#n.set(e,r)}return this.addSchema(r,t),{$ref:`#/components/schemas/${r}`}}#i(e,t,r){let i=r||n(t,e),a=this.#t.get(i);if(a===void 0)return this.#t.set(i,1),i;if(r)throw new m(`Duplicated operationId: "${r}"`,{method:t,isResponse:!1,path:e});return a++,this.#t.set(i,a),`${i}${a}`}#a(e){let t=JSON.stringify(e);for(let e in this.rootDoc.components?.securitySchemes||{})if(t===JSON.stringify(this.rootDoc.components?.securitySchemes?.[e]))return e;let n=(this.#e.get(e.type)||0)+1;return this.#e.set(e.type,n),`${e.type.toUpperCase()}_${n}`}#o({tags:e,server:t,info:n={title:`Generated by Express Zod API`,version:`0.0.0`}}){if(this.addInfo(n),e&&(this.rootDoc.tags=ge(e)),t)for(let e of Array.isArray(t)?t:[t])this.addServer(typeof e==`string`?{url:e}:e)}#s({config:e,descriptions:t,brandHandling:n,isHeader:r,summarizer:i=ve,composition:a=`inline`}){return(o,s,l)=>{let u={path:s,method:o,endpoint:l,composition:a,brandHandling:n,makeRef:this.#r.bind(this)},{description:f,summary:m,scopes:h,inputSchema:g}=l,_=c(o,e.inputSources),v=this.#i(s,o,l.getOperationId(o)),y=Q({...u,schema:g}),b=J({...u,inputSources:_,isHeader:r,securityHeaders:M(l.security,`header`),securityCookies:M(l.security,`cookie`),request:y,description:t?.requestParameter?.({method:o,path:s,operationId:v})}),x={};for(let e of p){let n=l.getResponses(e);for(let{mimeTypes:r,schema:i,statusCodes:a}of n)for(let c of a)x[c]=oe({...u,variant:e,schema:i,mimeTypes:r,statusCode:c,hasMultipleStatusCodes:n.length>1||a.length>1,description:t?.[`${e}Response`]?.({method:o,path:s,operationId:v,statusCode:c})})}let S=_.includes(`body`)?he({...u,request:y,paramNames:C.pluck(`name`,b),schema:g,mimeType:d[l.getProbableRequestType(o)],description:t?.requestBody?.({method:o,path:s,operationId:v})}):void 0,w=me(pe(A(l.security),_),h,e=>{let t=this.#a(e);return this.addSecurityScheme(t,e),t}),T={operationId:v,summary:i({summary:m,description:f,trim:_e}),description:f,deprecated:l.isDeprecated||void 0,tags:$(l.tags),parameters:$(b),requestBody:S,security:$(w),responses:x};this.addPath(te(s),{[o]:T})}}constructor({hasHeadMethod:e=!0,...t}){super(),this.#o(t);let n=this.#s(t),r=e?v(n):n;b({...t,onEndpoint:r})}};export{ye as Documentation};
|