UNPKG

express-zod-api

Version:

A Typescript framework to help you get an API server up and running with I/O schema validation and custom middlewares in minutes.

1 lines 12.5 kB
import{$ as e,A as t,G as n,H as r,J as i,K as a,M as o,N as s,O as c,U as l,V as u,X as d,Y as f,g as p,n as m,o as h,q as g,t as _,v,x as y,z as b}from"./routing-walker-DjzH37jF.js";import{t as x}from"./json-schema-helpers-BY2DVvm4.js";import{n as S,t as C}from"./security-ts3gqWG9.js";import{z as w}from"zod";import*as T from"ramda";import{OpenApiBuilder as E,isReferenceObject as D,isSchemaObject as O}from"openapi3-ts/oas32";let k;const A=()=>k??=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(`.`)),j={integer:0,number:0,string:``,boolean:!1,object:{},null:null,array:[]},ee=e=>e.replace(i,e=>`{${e.slice(1)}}`),M=({},e)=>{if(e.isResponse)throw new y(`Please use ez.upload() only for input.`,e);return{type:`string`,format:`binary`}},te=({jsonSchema:e})=>({...e,externalDocs:{description:`raw binary data`,url:`https://swagger.io/specification/#working-with-binary-data`}}),ne=({zodSchema:e,jsonSchema:t})=>{if(!n(e,`union`)||!(`discriminator`in e._zod.def))return t;let r=e._zod.def.discriminator;return{...t,discriminator:t.discriminator??{propertyName:r}}},N=T.tryCatch(({jsonSchema:e})=>{if(!e.allOf)throw`no allOf`;return x(e,`throw`)},(e,{jsonSchema:t})=>t),P=({jsonSchema:e})=>{if(!e.anyOf||!e.anyOf.length)return e;let t=e.anyOf[0];return Object.assign(t,{type:V(t.type)})},F=e=>e,I=({jsonSchema:e},t)=>{if(t.isResponse)throw new y(`Please use ez.dateOut() for output.`,t);return e},L=({jsonSchema:e},t)=>{if(!t.isResponse)throw new y(`Please use ez.dateIn() for input.`,t);return e},R=()=>({type:`string`,format:`bigint`,pattern:`^-?\\d+$`}),z=({zodSchema:e,jsonSchema:t})=>e._zod.def.rest===null?{...t,items:{not:{}}}:t,B=e=>{let t=Array.isArray(e.type)?e.type[0]:e.type;return j?.[t]},V=e=>e===`null`?e:typeof e==`string`?[e,`null`]:e&&[...new Set(e).add(`null`)],H=({zodSchema:e,jsonSchema:t},i)=>{let a=e._zod.def[i.isResponse?`out`:`in`],o=e._zod.def[i.isResponse?`in`:`out`];if(!n(a,`transform`))return t;let s=F(Y(o,{ctx:i}));if(O(s))if(i.isResponse){let e=r(a,B(s));if(e&&[`number`,`string`,`boolean`].includes(e))return{...t,type:e}}else{let{type:e,...t}=s;return{...t,format:`${t.format||e} (preprocessed)`}}return t},U=({jsonSchema:e})=>{if(e.type!==`object`)return e;let t=e;return!t.properties||!(`raw`in t.properties)||!l(t.properties.raw)?e:t.properties.raw},W=e=>e.length?T.fromPairs(T.zip(T.times(e=>`example${e+1}`,e.length),T.map(T.objOf(`dataValue`),e))):void 0,G=(e,t)=>t?.has(e)||e.startsWith(`x-`)||A().has(e),K=({path:e,method:t,request:n,inputSources:r,makeRef:i,composition:o,isHeader:s,securityHeaders:c,securityCookies:d,description:f=`${t.toUpperCase()} ${e} Parameter`})=>{let p=x(n),m=new Set(u(e)),h=r.includes(`query`),g=r.includes(`params`),_=r.includes(`headers`),v=r.includes(`cookies`)||r.includes(`signedCookies`),b=n=>{if(g&&m.has(n)&&m.delete(n))return`path`;if(v&&d?.has(n))return`cookie`;if(_&&(s?.(n,t,e)??G(n,c)))return`header`;if(h&&t!==`query`)return`query`},S=Object.entries(p.properties).reduce((e,[t,n])=>{if(!l(n))return e;let r=b(t);if(!r)return e;let s=F(n),c=o===`components`?i(n.id||JSON.stringify(n),s,n.id||a(f,t)):s;return e.concat({name:t,in:r,deprecated:n.deprecated,required:p.required?.includes(t)||!1,description:s.description||f,schema:c,examples:W(O(s)&&s.examples?.length?s.examples:T.pluck(t,p.examples?.filter(T.both(l,T.has(t)))||[]))})},[]);if(m.size)throw new y(`The input schema is missing the path parameter "${[...m][0]}"`,{method:t,path:e,isResponse:!1});return S},q={nullable:P,union:ne,bigint:R,intersection:N,tuple:z,pipe:H,[t]:I,[c]:L,[v]:M,[p]:U,[o]:te},J=(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(T.is(Object,a)){if(D(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,F(r),t).$ref}continue}r.push(...T.values(a))}T.is(Array,a)&&r.push(...T.values(a))}return e},Y=(e,{ctx:t,rules:n=q})=>{let{$defs:r={},properties:i={}}=w.toJSONSchema(w.object({subject:e}),{unrepresentable:`any`,io:t.isResponse?`output`:`input`,override:e=>{let r=w.globalRegistry.get(e.zodSchema)?.id;if(r){let n=t.seenIds.get(r);if(n&&n!==e.zodSchema)throw new y(`The meta id "${r}" is used by two different schemas. Please make the ids unique or reuse the same schema instance.`,t);t.seenIds.set(r,e.zodSchema)}let i=s(e.zodSchema),a=n[i&&i in n?i:e.zodSchema._zod.def.type];if(a){let n={...a(e,t)};for(let t in e.jsonSchema)delete e.jsonSchema[t];Object.assign(e.jsonSchema,n)}}});return J(l(i.subject)?i.subject:{},r,t)},X=(e,t)=>{if(D(e))return[e,!1];let n=!1,r=T.map(e=>{let[r,i]=X(e,t);return n||=i,r}),i=T.omit(t),a={properties:i,examples:T.map(i),required:T.without(t),allOf:r,oneOf:r,anyOf:r},o=T.evolve(a,e);return[o,n||!!o.required?.length]},Z=({method:t,path:n,schema:r,mimeTypes:i,variant:o,makeRef:s,composition:c,hasMultipleStatusCodes:l,statusCode:u,brandHandling:p,seenIds:m,description:h=`${t.toUpperCase()} ${n} ${d(o)} response ${l?u:``}`.trim()})=>{if(!f(t,i))return{description:h};let g=F(Y(r,{rules:{...p,...q},ctx:{isResponse:!0,makeRef:s,path:n,method:t,seenIds:m}})),_=[];O(g)&&g.examples&&(_.push(...g.examples),delete g.examples);let v=c===`components`?s(r,g,a(h)):g;return{description:h,content:T.fromPairs(i.map(t=>[t,{[t===e.sse?`itemSchema`:`schema`]:v,examples:W(_)}]))}},re=({format:e})=>{let t={type:`http`,scheme:`bearer`};return e&&(t.bearerFormat=e),t},ie=({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},ae=({name:e})=>({type:`apiKey`,in:`header`,name:e}),oe=({name:e})=>({type:`apiKey`,in:`cookie`,name:e}),se=({url:e})=>({type:`openIdConnect`,openIdConnectUrl:e}),Q=({flows:e={},oauth2MetadataUrl:t})=>({type:`oauth2`,flows:T.map(e=>({...e,scopes:e.scopes||{}}),T.reject(T.isNil,e)),oauth2MetadataUrl:t}),ce=(e,t=[])=>{let n=e=>e.type===`basic`?{type:`http`,scheme:`basic`}:e.type===`bearer`?re(e):e.type===`input`?ie(e,t):e.type===`header`?ae(e):e.type===`cookie`?oe(e):e.type===`openid`?se(e):Q(e);return e.map(e=>e.map(({deprecated:e,...t})=>({...n(t),deprecated:e})))},le=(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:[]})},{})),ue=({schema:e,brandHandling:t,makeRef:n,path:r,method:i,seenIds:a})=>Y(e,{rules:{...t,...q},ctx:{isResponse:!1,makeRef:n,path:r,method:i,seenIds:a}}),de=({method:t,path:n,schema:r,request:i,mimeType:o,makeRef:s,composition:c,paramNames:u,description:d=`${t.toUpperCase()} ${n} Request body`})=>{let[f,p]=X(i,u),m=F(f),h=[];O(m)&&m.examples&&(h.push(...m.examples),delete m.examples);let g={schema:c===`components`?s(r,m,a(d)):m,examples:W(h.length?h:x(i).examples?.filter(e=>l(e)&&!Array.isArray(e)).map(T.omit(u))||[])},_={description:d,content:{[o]:g}};return(p||o===e.raw)&&(_.required=!0),_},fe=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)},[]),pe=(e,t=50)=>!e||e.length<=t?e:e.slice(0,Math.max(1,t||0)-1)+`…`,$=e=>e.length?e.slice():void 0,me=({description:e,summary:t=e,trim:n})=>n(t);var he=class extends E{#e=new Map;#t=new Map;#n=new Map;#r=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}`}}#a(e,t,n){let r=n||a(t,e),i=this.#t.get(r);if(i===void 0)return this.#t.set(r,1),r;if(n)throw new y(`Duplicated operationId: "${n}"`,{method:t,isResponse:!1,path:e});return i++,this.#t.set(r,i),`${r}${i}`}#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=fe(e)),t)for(let e of Array.isArray(t)?t:[t])this.addServer(typeof e==`string`?{url:e}:e)}#c(e,t){if(e===`head`||!t.includes(`:`))return;let n=g(t),r=this.#r.get(n);if(r!==void 0&&r!==t)throw new y(`Path has a duplicate: the normalized path "${n}" is already registered with different parameter names at "${r}"`,{method:e,path:t,isResponse:!1});r===void 0&&this.#r.set(n,t)}#l({config:t,descriptions:n,brandHandling:r,isHeader:i,summarizer:a=me,composition:o=`inline`}){let s={composition:o,brandHandling:r,makeRef:this.#i.bind(this),seenIds:new Map};return(r,o,c)=>{this.#c(r,o);let l={...s,path:o,method:r,endpoint:c},{description:u,summary:d,scopes:f,inputSchema:p}=c,m=b(r,t.inputSources),g=this.#a(o,r,c.getOperationId(r)),_=ue({...l,schema:p}),v=K({...l,inputSources:m,isHeader:i,securityHeaders:C(c.security,`header`),securityCookies:C(c.security,`cookie`),request:_,description:n?.requestParameter?.({method:r,path:o,operationId:g})}),y={};for(let e of h){let t=c.getResponses(e);for(let{mimeTypes:i,schema:a,statusCodes:s}of t)for(let c of s)y[c]=Z({...l,variant:e,schema:a,mimeTypes:i,statusCode:c,hasMultipleStatusCodes:t.length>1||s.length>1,description:n?.[`${e}Response`]?.({method:r,path:o,operationId:g,statusCode:c})})}let x=m.includes(`body`)?de({...l,request:_,paramNames:T.pluck(`name`,v),schema:p,mimeType:e[c.getProbableRequestType(r)],description:n?.requestBody?.({method:r,path:o,operationId:g})}):void 0,w=le(ce(S(c.security),m),f,e=>{let t=this.#o(e);return this.addSecurityScheme(t,e),t}),E={operationId:g,summary:a({summary:d,description:u,trim:pe}),description:u,deprecated:c.isDeprecated||void 0,tags:$(c.tags),parameters:$(v),requestBody:x,security:$(w),responses:y};this.addPath(ee(o),{[r]:E})}}constructor({hasHeadMethod:e=!0,...t}){super(),this.#s(t);let n=this.#l(t),r=e?m(n):n;_({...t,onEndpoint:r})}};export{he as Documentation};