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.
7 lines (6 loc) • 22.1 kB
JavaScript
import{A as e,B as t,C as n,D as r,E as i,H as a,K as o,L as s,M as c,O as l,P as u,S as ee,T as d,U as f,V as te,Z as p,_ as m,a as ne,b as re,c as h,d as ie,f as g,g as _,h as v,k as ae,l as y,m as oe,nt as b,o as se,p as ce,s as x,t as le,tt as ue,u as S,w as de,z as C}from"./routing-walker-BXNIeg98.js";import{t as w}from"./peer-helpers-CtlzIbux.js";import{h as fe,l as pe,m as me,p as he}from"./documentation-helpers-BzgqHykt.js";import{globalRegistry as T,z as E}from"zod";import*as ge from"ramda";import D,{isHttpError as _e}from"http-errors";import ve,{blue as ye,cyanBright as be,green as xe,hex as O,italic as Se,red as Ce,whiteBright as k}from"ansis";import{inspect as we}from"node:util";import{performance as A}from"node:perf_hooks";import j from"express";import Te from"node:http";import Ee from"node:https";import{setInterval as De}from"node:timers/promises";import{createRequest as Oe,createResponse as ke}from"node-mocks-http";function Ae(e){return e}const je=(e,t)=>e&&t?e.and(t):e||t,Me=(e,t)=>e?e.and(t):t;var Ne=class e{#e;constructor(e){this.#e=e}async execute(...n){try{return await this.#e(...n)}catch(r){let{response:i,logger:a,error:o}=n[0],s=new l(t(r),o||void 0);e.lastResort({response:i,logger:a,error:s})}}static lastResort({error:e,logger:t,response:n}){t.error(`Result handler failure`,e);let r=h(D(500,`An error occurred while serving the result: ${e.message}.`+(e.handled?`\nOriginal error: ${e.handled.message}.`:``),{expose:_e(e.cause)?e.cause.expose:!1}));n.status(500).type(`text/plain`).end(r)}},M=class extends Ne{#e;#t;constructor(e){super(e.handler),this.#e=e.positive,this.#t=e.negative}getPositiveResponse(e){return S(this.#e,{variant:`positive`,args:[e]})}getNegativeResponse(){return S(this.#t,{variant:`negative`,args:[]})}};const N=E.object({status:E.literal(`error`),error:E.object({message:E.string()})});T.add(N,{examples:[{status:`error`,error:{message:`Sample error message`}}]});const P=new M({positive:e=>{let t=E.object({status:E.literal(`success`),data:e}),n=s(e);return n.length&&T.add(t,{examples:n.map(e=>({status:`success`,data:e}))}),t},negative:N,handler:({error:e,input:t,output:n,request:r,response:i,logger:a})=>{if(e){let n=x(e);y(n,a,r,t),i.status(n.statusCode).set(n.headers).json({status:`error`,error:{message:h(n)}});return}i.status(g.positive).json({status:`success`,data:n})}}),F=E.string();T.add(F,{examples:[`Sample error message`]});const I=new M({positive:e=>{let t=e instanceof E.ZodObject&&`items`in e.shape&&e.shape.items instanceof E.ZodArray?e.shape.items:E.array(E.any());if(s(t).length)return t;let n=s(e).filter(e=>o(e)&&`items`in e&&Array.isArray(e.items)).map(e=>e.items);if(n?.length){let e=t.meta();T.remove(t).add(t,{...e,examples:n})}return t},negative:{schema:F,mimeType:`text/plain`},handler:({response:e,output:t,error:n,logger:r,request:i,input:a})=>{if(n){let t=x(n);y(t,r,i,a),e.status(t.statusCode).type(`text/plain`).send(h(t));return}if(`items`in t&&Array.isArray(t.items)){e.status(g.positive).json(t.items);return}throw Error(`Property 'items' is missing in the endpoint output`)}}),L=e=>{let t=[];return e.scope&&t.push(e.scope),e.noStore&&t.push(`no-store`),e.noCache&&t.push(`no-cache`),e.maxAge!==void 0&&t.push(`max-age=${e.maxAge}`),e.sMaxAge!==void 0&&t.push(`s-maxage=${e.sMaxAge}`),e.mustRevalidate&&t.push(`must-revalidate`),e.proxyRevalidate&&t.push(`proxy-revalidate`),e.mustUnderstand&&t.push(`must-understand`),e.immutable&&t.push(`immutable`),e.noTransform&&t.push(`no-transform`),e.staleWhileRevalidate!==void 0&&t.push(`stale-while-revalidate=${e.staleWhileRevalidate}`),e.staleIfError!==void 0&&t.push(`stale-if-error=${e.staleIfError}`),t.join(`, `)},Pe={"max-age":`maxAge`,"max-stale":`maxStale`,"min-fresh":`minFresh`,"stale-if-error":`staleIfError`},Fe={"no-cache":`noCache`,"no-store":`noStore`,"no-transform":`noTransform`,"only-if-cached":`onlyIfCached`},Ie=e=>{if(!e)return;let t={};for(let n of e.toLowerCase().split(`,`)){let[e,r]=n.split(`=`),i=Pe[e.trim()];if(i){let e=parseInt(r?.trim()??``,10);isNaN(e)||(t[i]=e);continue}let a=Fe[e.trim()];a&&(t[a]=!0)}return t},R=e=>new v({handler:async({request:t,response:n})=>(e&&n.setHeader(`Cache-Control`,L(e)),{get ifNoneMatch(){let e=t.headers[`if-none-match`];if(!e)return;let n=e.trim();return n===`*`?n:n.split(`,`).map(e=>e.trim().replace(/^(?:W\/)?"/,``).replace(/"$/,``))},get ifModifiedSince(){let e=t.headers[`if-modified-since`];if(!e)return;let n=new Date(e);return isNaN(n.getTime())?void 0:n},get cacheControl(){return Ie(t.headers[`cache-control`])},addCachePolicy:t=>{n.setHeader(`Cache-Control`,L({...e,...t}))},setETag:e=>{n.setHeader(`ETag`,e.startsWith(`"`)?e:`"${e}"`)},setLastModified:e=>{n.setHeader(`Last-Modified`,e.toUTCString())},setVary:(...e)=>{n.setHeader(`Vary`,e.join(`, `))},setExpires:e=>{n.setHeader(`Expires`,e.toUTCString())},clearSiteData:()=>{n.setHeader(`Clear-Site-Data`,`"cache"`)},notModified:()=>{n.status(304).end()}})}),z=e=>new v({handler:async({request:t,response:n})=>({getCookie:e=>t.signedCookies?.[e]??t.cookies?.[e],setCookie:(t,r,i)=>{n.cookie(t,r,{...e,...i})},clearCookie:(t,r)=>{n.clearCookie(t,{...e,...r})}})}),B=e=>{let t=w(`express-rate-limit`)({statusCode:429,...e,handler:(e,t,n,r)=>{n(D(r.statusCode,r.message))}}),{getKey:n,resetKey:r}=t,i={getKey:n,resetKey:r};return new oe(t,{statusCode:e?.statusCode,provider:t=>({rateLimit:{...i,...t[e?.requestPropertyName??`rateLimit`]}})})};var V=class e{resultHandler;schema=void 0;statusCodes=new Set;middlewares=[];constructor(e){this.resultHandler=e}#e(t){let n=new e(this.resultHandler);return n.middlewares=this.middlewares.concat(t),n.schema=je(this.schema,t.schema),n.statusCodes=this.statusCodes.union(t.statusCodes),n}addMiddleware(e){return this.#e(e instanceof v?e:new v(e))}useCookies(...e){return this.#e(z(...e))}useCache(...e){return this.#e(R(...e))}useRateLimit(...e){return this.#e(B(...e))}use=this.addExpressMiddleware;addExpressMiddleware(...e){return this.#e(new oe(...e))}addContext(e){return this.#e(new v({handler:({ctx:t})=>e(t)}))}build({input:e=C,output:t,operationId:n,scope:r,tag:i,method:a,statusCode:o,...s}){let{middlewares:c,resultHandler:l}=this,u=new _(typeof a==`string`?[a]:a),ee=typeof n==`function`?n:e=>n&&`${n}${e===`head`?`__HEAD`:``}`,d=new _(typeof r==`string`?[r]:r),f=new _(typeof i==`string`?[i]:i);return new se({...s,middlewares:c,outputSchema:t,resultHandler:l,scopes:d,tags:f,methods:u,getOperationId:ee,inputSchema:Me(this.schema,e),statusCodes:this.statusCodes.union(new Set(typeof o==`number`?[o]:o||[]))})}buildVoid({handler:e,...t}){return this.build({...t,output:C,handler:async t=>(await e(t),{})})}};const Le=new V(P),Re=new V(I),H={debug:ye,info:xe,warn:O(`#FFA500`),error:Ce,ctx:be},U={debug:10,info:20,warn:30,error:40},ze=e=>o(e)&&Object.keys(U).some(t=>t in e),W=e=>e in U,Be=(e,t)=>U[e]<U[t],G=ge.memoizeWith((e,t)=>`${e}${t}`,(e,t=0)=>Intl.NumberFormat(void 0,{useGrouping:!1,minimumFractionDigits:0,maximumFractionDigits:t,style:`unit`,unitDisplay:`long`,unit:e})),Ve=e=>e<1e-6?G(`nanosecond`,3).format(e/1e-6):e<.001?G(`nanosecond`).format(e/1e-6):e<1?G(`microsecond`).format(e/.001):e<1e3?G(`millisecond`).format(e):e<6e4?G(`second`,2).format(e/1e3):G(`minute`,2).format(e/6e4);var K=class e{config;constructor({color:e=ve.isSupported(),level:t=p.isProduction?`warn`:`debug`,depth:n=2,ctx:r={}}={}){this.config={color:e,level:t,depth:n,ctx:r}}format(e){let{depth:t,color:n,level:r}=this.config;return we(e,{depth:t,colors:n,breakLength:r===`debug`?80:1/0,compact:r!==`debug`||3})}print(e,t,n){let{level:r,ctx:{requestId:i,...a},color:o}=this.config;if(r===`silent`||Be(e,r))return;let s=[new Date().toISOString()];i&&s.push(o?H.ctx(i):i),s.push(o?`${H[e](e)}:`:`${e}:`,t),n!==void 0&&s.push(this.format(n)),Object.keys(a).length>0&&s.push(this.format(a)),console.log(s.join(` `))}debug(e,t){this.print(`debug`,e,t)}info(e,t){this.print(`info`,e,t)}warn(e,t){this.print(`warn`,e,t)}error(e,t){this.print(`error`,e,t)}child(t){return new e({...this.config,ctx:t})}get ctx(){return this.config.ctx}profile(e){let t=A.now();return()=>{let n=A.now()-t,{message:r,severity:i=`debug`,formatter:a=Ve}=typeof e==`object`?e:{message:e};this.print(typeof i==`function`?i(n):i,r,a(n))}}},He=class{logger;config;#e=new WeakMap;constructor(e,t){this.logger=e,this.config=t}#t(e,t,n){if(!e.isSchemaChecked){for(let e of[`input`,`output`]){let r=[E.toJSONSchema(t[`${e}Schema`],{unrepresentable:`any`})];for(let t of r){t.type&&t.type!==`object`&&this.logger.warn(`Endpoint ${e} schema is not object-based`,n);for(let e of[`allOf`,`oneOf`,`anyOf`])t[e]&&r.push(...t[e])}}if(t.getProbableRequestType()===`json`){let e=m(t.inputSchema,`input`);e&&this.logger.warn(`The final input schema of the endpoint contains an unsupported JSON payload type.`,{...n,reason:e})}for(let e of ce)for(let{mimeTypes:r,schema:i}of t.getResponses(e)){if(!r?.includes(b.json))continue;let t=m(i,`output`);t&&this.logger.warn(`The final ${e} response schema of the endpoint contains an unsupported JSON payload type.`,{...n,reason:t})}e.isSchemaChecked=!0}}#n(e,t,n,r,i){if(e.paths.has(r))return;let{pathParams:s,getLocation:c,isQueryEnabled:l}=pe({method:n,path:r,security:t.security,inputSources:a(n,this.config.inputSources)});if(!(s.size===0&&!l)){e.flat??=me(E.toJSONSchema(t.inputSchema,{unrepresentable:`any`,io:`input`,override:({zodSchema:e,jsonSchema:t})=>{(e._zod.traits.has(`$ZodPreprocess`)||`coerce`in e._zod.def&&e._zod.def.coerce)&&(t[he]=!0)}}));for(let[t,n]of Object.entries(e.flat.properties)){if(!o(n))continue;let r=c(t);(r===`path`||r===`query`)&&(r===`path`&&!e.flat.required?.includes(t)&&this.logger.warn(`The path parameter "${t}" is declared optional in the input schema, but path parameters are always required since Express matches the route only when the segment is present.`,{...i,name:t}),!fe(n,r)&&this.logger.warn(`The ${r} parameter "${t}" has a schema that most likely would not accept the parsed data, ${r===`path`?`since path parameters always arrive as strings`:`depending on the "queryParser" config option`}. Convert the parsed value from "z.string()" using ".transform()" method, or use "z.coerce" at least.`,{...i,name:t,jsonSchema:n}))}for(let e of s)this.logger.warn(`The input schema of the endpoint is most likely missing the parameter of the path it's assigned to.`,{...i,param:e});e.paths.add(r)}}check=(e,t,n)=>{let r=this.#e.get(n);r||(r={isSchemaChecked:!1,paths:new Set},this.#e.set(n,r)),this.#t(r,n,{method:e,path:t}),this.#n(r,n,e,t,{method:e,path:t})}};const q=e=>e.sort((e,t)=>ue(t)-+ue(e)||e.localeCompare(t)).join(`, `).toUpperCase(),Ue=e=>({method:t},n,r)=>{let i=q(e);n.set({Allow:i}),r(D(405,`${t} is not allowed`,{headers:{Allow:i}}))},We=({app:e,getLogger:t,config:n,routing:r})=>{let i=p.isProduction?void 0:new He(t(),n),a=new Map;return le({routing:r,config:n,onEndpoint:(e,t,r)=>{i?.check(e,t,r),a.has(t)||a.set(t,new Map(n.cors?[[`options`,r]]:[])),a.get(t)?.set(e,r)},onStatic:e.use.bind(e)}),a},J=({app:e,config:t,getLogger:n,...r})=>{let i=We({app:e,getLogger:n,config:t,...r}),a=new Map;for(let[r,o]of i){let i=Array.from(o.keys());i.includes(`get`)&&i.push(`head`);for(let[a,s]of o){let o=[];t.cors&&o.push((e,t,n)=>{t.set(`Access-Control-Allow-Methods`,q(i)),n()}),o.push(async(e,r)=>{let i=n(e);return s.execute({request:e,response:r,logger:i,config:t})}),e[a]?.(r,...o)}t.hintAllowedMethods!==!1&&a.set(r,Ue(i))}for(let[t,n]of a)e.all(t,n)},Ge=e=>`_httpMessage`in e&&typeof e._httpMessage==`object`&&e._httpMessage!==null&&`headersSent`in e._httpMessage&&typeof e._httpMessage.headersSent==`boolean`&&`setHeader`in e._httpMessage&&typeof e._httpMessage.setHeader==`function`,Ke=e=>`server`in e&&typeof e.server==`object`&&e.server!==null&&`close`in e.server&&typeof e.server.close==`function`,qe=e=>`encrypted`in e&&typeof e.encrypted==`boolean`&&e.encrypted,Je=({},e)=>void(!e.headersSent&&e.setHeader(`connection`,`close`)),Ye=e=>{let{promise:t,resolve:n,reject:r}=Promise.withResolvers();return e.close(e=>e?r(e):n()),t},Xe=({timeout:e=1e3,logger:t}={})=>{let n,r=new Set,i=new Set,a=e=>void i.delete(e),o=e=>a(e.destroy()),s=e=>void(Ge(e)?!e._httpMessage.headersSent&&e._httpMessage.setHeader(`connection`,`close`):o(e)),c=e=>void(n?e.destroy():i.add(e.once(`close`,()=>a(e)).once(`error`,()=>o(e)))),l=async()=>{for(let e of r)e.on(`request`,Je);t?.info(`Graceful shutdown`,{sockets:i.size,timeout:e});for(let e of i)(qe(e)||Ke(e))&&s(e);for await(let t of De(10,Date.now()))if(i.size===0||Date.now()-t>=e)break;for(let e of i)o(e);return Promise.allSettled(r.values().map(Ye))},u={sockets:i,add:(...e)=>{for(let t of e)if(!r.has(t)){r.add(t);for(let e of[`connection`,`secureConnection`])t.on(e,c)}return u},shutdown:()=>n??=l(),get isShuttingDown(){return!!n}};return u},Y=Symbol.for(`express-zod-api`),Ze=({errorHandler:e,getLogger:n})=>async(r,i,a,o)=>r?e.execute({error:t(r),request:i,response:a,input:null,output:null,ctx:{},logger:n(i)}):o(),Qe=({errorHandler:e,getLogger:t})=>async(n,r)=>{let i=D(404,`Can not ${n.method} ${n.path}`),a=t(n);await e.execute({request:n,response:r,logger:a,error:i,input:null,output:null,ctx:{}})},$e=e=>(t,{},n)=>{if(Object.values(t?.files||[]).flat().find(({truncated:e})=>e))return n(e);n()},et=({config:e})=>{let t=w(`cookie-parser`),{secret:n,...r}={...typeof e.cookies==`object`&&e.cookies};return t(n,Object.keys(r).length?r:void 0)},X=e=>typeof e==`function`?e:({},e,t)=>{e.set({"Access-Control-Allow-Origin":`*`,"Access-Control-Allow-Headers":`content-type`}),t()},tt=e=>({log:t=>{/not eligible/i.test(t)||e.debug(t)}}),nt=({getLogger:e,config:t})=>{let n=w(`express-fileupload`),{limitError:r,beforeUpload:i,...a}={...typeof t.upload==`object`&&t.upload},o=[];return o.push(async(t,r,o)=>{let s=e(t);return await i?.({request:t,logger:s}),n({debug:!0,...a,abortOnLimit:!1,parseNested:!0,logger:tt(s)})(t,r,o)}),r&&o.push($e(r)),o},rt=(e,{},t)=>{Buffer.isBuffer(e.body)&&(e.body={raw:e.body}),t()},it=({logger:e,config:{childLoggerProvider:t,accessLogger:n=({method:e,path:t},n)=>n.debug(`${e}: ${t}`)}})=>async(r,i,a)=>{let o=await t?.({request:r,parent:e})||e;n?.(r,o),r.res&&(r.res.locals[Y]={logger:o}),a()},at=e=>t=>t?.res?.locals[Y]?.logger||e;let ot=!1;const st=e=>{ot||(ot=!0,process.on(`deprecation`,({message:t,namespace:n,name:r,stack:i})=>e.warn(`${r} (${n}): ${t}`,i.split(`
`).slice(1))))};let Z,Q,$;const ct=({servers:e,logger:t,options:{timeout:n,beforeExit:r,events:i=[`SIGINT`,`SIGTERM`]}})=>{Z??=Xe({logger:t,timeout:n}),Z.add(...e),r&&($??=new Set).add(r),Q??=async()=>{Z?.isShuttingDown||(await Z?.shutdown(),$&&await Promise.allSettled($.values().map(async e=>e())),process.exit())};for(let e of i)process.listeners(e).includes(Q)||process.on(e,Q)},lt=e=>{if(e.columns<62)return;let t=Se(`for Angie`.padStart(52)),n=O(`#F5A9B8`),r=O(`#5BCEFA`),i=[r`8888888888${t}`,r`888`,r`888`,r`8888888 888 888 88888b. 888d888 .d88b. .d8888b .d8888b`,n`888 `Y8bd8P' 888 "88b 888P" d8P Y8b 88K 88K`,n`888 X88K 888 888 888 88888888 "Y8888b. "Y8888b.`,n`888 .d8""8b. 888 d88P 888 Y8b. X88 X88`,k`8888888888 888 888 88888P" 888 "Y8888 88888P' 88888P'`,k` 888`,k`8888888888P 888 888 d8888 8888888b. 8888888`,k` d88P 888 d88888 888 Y88b 888`,n` d88P 888 d88P888 888 888 888`,n` d88P .d88b. .d88888 d88P 888 888 d88P 888`,n` d88P d88""88b d88" 888 d88P 888 8888888P" 888`,r` d88P 888 888 888 888 d88P 888 888 888`,r` d88P Y88..88P Y88b 888 d8888888888 888 888`,r`d8888888888 "Y88P" "Y88888 d88P 888 888 8888888`];e.write(`
`+i.join(`
`)+`
`)},ut=e=>{e.startupLogo!==!1&<(process.stdout);let t=e.errorHandler||P,n=ze(e.logger)?e.logger:new K(e.logger);n.debug(`Running`,{build:`v29.3.4`,env:p.env}),p.isProduction&&n instanceof K&&n.warn(`Using the built-in logger in production may degrade performance due to synchronous printing. Consider installing a professional logging solution, such as Pino or Winston.`),st(n);let r=it({logger:n,config:e}),i={getLogger:at(n),errorHandler:t},a=Qe(i),o=Ze(i);return{...i,logger:n,notFoundHandler:a,catcher:o,loggingMiddleware:r}},dt=(e,t)=>{let{logger:n,getLogger:r,notFoundHandler:i,loggingMiddleware:a}=ut(e),o=e.app.use(a);return e.cors&&o.use(X(e.cors)),J({app:o,routing:t,getLogger:r,config:e}),{notFoundHandler:i,logger:n}},ft=(e,t)=>{let{logger:n,getLogger:r,notFoundHandler:i,catcher:a,loggingMiddleware:o}=ut(e),s=j().disable(`x-powered-by`).set(`query parser`,e.queryParser??`simple`).use(o);if(e.beforeParsers?.({app:s,getLogger:r}),e.compression){let t=w(`compression`);s.use(t(typeof e.compression==`object`?e.compression:void 0))}return e.cookies&&s.use(et({config:e})),e.cors&&s.use(X(e.cors)),s.use(e.jsonParser||j.json()).use(e.formParser||j.urlencoded()).use(e.rawParser||j.raw(),rt),e.upload&&s.use(...nt({config:e,getLogger:r})),e.beforeRouting?.({app:s,getLogger:r}),J({app:s,routing:t,getLogger:r,config:e}),e.afterRouting?.({app:s,getLogger:r}),s.use(a,i),{app:s,logger:n}},pt=(e,t)=>{let{app:n,logger:r}=ft(e,t),i=[],a=(e,t)=>()=>e.listen(t,()=>r.info(`Listening`,t)),o=[];if(e.http){let t=Te.createServer(n);i.push(t),o.push(a(t,e.http.listen))}if(e.https){let t=Ee.createServer(e.https.options,n);i.push(t),o.push(a(t,e.https.listen))}return i.length||r.warn(`No servers configured.`),e.gracefulShutdown&&ct({logger:r,servers:i,options:e.gracefulShutdown===!0?{}:e.gracefulShutdown}),{app:n,logger:r,servers:o.map(e=>e())}},mt=e=>Oe({...e,headers:{"content-type":b.json,...e?.headers}}),ht=e=>ke(e),gt=e=>{let t={warn:[],error:[],info:[],debug:[]};return new Proxy(e||{},{get(e,n,r){return n===`_getLogs`?()=>t:W(n)?(...e)=>t[n].push(e):Reflect.get(e,n,r)},has(e,t){return W(t)||t===`_getLogs`?!0:Reflect.has(e,t)}})},_t=({requestProps:e,responseOptions:t,configProps:n,loggerProps:r})=>{let i=mt(e),a=ht({req:i,...t});a.req=t?.req||i,i.res=a;let o=gt(r);return{requestMock:i,responseMock:a,loggerMock:o,configMock:{cors:!1,logger:o,...n}}},vt=async({endpoint:e,...t})=>{let{requestMock:n,responseMock:r,loggerMock:i,configMock:a}=_t(t);return await e.execute({request:n,response:r,config:a,logger:i}),{requestMock:n,responseMock:r,loggerMock:i}},yt=async({middleware:e,ctx:n={},...r})=>{let{configMock:{inputSources:i,errorHandler:a=P},...o}=_t(r),s=te(o.requestMock,i),c={request:o.requestMock,response:o.responseMock,logger:o.loggerMock,input:s,ctx:n};try{let t=await e.execute(c);return{...o,output:t}}catch(e){return await a.execute({...c,error:t(e),output:null}),{...o,output:{}}}},bt=(e,t)=>E.object({data:t,event:E.literal(e),id:E.string().optional(),retry:E.int().positive().optional()}),xt=(e,t,n)=>bt(String(t),e[t]).transform(e=>[`event: ${e.event}`,`data: ${JSON.stringify(e.data)}`,``,``].join(`
`)).parse({event:t,data:n}),St=e=>e.headersSent||e.writeHead(200,{connection:`keep-alive`,"content-type":b.sse,"cache-control":`no-cache`}),Ct=e=>new v({handler:async({request:t,response:n})=>{let r=new AbortController,i=setTimeout(()=>St(n),1e4);return t.once(`close`,()=>{clearTimeout(i),r.abort()}),{isClosed:()=>n.writableEnded||n.closed,signal:r.signal,emit:(t,r)=>{St(n),n.write(xt(e,t,r),`utf-8`),n.flush?.()}}}}),wt=e=>new M({positive:()=>{let[t,...n]=Object.entries(e).map(([e,t])=>bt(e,t));if(!t)throw new l(Error(`At least one SSE event is required.`));return{mimeType:b.sse,schema:n.length?E.discriminatedUnion(`event`,[t,...n]):t}},negative:{mimeType:`text/plain`,schema:E.string()},handler:async({response:e,error:t,logger:n,request:r,input:i})=>{if(t){let a=x(t);y(a,n,r,i),e.headersSent||e.status(a.statusCode).type(`text/plain`).write(h(a),`utf-8`)}e.end()}});var Tt=class extends V{constructor(e){super(wt(e)),this.middlewares=[Ct(e)]}};const Et=[`total`,`limit`,`offset`],Dt=[`nextCursor`,`limit`];function Ot({style:e,itemSchema:t,itemsName:n=`items`,maxLimit:r=100,defaultLimit:i=20}){if(!Number.isInteger(r)||r<1)throw Error(`ez.paginated: maxLimit must be a positive integer`);if(!Number.isInteger(i)||i<1)throw Error(`ez.paginated: defaultLimit must be a positive integer`);if(i>r)throw Error(`ez.paginated: defaultLimit must not be greater than maxLimit`);if(e===`offset`&&Et.includes(n))throw Error(`ez.paginated: itemsName must not match reserved keys for offset output (${Et.join(`, `)})`);if(e===`cursor`&&Dt.includes(n))throw Error(`ez.paginated: itemsName must not match reserved keys for cursor output (${Dt.join(`, `)})`);let a=E.coerce.number().int().min(1).max(r).default(i).describe(`Page size (number of ${n} per page)`);if(e===`offset`){let e=E.coerce.number().int().min(0).default(0).describe(`Number of ${n} to skip`);return{input:E.object({limit:a,offset:e}),output:E.object({[n]:E.array(t).describe(`Page of ${n}`),total:E.number().int().min(0).describe(`Total number of ${n}`),limit:E.number().int().min(1).describe(`Page size used`),offset:E.number().int().min(0).describe(`Offset used`)})}}let o=E.string().optional().describe(`Cursor for the next page; omit for first page`);return{input:E.object({cursor:o,limit:a}),output:E.object({[n]:E.array(t).describe(`Page of ${n}`),nextCursor:E.string().nullable().describe(`Cursor for the next page, or null if no more pages`),limit:E.number().int().min(1).describe(`Page size used`)})}}const kt={dateIn:c,dateOut:e,form:n,upload:ee,raw:re,buffer:u,paginated:Ot};export{K as BuiltinLogger,de as DocumentationError,V as EndpointsFactory,Tt as EventStreamFactory,d as InputValidationError,v as Middleware,i as MissingPeerError,r as OutputValidationError,M as ResultHandler,ae as RoutingError,ne as ServeStatic,Re as arrayEndpointsFactory,I as arrayResultHandler,dt as attachRouting,ie as createApiResponse,R as createCacheMiddleware,Ae as createConfig,z as createCookieMiddleware,B as createRateLimitMiddleware,pt as createServer,Le as defaultEndpointsFactory,P as defaultResultHandler,x as ensureHttpError,kt as ez,f as getMessageFromError,vt as testEndpoint,yt as testMiddleware};