express-zod-api 29.6.0 → 29.6.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 CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## Version 29
4
4
 
5
+ ### v29.6.1
6
+
7
+ - Performance optimization in the SSE event emission:
8
+ - Removed an intermediate schema on each event: made formatting messages within `emit()` method about 230x faster.
9
+
5
10
  ### v29.6.0
6
11
 
7
12
  - Added the `isCookie` (a boolean returning function) option to the Documentation generator for your customizations:
package/dist/index.d.ts CHANGED
@@ -571,6 +571,7 @@ declare const testMiddleware: <LOG extends FlatObject, REQ extends RequestOption
571
571
  };
572
572
  output: Partial<RET>;
573
573
  }>;
574
+ /** @desc The declaration mapping the event names to the schemas of their data. */
574
575
  type EventsMap = Record<string, z.ZodType>;
575
576
  interface Emitter<E extends EventsMap> extends FlatObject {
576
577
  /** @desc Returns true when the connection was closed or terminated */
@@ -581,6 +582,7 @@ interface Emitter<E extends EventsMap> extends FlatObject {
581
582
  emit: <K extends keyof E>(event: K, data: z.input<E[K]>) => void;
582
583
  }
583
584
  declare class EventStreamFactory<E extends EventsMap> extends EndpointsFactory<undefined, Emitter<E>> {
585
+ /** @todo compile these schemas in v30 */
584
586
  constructor(events: E);
585
587
  }
586
588
  declare const buffer: () => z.ZodCustom<Buffer<ArrayBufferLike>, Buffer<ArrayBufferLike>>;
package/dist/index.js CHANGED
@@ -3,5 +3,5 @@ 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 a
3
3
  `+i.join(`
4
4
  `)+`
5
5
 
6
- `)},dt=e=>{e.startupLogo!==!1&&ut(process.stdout);let t=e.errorHandler||I,n=Le(e.logger)?e.logger:new J(e.logger);n.debug(`Running`,{build:`v29.6.0`,env:m.env}),m.isProduction&&n instanceof J&&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.`),ct(n);let r=at({logger:n,config:e}),i={getLogger:ot(n),errorHandler:t},a=Qe(i),o=Ze(i);return{...i,logger:n,notFoundHandler:a,catcher:o,loggingMiddleware:r}},ft=(e,t)=>{let{logger:n,getLogger:r,notFoundHandler:i,loggingMiddleware:a}=dt(e),o=e.app.use(a);return e.cors&&o.use(tt(e.cors)),Ue({app:o,routing:t,getLogger:r,config:e}),{notFoundHandler:i,logger:n}},pt=(e,t)=>{let{logger:n,getLogger:r,notFoundHandler:i,catcher:a,loggingMiddleware:o}=dt(e),s=N().disable(`x-powered-by`).set(`query parser`,e.queryParser??`simple`).use(o);if(e.beforeParsers?.({app:s,getLogger:r}),e.compression){let t=E(`compression`);s.use(t(typeof e.compression==`object`?e.compression:void 0))}return e.cookies&&s.use(et({config:e})),e.cors&&s.use(tt(e.cors)),s.use(e.jsonParser||N.json()).use(e.formParser||N.urlencoded()).use(e.rawParser||N.raw(),it),e.upload&&s.use(...rt({config:e,getLogger:r})),e.beforeRouting?.({app:s,getLogger:r}),Ue({app:s,routing:t,getLogger:r,config:e}),e.afterRouting?.({app:s,getLogger:r}),s.use(a,i),{app:s,logger:n}},mt=(e,t)=>{let{app:n,logger:r}=pt(e,t),i=[],a=(e,t)=>()=>e.listen(t,()=>r.info(`Listening`,t)),o=[];if(e.http){let t=Ce.createServer(n);i.push(t),o.push(a(t,e.http.listen))}if(e.https){let t=we.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&&lt({logger:r,servers:i,options:e.gracefulShutdown===!0?{}:e.gracefulShutdown}),{app:n,logger:r,servers:o.map(e=>e())}},ht=e=>Ee({...e,headers:{"content-type":x.json,...e?.headers}}),gt=e=>De(e),_t=e=>{let t={warn:[],error:[],info:[],debug:[]};return new Proxy(e||{},{get(e,n,r){return n===`_getLogs`?()=>t:K(n)?(...e)=>t[n].push(e):Reflect.get(e,n,r)},has(e,t){return K(t)||t===`_getLogs`?!0:Reflect.has(e,t)}})},vt=({requestProps:e,responseOptions:t,configProps:n,loggerProps:r})=>{let i=ht(e),a=gt({req:i,...t});a.req=t?.req||i,i.res=a;let o=_t(r);return{requestMock:i,responseMock:a,loggerMock:o,configMock:{cors:!1,logger:o,...n}}},yt=async({endpoint:e,...t})=>{let{requestMock:n,responseMock:r,loggerMock:i,configMock:a}=vt(t);return await e.execute({request:n,response:r,config:a,logger:i}),{requestMock:n,responseMock:r,loggerMock:i}},bt=async({middleware:e,ctx:n={},...r})=>{let{configMock:i,...a}=vt(r),{inputSources:o,errorHandler:s=I}=i,c=p(a.requestMock,o),l={request:a.requestMock,response:a.responseMock,logger:a.loggerMock,input:c,ctx:n};try{let t=await e.execute({...l,config:i});return{...a,output:t}}catch(e){return await s.execute({...l,error:t(e),output:null}),{...a,output:{}}}},xt=(e,t)=>O.object({data:t,event:O.literal(e),id:O.string().optional(),retry:O.int().positive().optional()}),St=(e,t,n)=>xt(String(t),e[t]).transform(e=>[`event: ${e.event}`,`data: ${JSON.stringify(e.data)}`,``,``].join(`
7
- `)).parse({event:t,data:n}),Ct=e=>e.headersSent||e.writeHead(200,{connection:`keep-alive`,"content-type":x.sse,"cache-control":`no-cache`}),wt=e=>new v({handler:async({request:t,response:n})=>{let r=new AbortController,i=setTimeout(()=>Ct(n),1e4);return t.once(`close`,()=>{clearTimeout(i),r.abort()}),{isClosed:()=>n.writableEnded||n.closed,signal:r.signal,emit:(t,r)=>{Ct(n),n.write(St(e,t,r),`utf-8`),n.flush?.()}}}}),Tt=e=>new P({positive:()=>{let[t,...n]=Object.entries(e).map(([e,t])=>xt(e,t));if(!t)throw new l(Error(`At least one SSE event is required.`));return{mimeType:x.sse,schema:n.length?O.discriminatedUnion(`event`,[t,...n]):t}},negative:{mimeType:`text/plain`,schema:O.string()},handler:async({response:e,error:t,logger:n,request:r,input:i})=>{if(t){let a=S(t);y(a,n,r,i),e.headersSent||e.status(a.statusCode).type(`text/plain`).write(h(a),`utf-8`)}e.end()}});var Et=class extends U{constructor(e){super(Tt(e)),this.middlewares=[wt(e)]}};const Dt=[`total`,`limit`,`offset`],$=[`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`&&Dt.includes(n))throw Error(`ez.paginated: itemsName must not match reserved keys for offset output (${Dt.join(`, `)})`);if(e===`cursor`&&$.includes(n))throw Error(`ez.paginated: itemsName must not match reserved keys for cursor output (${$.join(`, `)})`);let a=O.coerce.number().int().min(1).max(r).default(i).describe(`Page size (number of ${n} per page)`);if(e===`offset`){let e=O.coerce.number().int().min(0).default(0).describe(`Number of ${n} to skip`);return{input:O.object({limit:a,offset:e}),output:O.object({[n]:O.array(t).describe(`Page of ${n}`),total:O.number().int().min(0).describe(`Total number of ${n}`),limit:O.number().int().min(1).describe(`Page size used`),offset:O.number().int().min(0).describe(`Offset used`)})}}let o=O.string().optional().describe(`Cursor for the next page; omit for first page`);return{input:O.object({cursor:o,limit:a}),output:O.object({[n]:O.array(t).describe(`Page of ${n}`),nextCursor:O.string().nullable().describe(`Cursor for the next page, or null if no more pages`),limit:O.number().int().min(1).describe(`Page size used`)})}}const kt={dateIn:c,dateOut:e,form:ee,upload:n,raw:ce,buffer:u,paginated:Ot};export{J as BuiltinLogger,se as DocumentationError,U as EndpointsFactory,Et as EventStreamFactory,d as InputValidationError,v as Middleware,i as MissingPeerError,r as OutputValidationError,P as ResultHandler,re as RoutingError,te as ServeStatic,Ie as arrayEndpointsFactory,R as arrayResultHandler,ft as attachRouting,ne as createApiResponse,B as createCacheMiddleware,Oe as createConfig,V as createCookieMiddleware,H as createRateLimitMiddleware,mt as createServer,Fe as defaultEndpointsFactory,I as defaultResultHandler,S as ensureHttpError,kt as ez,f as getMessageFromError,yt as testEndpoint,bt as testMiddleware};
6
+ `)},dt=e=>{e.startupLogo!==!1&&ut(process.stdout);let t=e.errorHandler||I,n=Le(e.logger)?e.logger:new J(e.logger);n.debug(`Running`,{build:`v29.6.1`,env:m.env}),m.isProduction&&n instanceof J&&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.`),ct(n);let r=at({logger:n,config:e}),i={getLogger:ot(n),errorHandler:t},a=Qe(i),o=Ze(i);return{...i,logger:n,notFoundHandler:a,catcher:o,loggingMiddleware:r}},ft=(e,t)=>{let{logger:n,getLogger:r,notFoundHandler:i,loggingMiddleware:a}=dt(e),o=e.app.use(a);return e.cors&&o.use(tt(e.cors)),Ue({app:o,routing:t,getLogger:r,config:e}),{notFoundHandler:i,logger:n}},pt=(e,t)=>{let{logger:n,getLogger:r,notFoundHandler:i,catcher:a,loggingMiddleware:o}=dt(e),s=N().disable(`x-powered-by`).set(`query parser`,e.queryParser??`simple`).use(o);if(e.beforeParsers?.({app:s,getLogger:r}),e.compression){let t=E(`compression`);s.use(t(typeof e.compression==`object`?e.compression:void 0))}return e.cookies&&s.use(et({config:e})),e.cors&&s.use(tt(e.cors)),s.use(e.jsonParser||N.json()).use(e.formParser||N.urlencoded()).use(e.rawParser||N.raw(),it),e.upload&&s.use(...rt({config:e,getLogger:r})),e.beforeRouting?.({app:s,getLogger:r}),Ue({app:s,routing:t,getLogger:r,config:e}),e.afterRouting?.({app:s,getLogger:r}),s.use(a,i),{app:s,logger:n}},mt=(e,t)=>{let{app:n,logger:r}=pt(e,t),i=[],a=(e,t)=>()=>e.listen(t,()=>r.info(`Listening`,t)),o=[];if(e.http){let t=Ce.createServer(n);i.push(t),o.push(a(t,e.http.listen))}if(e.https){let t=we.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&&lt({logger:r,servers:i,options:e.gracefulShutdown===!0?{}:e.gracefulShutdown}),{app:n,logger:r,servers:o.map(e=>e())}},ht=e=>Ee({...e,headers:{"content-type":x.json,...e?.headers}}),gt=e=>De(e),_t=e=>{let t={warn:[],error:[],info:[],debug:[]};return new Proxy(e||{},{get(e,n,r){return n===`_getLogs`?()=>t:K(n)?(...e)=>t[n].push(e):Reflect.get(e,n,r)},has(e,t){return K(t)||t===`_getLogs`?!0:Reflect.has(e,t)}})},vt=({requestProps:e,responseOptions:t,configProps:n,loggerProps:r})=>{let i=ht(e),a=gt({req:i,...t});a.req=t?.req||i,i.res=a;let o=_t(r);return{requestMock:i,responseMock:a,loggerMock:o,configMock:{cors:!1,logger:o,...n}}},yt=async({endpoint:e,...t})=>{let{requestMock:n,responseMock:r,loggerMock:i,configMock:a}=vt(t);return await e.execute({request:n,response:r,config:a,logger:i}),{requestMock:n,responseMock:r,loggerMock:i}},bt=async({middleware:e,ctx:n={},...r})=>{let{configMock:i,...a}=vt(r),{inputSources:o,errorHandler:s=I}=i,c=p(a.requestMock,o),l={request:a.requestMock,response:a.responseMock,logger:a.loggerMock,input:c,ctx:n};try{let t=await e.execute({...l,config:i});return{...a,output:t}}catch(e){return await s.execute({...l,error:t(e),output:null}),{...a,output:{}}}},xt=(e,t)=>O.object({data:t,event:O.literal(e),id:O.string().optional(),retry:O.int().positive().optional()}),St=(e,t,n)=>{if(!Object.prototype.hasOwnProperty.call(e,t))throw Error(`Unknown event: ${t}`);let r=e[t].parse(n);return[`event: ${t}`,`data: ${JSON.stringify(r)}`,``,``].join(`
7
+ `)},Ct=e=>e.headersSent||e.writeHead(200,{connection:`keep-alive`,"content-type":x.sse,"cache-control":`no-cache`}),wt=e=>new v({handler:async({request:t,response:n})=>{let r=new AbortController,i=setTimeout(()=>Ct(n),1e4);return t.once(`close`,()=>{clearTimeout(i),r.abort()}),{isClosed:()=>n.writableEnded||n.closed,signal:r.signal,emit:(t,r)=>{Ct(n),n.write(St(e,String(t),r),`utf-8`),n.flush?.()}}}}),Tt=e=>new P({positive:()=>{let[t,...n]=Object.entries(e).map(([e,t])=>xt(e,t));if(!t)throw new l(Error(`At least one SSE event is required.`));return{mimeType:x.sse,schema:n.length?O.discriminatedUnion(`event`,[t,...n]):t}},negative:{mimeType:`text/plain`,schema:O.string()},handler:async({response:e,error:t,logger:n,request:r,input:i})=>{if(t){let a=S(t);y(a,n,r,i),e.headersSent||e.status(a.statusCode).type(`text/plain`).write(h(a),`utf-8`)}e.end()}});var Et=class extends U{constructor(e){super(Tt(e)),this.middlewares=[wt(e)]}};const Dt=[`total`,`limit`,`offset`],$=[`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`&&Dt.includes(n))throw Error(`ez.paginated: itemsName must not match reserved keys for offset output (${Dt.join(`, `)})`);if(e===`cursor`&&$.includes(n))throw Error(`ez.paginated: itemsName must not match reserved keys for cursor output (${$.join(`, `)})`);let a=O.coerce.number().int().min(1).max(r).default(i).describe(`Page size (number of ${n} per page)`);if(e===`offset`){let e=O.coerce.number().int().min(0).default(0).describe(`Number of ${n} to skip`);return{input:O.object({limit:a,offset:e}),output:O.object({[n]:O.array(t).describe(`Page of ${n}`),total:O.number().int().min(0).describe(`Total number of ${n}`),limit:O.number().int().min(1).describe(`Page size used`),offset:O.number().int().min(0).describe(`Offset used`)})}}let o=O.string().optional().describe(`Cursor for the next page; omit for first page`);return{input:O.object({cursor:o,limit:a}),output:O.object({[n]:O.array(t).describe(`Page of ${n}`),nextCursor:O.string().nullable().describe(`Cursor for the next page, or null if no more pages`),limit:O.number().int().min(1).describe(`Page size used`)})}}const kt={dateIn:c,dateOut:e,form:ee,upload:n,raw:ce,buffer:u,paginated:Ot};export{J as BuiltinLogger,se as DocumentationError,U as EndpointsFactory,Et as EventStreamFactory,d as InputValidationError,v as Middleware,i as MissingPeerError,r as OutputValidationError,P as ResultHandler,re as RoutingError,te as ServeStatic,Ie as arrayEndpointsFactory,R as arrayResultHandler,ft as attachRouting,ne as createApiResponse,B as createCacheMiddleware,Oe as createConfig,V as createCookieMiddleware,H as createRateLimitMiddleware,mt as createServer,Fe as defaultEndpointsFactory,I as defaultResultHandler,S as ensureHttpError,kt as ez,f as getMessageFromError,yt as testEndpoint,bt as testMiddleware};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "express-zod-api",
3
- "version": "29.6.0",
3
+ "version": "29.6.1",
4
4
  "description": "A Typescript framework to help you get an API server up and running with I/O schema validation and custom middlewares in minutes.",
5
5
  "license": "MIT",
6
6
  "repository": {