@minisylar/express-typed-router 1.7.0 → 1.7.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/README.md CHANGED
@@ -219,6 +219,7 @@ const api = createTypedRouter()
219
219
 
220
220
  app.use("/docs", api.docs({ title: "My API", version: "1.0.0" }));
221
221
  // Discovers all sub-routers and merges routes with correct prefixes
222
+
222
223
  ```
223
224
 
224
225
  ### Schema library support for docs
@@ -9,4 +9,4 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.
9
9
  <script id="api-reference" data-url="${w(t)}"><\/script>
10
10
  <script src="${w(n)}"><\/script>
11
11
  </body>
12
- </html>`}async function D(e,t){let n=Object.create(null);for(let t of e){if(t.method===`all`||t.hidden)continue;let e=v(t.path);n[e]||(n[e]={});let r=y(t.path).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await S(t.querySchema),n=e.properties??{},i=e.required??[];for(let[e,t]of Object.entries(n))r.push({name:e,in:`query`,required:i.includes(e),schema:t})}let i={summary:t.summary??x(t.method,t.path),tags:t.tags??[b(t.path)],parameters:r};t.description&&(i.description=t.description),t.deprecated&&(i.deprecated=!0),t.bodySchema&&(i.requestBody={required:!0,content:{"application/json":{schema:await S(t.bodySchema)}}});let a={};if(t.responseSamples.size>0)for(let[e,n]of t.responseSamples)a[String(e)]={description:e<400?`Success`:`Error`,content:{"application/json":{example:n}}};else a[200]={description:`Success`};i.responses=a,n[e][t.method]=i}return{openapi:`3.1.0`,info:{title:t.title??`API`,version:t.version??`1.0.0`,...t.description?{description:t.description}:{}},...t.servers?{servers:t.servers}:{},paths:n}}const O=new WeakMap;var k=class{router;routes=[];mountedRouters=[];constructor(){this.router=c.default.Router(),O.set(this.router,this)}useMiddleware(e){return this.router.use(e),this}getRouter(){return this.router}use(e,...t){let n=typeof e==`string`,r=n?e:``,i=n?t:[e,...t];for(let e of i){let t=O.get(e);t&&this.mountedRouters.push({prefix:r,router:t})}return n?this.router.use(e,...i):this.router.use(...i),this}mount(e,t){if(typeof e==`string`){let n=t;this.router.use(e,n.getRouter()),this.mountedRouters.push({prefix:e,router:n})}else this.router.use(e.getRouter()),this.mountedRouters.push({prefix:``,router:e});return this}getRouteMetadata(){let e=this.mountedRouters.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,path:e+t.path})));return[...this.routes,...e]}docs(e={}){let t=c.default.Router();return t.get(`/openapi.json`,async(t,n)=>{try{let t=await D(this.getRouteMetadata(),e);e.specOutputPath&&await(await p(`fs/promises`)).writeFile(e.specOutputPath,JSON.stringify(t,null,2),`utf8`).catch(()=>{}),n.json(t)}catch(e){n.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),t.get(`/`,(t,n)=>{let r=`${t.baseUrl}/openapi.json`,i=e.cdnUrl??T;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(E(e.title??`API`,r,i))}),t}get(e,t,n){return this.registerRoute(`get`,e,t,n)}post(e,t,n){return this.registerRoute(`post`,e,t,n)}put(e,t,n){return this.registerRoute(`put`,e,t,n)}patch(e,t,n){return this.registerRoute(`patch`,e,t,n)}delete(e,t,n){return this.registerRoute(`delete`,e,t,n)}options(e,t,n){return this.registerRoute(`options`,e,t,n)}head(e,t,n){return this.registerRoute(`head`,e,t,n)}all(e,t,n){return this.registerRoute(`all`,e,t,n)}registerRoute(e,t,n,r){let i=[],a={method:e,path:t,responseSamples:new Map};if(this.routes.push(a),typeof n==`object`){let e=n;a.bodySchema=e.bodySchema,a.querySchema=e.querySchema,a.tags=e.tags,a.description=e.description,a.summary=e.summary,a.deprecated=e.deprecated,a.responseSchema=e.responseSchema,a.hidden=e.hidden,e.middleware&&i.push(...e.middleware),e.bodySchema&&i.push(this.createBodyValidationMiddleware(e.bodySchema)),e.querySchema&&i.push(this.createQueryValidationMiddleware(e.querySchema)),i.push(r)}else i.push(n);return this.router[e](t,(e,t,n)=>{if(a.hidden||a.responseSamples.size>=10){n();return}let r=t.json;t.json=e=>(a.responseSamples.has(t.statusCode)||a.responseSamples.set(t.statusCode,e),r.call(t,e)),n()},...i),this}createBodyValidationMiddleware(e){return async(t,n,r)=>{try{let i=d(e,t.body),a=i&&typeof i.then==`function`?await i:i;if(a&&`issues`in a&&a.issues){n.status(400).json({error:`Validation failed`,details:a.errors||a.issues});return}t.body=a&&`value`in a?a.value:a,r()}catch(e){f(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):r(e)}}}createQueryValidationMiddleware(e){return async(t,n,r)=>{try{let i=d(e,t.query),a=i&&typeof i.then==`function`?await i:i;if(a&&`issues`in a&&a.issues){n.status(400).json({error:`Validation failed`,details:a.errors||a.issues});return}let o=a&&`value`in a?a.value:a;Object.defineProperty(t,"query",{value:o,writable:!1,enumerable:!0,configurable:!0}),r()}catch(e){f(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):r(e)}}}};function A(){return new k}function j(e){let t=new k;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function M(...e){let t=new k;for(let n of e)t=t.useMiddleware(n);return t}function N(e,t={}){let n=(Array.isArray(e)?e:[e]).map(e=>`prefix`in e?e:{prefix:``,router:e}),r=c.default.Router();return r.get(`/openapi.json`,async(e,r)=>{try{let e=await D(n.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,path:e+t.path}))),t);r.json(e)}catch(e){r.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),r.get(`/`,(e,n)=>{let r=`${e.baseUrl}/openapi.json`,i=t.cdnUrl??T;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(E(t.title??`API`,r,i))}),r}exports.TypedRouter=k,exports.createDocs=N,exports.createTypedRouter=A,exports.createTypedRouterWithConfig=j,exports.createTypedRouterWithMiddleware=M,exports.isSchemaError=f,exports.parseSchema=u,exports.safeParseSchema=d;
12
+ </html>`}async function D(e,t){let n=Object.create(null);for(let t of e){if(t.method===`all`||t.hidden)continue;let e=v(t.path);n[e]||(n[e]={});let r=y(t.path).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await S(t.querySchema),n=e.properties??{},i=e.required??[];for(let[e,t]of Object.entries(n))r.push({name:e,in:`query`,required:i.includes(e),schema:t})}let i={summary:t.summary??x(t.method,t.path),tags:t.tags??[b(t.path)],parameters:r};t.description&&(i.description=t.description),t.deprecated&&(i.deprecated=!0),t.bodySchema&&(i.requestBody={required:!0,content:{"application/json":{schema:await S(t.bodySchema)}}});let a={};if(t.responseSamples.size>0)for(let[e,n]of t.responseSamples)a[String(e)]={description:e<400?`Success`:`Error`,content:{"application/json":{example:n}}};else a[200]={description:`Success`};i.responses=a,n[e][t.method]=i}return{openapi:`3.1.0`,info:{title:t.title??`API`,version:t.version??`1.0.0`,...t.description?{description:t.description}:{}},...t.servers?{servers:t.servers}:{},paths:n}}const O=new WeakMap;var k=class e{router;routes=[];mountedRouters=[];constructor(){this.router=c.default.Router(),O.set(this.router,this)}useMiddleware(e){return this.router.use(e),this}getRouter(){return this.router}use(t,...n){let r=typeof t==`string`,i=r?t:``,a=(r?n:[t,...n]).map(t=>{if(t instanceof e)return this.mountedRouters.push({prefix:i,router:t}),t.getRouter();let n=O.get(t);return n&&this.mountedRouters.push({prefix:i,router:n}),t});return r?this.router.use(t,...a):this.router.use(...a),this}mount(e,t){if(typeof e==`string`){let n=t;this.router.use(e,n.getRouter()),this.mountedRouters.push({prefix:e,router:n})}else this.router.use(e.getRouter()),this.mountedRouters.push({prefix:``,router:e});return this}getRouteMetadata(){let e=this.mountedRouters.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,path:e+t.path})));return[...this.routes,...e]}docs(e={}){let t=c.default.Router();return t.get(`/openapi.json`,async(t,n)=>{try{let t=await D(this.getRouteMetadata(),e);e.specOutputPath&&await(await p(`fs/promises`)).writeFile(e.specOutputPath,JSON.stringify(t,null,2),`utf8`).catch(()=>{}),n.json(t)}catch(e){n.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),t.get(`/`,(t,n)=>{let r=`${t.baseUrl}/openapi.json`,i=e.cdnUrl??T;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(E(e.title??`API`,r,i))}),t}get(e,t,n){return this.registerRoute(`get`,e,t,n)}post(e,t,n){return this.registerRoute(`post`,e,t,n)}put(e,t,n){return this.registerRoute(`put`,e,t,n)}patch(e,t,n){return this.registerRoute(`patch`,e,t,n)}delete(e,t,n){return this.registerRoute(`delete`,e,t,n)}options(e,t,n){return this.registerRoute(`options`,e,t,n)}head(e,t,n){return this.registerRoute(`head`,e,t,n)}all(e,t,n){return this.registerRoute(`all`,e,t,n)}registerRoute(e,t,n,r){let i=[],a={method:e,path:t,responseSamples:new Map};if(this.routes.push(a),typeof n==`object`){let e=n;a.bodySchema=e.bodySchema,a.querySchema=e.querySchema,a.tags=e.tags,a.description=e.description,a.summary=e.summary,a.deprecated=e.deprecated,a.responseSchema=e.responseSchema,a.hidden=e.hidden,e.middleware&&i.push(...e.middleware),e.bodySchema&&i.push(this.createBodyValidationMiddleware(e.bodySchema)),e.querySchema&&i.push(this.createQueryValidationMiddleware(e.querySchema)),i.push(r)}else i.push(n);return this.router[e](t,(e,t,n)=>{if(a.hidden||a.responseSamples.size>=10){n();return}let r=t.json;t.json=e=>(a.responseSamples.has(t.statusCode)||a.responseSamples.set(t.statusCode,e),r.call(t,e)),n()},...i),this}createBodyValidationMiddleware(e){return async(t,n,r)=>{try{let i=d(e,t.body),a=i&&typeof i.then==`function`?await i:i;if(a&&`issues`in a&&a.issues){n.status(400).json({error:`Validation failed`,details:a.errors||a.issues});return}t.body=a&&`value`in a?a.value:a,r()}catch(e){f(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):r(e)}}}createQueryValidationMiddleware(e){return async(t,n,r)=>{try{let i=d(e,t.query),a=i&&typeof i.then==`function`?await i:i;if(a&&`issues`in a&&a.issues){n.status(400).json({error:`Validation failed`,details:a.errors||a.issues});return}let o=a&&`value`in a?a.value:a;Object.defineProperty(t,"query",{value:o,writable:!1,enumerable:!0,configurable:!0}),r()}catch(e){f(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):r(e)}}}};function A(){return new k}function j(e){let t=new k;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function M(...e){let t=new k;for(let n of e)t=t.useMiddleware(n);return t}function N(e,t={}){let n=(Array.isArray(e)?e:[e]).map(e=>`prefix`in e?e:{prefix:``,router:e}),r=c.default.Router();return r.get(`/openapi.json`,async(e,r)=>{try{let e=await D(n.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,path:e+t.path}))),t);r.json(e)}catch(e){r.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),r.get(`/`,(e,n)=>{let r=`${e.baseUrl}/openapi.json`,i=t.cdnUrl??T;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(E(t.title??`API`,r,i))}),r}exports.TypedRouter=k,exports.createDocs=N,exports.createTypedRouter=A,exports.createTypedRouterWithConfig=j,exports.createTypedRouterWithMiddleware=M,exports.isSchemaError=f,exports.parseSchema=u,exports.safeParseSchema=d;
@@ -225,18 +225,18 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
225
225
  * automatically recognised and tracked for .docs() — no extra wiring needed.
226
226
  *
227
227
  * @example
228
- * // v1.routes.ts — keep your exact existing pattern, just use TypedRouter
228
+ * // v1.routes.ts — pass TypedRouter instances directly, no .getRouter() needed
229
229
  * export const v1Routes = createTypedRouter()
230
230
  *
231
- * v1Routes.use('/products', productRoutes.getRouter()) // tracked ✓
232
- * v1Routes.use('/profile', profileRoutes.getRouter()) // tracked ✓
233
- * v1Routes.use('/', callbackRouter) // plain Express, not tracked
231
+ * v1Routes.use('/products', productRoutes) // tracked ✓
232
+ * v1Routes.use('/profile', profileRoutes) // tracked ✓
233
+ * v1Routes.use('/', callbackRouter) // plain Express, also works
234
234
  *
235
235
  * app.use('/v1', v1Routes.getRouter())
236
236
  * app.use('/docs', v1Routes.docs({ title: 'My API' })) // just works
237
237
  */
238
- use(path: string, ...handlers: Array<express.RequestHandler | express.Router>): TypedRouter<Req, Locals>;
239
- use(...handlers: Array<express.RequestHandler | express.Router>): TypedRouter<Req, Locals>;
238
+ use(path: string, ...handlers: Array<express.RequestHandler | express.Router | TypedRouter<any, any>>): TypedRouter<Req, Locals>;
239
+ use(...handlers: Array<express.RequestHandler | express.Router | TypedRouter<any, any>>): TypedRouter<Req, Locals>;
240
240
  /**
241
241
  * Mount a TypedRouter at a path prefix, registering it both on the Express
242
242
  * router and in the docs registry so .docs() picks it up automatically.
@@ -355,6 +355,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
355
355
  delete<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
356
356
  querySchema: QuerySchema;
357
357
  }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
358
+ delete<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
358
359
  delete<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
359
360
  middleware: [...M];
360
361
  }, // Using tuple spread pattern
@@ -372,6 +373,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
372
373
  options<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
373
374
  querySchema: QuerySchema;
374
375
  }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
376
+ options<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
375
377
  options<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
376
378
  middleware: [...M];
377
379
  }, // Using tuple spread pattern
@@ -389,6 +391,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
389
391
  head<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
390
392
  querySchema: QuerySchema;
391
393
  }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
394
+ head<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
392
395
  head<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
393
396
  middleware: [...M];
394
397
  }, // Using tuple spread pattern
@@ -225,18 +225,18 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
225
225
  * automatically recognised and tracked for .docs() — no extra wiring needed.
226
226
  *
227
227
  * @example
228
- * // v1.routes.ts — keep your exact existing pattern, just use TypedRouter
228
+ * // v1.routes.ts — pass TypedRouter instances directly, no .getRouter() needed
229
229
  * export const v1Routes = createTypedRouter()
230
230
  *
231
- * v1Routes.use('/products', productRoutes.getRouter()) // tracked ✓
232
- * v1Routes.use('/profile', profileRoutes.getRouter()) // tracked ✓
233
- * v1Routes.use('/', callbackRouter) // plain Express, not tracked
231
+ * v1Routes.use('/products', productRoutes) // tracked ✓
232
+ * v1Routes.use('/profile', profileRoutes) // tracked ✓
233
+ * v1Routes.use('/', callbackRouter) // plain Express, also works
234
234
  *
235
235
  * app.use('/v1', v1Routes.getRouter())
236
236
  * app.use('/docs', v1Routes.docs({ title: 'My API' })) // just works
237
237
  */
238
- use(path: string, ...handlers: Array<express.RequestHandler | express.Router>): TypedRouter<Req, Locals>;
239
- use(...handlers: Array<express.RequestHandler | express.Router>): TypedRouter<Req, Locals>;
238
+ use(path: string, ...handlers: Array<express.RequestHandler | express.Router | TypedRouter<any, any>>): TypedRouter<Req, Locals>;
239
+ use(...handlers: Array<express.RequestHandler | express.Router | TypedRouter<any, any>>): TypedRouter<Req, Locals>;
240
240
  /**
241
241
  * Mount a TypedRouter at a path prefix, registering it both on the Express
242
242
  * router and in the docs registry so .docs() picks it up automatically.
@@ -355,6 +355,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
355
355
  delete<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
356
356
  querySchema: QuerySchema;
357
357
  }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
358
+ delete<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
358
359
  delete<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
359
360
  middleware: [...M];
360
361
  }, // Using tuple spread pattern
@@ -372,6 +373,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
372
373
  options<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
373
374
  querySchema: QuerySchema;
374
375
  }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
376
+ options<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
375
377
  options<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
376
378
  middleware: [...M];
377
379
  }, // Using tuple spread pattern
@@ -389,6 +391,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
389
391
  head<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
390
392
  querySchema: QuerySchema;
391
393
  }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
394
+ head<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
392
395
  head<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
393
396
  middleware: [...M];
394
397
  }, // Using tuple spread pattern
@@ -9,4 +9,4 @@ import e from"express";import{SchemaError as t}from"@standard-schema/utils";func
9
9
  <script id="api-reference" data-url="${g(t)}"><\/script>
10
10
  <script src="${g(n)}"><\/script>
11
11
  </body>
12
- </html>`}async function y(e,t){let n=Object.create(null);for(let t of e){if(t.method===`all`||t.hidden)continue;let e=u(t.path);n[e]||(n[e]={});let r=d(t.path).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await m(t.querySchema),n=e.properties??{},i=e.required??[];for(let[e,t]of Object.entries(n))r.push({name:e,in:`query`,required:i.includes(e),schema:t})}let i={summary:t.summary??p(t.method,t.path),tags:t.tags??[f(t.path)],parameters:r};t.description&&(i.description=t.description),t.deprecated&&(i.deprecated=!0),t.bodySchema&&(i.requestBody={required:!0,content:{"application/json":{schema:await m(t.bodySchema)}}});let a={};if(t.responseSamples.size>0)for(let[e,n]of t.responseSamples)a[String(e)]={description:e<400?`Success`:`Error`,content:{"application/json":{example:n}}};else a[200]={description:`Success`};i.responses=a,n[e][t.method]=i}return{openapi:`3.1.0`,info:{title:t.title??`API`,version:t.version??`1.0.0`,...t.description?{description:t.description}:{}},...t.servers?{servers:t.servers}:{},paths:n}}const b=new WeakMap;var x=class{router;routes=[];mountedRouters=[];constructor(){this.router=e.Router(),b.set(this.router,this)}useMiddleware(e){return this.router.use(e),this}getRouter(){return this.router}use(e,...t){let n=typeof e==`string`,r=n?e:``,i=n?t:[e,...t];for(let e of i){let t=b.get(e);t&&this.mountedRouters.push({prefix:r,router:t})}return n?this.router.use(e,...i):this.router.use(...i),this}mount(e,t){if(typeof e==`string`){let n=t;this.router.use(e,n.getRouter()),this.mountedRouters.push({prefix:e,router:n})}else this.router.use(e.getRouter()),this.mountedRouters.push({prefix:``,router:e});return this}getRouteMetadata(){let e=this.mountedRouters.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,path:e+t.path})));return[...this.routes,...e]}docs(t={}){let n=e.Router();return n.get(`/openapi.json`,async(e,n)=>{try{let e=await y(this.getRouteMetadata(),t);t.specOutputPath&&await(await a(`fs/promises`)).writeFile(t.specOutputPath,JSON.stringify(e,null,2),`utf8`).catch(()=>{}),n.json(e)}catch(e){n.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),n.get(`/`,(e,n)=>{let r=`${e.baseUrl}/openapi.json`,i=t.cdnUrl??_;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(v(t.title??`API`,r,i))}),n}get(e,t,n){return this.registerRoute(`get`,e,t,n)}post(e,t,n){return this.registerRoute(`post`,e,t,n)}put(e,t,n){return this.registerRoute(`put`,e,t,n)}patch(e,t,n){return this.registerRoute(`patch`,e,t,n)}delete(e,t,n){return this.registerRoute(`delete`,e,t,n)}options(e,t,n){return this.registerRoute(`options`,e,t,n)}head(e,t,n){return this.registerRoute(`head`,e,t,n)}all(e,t,n){return this.registerRoute(`all`,e,t,n)}registerRoute(e,t,n,r){let i=[],a={method:e,path:t,responseSamples:new Map};if(this.routes.push(a),typeof n==`object`){let e=n;a.bodySchema=e.bodySchema,a.querySchema=e.querySchema,a.tags=e.tags,a.description=e.description,a.summary=e.summary,a.deprecated=e.deprecated,a.responseSchema=e.responseSchema,a.hidden=e.hidden,e.middleware&&i.push(...e.middleware),e.bodySchema&&i.push(this.createBodyValidationMiddleware(e.bodySchema)),e.querySchema&&i.push(this.createQueryValidationMiddleware(e.querySchema)),i.push(r)}else i.push(n);return this.router[e](t,(e,t,n)=>{if(a.hidden||a.responseSamples.size>=10){n();return}let r=t.json;t.json=e=>(a.responseSamples.has(t.statusCode)||a.responseSamples.set(t.statusCode,e),r.call(t,e)),n()},...i),this}createBodyValidationMiddleware(e){return async(t,n,a)=>{try{let i=r(e,t.body),o=i&&typeof i.then==`function`?await i:i;if(o&&`issues`in o&&o.issues){n.status(400).json({error:`Validation failed`,details:o.errors||o.issues});return}t.body=o&&`value`in o?o.value:o,a()}catch(e){i(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):a(e)}}}createQueryValidationMiddleware(e){return async(t,n,a)=>{try{let i=r(e,t.query),o=i&&typeof i.then==`function`?await i:i;if(o&&`issues`in o&&o.issues){n.status(400).json({error:`Validation failed`,details:o.errors||o.issues});return}let s=o&&`value`in o?o.value:o;Object.defineProperty(t,"query",{value:s,writable:!1,enumerable:!0,configurable:!0}),a()}catch(e){i(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):a(e)}}}};function S(){return new x}function C(e){let t=new x;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function w(...e){let t=new x;for(let n of e)t=t.useMiddleware(n);return t}function T(t,n={}){let r=(Array.isArray(t)?t:[t]).map(e=>`prefix`in e?e:{prefix:``,router:e}),i=e.Router();return i.get(`/openapi.json`,async(e,t)=>{try{let e=await y(r.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,path:e+t.path}))),n);t.json(e)}catch(e){t.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),i.get(`/`,(e,t)=>{let r=`${e.baseUrl}/openapi.json`,i=n.cdnUrl??_;t.setHeader(`Content-Type`,`text/html; charset=utf-8`),t.send(v(n.title??`API`,r,i))}),i}export{x as TypedRouter,T as createDocs,S as createTypedRouter,C as createTypedRouterWithConfig,w as createTypedRouterWithMiddleware,i as isSchemaError,n as parseSchema,r as safeParseSchema};
12
+ </html>`}async function y(e,t){let n=Object.create(null);for(let t of e){if(t.method===`all`||t.hidden)continue;let e=u(t.path);n[e]||(n[e]={});let r=d(t.path).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await m(t.querySchema),n=e.properties??{},i=e.required??[];for(let[e,t]of Object.entries(n))r.push({name:e,in:`query`,required:i.includes(e),schema:t})}let i={summary:t.summary??p(t.method,t.path),tags:t.tags??[f(t.path)],parameters:r};t.description&&(i.description=t.description),t.deprecated&&(i.deprecated=!0),t.bodySchema&&(i.requestBody={required:!0,content:{"application/json":{schema:await m(t.bodySchema)}}});let a={};if(t.responseSamples.size>0)for(let[e,n]of t.responseSamples)a[String(e)]={description:e<400?`Success`:`Error`,content:{"application/json":{example:n}}};else a[200]={description:`Success`};i.responses=a,n[e][t.method]=i}return{openapi:`3.1.0`,info:{title:t.title??`API`,version:t.version??`1.0.0`,...t.description?{description:t.description}:{}},...t.servers?{servers:t.servers}:{},paths:n}}const b=new WeakMap;var x=class t{router;routes=[];mountedRouters=[];constructor(){this.router=e.Router(),b.set(this.router,this)}useMiddleware(e){return this.router.use(e),this}getRouter(){return this.router}use(e,...n){let r=typeof e==`string`,i=r?e:``,a=(r?n:[e,...n]).map(e=>{if(e instanceof t)return this.mountedRouters.push({prefix:i,router:e}),e.getRouter();let n=b.get(e);return n&&this.mountedRouters.push({prefix:i,router:n}),e});return r?this.router.use(e,...a):this.router.use(...a),this}mount(e,t){if(typeof e==`string`){let n=t;this.router.use(e,n.getRouter()),this.mountedRouters.push({prefix:e,router:n})}else this.router.use(e.getRouter()),this.mountedRouters.push({prefix:``,router:e});return this}getRouteMetadata(){let e=this.mountedRouters.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,path:e+t.path})));return[...this.routes,...e]}docs(t={}){let n=e.Router();return n.get(`/openapi.json`,async(e,n)=>{try{let e=await y(this.getRouteMetadata(),t);t.specOutputPath&&await(await a(`fs/promises`)).writeFile(t.specOutputPath,JSON.stringify(e,null,2),`utf8`).catch(()=>{}),n.json(e)}catch(e){n.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),n.get(`/`,(e,n)=>{let r=`${e.baseUrl}/openapi.json`,i=t.cdnUrl??_;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(v(t.title??`API`,r,i))}),n}get(e,t,n){return this.registerRoute(`get`,e,t,n)}post(e,t,n){return this.registerRoute(`post`,e,t,n)}put(e,t,n){return this.registerRoute(`put`,e,t,n)}patch(e,t,n){return this.registerRoute(`patch`,e,t,n)}delete(e,t,n){return this.registerRoute(`delete`,e,t,n)}options(e,t,n){return this.registerRoute(`options`,e,t,n)}head(e,t,n){return this.registerRoute(`head`,e,t,n)}all(e,t,n){return this.registerRoute(`all`,e,t,n)}registerRoute(e,t,n,r){let i=[],a={method:e,path:t,responseSamples:new Map};if(this.routes.push(a),typeof n==`object`){let e=n;a.bodySchema=e.bodySchema,a.querySchema=e.querySchema,a.tags=e.tags,a.description=e.description,a.summary=e.summary,a.deprecated=e.deprecated,a.responseSchema=e.responseSchema,a.hidden=e.hidden,e.middleware&&i.push(...e.middleware),e.bodySchema&&i.push(this.createBodyValidationMiddleware(e.bodySchema)),e.querySchema&&i.push(this.createQueryValidationMiddleware(e.querySchema)),i.push(r)}else i.push(n);return this.router[e](t,(e,t,n)=>{if(a.hidden||a.responseSamples.size>=10){n();return}let r=t.json;t.json=e=>(a.responseSamples.has(t.statusCode)||a.responseSamples.set(t.statusCode,e),r.call(t,e)),n()},...i),this}createBodyValidationMiddleware(e){return async(t,n,a)=>{try{let i=r(e,t.body),o=i&&typeof i.then==`function`?await i:i;if(o&&`issues`in o&&o.issues){n.status(400).json({error:`Validation failed`,details:o.errors||o.issues});return}t.body=o&&`value`in o?o.value:o,a()}catch(e){i(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):a(e)}}}createQueryValidationMiddleware(e){return async(t,n,a)=>{try{let i=r(e,t.query),o=i&&typeof i.then==`function`?await i:i;if(o&&`issues`in o&&o.issues){n.status(400).json({error:`Validation failed`,details:o.errors||o.issues});return}let s=o&&`value`in o?o.value:o;Object.defineProperty(t,"query",{value:s,writable:!1,enumerable:!0,configurable:!0}),a()}catch(e){i(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):a(e)}}}};function S(){return new x}function C(e){let t=new x;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function w(...e){let t=new x;for(let n of e)t=t.useMiddleware(n);return t}function T(t,n={}){let r=(Array.isArray(t)?t:[t]).map(e=>`prefix`in e?e:{prefix:``,router:e}),i=e.Router();return i.get(`/openapi.json`,async(e,t)=>{try{let e=await y(r.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,path:e+t.path}))),n);t.json(e)}catch(e){t.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),i.get(`/`,(e,t)=>{let r=`${e.baseUrl}/openapi.json`,i=n.cdnUrl??_;t.setHeader(`Content-Type`,`text/html; charset=utf-8`),t.send(v(n.title??`API`,r,i))}),i}export{x as TypedRouter,T as createDocs,S as createTypedRouter,C as createTypedRouterWithConfig,w as createTypedRouterWithMiddleware,i as isSchemaError,n as parseSchema,r as safeParseSchema};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minisylar/express-typed-router",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "description": "A strongly-typed Express router with Zod validation and automatic type inference for params, body, query, and middleware",
5
5
  "type": "module",
6
6
  "main": "dist/schema-router.cjs",