express-zod-api 29.8.1 → 30.0.0-beta.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/CHANGELOG.md +67 -0
- package/README.md +23 -54
- package/SECURITY.md +1 -0
- package/dist/documentation-helpers-Df2sKJq9.js +1 -0
- package/dist/documentation.d.ts +12 -16
- package/dist/documentation.js +1 -1
- package/dist/{errors-Dbnie2Mk.d.ts → errors-masq5u05.d.ts} +6 -20
- package/dist/index.d.ts +179 -397
- package/dist/index.js +3 -3
- package/dist/integration.d.ts +8 -21
- package/dist/integration.js +8 -9
- package/dist/{peer-helpers-BguVeFhx.js → peer-helpers-CURhdLch.js} +1 -1
- package/dist/{routing-oAHiW3zy.d.ts → routing-BE8APxgy.d.ts} +74 -168
- package/dist/routing-walker-BpLERmiE.js +1 -0
- package/package.json +5 -5
- package/dist/documentation-helpers-C5Tkir01.js +0 -1
- package/dist/routing-walker-CelPngqp.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,74 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Version 30
|
|
4
|
+
|
|
5
|
+
### v30.0.0
|
|
6
|
+
|
|
7
|
+
- Supported Zod versions: `^4.5.4`:
|
|
8
|
+
- Using [compiled](https://zod.dev/compile) I/O schemas for Endpoint runtime performance boost.
|
|
9
|
+
- Supported TypeScript versions (optional peer): `^6.0.3`:
|
|
10
|
+
- Entirely `#private` props are now restored in the classes of the distributed `.d.ts` files.
|
|
11
|
+
- Config option `trySyncValidation` is now enabled by default (potentially breaking behavior change):
|
|
12
|
+
- First request to Endpoints having schemas with async refinements would execute those refinements twice. If those
|
|
13
|
+
refinements have side effects sensitive to the number of calls, then the option should be disabled.
|
|
14
|
+
- The `config` property on the `Middleware::execute()` argument is now required: `testMiddleware()` already handles it.
|
|
15
|
+
- `DocumentationError` removed from main entrypoint: import from `express-zod-api/documentation` instead.
|
|
16
|
+
- The rate-limiting Middleware now explicitly declares the `statusCode` that it may interrupt the request handling:
|
|
17
|
+
- Uses the `statusCode` option given to `EndpointsFactory::useRateLimit()` and `createRateLimitMiddleware()` or 429;
|
|
18
|
+
- This will override or narrow the negative status codes declared in ResultHandler when generating Documentation;
|
|
19
|
+
- The Endpoints using this Middleware should declare its other negative status codes explicitly to preserve them.
|
|
20
|
+
- The `defaultResultHandler` (and `defaultEndpointsFactory`) changed both positive and negative response schemas:
|
|
21
|
+
- The positive response is now the Endpoint output as is: the `{ status: "success", data: {...} }` wrapper removed;
|
|
22
|
+
- The negative response is now the `{ message }`: the `{ status: "error", error: {...} }` wrapper removed;
|
|
23
|
+
- Existing APIs can migrate to `legacyResultHandler` and `legacyEndpointsFactory` for preserving those wrappers.
|
|
24
|
+
- The `Integration` generator changed to discriminate responses by HTTP status code or legacy `success|error` fallback:
|
|
25
|
+
- New `EncodedResponse` interface holding payloads along with `status: number, discriminator: "success" | "error"`;
|
|
26
|
+
- New public static method `Client::discriminate(status: number): "success" | "error"`;
|
|
27
|
+
- If using a custom `Implementation`, it now has to return `Promise<{ status: number, data: any }>`;
|
|
28
|
+
- The `Client::provide()` method now returns `Promise<{ status, discriminator, data }>` for further discrimination;
|
|
29
|
+
- The basic approach is to discriminate by `status` (the actual `Response.status`);
|
|
30
|
+
- In case of receiving an unlisted status, use the `discriminator` ("success" or "error" fallback);
|
|
31
|
+
- Unlike previous versions, the `data` property always relays the original payload, including the error `{ message }`.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
const client = new Client();
|
|
35
|
+
const { status, discriminator, data } = await client.provide(
|
|
36
|
+
"get /v1/user/retrieve",
|
|
37
|
+
{ id: "10" },
|
|
38
|
+
);
|
|
39
|
+
if (status === 200)
|
|
40
|
+
console.log(data.name); // success
|
|
41
|
+
else if (status === 400 || discriminator === "error")
|
|
42
|
+
console.error(data.message); // error
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```diff
|
|
46
|
+
@@ Keeping the response wrappers: @@
|
|
47
|
+
- import { defaultEndpointsFactory } from "express-zod-api";
|
|
48
|
+
+ import { legacyEndpointsFactory } from "express-zod-api";
|
|
49
|
+
const factory = new EndpointsFactory(
|
|
50
|
+
- defaultResultHandler
|
|
51
|
+
+ legacyResultHandler
|
|
52
|
+
);
|
|
53
|
+
```
|
|
54
|
+
|
|
3
55
|
## Version 29
|
|
4
56
|
|
|
57
|
+
### v29.8.3
|
|
58
|
+
|
|
59
|
+
- Integration generator refactoring:
|
|
60
|
+
- Consolidated the production of aliases for traversing lazy and recursive schemas;
|
|
61
|
+
- Suggested and implemented by [@giovio](https://github.com/giovio).
|
|
62
|
+
|
|
63
|
+
### v29.8.2
|
|
64
|
+
|
|
65
|
+
- Fixed alias production for Integration generator:
|
|
66
|
+
- Recursive and lazy schemas that are reused both for input and output used to get the same alias;
|
|
67
|
+
- In certain cases the direction affects the generated type, for example for schemas having `.default()`;
|
|
68
|
+
- This version emits an alias for each direction for such schemas (even if they are the same);
|
|
69
|
+
- An alias that used to be named `Type1` will now be either `InputType1` or `ResponseType1` or both;
|
|
70
|
+
- The issue was found and reported by [@giovio](https://github.com/giovio).
|
|
71
|
+
|
|
5
72
|
### v29.8.1
|
|
6
73
|
|
|
7
74
|
- Fixed the `HEAD` method of the generated client to avoid parsing the response body by the default implementation.
|
package/README.md
CHANGED
|
@@ -89,6 +89,7 @@ Therefore, many basic tasks can be achieved faster and easier, in particular:
|
|
|
89
89
|
|
|
90
90
|
These people contributed to the improvement of the framework by reporting bugs, making changes, and suggesting ideas:
|
|
91
91
|
|
|
92
|
+
[<img src="https://github.com/giovio.png" alt="@giovio" width="50" />](https://github.com/giovio)
|
|
92
93
|
[<img src="https://github.com/marco-carvalho.png" alt="@marco-carvalho" width="50" />](https://github.com/marco-carvalho)
|
|
93
94
|
[<img src="https://github.com/BetterAndBetterII.png" alt="@BetterAndBetterII" width="50" />](https://github.com/BetterAndBetterII)
|
|
94
95
|
[<img src="https://github.com/pycanis.png" alt="@pycanis" width="50" />](https://github.com/pycanis)
|
|
@@ -278,7 +279,7 @@ curl -L -X GET 'localhost:8090/v1/hello?name=Rick'
|
|
|
278
279
|
You should receive the following response:
|
|
279
280
|
|
|
280
281
|
```json
|
|
281
|
-
{ "
|
|
282
|
+
{ "greetings": "Hello, Rick. Happy coding!" }
|
|
282
283
|
```
|
|
283
284
|
|
|
284
285
|
# Basic features
|
|
@@ -455,43 +456,15 @@ const factory = defaultEndpointsFactory.use(auth(), {
|
|
|
455
456
|
|
|
456
457
|
## Refinements
|
|
457
458
|
|
|
458
|
-
|
|
459
|
-
|
|
459
|
+
[Refinements](https://zod.dev/api#refinements) provide additional validations for your schemas. Albeit those functions
|
|
460
|
+
can be async, the framework attempts to parse the first request to an Endpoint synchronously, which will run such async
|
|
461
|
+
functions twice. Therefore, refinements should avoid side effects sensitive to the number of calls.
|
|
460
462
|
|
|
461
463
|
```ts
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
input: z.object({
|
|
467
|
-
nickname: z
|
|
468
|
-
.string()
|
|
469
|
-
.min(1)
|
|
470
|
-
.refine(
|
|
471
|
-
(nick) => !/^\d.*$/.test(nick),
|
|
472
|
-
"Nickname cannot start with a digit",
|
|
473
|
-
),
|
|
474
|
-
}),
|
|
475
|
-
// ...,
|
|
476
|
-
});
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
By the way, you can also refine the whole I/O object, for example, in case you need a complex validation of its props.
|
|
480
|
-
|
|
481
|
-
```ts
|
|
482
|
-
const endpoint = endpointsFactory.build({
|
|
483
|
-
input: z
|
|
484
|
-
.object({
|
|
485
|
-
email: z.email().optional(),
|
|
486
|
-
id: z.string().optional(),
|
|
487
|
-
otherThing: z.string().optional(),
|
|
488
|
-
})
|
|
489
|
-
.refine(
|
|
490
|
-
(inputs) => Object.keys(inputs).length >= 1,
|
|
491
|
-
"Please provide at least one property",
|
|
492
|
-
),
|
|
493
|
-
// ...,
|
|
494
|
-
});
|
|
464
|
+
const nickname = z
|
|
465
|
+
.string()
|
|
466
|
+
.min(1)
|
|
467
|
+
.refine((nick) => !/^\d.*$/.test(nick), "Nickname cannot start with a digit");
|
|
495
468
|
```
|
|
496
469
|
|
|
497
470
|
## Query string parser
|
|
@@ -904,11 +877,7 @@ Install `express-rate-limit`. Consider the `createRateLimitMiddleware()` to enab
|
|
|
904
877
|
|
|
905
878
|
```ts
|
|
906
879
|
const endpoint = factory
|
|
907
|
-
.useRateLimit({
|
|
908
|
-
windowMs: 60000,
|
|
909
|
-
max: 100,
|
|
910
|
-
statusCode: 429, // when set explicitly, it will be reflected in the Documentation
|
|
911
|
-
}) // shorthand, or .addMiddleware(createRateLimitMiddleware())
|
|
880
|
+
.useRateLimit({ windowMs: 60000, max: 100 }) // shorthand, or .addMiddleware(createRateLimitMiddleware())
|
|
912
881
|
.buildVoid({
|
|
913
882
|
handler: async ({ ctx: { rateLimit, logger } }) => {
|
|
914
883
|
logger.debug("Features", rateLimit); // { limit, used, remaining, resetTime, getKey, resetKey }
|
|
@@ -923,8 +892,8 @@ The `defaultResultHandler` sets the HTTP status code and ensures the following t
|
|
|
923
892
|
|
|
924
893
|
```ts
|
|
925
894
|
type DefaultResponse<OUT> =
|
|
926
|
-
|
|
|
927
|
-
| {
|
|
895
|
+
| OUT // Positive response
|
|
896
|
+
| { message: string }; // or Negative response
|
|
928
897
|
```
|
|
929
898
|
|
|
930
899
|
You can create your own result handler by using this example as a template:
|
|
@@ -938,18 +907,18 @@ import {
|
|
|
938
907
|
} from "express-zod-api";
|
|
939
908
|
|
|
940
909
|
const yourResultHandler = new ResultHandler({
|
|
941
|
-
positive: (
|
|
942
|
-
schema:
|
|
943
|
-
mimeType: "application/json", //
|
|
910
|
+
positive: (output) => ({
|
|
911
|
+
schema: output,
|
|
912
|
+
mimeType: "application/json", // optional or array
|
|
944
913
|
}),
|
|
945
|
-
negative: z.object({
|
|
914
|
+
negative: z.object({ message: z.string() }),
|
|
946
915
|
handler: ({ error, input, output, request, response, logger }) => {
|
|
947
916
|
if (error) {
|
|
948
917
|
const { statusCode } = ensureHttpError(error);
|
|
949
918
|
const message = getMessageFromError(error);
|
|
950
|
-
return void response.status(statusCode).json({
|
|
919
|
+
return void response.status(statusCode).json({ message });
|
|
951
920
|
}
|
|
952
|
-
response.status(200).json(
|
|
921
|
+
response.status(200).json(output);
|
|
953
922
|
},
|
|
954
923
|
});
|
|
955
924
|
```
|
|
@@ -1164,7 +1133,7 @@ test("should respond successfully", async () => {
|
|
|
1164
1133
|
expect(loggerMock._getLogs().error).toHaveLength(0);
|
|
1165
1134
|
expect(responseMock._getStatusCode()).toBe(200);
|
|
1166
1135
|
expect(responseMock._getHeaders()).toHaveProperty("x-custom", "one"); // lower case!
|
|
1167
|
-
expect(responseMock._getJSONData()).toEqual({
|
|
1136
|
+
expect(responseMock._getJSONData()).toEqual({ greetings: "Hello, World" });
|
|
1168
1137
|
});
|
|
1169
1138
|
```
|
|
1170
1139
|
|
|
@@ -1397,18 +1366,18 @@ response schemas and their corresponding status codes.
|
|
|
1397
1366
|
import { ResultHandler } from "express-zod-api";
|
|
1398
1367
|
|
|
1399
1368
|
const resultHandler = new ResultHandler({
|
|
1400
|
-
positive: (
|
|
1369
|
+
positive: (output) => ({
|
|
1401
1370
|
statusCode: [201, 202], // created or will be created
|
|
1402
|
-
schema:
|
|
1371
|
+
schema: output,
|
|
1403
1372
|
}),
|
|
1404
1373
|
negative: [
|
|
1405
1374
|
{
|
|
1406
1375
|
statusCode: 409, // conflict: entity already exists
|
|
1407
|
-
schema: z.object({
|
|
1376
|
+
schema: z.object({ id: z.int().describe("id of the existing entity") }),
|
|
1408
1377
|
},
|
|
1409
1378
|
{
|
|
1410
1379
|
statusCode: [400, 500], // validation or internal error
|
|
1411
|
-
schema: z.object({
|
|
1380
|
+
schema: z.object({ reason: z.string() }),
|
|
1412
1381
|
},
|
|
1413
1382
|
],
|
|
1414
1383
|
});
|
package/SECURITY.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
| Version | Code name | Release | Supported |
|
|
6
6
|
| ------: | :------------ | :------ | :----------------: |
|
|
7
|
+
| 30.x.x | Rita | 11.2026 | :white_check_mark: |
|
|
7
8
|
| 29.x.x | Angie | 08.2026 | :white_check_mark: |
|
|
8
9
|
| 28.x.x | Koko | 05.2026 | :white_check_mark: |
|
|
9
10
|
| 27.x.x | Nikki | 02.2026 | :white_check_mark: |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,F as t,G as n,I as r,J as i,K as a,N as o,R as s,S as c,Y as l,Z as u,b as d,et as f,j as p,q as m,r as h,rt as g,w as _}from"./routing-walker-BpLERmiE.js";import{z as v}from"zod";import*as y from"ramda";import{isReferenceObject as b,isSchemaObject as x}from"openapi3-ts/oas32";const ee=e=>e.type===`object`,te=y.mergeDeepWith((e,t)=>{if(Array.isArray(e)&&Array.isArray(t))return y.concat(e,t);if(e===t)return t;throw Error(`Can not flatten properties`,{cause:{a:e,b:t}})}),S=/* @__PURE__ */ new Set([`type`,`properties`,`required`,`examples`,`description`,`additionalProperties`]),C=e=>{for(let t of Object.keys(e))if(!S.has(t))return!1;return!0},w=y.pair(!0),ne=(e,t,n)=>!(`allOf`in e)||!e.allOf?[]:e.allOf.map(e=>{if(t===`throw`&&!(e.type===`object`&&C(e)))throw Error(`Can not merge`);return y.pair(n,e)}),re=e=>{let t=[];return e.anyOf&&t.push(...y.map(w,e.anyOf)),e.oneOf&&t.push(...y.map(w,e.oneOf)),t},ie=(e,t,n,r)=>{if(!m(e.propertyNames))return;let i=[];typeof e.propertyNames.const==`string`&&i.push(e.propertyNames.const),e.propertyNames.enum&&i.push(...e.propertyNames.enum.filter(e=>typeof e==`string`));let a={...Object(e.additionalProperties)};for(let e of i)t.properties[e]??=a;r||n.push(...i)},ae=(e,t,n)=>{t.examples?.length&&(e.examples=n?y.concat(e.examples||[],t.examples):s(e.examples?.filter(m)||[],t.examples.filter(m),([e,t])=>y.mergeDeepRight(e,t)))},T=(e,t=`coerce`)=>{let n=[y.pair(!1,e)],r={type:`object`,properties:{}},i=[];for(let[e,a]of n)a.description&&(r.description??=a.description),n.push(...ne(a,t,e)),n.push(...re(a)),ae(r,a,e),ee(a)&&(n.push([e,{examples:E(a)}]),a.properties&&(r.properties=(t===`throw`?te:y.mergeDeepRight)(r.properties,a.properties),!e&&a.required&&i.push(...a.required)),ie(a,r,i,e));return i.length&&(r.required=[...new Set(i)]),r},E=e=>Object.entries(e.properties||{}).reduce((e,[t,n])=>{let{examples:r=[]}=m(n)?n:{};return s(e,r.map(y.objOf(t)),([e,t])=>({...e,...t}))},[]),D=`x-coerce`,O=/* @__PURE__ */ new Set([`array`,`object`]),k=(e,t)=>{let n=[e.items,e.prefixItems,e.additionalItems,Object.values(e.properties??{}),e.additionalProperties,e.propertyNames];for(let e of n){if(!m(e))continue;let n=Array.isArray(e)?e:[e];for(let e of n)if(m(e)&&!A(e,t))return!1}return!0},A=(e,t)=>e[`x-coerce`]===!0?!0:e.anyOf?e.anyOf.some(e=>A(e,t)):e.oneOf?e.oneOf.some(e=>A(e,t)):e.allOf?e.allOf.every(e=>A(e,t)):e.type===void 0||e.type===`string`?!0:Array.isArray(e.type)?A({anyOf:e.type.map(t=>({...e,type:t}))},t):t!==`path`&&O.has(e.type)&&k(e,t);let j;const M=()=>j??=/* @__PURE__ */ new Set([`session`,`session_id`,`sessionId`]);let N;const P=()=>N??=/* @__PURE__ */ new Set(/* @__PURE__ */ `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.connect-udp-bind.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(`.`)),F={integer:0,number:0,string:``,boolean:!1,object:{},null:null,array:[]},I=e=>e.replace(u,e=>`{${e.slice(1)}}`),L=({},e)=>{if(e.isResponse)throw new _(`Please use ez.upload() only for input.`,e);return{type:`string`,format:`binary`}},R=({jsonSchema:e})=>({...e,externalDocs:{description:`raw binary data`,url:`https://swagger.io/specification/#working-with-binary-data`}}),z=({zodSchema:e,jsonSchema:t})=>{if(!i(e,`union`)||!(`discriminator`in e._zod.def))return t;let n=e._zod.def.discriminator;return{...t,discriminator:t.discriminator??{propertyName:n}}},B=y.tryCatch(({jsonSchema:e})=>{if(!e.allOf)throw`no allOf`;return T(e,`throw`)},(e,{jsonSchema:t})=>t),V=({jsonSchema:e})=>{if(!e.anyOf||!e.anyOf.length)return e;let t=e.anyOf[0];return Object.assign(t,{type:J(t.type)})},H=e=>e,U=({jsonSchema:e},t)=>{if(t.isResponse)throw new _(`Please use ez.dateOut() for output.`,t);return e},W=({jsonSchema:e},t)=>{if(!t.isResponse)throw new _(`Please use ez.dateIn() for input.`,t);return e},G=()=>({type:`string`,format:`bigint`,pattern:`^-?\\d+$`}),K=({zodSchema:e,jsonSchema:t})=>e._zod.def.rest===null?{...t,items:{not:{}}}:t,q=e=>{let t=Array.isArray(e.type)?e.type[0]:e.type;return F?.[t]},J=e=>e===`null`?e:typeof e==`string`?[e,`null`]:e&&[...new Set(e).add(`null`)],oe=/* @__PURE__ */ new Set([`number`,`string`,`boolean`]),se=({zodSchema:e,jsonSchema:t},n)=>{let r=e._zod.def[n.isResponse?`out`:`in`],o=e._zod.def[n.isResponse?`in`:`out`];if(!i(r,`transform`))return t;let s=H(Z(o,{ctx:n}));if(x(s)){if(n.isResponse){let e=a(r,q(s));if(e&&oe.has(e))return{...t,type:e}}else{let{type:e,...t}=s;return{...t,format:`${t.format||e} (preprocessed)`}}}return t},ce=({jsonSchema:e})=>{if(e.type!==`object`)return e;let t=e;return!t.properties||!(`raw`in t.properties)||!m(t.properties.raw)?e:t.properties.raw},Y=e=>e.length?y.fromPairs(y.zip(y.times(e=>`example${e+1}`,e.length),y.map(y.objOf(`dataValue`),e))):void 0,le=(e,t)=>t?.has(e)||e.startsWith(`x-`)||P().has(e),ue=(e,t)=>t?.has(e)||M().has(e),de=({method:e,path:t,security:r,inputSources:i,isHeader:a,isCookie:o})=>{let s=new Set(n(t)),c=i.includes(`query`),l=i.includes(`body`),u=i.includes(`params`),d=i.includes(`headers`),f=i.includes(`cookies`)||i.includes(`signedCookies`),p;d&&r&&(p=h(r,`header`));let m;f&&r&&(m=h(r,`cookie`));let g=n=>o?.(n,e,t)??ue(n,m);return{pathParams:s,getLocation:n=>{if(u&&s.has(n)&&s.delete(n))return`path`;if(f&&g(n))return`cookie`;if(d&&(a?.(n,e,t)??le(n,p)))return`header`;if(!l){if(c)return`query`;if(f)return`cookie`}},isQueryEnabled:c,isBodyEnabled:l}},fe=({path:e,method:t,flatRequest:n,makeRef:r,composition:i,getLocation:a,description:o=`${t.toUpperCase()} ${e} Parameter`})=>{let s=[];for(let[e,t]of Object.entries(n.properties)){if(!m(t))continue;let c=a(e);if(!c)continue;let u=H(t),d=i===`components`?r(t.id||JSON.stringify(t),u,t.id||l(o,e)):u;s.push({name:e,in:c,deprecated:t.deprecated,required:n.required?.includes(e)||c===`path`,description:u.description||o,schema:d,examples:Y(x(u)&&u.examples?.length?u.examples:y.pluck(e,n.examples?.filter(y.both(m,y.has(e)))||[]))})}return s},X={nullable:V,union:z,bigint:G,intersection:B,tuple:K,pipe:se,[o]:U,[p]:W,[c]:L,[d]:ce,[t]:R},pe=(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(y.is(Object,a)){if(b(a)&&!a.$ref.startsWith(`#/components`)){let e=a.$ref.split(`/`).pop(),r=t[e];if(r){let t=r.id||i(e);a.$ref=n.makeRef(t||r,H(r),t).$ref}continue}r.push(...y.values(a))}y.is(Array,a)&&r.push(...y.values(a))}return e},Z=(e,{ctx:t,rules:n=X})=>{let{$defs:i={},properties:a={}}=v.toJSONSchema(v.object({subject:e}),{unrepresentable:`any`,io:t.isResponse?`output`:`input`,override:e=>{let i=v.globalRegistry.get(e.zodSchema)?.id;if(i){let n=t.seenIds.get(i);if(n&&n!==e.zodSchema)throw new _(`The meta id "${i}" is used by two different schemas. Please make the ids unique or reuse the same schema instance.`,t);t.seenIds.set(i,e.zodSchema)}let a=r(e.zodSchema),o=n[a&&a in n?a:e.zodSchema._zod.def.type];if(o){let n={...o(e,t)};for(let t in e.jsonSchema)delete e.jsonSchema[t];Object.assign(e.jsonSchema,n)}}});return pe(m(a.subject)?a.subject:{},i,t)},Q=(e,t)=>{if(b(e))return[e,!1];let n=!1,r=y.map(e=>{let[r,i]=Q(e,t);return n||=i,r}),i=y.omit(t),a={properties:i,examples:y.map(i),required:y.without(t),allOf:r,oneOf:r,anyOf:r},o=y.evolve(a,e);return[o,n||!!o.required?.length]},me=({method:t,path:n,schema:r,mimeTypes:i,variant:a,makeRef:o,composition:s,hasMultipleStatusCodes:c,statusCode:u,brandHandling:d,seenIds:p,description:m=`${t.toUpperCase()} ${n} ${f(a)} response ${c?u:``}`.trim()})=>{if(!e(t,i))return{description:m};let h=H(Z(r,{rules:{...d,...X},ctx:{isResponse:!0,makeRef:o,path:n,method:t,seenIds:p}})),_=[];x(h)&&h.examples&&(_.push(...h.examples),delete h.examples);let v=s===`components`?o(r,h,l(m)):h;return{description:m,content:y.fromPairs(i.map(e=>[e,{[e===g.sse?`itemSchema`:`schema`]:v,examples:Y(_)}]))}},he=({format:e})=>{let t={type:`http`,scheme:`bearer`};return e&&(t.bearerFormat=e),t},ge=({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},_e=({name:e})=>({type:`apiKey`,in:`header`,name:e}),ve=({name:e})=>({type:`apiKey`,in:`cookie`,name:e}),ye=({url:e})=>({type:`openIdConnect`,openIdConnectUrl:e}),be=({flows:e={},oauth2MetadataUrl:t})=>({type:`oauth2`,flows:y.map(e=>({...e,scopes:e.scopes||{}}),y.reject(y.isNil,e)),oauth2MetadataUrl:t}),$=(e,t=[])=>{let n=e=>e.type===`basic`?{type:`http`,scheme:`basic`}:e.type===`bearer`?he(e):e.type===`input`?ge(e,t):e.type===`header`?_e(e):e.type===`cookie`?ve(e):e.type===`openid`?ye(e):be(e);return e.map(e=>e.map(({deprecated:e,...t})=>({...n(t),deprecated:e})))},xe=Set.prototype.has.bind(/* @__PURE__ */ new Set([`oauth2`,`openIdConnect`])),Se=(e,t,n)=>{let r=Array.from(t);return e.map(e=>{let t={};for(let i of e){let e=n(i);t[e]=xe(i.type)?r:[]}return t})},Ce=({schema:e,brandHandling:t,makeRef:n,path:r,method:i,seenIds:a})=>Z(e,{rules:{...t,...X},ctx:{isResponse:!1,makeRef:n,path:r,method:i,seenIds:a}}),we=({method:e,path:t,bodyJsonSchema:n,hasRequiredBodyProps:r,flatRequest:i,mimeType:a,makeRef:o,composition:s,paramNames:c,description:u=`${e.toUpperCase()} ${t} Request body`})=>{let d=H(n),f=[];x(d)&&d.examples&&(f.push(...d.examples),delete d.examples);let p={schema:s===`components`?o(JSON.stringify(d),d,l(u)):d,examples:Y(f.length?f:i.examples?.filter(e=>m(e)&&!Array.isArray(e)).map(y.omit(c))||[])},h={description:u,content:{[a]:p}};return(r||a===g.raw)&&(h.required=!0),h},Te=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)},[]),Ee=(e,t=50)=>!e||e.length<=t?e:e.slice(0,Math.max(1,t||0)-1)+`…`,De=e=>{let t=Array.from(e);return t.length?t:void 0};export{$ as a,Q as c,I as d,Ee as f,A as h,me as i,de as l,T as m,Ce as n,Se as o,D as p,fe as r,Te as s,we as t,De as u};
|
package/dist/documentation.d.ts
CHANGED
|
@@ -1,22 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
c as ParamRecognizer,
|
|
4
|
-
l as depictTags,
|
|
5
|
-
o as BrandHandling,
|
|
6
|
-
s as Depicter,
|
|
7
|
-
t as DocumentationError,
|
|
8
|
-
u as trimSummary,
|
|
9
|
-
} from "./errors-Dbnie2Mk.js";
|
|
1
|
+
import { N as ResponseVariant, g as CommonConfig, t as Routing } from "./routing-BE8APxgy.js";
|
|
2
|
+
import { c as ParamRecognizer, l as depictTags, o as BrandHandling, s as Depicter, t as DocumentationError, u as trimSummary } from "./errors-masq5u05.js";
|
|
10
3
|
import { z } from "zod";
|
|
11
4
|
import { InfoObject, OpenApiBuilder, SchemaObjectValue, ServerObject } from "openapi3-ts/oas32";
|
|
12
5
|
type Component = `${ResponseVariant}Response` | "requestParameter" | "requestBody";
|
|
13
6
|
/** @desc user defined function that creates a component description from its properties */
|
|
14
|
-
type Descriptor = (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
type Descriptor = (props: Record<"method" | "path" | "operationId", string> & {
|
|
8
|
+
statusCode?: number | string;
|
|
9
|
+
}) => string;
|
|
10
|
+
type Summarizer = (params: {
|
|
11
|
+
summary?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
trim: typeof trimSummary;
|
|
14
|
+
}) => string | undefined;
|
|
20
15
|
interface DocumentationParams {
|
|
21
16
|
/**
|
|
22
17
|
* @desc The metadata about the API
|
|
@@ -84,6 +79,7 @@ interface DocumentationParams {
|
|
|
84
79
|
tags?: Parameters<typeof depictTags>[0];
|
|
85
80
|
}
|
|
86
81
|
export declare class Documentation extends OpenApiBuilder {
|
|
82
|
+
#private;
|
|
87
83
|
/**
|
|
88
84
|
* @desc Resolves a ReferenceObject to the referenced schema. Non-reference input is returned as is.
|
|
89
85
|
* @see getSpec
|
|
@@ -92,4 +88,4 @@ export declare class Documentation extends OpenApiBuilder {
|
|
|
92
88
|
resolve<T extends SchemaObjectValue | z.core.JSONSchema.BaseSchema>(subject: T): T;
|
|
93
89
|
constructor({ hasHeadMethod, ...rest }: DocumentationParams);
|
|
94
90
|
}
|
|
95
|
-
export { type Depicter, DocumentationError };
|
|
91
|
+
export { type Depicter, DocumentationError };
|
package/dist/documentation.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{U as e,X as t,Y as n,i as r,n as i,p as a,rt as o,t as s,w as c}from"./routing-walker-BpLERmiE.js";import{a as l,c as u,d,f,i as p,l as m,m as h,n as g,o as _,r as v,s as y,t as b,u as x}from"./documentation-helpers-Df2sKJq9.js";import"zod";import*as S from"ramda";import{OpenApiBuilder as C,isReferenceObject as w}from"openapi3-ts/oas32";const T=e=>`${Math.floor(e/100)}XX`,E=e=>{let t=/* @__PURE__ */ new Map;for(let{schema:n,mimeTypes:r,statusCodes:i}of e){let e=r?[...r].sort().join(`,`):``,a=t.get(n)||/* @__PURE__ */ new Map;t.set(n,a);let o=a.get(e),s=new Set(i);a.set(e,{schema:n,mimeTypes:r,statusCodes:o?s.union(o.statusCodes):s})}let n=[];for(let e of t.values())for(let t of e.values())n.push(t);return n},D=({schema:e,mimeTypes:t,statusCodes:n},r)=>{let i=[],a=/* @__PURE__ */ new Map;for(let e of n){let t=T(e),n=a.get(t);a.set(t,n?(/* @__PURE__ */ new Set([e])).union(n):/* @__PURE__ */ new Set([e]))}for(let[n,o]of a){let a=r.some(e=>T(e)===n&&!o.has(e));if(o.size>1&&!a)i.push({schema:e,mimeTypes:t,statusCodes:[n]});else for(let n of o)i.push({schema:e,mimeTypes:t,statusCodes:[n]})}return i},O=({statusCodes:e})=>typeof e[0]==`string`,k=e=>{let t=S.chain(S.prop(`statusCodes`),e),n=S.chain(e=>D(e,t),E(e));return n.some(O)?n:e},A=({description:e,summary:t=e,trim:n})=>n(t);var j=class extends C{#e=/* @__PURE__ */ new Map;#t=/* @__PURE__ */ new Map;#n=/* @__PURE__ */ new Map;#r=/* @__PURE__ */ new Map;#i(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}`}}resolve(e){let t=e,n=/* @__PURE__ */ new Set;for(;w(t);){if(n.has(t.$ref))return e;n.add(t.$ref);let r=S.path(t.$ref.split(`/`).slice(1),this.rootDoc);if(!r)return e;t=r}return t}#a(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 c(`Duplicated operationId: "${r}"`,{method:t,isResponse:!1,path:e});return a++,this.#t.set(i,a),`${i}${a}`}#o(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}`}#s({tags:e,server:t,info:n={title:`Generated by Express Zod API`,version:`0.0.0`}}){if(this.addInfo(n),e&&(this.rootDoc.tags=y(e)),t)for(let e of Array.isArray(t)?t:[t])this.addServer(typeof e==`string`?{url:e}:e)}#c(e,n){if(e===`head`||!n.includes(`:`))return;let r=t(n),i=this.#r.get(r);if(i!==void 0&&i!==n)throw new c(`Path has a duplicate: the normalized path "${r}" is already registered with different parameter names at "${i}"`,{method:e,path:n,isResponse:!1});i===void 0&&this.#r.set(r,n)}#l({config:t,descriptions:n,brandHandling:i,isHeader:s,isCookie:y,hasStatusCodeRanges:C=!1,summarizer:w=A,composition:T=`inline`}){let E={composition:T,brandHandling:i,makeRef:this.#i.bind(this),seenIds:/* @__PURE__ */ new Map};return(i,T,D)=>{this.#c(i,T);let O={...E,path:T,method:i,endpoint:D},{description:A,summary:j,scopes:M,inputSchema:N,security:P}=D,F=e(i,t.inputSources),{pathParams:I,getLocation:L,isBodyEnabled:R}=m({method:i,path:T,security:P,inputSources:F,isHeader:s,isCookie:y}),z=this.#a(T,i,D.getOperationId(i)),B=g({...O,schema:N}),V=this.resolve(B),H=h(V),U=v({...O,getLocation:L,flatRequest:H,description:n?.requestParameter?.({method:i,path:T,operationId:z})});if(I.size)throw new c(`The input schema is missing the path parameter "${[...I][0]}"`,{method:i,path:T,isResponse:!1});let W={};for(let e of a){let t=D.getResponses(e),r=C?k(t):t;for(let{mimeTypes:t,schema:a,statusCodes:o}of r)for(let s of o)W[s]=p({...O,variant:e,schema:a,mimeTypes:t,statusCode:s,hasMultipleStatusCodes:r.length>1||o.length>1,description:n?.[`${e}Response`]?.({method:i,path:T,operationId:z,statusCode:s})})}let G;if(R){let e=S.pluck(`name`,U),[t,r]=u(e.length?V:B,e);G=b({...O,bodyJsonSchema:t,hasRequiredBodyProps:r,flatRequest:H,paramNames:e,mimeType:o[D.getProbableRequestType(i)],description:n?.requestBody?.({method:i,path:T,operationId:z})})}let K=_(l(r(P),F),M,e=>{let t=this.#o(e);return this.addSecurityScheme(t,e),t}),q={operationId:z,summary:w({summary:j,description:A,trim:f}),description:A,deprecated:D.isDeprecated||void 0,tags:x(D.tags),parameters:x(U),requestBody:G,security:x(K),responses:W};this.addPath(d(T),{[i]:q})}}constructor({hasHeadMethod:e=!0,...t}){super(),this.#s(t);let n=this.#l(t),r=e?i(n):n;s({...t,onEndpoint:r})}};export{j as Documentation,c as DocumentationError};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { O as ClientMethod, f as Tag, k as Method } from "./routing-BE8APxgy.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { ReferenceObject, SchemaObjectValue, TagObject } from "openapi3-ts/oas32";
|
|
4
4
|
interface ReqResCommons {
|
|
@@ -10,13 +10,10 @@ interface ReqResCommons {
|
|
|
10
10
|
interface OpenAPIContext extends ReqResCommons {
|
|
11
11
|
isResponse: boolean;
|
|
12
12
|
}
|
|
13
|
-
type Depicter = (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
},
|
|
18
|
-
oasCtx: OpenAPIContext,
|
|
19
|
-
) => z.core.JSONSchema.BaseSchema | SchemaObjectValue;
|
|
13
|
+
type Depicter = (zodCtx: {
|
|
14
|
+
zodSchema: z.core.$ZodType;
|
|
15
|
+
jsonSchema: z.core.JSONSchema.BaseSchema;
|
|
16
|
+
}, oasCtx: OpenAPIContext) => z.core.JSONSchema.BaseSchema | SchemaObjectValue;
|
|
20
17
|
/** @desc Using defaultIsHeader or defaultIsCookie when returns null or undefined */
|
|
21
18
|
type ParamRecognizer = (name: string, method: ClientMethod, path: string) => boolean | null | undefined;
|
|
22
19
|
type BrandHandling = Record<string | symbol, Depicter>;
|
|
@@ -65,15 +62,4 @@ declare class MissingPeerError extends Error {
|
|
|
65
62
|
name: string;
|
|
66
63
|
constructor(module: string);
|
|
67
64
|
}
|
|
68
|
-
export {
|
|
69
|
-
RoutingError as a,
|
|
70
|
-
ParamRecognizer as c,
|
|
71
|
-
OutputValidationError as i,
|
|
72
|
-
depictTags as l,
|
|
73
|
-
InputValidationError as n,
|
|
74
|
-
BrandHandling as o,
|
|
75
|
-
MissingPeerError as r,
|
|
76
|
-
Depicter as s,
|
|
77
|
-
DocumentationError as t,
|
|
78
|
-
trimSummary as u,
|
|
79
|
-
};
|
|
65
|
+
export { RoutingError as a, ParamRecognizer as c, OutputValidationError as i, depictTags as l, InputValidationError as n, BrandHandling as o, MissingPeerError as r, Depicter as s, DocumentationError as t, trimSummary as u };
|