express-zod-api 29.8.2 → 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 +58 -0
- package/README.md +21 -53
- 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,65 @@
|
|
|
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
|
+
|
|
5
63
|
### v29.8.2
|
|
6
64
|
|
|
7
65
|
- Fixed alias production for Integration generator:
|
package/README.md
CHANGED
|
@@ -279,7 +279,7 @@ curl -L -X GET 'localhost:8090/v1/hello?name=Rick'
|
|
|
279
279
|
You should receive the following response:
|
|
280
280
|
|
|
281
281
|
```json
|
|
282
|
-
{ "
|
|
282
|
+
{ "greetings": "Hello, Rick. Happy coding!" }
|
|
283
283
|
```
|
|
284
284
|
|
|
285
285
|
# Basic features
|
|
@@ -456,43 +456,15 @@ const factory = defaultEndpointsFactory.use(auth(), {
|
|
|
456
456
|
|
|
457
457
|
## Refinements
|
|
458
458
|
|
|
459
|
-
|
|
460
|
-
|
|
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.
|
|
461
462
|
|
|
462
463
|
```ts
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
input: z.object({
|
|
468
|
-
nickname: z
|
|
469
|
-
.string()
|
|
470
|
-
.min(1)
|
|
471
|
-
.refine(
|
|
472
|
-
(nick) => !/^\d.*$/.test(nick),
|
|
473
|
-
"Nickname cannot start with a digit",
|
|
474
|
-
),
|
|
475
|
-
}),
|
|
476
|
-
// ...,
|
|
477
|
-
});
|
|
478
|
-
```
|
|
479
|
-
|
|
480
|
-
By the way, you can also refine the whole I/O object, for example, in case you need a complex validation of its props.
|
|
481
|
-
|
|
482
|
-
```ts
|
|
483
|
-
const endpoint = endpointsFactory.build({
|
|
484
|
-
input: z
|
|
485
|
-
.object({
|
|
486
|
-
email: z.email().optional(),
|
|
487
|
-
id: z.string().optional(),
|
|
488
|
-
otherThing: z.string().optional(),
|
|
489
|
-
})
|
|
490
|
-
.refine(
|
|
491
|
-
(inputs) => Object.keys(inputs).length >= 1,
|
|
492
|
-
"Please provide at least one property",
|
|
493
|
-
),
|
|
494
|
-
// ...,
|
|
495
|
-
});
|
|
464
|
+
const nickname = z
|
|
465
|
+
.string()
|
|
466
|
+
.min(1)
|
|
467
|
+
.refine((nick) => !/^\d.*$/.test(nick), "Nickname cannot start with a digit");
|
|
496
468
|
```
|
|
497
469
|
|
|
498
470
|
## Query string parser
|
|
@@ -905,11 +877,7 @@ Install `express-rate-limit`. Consider the `createRateLimitMiddleware()` to enab
|
|
|
905
877
|
|
|
906
878
|
```ts
|
|
907
879
|
const endpoint = factory
|
|
908
|
-
.useRateLimit({
|
|
909
|
-
windowMs: 60000,
|
|
910
|
-
max: 100,
|
|
911
|
-
statusCode: 429, // when set explicitly, it will be reflected in the Documentation
|
|
912
|
-
}) // shorthand, or .addMiddleware(createRateLimitMiddleware())
|
|
880
|
+
.useRateLimit({ windowMs: 60000, max: 100 }) // shorthand, or .addMiddleware(createRateLimitMiddleware())
|
|
913
881
|
.buildVoid({
|
|
914
882
|
handler: async ({ ctx: { rateLimit, logger } }) => {
|
|
915
883
|
logger.debug("Features", rateLimit); // { limit, used, remaining, resetTime, getKey, resetKey }
|
|
@@ -924,8 +892,8 @@ The `defaultResultHandler` sets the HTTP status code and ensures the following t
|
|
|
924
892
|
|
|
925
893
|
```ts
|
|
926
894
|
type DefaultResponse<OUT> =
|
|
927
|
-
|
|
|
928
|
-
| {
|
|
895
|
+
| OUT // Positive response
|
|
896
|
+
| { message: string }; // or Negative response
|
|
929
897
|
```
|
|
930
898
|
|
|
931
899
|
You can create your own result handler by using this example as a template:
|
|
@@ -939,18 +907,18 @@ import {
|
|
|
939
907
|
} from "express-zod-api";
|
|
940
908
|
|
|
941
909
|
const yourResultHandler = new ResultHandler({
|
|
942
|
-
positive: (
|
|
943
|
-
schema:
|
|
910
|
+
positive: (output) => ({
|
|
911
|
+
schema: output,
|
|
944
912
|
mimeType: "application/json", // optional or array
|
|
945
913
|
}),
|
|
946
|
-
negative: z.object({
|
|
914
|
+
negative: z.object({ message: z.string() }),
|
|
947
915
|
handler: ({ error, input, output, request, response, logger }) => {
|
|
948
916
|
if (error) {
|
|
949
917
|
const { statusCode } = ensureHttpError(error);
|
|
950
918
|
const message = getMessageFromError(error);
|
|
951
|
-
return void response.status(statusCode).json({
|
|
919
|
+
return void response.status(statusCode).json({ message });
|
|
952
920
|
}
|
|
953
|
-
response.status(200).json(
|
|
921
|
+
response.status(200).json(output);
|
|
954
922
|
},
|
|
955
923
|
});
|
|
956
924
|
```
|
|
@@ -1165,7 +1133,7 @@ test("should respond successfully", async () => {
|
|
|
1165
1133
|
expect(loggerMock._getLogs().error).toHaveLength(0);
|
|
1166
1134
|
expect(responseMock._getStatusCode()).toBe(200);
|
|
1167
1135
|
expect(responseMock._getHeaders()).toHaveProperty("x-custom", "one"); // lower case!
|
|
1168
|
-
expect(responseMock._getJSONData()).toEqual({
|
|
1136
|
+
expect(responseMock._getJSONData()).toEqual({ greetings: "Hello, World" });
|
|
1169
1137
|
});
|
|
1170
1138
|
```
|
|
1171
1139
|
|
|
@@ -1398,18 +1366,18 @@ response schemas and their corresponding status codes.
|
|
|
1398
1366
|
import { ResultHandler } from "express-zod-api";
|
|
1399
1367
|
|
|
1400
1368
|
const resultHandler = new ResultHandler({
|
|
1401
|
-
positive: (
|
|
1369
|
+
positive: (output) => ({
|
|
1402
1370
|
statusCode: [201, 202], // created or will be created
|
|
1403
|
-
schema:
|
|
1371
|
+
schema: output,
|
|
1404
1372
|
}),
|
|
1405
1373
|
negative: [
|
|
1406
1374
|
{
|
|
1407
1375
|
statusCode: 409, // conflict: entity already exists
|
|
1408
|
-
schema: z.object({
|
|
1376
|
+
schema: z.object({ id: z.int().describe("id of the existing entity") }),
|
|
1409
1377
|
},
|
|
1410
1378
|
{
|
|
1411
1379
|
statusCode: [400, 500], // validation or internal error
|
|
1412
|
-
schema: z.object({
|
|
1380
|
+
schema: z.object({ reason: z.string() }),
|
|
1413
1381
|
},
|
|
1414
1382
|
],
|
|
1415
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 };
|