@minisylar/express-typed-router 1.9.3 → 1.9.5

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
@@ -1,5 +1,7 @@
1
1
  # @minisylar/express-typed-router
2
2
 
3
+ [![Sponsor](https://img.shields.io/github/sponsors/Mini-Sylar)](https://github.com/sponsors/Mini-Sylar)
4
+
3
5
  A strongly typed Express router with **Standard Schema** validation, automatic type inference, and ✨ OpenAPI docs.
4
6
 
5
7
  Define routes once, infer `params` / `body` / `query`, and generate a clean API spec for docs or client generation.
@@ -491,6 +493,40 @@ app.use("/docs", api.docs({ title: "My API", version: "1.0.0" }));
491
493
 
492
494
  ---
493
495
 
496
+ ## Gotchas
497
+
498
+ ### `querySchema` booleans: `req.query.flag` is text, not a real boolean
499
+
500
+ Query strings have no wire format for booleans — `?flag=false` arrives at your schema as the *string* `"false"`, no matter what the client intended. This isn't specific to this library; it's true of every Express app, but it trips people up specifically when picking a schema for `querySchema`:
501
+
502
+ ```ts
503
+ router.get(
504
+ "/search",
505
+ { querySchema: z.object({ flag: z.boolean() }) }, // ❌ always rejects
506
+ handler,
507
+ );
508
+ ```
509
+
510
+ `z.boolean()` rejects **every** request here, since `typeof "false" !== "boolean"` — it fails before it even looks at the text.
511
+
512
+ The obvious fix has its own trap:
513
+
514
+ ```ts
515
+ { querySchema: z.object({ flag: z.coerce.boolean() }) } // ❌ silently wrong
516
+ ```
517
+
518
+ `z.coerce.boolean()` does plain JS `Boolean(value)` — and `Boolean("false")` is `true`, because any non-empty string is truthy in JavaScript. `?flag=false` stops erroring and instead silently becomes `true`.
519
+
520
+ Use a schema that actually parses the text:
521
+
522
+ ```ts
523
+ { querySchema: z.object({ flag: z.stringbool() }) } // ✅ zod v4+
524
+ ```
525
+
526
+ `z.stringbool()` parses `"true"`/`"false"` (and a few common variants) into the correct boolean. This isn't an express-typed-router limitation to work around — the router runs whatever schema you give it exactly as written; the fix is picking the right schema primitive, not something the router could safely guess on your behalf.
527
+
528
+ ---
529
+
494
530
  ## API surface
495
531
 
496
532
  | | |
@@ -1,12 +1,12 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require("express");c=s(c,1);let l=require("@standard-schema/utils");function u(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`){let e=n[`~standard`].validate(t);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new l.SchemaError(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function d(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function f(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}const p=Function(`m`,`return import(m)`);let m,h;async function g(e){try{m??=await p(`module`),h??=await p(`url`);let t=globalThis.process?.cwd?.()??``,n=m.createRequire(h.pathToFileURL(t+`/`).href).resolve(e);return p(h.pathToFileURL(n).href)}catch{return p(e)}}const _=new WeakMap;function v(e){return e.replace(/\{([^{}]*)\}/g,`$1`).replace(/:([A-Za-z0-9_]+)(?:\([^)]*\))?[?+*]?/g,`{$1}`).replace(/\/{2,}/g,`/`)}function y(e){return[...e.replace(/\{([^{}]*)\}/g,`$1`).matchAll(/:([A-Za-z0-9_]+)/g)].map(e=>e[1])}function b(e){return e.replace(/[{}]/g,``).split(`/`).filter(Boolean).find(e=>!e.startsWith(`:`)&&!e.startsWith(`*`))??`default`}function x(e,t){let n=t.replace(/[{}]/g,``).split(`/`).filter(e=>e&&!e.startsWith(`:`)&&!e.startsWith(`*`)),r=n[n.length-1]??`resource`;return`${{get:`Get`,post:`Create`,put:`Update`,patch:`Patch`,delete:`Delete`,head:`Head`,options:`Options`}[e]??e} ${r}`}async function S(e){let t=_.get(e);if(t)return t;let n=await C(e);return _.set(e,n),n}async function C(e){if(typeof e.toJsonSchema==`function`)try{return e.toJsonSchema()}catch{}let t=e[`~standard`]?.vendor;if(t===`zod`){try{let t=await g(`zod`);if(typeof t.toJSONSchema==`function`)return t.toJSONSchema(e)}catch{}try{let t=await g(`zod-to-json-schema`),n=t.zodToJsonSchema??t.default?.zodToJsonSchema;if(typeof n==`function`)return n(e)}catch{}}if(t===`valibot`)try{let t=await g(`@valibot/to-json-schema`),n=t.toJsonSchema??t.default?.toJsonSchema;if(typeof n==`function`)return n(e)}catch{}if(t===`effect`)try{let t=await g(`effect`),n=t.JSONSchema?.make??t.default?.JSONSchema?.make;if(typeof n==`function`)return n(e)}catch{}return{}}function w(e){return T(e,0,new WeakSet)}function T(e,t,n){if(e==null)return{type:`null`};if(t>=12)return{};if(e instanceof Date)return{type:`string`,format:`date-time`};if(typeof e==`bigint`)return{type:`integer`};if(typeof e==`object`&&typeof e.toJSON==`function`)return T(e.toJSON(),t,n);if(Array.isArray(e)){if(e.length===0||n.has(e))return{type:`array`,items:{}};n.add(e);let r=Math.min(e.length,20),i=T(e[0],t+1,n);for(let a=1;a<r;a++)i=O(i,T(e[a],t+1,n));return n.delete(e),{type:`array`,items:i}}switch(typeof e){case`string`:return{type:`string`};case`boolean`:return{type:`boolean`};case`number`:return Number.isFinite(e)?{type:Number.isInteger(e)?`integer`:`number`}:{type:`null`};case`object`:{if(n.has(e))return{type:`object`};n.add(e);let r={},i=[];for(let[a,o]of Object.entries(e))typeof o==`function`||o===void 0||(r[a]=T(o,t+1,n),i.push(a));n.delete(e);let a={type:`object`,properties:r};return i.length&&(a.required=i),a}default:return{}}}function E(e){return!e||e.type===void 0?new Set:new Set(Array.isArray(e.type)?e.type:[e.type])}function D(e){e.has(`integer`)&&e.has(`number`)&&e.delete(`integer`);let t=[...e];if(t.length!==0)return t.length===1?t[0]:t}function O(e,t){if(!e||Object.keys(e).length===0)return t??{};if(!t||Object.keys(t).length===0)return e??{};let n=new Set([...E(e),...E(t)]),r={},i=D(n);if(i!==void 0&&(r.type=i),n.has(`object`)&&(e.properties||t.properties)){let n=e.properties??{},i=t.properties??{},a={};for(let e of new Set([...Object.keys(n),...Object.keys(i)]))a[e]=O(n[e],i[e]);r.properties=a;let o=e.required??[],s=t.required??[],c=o.filter(e=>s.includes(e));c.length&&(r.required=c)}if(n.has(`array`)){let n=O(e.items,t.items);n&&Object.keys(n).length&&(r.items=n)}return r}function k(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}const A=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function j(e,t,n){return`<!doctype html>
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));let c=require("express");c=s(c,1);let l=require("@standard-schema/utils");function u(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`){let e=n[`~standard`].validate(t);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new l.SchemaError(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function d(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function f(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}const p=Function(`m`,`return import(m)`);let m,h;async function g(e){m??=await p(`module`),h??=await p(`url`);let t=[],n=globalThis.process?.argv?.[1];n&&t.push(h.pathToFileURL(n).href);let r=globalThis.process?.cwd?.()??``;r&&t.push(h.pathToFileURL(r+`/`).href);for(let n of t)try{let t=m.createRequire(n).resolve(e);return await p(h.pathToFileURL(t).href)}catch{}return p(e)}const _=new WeakMap,v=/\(\?<[^>]+>/;function y(e){return typeof e==`string`&&v.test(e)?new RegExp(e):e}function b(e){if(typeof e.path==`string`)return e.path;if(e.pathExample)return e.pathExample;let t=0;return e.path.source.replace(/\\\//g,`/`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function x(e){return e.replace(/\{([^{}]*)\}/g,`$1`).replace(/:([A-Za-z0-9_]+)(?:\([^)]*\))?[?+*]?/g,`{$1}`).replace(/\(\?<([A-Za-z0-9_]+)>[^)]*\)/g,`{$1}`).replace(/^\^|\$$/g,``).replace(/\/{2,}/g,`/`)}function S(e){let t=e.replace(/\{([^{}]*)\}/g,`$1`);return[...t.matchAll(/:([A-Za-z0-9_]+)/g),...t.matchAll(/\(\?<([A-Za-z0-9_]+)>/g)].map(e=>e[1])}function C(e){return e.startsWith(`:`)||e.startsWith(`*`)||e.includes(`(?<`)}function w(e){return e.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(Boolean).find(e=>!C(e))??`default`}function T(e,t){let n=t.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(e=>e&&!C(e)),r=n[n.length-1]??`resource`;return`${{get:`Get`,post:`Create`,put:`Update`,patch:`Patch`,delete:`Delete`,head:`Head`,options:`Options`}[e]??e} ${r}`}async function E(e){let t=_.get(e);if(t)return t;let n=await D(e);return _.set(e,n),n}async function D(e){if(typeof e.toJsonSchema==`function`)try{return e.toJsonSchema()}catch{}let t=e[`~standard`]?.vendor;if(t===`zod`){try{let t=await g(`zod`);if(typeof t.toJSONSchema==`function`)return t.toJSONSchema(e)}catch{}try{let t=await g(`zod-to-json-schema`),n=t.zodToJsonSchema??t.default?.zodToJsonSchema;if(typeof n==`function`)return n(e)}catch{}}if(t===`valibot`)try{let t=await g(`@valibot/to-json-schema`),n=t.toJsonSchema??t.default?.toJsonSchema;if(typeof n==`function`)return n(e)}catch{}if(t===`effect`)try{let t=await g(`effect`),n=t.JSONSchema?.make??t.default?.JSONSchema?.make;if(typeof n==`function`)return n(e)}catch{}return{}}function O(e){return k(e,0,new WeakSet)}function k(e,t,n){if(e==null)return{type:`null`};if(t>=12)return{};if(e instanceof Date)return{type:`string`,format:`date-time`};if(typeof e==`bigint`)return{type:`integer`};if(typeof e==`object`&&typeof e.toJSON==`function`)return k(e.toJSON(),t,n);if(Array.isArray(e)){if(e.length===0||n.has(e))return{type:`array`,items:{}};n.add(e);let r=Math.min(e.length,20),i=k(e[0],t+1,n);for(let a=1;a<r;a++)i=M(i,k(e[a],t+1,n));return n.delete(e),{type:`array`,items:i}}switch(typeof e){case`string`:return{type:`string`};case`boolean`:return{type:`boolean`};case`number`:return Number.isFinite(e)?{type:Number.isInteger(e)?`integer`:`number`}:{type:`null`};case`object`:{if(n.has(e))return{type:`object`};n.add(e);let r={},i=[];for(let[a,o]of Object.entries(e))typeof o!=`function`&&o!==void 0&&(r[a]=k(o,t+1,n),i.push(a));n.delete(e);let a={type:`object`,properties:r};return i.length&&(a.required=i),a}default:return{}}}function A(e){return!e||e.type===void 0?new Set:new Set(Array.isArray(e.type)?e.type:[e.type])}function j(e){e.has(`integer`)&&e.has(`number`)&&e.delete(`integer`);let t=[...e];if(t.length!==0)return t.length===1?t[0]:t}function M(e,t){if(!e||Object.keys(e).length===0)return t??{};if(!t||Object.keys(t).length===0)return e??{};let n=new Set([...A(e),...A(t)]),r={},i=j(n);if(i!==void 0&&(r.type=i),n.has(`object`)&&(e.properties||t.properties)){let n=e.properties??{},i=t.properties??{},a={};for(let e of new Set([...Object.keys(n),...Object.keys(i)]))a[e]=M(n[e],i[e]);r.properties=a;let o=e.required??[],s=t.required??[],c=o.filter(e=>s.includes(e));c.length&&(r.required=c)}if(n.has(`array`)){let n=M(e.items,t.items);n&&Object.keys(n).length&&(r.items=n)}return r}function N(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}const P=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function F(e,t,n){return`<!doctype html>
2
2
  <html>
3
3
  <head>
4
- <title>${k(e)}</title>
4
+ <title>${N(e)}</title>
5
5
  <meta charset="utf-8" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1" />
7
7
  </head>
8
8
  <body>
9
- <script id="api-reference" data-url="${k(t)}"><\/script>
10
- <script src="${k(n)}"><\/script>
9
+ <script id="api-reference" data-url="${N(t)}"><\/script>
10
+ <script src="${N(n)}"><\/script>
11
11
  </body>
12
- </html>`}async function M(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={};for(let[e,n]of t.responseSamples){let t={schema:n.schema};n.example!==void 0&&(t.example=n.example),a[String(e)]={description:e<400?`Success`:`Error`,content:{"application/json":t}}}if(t.responseSchema){let e=await S(t.responseSchema);if(e&&Object.keys(e).length){let n=`200`;for(let e of t.responseSamples.keys())if(e>=200&&e<300){n=String(e);break}a[n]={description:`Success`,content:{"application/json":{schema:e}}}}}Object.keys(a).length===0&&(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 N=new WeakMap;var P=class e{router;routes=[];mountedRouters=[];sampleMode=`off`;scheduleSpecWrite;constructor(){this.router=c.default.Router(),N.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.trackMounted(i,t),t.getRouter();let n=N.get(t);return n&&this.trackMounted(i,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.trackMounted(e,n)}else this.router.use(e.getRouter()),this.trackMounted(``,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]}enableSampling(e=`redacted`,t,n=new Set){if(!n.has(this)){n.add(this),this.sampleMode=e,this.scheduleSpecWrite=t;for(let{router:r}of this.mountedRouters)r.enableSampling(e,t,n)}}trackMounted(e,t){this.mountedRouters.some(n=>n.router===t&&n.prefix===e)||(this.mountedRouters.push({prefix:e,router:t}),this.sampleMode!==`off`&&t.enableSampling(this.sampleMode,this.scheduleSpecWrite))}hydrateResponses(e,t=``,n=new Set){if(n.has(this))return;n.add(this);let r=e?.paths??{};for(let e of this.routes){let n=r[v(t+e.path)]?.[e.method]?.responses;if(n)for(let t of Object.keys(n)){let r=Number(t);if(Number.isNaN(r)||e.responseSamples.has(r))continue;let i=n[t]?.content?.[`application/json`];i?.schema&&e.responseSamples.set(r,i.example===void 0?{schema:i.schema}:{schema:i.schema,example:i.example})}}for(let{prefix:r,router:i}of this.mountedRouters)i.hydrateResponses(e,t+r,n)}docs(e={}){let t=c.default.Router(),n;if(e.specOutputPath){let t=e.specOutputPath,r=!1,i=!1,a=async()=>{if(r){i=!0;return}r=!0;try{let n=await M(this.getRouteMetadata(),e),r=await p(`fs/promises`),i=t.replace(/[/\\][^/\\]*$/,``);i&&i!==t&&await r.mkdir(i,{recursive:!0}).catch(()=>{});let a=`${t}.${globalThis.process?.pid??`0`}.tmp`;await r.writeFile(a,JSON.stringify(n,null,2),`utf8`),await r.rename(a,t)}catch{}finally{r=!1,i&&(i=!1,a())}},o;n=()=>{o&&clearTimeout(o),o=setTimeout(a,300),o.unref?.()},setImmediate(async()=>{try{let e=await(await p(`fs/promises`)).readFile(t,`utf8`).catch(()=>null);if(e)try{this.hydrateResponses(JSON.parse(e))}catch{}}catch{}await a()})}return e.sampleResponses!==!1&&this.enableSampling(e.sampleResponses===`live`?`live`:`redacted`,n),t.get(`/openapi.json`,async(t,n)=>{try{let t=await M(this.getRouteMetadata(),e);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??A;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(j(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);let o=0,s=(e,t)=>{let n=e.statusCode,r=w(t),i=a.responseSamples.get(n),o=i?O(i.schema,r):r,s=this.sampleMode===`live`?i?.example??t:void 0,c=!i||JSON.stringify(i.schema)!==JSON.stringify(o);a.responseSamples.set(n,{schema:o,example:s}),c&&this.scheduleSpecWrite?.()};return this.router[e](t,(e,t,n)=>{if(this.sampleMode===`off`||a.hidden||o>=50||a.responseSamples.size>=10){n();return}o++;let r=t.json;t.json=function(e){return s(t,e),r.call(this,e)};let i=t.send;t.send=function(e){return typeof e==`object`&&e&&!Buffer.isBuffer(e)&&s(t,e),i.call(this,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 F(){return new P}function I(e){let t=new P;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function L(...e){let t=new P;for(let n of e)t=t.useMiddleware(n);return t}function R(e,t={}){let n=(Array.isArray(e)?e:[e]).map(e=>`prefix`in e?e:{prefix:``,router:e});if(t.sampleResponses!==!1){let e=t.sampleResponses===`live`?`live`:`redacted`;for(let{router:t}of n)t.enableSampling(e)}let r=c.default.Router();return r.get(`/openapi.json`,async(e,r)=>{try{let e=await M(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??A;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(j(t.title??`API`,r,i))}),r}exports.TypedRouter=P,exports.createDocs=R,exports.createTypedRouter=F,exports.createTypedRouterWithConfig=I,exports.createTypedRouterWithMiddleware=L,exports.inferJsonSchema=w,exports.isSchemaError=f,exports.parseSchema=u,exports.safeParseSchema=d;
12
+ </html>`}async function I(e,t){let n=Object.create(null);for(let t of e){if(t.method===`all`||t.hidden)continue;let e=b(t),r=x(e);n[r]||(n[r]={});let i;if(t.paramsSchema){let e=await E(t.paramsSchema),n=e.properties??{},r=e.required??[];i=Object.entries(n).map(([e,t])=>({name:e,in:`path`,required:r.includes(e),schema:t}))}else i=S(e).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await E(t.querySchema),n=e.properties??{},r=e.required??[];for(let[e,t]of Object.entries(n))i.push({name:e,in:`query`,required:r.includes(e),schema:t})}let a={summary:t.summary??T(t.method,e),tags:t.tags??[w(e)],parameters:i};t.description&&(a.description=t.description),t.deprecated&&(a.deprecated=!0),t.bodySchema&&(a.requestBody={required:!0,content:{"application/json":{schema:await E(t.bodySchema)}}});let o={};for(let[e,n]of t.responseSamples){let t={schema:n.schema};n.example!==void 0&&(t.example=n.example),o[String(e)]={description:e<400?`Success`:`Error`,content:{"application/json":t}}}if(t.responseSchema){let e=await E(t.responseSchema);if(e&&Object.keys(e).length){let n=`200`;for(let e of t.responseSamples.keys())if(e>=200&&e<300){n=String(e);break}o[n]={description:`Success`,content:{"application/json":{schema:e}}}}}Object.keys(o).length===0&&(o[200]={description:`Success`}),a.responses=o,n[r][t.method]=a}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 L=new WeakMap;var R=class e{router;routes=[];mountedRouters=[];sampleMode=`off`;scheduleSpecWrite;constructor(){this.router=c.default.Router(),L.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.trackMounted(i,t),t.getRouter();let n=L.get(t);return n&&this.trackMounted(i,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.trackMounted(e,n)}else this.router.use(e.getRouter()),this.trackMounted(``,e);return this}getRouteMetadata(){let e=this.mountedRouters.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:e+b(t)}})));return[...this.routes,...e]}enableSampling(e=`redacted`,t,n=new Set){if(!n.has(this)){n.add(this),this.sampleMode=e,this.scheduleSpecWrite=t;for(let{router:r}of this.mountedRouters)r.enableSampling(e,t,n)}}trackMounted(e,t){this.mountedRouters.some(n=>n.router===t&&n.prefix===e)||(this.mountedRouters.push({prefix:e,router:t}),this.sampleMode!==`off`&&t.enableSampling(this.sampleMode,this.scheduleSpecWrite))}hydrateResponses(e,t=``,n=new Set){if(n.has(this))return;n.add(this);let r=e?.paths??{};for(let e of this.routes){let n=r[x(t+b(e))]?.[e.method]?.responses;if(n)for(let t of Object.keys(n)){let r=Number(t);if(Number.isNaN(r)||e.responseSamples.has(r))continue;let i=n[t]?.content?.[`application/json`];i?.schema&&e.responseSamples.set(r,i.example===void 0?{schema:i.schema}:{schema:i.schema,example:i.example})}}for(let{prefix:r,router:i}of this.mountedRouters)i.hydrateResponses(e,t+r,n)}docs(e={}){let t=c.default.Router(),n;if(e.specOutputPath){let t=e.specOutputPath,r=!1,i=!1,a=async()=>{if(r){i=!0;return}r=!0;try{let n=await I(this.getRouteMetadata(),e),r=await p(`fs/promises`),i=t.replace(/[/\\][^/\\]*$/,``);i&&i!==t&&await r.mkdir(i,{recursive:!0}).catch(()=>{});let a=globalThis.process?.pid??`0`,o=`${t}.${a}.tmp`;await r.writeFile(o,JSON.stringify(n,null,2),`utf8`),await r.rename(o,t)}catch{}finally{r=!1,i&&(i=!1,a())}},o;n=()=>{o&&clearTimeout(o),o=setTimeout(a,300),o.unref?.()},setImmediate(async()=>{try{let e=await(await p(`fs/promises`)).readFile(t,`utf8`).catch(()=>null);if(e)try{this.hydrateResponses(JSON.parse(e))}catch{}}catch{}await a()})}return e.sampleResponses!==!1&&this.enableSampling(e.sampleResponses===`live`?`live`:`redacted`,n),t.get(`/openapi.json`,async(t,n)=>{try{let t=await I(this.getRouteMetadata(),e);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??P;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(F(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.paramsSchema=e.paramsSchema,a.tags=e.tags,a.description=e.description,a.summary=e.summary,a.deprecated=e.deprecated,a.responseSchema=e.responseSchema,a.hidden=e.hidden,a.pathExample=e.pathExample,e.middleware&&i.push(...e.middleware),e.bodySchema&&i.push(this.createBodyValidationMiddleware(e.bodySchema)),e.querySchema&&i.push(this.createQueryValidationMiddleware(e.querySchema)),e.paramsSchema&&i.push(this.createParamsValidationMiddleware(e.paramsSchema)),i.push(r)}else i.push(n);let o=0,s=(e,t)=>{let n=e.statusCode,r=O(t),i=a.responseSamples.get(n),o=i?M(i.schema,r):r,s=this.sampleMode===`live`?i?.example??t:void 0,c=!i||JSON.stringify(i.schema)!==JSON.stringify(o);a.responseSamples.set(n,{schema:o,example:s}),c&&this.scheduleSpecWrite?.()};return this.router[e](y(t),(e,t,n)=>{if(this.sampleMode===`off`||a.hidden||o>=50||a.responseSamples.size>=10){n();return}o++;let r=t.json;t.json=function(e){return s(t,e),r.call(this,e)};let i=t.send;t.send=function(e){return typeof e==`object`&&e&&!Buffer.isBuffer(e)&&s(t,e),i.call(this,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)}}}createParamsValidationMiddleware(e){return async(t,n,r)=>{try{let i=d(e,t.params),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.params=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 z(){return new R}function B(e){let t=new R;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function V(...e){let t=new R;for(let n of e)t=t.useMiddleware(n);return t}function H(e,t={}){let n=(Array.isArray(e)?e:[e]).map(e=>`prefix`in e?e:{prefix:``,router:e});if(t.sampleResponses!==!1){let e=t.sampleResponses===`live`?`live`:`redacted`;for(let{router:t}of n)t.enableSampling(e)}let r=c.default.Router();return r.get(`/openapi.json`,async(e,r)=>{try{let e=await I(n.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:e+b(t)}}))),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??P;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(F(t.title??`API`,r,i))}),r}exports.TypedRouter=R,exports.createDocs=H,exports.createTypedRouter=z,exports.createTypedRouterWithConfig=B,exports.createTypedRouterWithMiddleware=V,exports.inferJsonSchema=O,exports.isSchemaError=f,exports.parseSchema=u,exports.safeParseSchema=d;
@@ -1,8 +1,14 @@
1
1
  import express, { NextFunction, Request, Response } from "express";
2
2
  import { StandardSchemaV1 } from "@standard-schema/spec";
3
-
4
3
  //#region src/schema-router.d.ts
5
4
  type AnyStandardSchema = StandardSchemaV1<any, any>;
5
+ type SchemaLike = AnyStandardSchema | {
6
+ safeParse: (...args: any[]) => any;
7
+ } | {
8
+ parse: (...args: any[]) => any;
9
+ } | {
10
+ validate: (...args: any[]) => any;
11
+ };
6
12
  type InferOutput<T> = T extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<T> : unknown;
7
13
  type InferInput<T> = T extends StandardSchemaV1 ? StandardSchemaV1.InferInput<T> : unknown;
8
14
  type InferFromSafeParse<T> = T extends {
@@ -43,28 +49,36 @@ declare function isSchemaError(error: unknown): error is {
43
49
  * - Optional parameters: /posts/:year/:month? → { year: string; month?: string }
44
50
  * - Wildcard parameters: /files/* → { "0": string }
45
51
  * - Multiple wildcards: /a/star/b/star → { "0": string; "1": string }
52
+ *
53
+ * A path containing a named regex capture group — `(?<id>...)` — is treated
54
+ * as a raw regex pattern instead: /^\/legacy\/(?<id>\d+)$/ → { id: string }.
55
+ * This syntax never appears in Express's own path syntax, so detecting it is
56
+ * unambiguous; registerRoute converts the string to a real RegExp at
57
+ * runtime so Express matches it as one.
46
58
  */
47
- type ExtractRouteParams<Path extends string> = string extends Path ? Record<string, string> : ExtractParams<Path>;
59
+ type ExtractRouteParams<Path extends string> = string extends Path ? Record<string, string> : Path extends `${infer _Before}(?<${infer _Name}>${infer _Rest}` ? ExtractRegexGroupParams<Path> : ExtractParams<Path>;
60
+ type ExtractRegexGroupParams<S extends string> = S extends `${infer _Before}(?<${infer Name}>${infer _Rest}` ? { [K in Name]: string; } & ExtractRegexGroupParams<RemoveFirstRegexGroup<S>> : {};
61
+ type RemoveFirstRegexGroup<S extends string> = S extends `${infer _Before}(?<${infer _Name}>${infer After}` ? After extends `${infer _Inner})${infer Rest}` ? Rest : "" : "";
48
62
  /**
49
63
  * Main parameter extraction logic - enhanced for Express 5 support with recursion depth limit
50
64
  */
51
- type ExtractParams<Path extends string> = Path extends `${infer Before}{${infer OptionalContent}}${infer After}` ? ExtractOptionalSegment<OptionalContent> & ExtractParams<`${Before}${After}`> : Path extends `${infer _Before}:${infer Rest}` ? ExtractSingleParam<Rest> & ExtractParams<RemoveFirstParam<Path>> : Path extends `${infer _Before}*${infer Name}/${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`/${After}`> : { [K in Name]: string[] } & ExtractParams<`/${After}`> : Path extends `${infer _Before}*${infer Name}-${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`-${After}`> : { [K in Name]: string[] } & ExtractParams<`-${After}`> : Path extends `${infer _Before}*${infer Name}.${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`.${After}`> : { [K in Name]: string[] } & ExtractParams<`.${After}`> : Path extends `${infer _Before}*${infer Name}#${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`#${After}`> : { [K in Name]: string[] } & ExtractParams<`#${After}`> : Path extends `${infer _Before}*${infer Name}:${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`:${After}`> : { [K in Name]: string[] } & ExtractParams<`:${After}`> : Path extends `${infer _Before}*${infer Name}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<``> : { [K in Name]: string[] } & ExtractParams<``> : Path extends `${infer _Before}*${infer After}` ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<After> : {};
65
+ type ExtractParams<Path extends string> = Path extends `${infer Before}{${infer OptionalContent}}${infer After}` ? ExtractOptionalSegment<OptionalContent> & ExtractParams<`${Before}${After}`> : Path extends `${infer _Before}:${infer Rest}` ? ExtractSingleParam<Rest> & ExtractParams<RemoveFirstParam<Path>> : Path extends `${infer _Before}*${infer Name}/${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`/${After}`> : { [K in Name]: string[]; } & ExtractParams<`/${After}`> : Path extends `${infer _Before}*${infer Name}-${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`-${After}`> : { [K in Name]: string[]; } & ExtractParams<`-${After}`> : Path extends `${infer _Before}*${infer Name}.${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`.${After}`> : { [K in Name]: string[]; } & ExtractParams<`.${After}`> : Path extends `${infer _Before}*${infer Name}#${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`#${After}`> : { [K in Name]: string[]; } & ExtractParams<`#${After}`> : Path extends `${infer _Before}*${infer Name}:${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`:${After}`> : { [K in Name]: string[]; } & ExtractParams<`:${After}`> : Path extends `${infer _Before}*${infer Name}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<``> : { [K in Name]: string[]; } & ExtractParams<``> : Path extends `${infer _Before}*${infer After}` ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<After> : {};
52
66
  /**
53
67
  * Extract parameters from Express 5 optional segments in braces
54
68
  * Handles patterns like {/:param}, {.:ext}, {/optional/:param}
55
69
  */
56
- type ExtractOptionalSegment<Content extends string> = Content extends `*${infer Name}` ? Name extends "" ? {} : { [K in Name]?: string[] } : Content extends `/${infer Rest}` ? Rest extends `*${infer Name}` ? Name extends "" ? {} : { [K in Name]?: string[] } : Rest extends `:${infer R}` ? ExtractOptionalParam<R> : {} : Content extends `/:${infer Rest}` ? ExtractOptionalParam<Rest> : Content extends `.:${infer Rest}` ? ExtractOptionalParam<Rest> : Content extends `${infer _Path}:${infer Rest}` ? ExtractOptionalParam<Rest> : {};
70
+ type ExtractOptionalSegment<Content extends string> = Content extends `*${infer Name}` ? Name extends "" ? {} : { [K in Name]?: string[]; } : Content extends `/${infer Rest}` ? Rest extends `*${infer Name}` ? Name extends "" ? {} : { [K in Name]?: string[]; } : Rest extends `:${infer R}` ? ExtractOptionalParam<R> : {} : Content extends `/:${infer Rest}` ? ExtractOptionalParam<Rest> : Content extends `.:${infer Rest}` ? ExtractOptionalParam<Rest> : Content extends `${infer _Path}:${infer Rest}` ? ExtractOptionalParam<Rest> : {};
57
71
  /**
58
72
  * Extract a single optional parameter from brace content
59
73
  */
60
- type ExtractOptionalParam<Rest extends string> = Rest extends `${infer ParamName}/${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}-${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}.${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}` ? { [K in ParamName]?: string } : {};
74
+ type ExtractOptionalParam<Rest extends string> = Rest extends `${infer ParamName}/${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}-${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}.${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}` ? { [K in ParamName]?: string; } : {};
61
75
  /**
62
76
  * Extract a single parameter name from the rest of the path
63
77
  * Enhanced to handle Express 5 patterns and optional parameters correctly
64
78
  * Special handling for consecutive parameters like :from-:to
65
79
  * Order matters: regex constraints must be handled before repeating parameters
66
80
  */
67
- type ExtractSingleParam<Rest extends string> = Rest extends `${infer ParamName}(${infer _Constraint})${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}-:${infer _NextParam}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}.:${infer _NextParam}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}?/${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}?-${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}?.${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}?#${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}?:${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}/${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}-${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}.${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}#${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}:${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}+${infer _After}` ? { [K in ParamName]: string[] } : Rest extends `${infer ParamName}*${infer _After}` ? { [K in ParamName]?: string[] } : Rest extends `${infer ParamName}?${infer _After}` ? { [K in ParamName]?: string } : Rest extends string ? Rest extends "" ? {} : Rest extends `${infer ParamName}?` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}+` ? { [K in ParamName]: string[] } : Rest extends `${infer ParamName}*` ? { [K in ParamName]?: string[] } : { [K in Rest]: string } : {};
81
+ type ExtractSingleParam<Rest extends string> = Rest extends `${infer ParamName}(${infer _Constraint})${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}-:${infer _NextParam}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}.:${infer _NextParam}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}?/${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}?-${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}?.${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}?#${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}?:${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}/${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}-${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}.${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}#${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}:${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}+${infer _After}` ? { [K in ParamName]: string[]; } : Rest extends `${infer ParamName}*${infer _After}` ? { [K in ParamName]?: string[]; } : Rest extends `${infer ParamName}?${infer _After}` ? { [K in ParamName]?: string; } : Rest extends string ? Rest extends "" ? {} : Rest extends `${infer ParamName}?` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}+` ? { [K in ParamName]: string[]; } : Rest extends `${infer ParamName}*` ? { [K in ParamName]?: string[]; } : { [K in Rest]: string; } : {};
68
82
  /**
69
83
  * Remove the first parameter from path to continue parsing
70
84
  * Enhanced to handle Express 5 patterns and optional parameters
@@ -100,24 +114,39 @@ type RequestOnlyMiddleware<TReq extends Record<string, any>> = TypedMiddleware<T
100
114
  type LocalsOnlyMiddleware<TLocals extends Record<string, any>> = TypedMiddleware<{}, TLocals>;
101
115
  type InferMiddlewareProps<T extends readonly TypedMiddleware<any, any>[]> = T extends readonly [infer First, ...infer Rest] ? First extends TypedMiddleware<infer FirstReq, any> ? Rest extends readonly TypedMiddleware<any, any>[] ? FirstReq & InferMiddlewareProps<Rest> : FirstReq : {} : {};
102
116
  type InferMiddlewareLocals<T extends readonly TypedMiddleware<any, any>[]> = T extends readonly [infer First, ...infer Rest] ? First extends TypedMiddleware<any, infer FirstLocals> ? Rest extends readonly TypedMiddleware<any, any>[] ? FirstLocals & InferMiddlewareLocals<Rest> : FirstLocals : {} : {};
103
- type SchemaRequest<Path extends string = string, BodySchema extends AnyStandardSchema | unknown = unknown, QuerySchema extends AnyStandardSchema | unknown = unknown, MiddlewareProps extends Record<string, any> = {}> = Omit<Request, "params" | "query" | "body"> & {
104
- params: ExtractRouteParams<Path>;
105
- body: BodySchema extends unknown ? InferSchemaOutput<BodySchema> : unknown;
106
- query: QuerySchema extends unknown ? InferSchemaOutput<QuerySchema> : unknown;
117
+ type SchemaRequest<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}, ParamsSchema extends SchemaLike | undefined = undefined> = Omit<Request, "params" | "query" | "body"> & {
118
+ params: ParamsSchema extends undefined ? ExtractRouteParams<Path> : InferSchemaOutput<ParamsSchema>;
119
+ body: BodySchema extends undefined ? unknown : InferSchemaOutput<BodySchema>;
120
+ query: QuerySchema extends undefined ? unknown : InferSchemaOutput<QuerySchema>;
107
121
  } & MiddlewareProps;
108
- type SchemaRouteHandler<Path extends string = string, BodySchema extends AnyStandardSchema | unknown = unknown, QuerySchema extends AnyStandardSchema | unknown = unknown, MiddlewareProps extends Record<string, any> = {}, ResponseLocals extends Record<string, any> = {}> = (req: SchemaRequest<Path, BodySchema, QuerySchema, MiddlewareProps>, res: Response<any, ResponseLocals>, next?: NextFunction) => void | undefined | Promise<void | undefined> | Response | Promise<Response> | Promise<Response | undefined>;
122
+ type SchemaRouteHandler<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}, ResponseLocals extends Record<string, any> = {}, ParamsSchema extends SchemaLike | undefined = undefined> = (req: SchemaRequest<Path, BodySchema, QuerySchema, MiddlewareProps, ParamsSchema>, res: Response<any, ResponseLocals>, next?: NextFunction) => void | undefined | Promise<void | undefined> | Response | Promise<Response> | Promise<Response | undefined>;
109
123
  /**
110
124
  * Options for defining a typed route, including schemas and middleware.
111
125
  *
112
126
  * @template BodySchema - Schema for request body validation.
113
127
  * @template QuerySchema - Schema for query parameter validation.
128
+ * @template ParamsSchema - Schema for route param validation.
114
129
  * @property bodySchema - Optional schema for validating the request body.
115
130
  * @property querySchema - Optional schema for validating the query string.
131
+ * @property paramsSchema - Optional schema for validating route params.
116
132
  * @property middleware - Optional array of TypedMiddleware for this route.
117
133
  */
118
- interface RouteOptions<BodySchema extends AnyStandardSchema | unknown = unknown, QuerySchema extends AnyStandardSchema | unknown = unknown> {
134
+ interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined> {
119
135
  bodySchema?: BodySchema;
136
+ /**
137
+ * Every value here arrives as a string, never a real boolean/number —
138
+ * `z.coerce.boolean()` treats `"false"` as truthy. Use a text-aware parser
139
+ * like `z.stringbool()` for boolean fields.
140
+ * @see https://github.com/Mini-Sylar/express-typed-router#gotchas
141
+ */
120
142
  querySchema?: QuerySchema;
143
+ /**
144
+ * Overrides the inferred `req.params` type with this schema's output —
145
+ * useful for coercing a numeric-looking param (`z.coerce.number()`) since
146
+ * Express never converts params from strings on its own. Same
147
+ * string-arrival caveat as `querySchema` applies; see its docs above.
148
+ */
149
+ paramsSchema?: ParamsSchema;
121
150
  middleware?: TypedMiddleware<any, any>[];
122
151
  tags?: string[];
123
152
  description?: string;
@@ -126,8 +155,16 @@ interface RouteOptions<BodySchema extends AnyStandardSchema | unknown = unknown,
126
155
  responseSchema?: AnyStandardSchema;
127
156
  /** Exclude this route from the generated OpenAPI spec entirely. */
128
157
  hidden?: boolean;
158
+ /**
159
+ * Only used when `path` is a `RegExp`. A readable stand-in path (e.g.
160
+ * `/legacy/:id`) for the OpenAPI doc, since one can't be derived from a
161
+ * `RegExp` value. Defaults to `regex.toString()`. Doc-only — `req.params`
162
+ * types as `Record<string, string>` for RegExp routes unless overridden
163
+ * by `paramsSchema`.
164
+ */
165
+ pathExample?: string;
129
166
  }
130
- type DocMeta = Pick<RouteOptions<unknown, unknown>, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden">;
167
+ type DocMeta = Pick<RouteOptions, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden" | "pathExample">;
131
168
  type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "all";
132
169
  interface DocsOptions {
133
170
  title?: string;
@@ -184,15 +221,18 @@ interface DocsOptions {
184
221
  }
185
222
  interface RouteMetadata {
186
223
  method: HttpMethod;
187
- path: string;
188
- bodySchema?: AnyStandardSchema;
189
- querySchema?: AnyStandardSchema;
190
- tags?: string[];
191
- description?: string;
192
- summary?: string;
193
- deprecated?: boolean;
194
- responseSchema?: AnyStandardSchema;
195
- hidden?: boolean;
224
+ path: string | RegExp;
225
+ /** Doc-only path override for RegExp routes — see RouteOptions.pathExample. */
226
+ pathExample?: string | undefined;
227
+ bodySchema?: AnyStandardSchema | undefined;
228
+ querySchema?: AnyStandardSchema | undefined;
229
+ paramsSchema?: AnyStandardSchema | undefined;
230
+ tags?: string[] | undefined;
231
+ description?: string | undefined;
232
+ summary?: string | undefined;
233
+ deprecated?: boolean | undefined;
234
+ responseSchema?: AnyStandardSchema | undefined;
235
+ hidden?: boolean | undefined;
196
236
  responseSamples: Map<number, {
197
237
  schema: Record<string, any>;
198
238
  example?: unknown;
@@ -235,14 +275,14 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
235
275
  * and/or adds properties to response.locals
236
276
  */
237
277
  /**
238
- * Add typed middleware to the router.
239
- * This middleware will apply to all routes defined after this call.
240
- *
241
- * @template TReq - Type extensions for the request object
242
- * @template TLocals - Type extensions for response.locals
243
- * @param middleware - The typed middleware function
244
- * @returns A new router instance with updated types
245
- */
278
+ * Add typed middleware to the router.
279
+ * This middleware will apply to all routes defined after this call.
280
+ *
281
+ * @template TReq - Type extensions for the request object
282
+ * @template TLocals - Type extensions for response.locals
283
+ * @param middleware - The typed middleware function
284
+ * @returns A new router instance with updated types
285
+ */
246
286
  useMiddleware<TReq extends Record<string, any> = {}, TLocals extends Record<string, any> = {}>(middleware: TypedMiddleware<TReq, TLocals>): TypedRouter<Req & TReq, Locals & TLocals>;
247
287
  /**
248
288
  * Get the underlying Express router, typed as a RequestHandler so it can be
@@ -318,168 +358,115 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
318
358
  * // GET /docs/openapi.json → raw OpenAPI 3.1 spec
319
359
  */
320
360
  docs(options?: DocsOptions): express.Router & express.RequestHandler;
321
- get<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
322
- get<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
323
- get<Path extends string, Middleware extends readonly TypedMiddleware<any, any>[]>(path: Path, options: {
324
- middleware: Middleware;
325
- }, handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<Middleware>, Locals & InferMiddlewareLocals<Middleware>>): TypedRouter<Req, Locals>;
326
- get<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: RouteOptions<BodySchema, QuerySchema> & {
327
- middleware: [...M];
328
- }, // Using tuple spread pattern
329
- handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
330
- // Make it readonly for type inference
331
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
332
- post<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
333
- bodySchema: BodySchema;
361
+ get<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
362
+ get<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
363
+ bodySchema?: BodySchema;
364
+ querySchema?: QuerySchema;
365
+ paramsSchema?: ParamsSchema;
366
+ middleware?: [...M];
367
+ }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
368
+ get(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
369
+ get<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
370
+ bodySchema?: BodySchema;
371
+ querySchema?: QuerySchema;
372
+ paramsSchema?: ParamsSchema;
373
+ middleware?: [...M];
374
+ }, handler: SchemaRouteHandler<string, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
375
+ post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
376
+ post<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
377
+ bodySchema?: BodySchema;
378
+ querySchema?: QuerySchema;
379
+ paramsSchema?: ParamsSchema;
380
+ middleware?: [...M];
381
+ }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
382
+ post(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
383
+ post<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
384
+ bodySchema?: BodySchema;
385
+ querySchema?: QuerySchema;
386
+ paramsSchema?: ParamsSchema;
387
+ middleware?: [...M];
388
+ }, handler: SchemaRouteHandler<string, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
389
+ put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
390
+ put<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
391
+ bodySchema?: BodySchema;
392
+ querySchema?: QuerySchema;
393
+ paramsSchema?: ParamsSchema;
394
+ middleware?: [...M];
395
+ }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
396
+ put(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
397
+ put<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
398
+ bodySchema?: BodySchema;
399
+ querySchema?: QuerySchema;
400
+ paramsSchema?: ParamsSchema;
401
+ middleware?: [...M];
402
+ }, handler: SchemaRouteHandler<string, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
403
+ patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
404
+ patch<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
405
+ bodySchema?: BodySchema;
406
+ querySchema?: QuerySchema;
407
+ paramsSchema?: ParamsSchema;
408
+ middleware?: [...M];
409
+ }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
410
+ patch(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
411
+ patch<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
412
+ bodySchema?: BodySchema;
413
+ querySchema?: QuerySchema;
414
+ paramsSchema?: ParamsSchema;
415
+ middleware?: [...M];
416
+ }, handler: SchemaRouteHandler<string, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
417
+ delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
418
+ delete<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
419
+ querySchema?: QuerySchema;
420
+ paramsSchema?: ParamsSchema;
421
+ middleware?: [...M];
422
+ }, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
423
+ delete(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
424
+ delete<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
425
+ querySchema?: QuerySchema;
426
+ paramsSchema?: ParamsSchema;
427
+ middleware?: [...M];
428
+ }, handler: SchemaRouteHandler<string, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
429
+ options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
430
+ options<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
431
+ querySchema?: QuerySchema;
432
+ paramsSchema?: ParamsSchema;
433
+ middleware?: [...M];
434
+ }, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
435
+ options(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
436
+ options<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
437
+ querySchema?: QuerySchema;
438
+ paramsSchema?: ParamsSchema;
439
+ middleware?: [...M];
440
+ }, handler: SchemaRouteHandler<string, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
441
+ head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
442
+ head<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
334
443
  querySchema?: QuerySchema;
335
- middleware: [...M];
336
- }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
337
- // Make it readonly for type inference
338
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
339
- post<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
340
- bodySchema: BodySchema;
341
- middleware: [...M];
342
- }, // Using tuple spread pattern
343
- handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
344
- // Make it readonly for type inference
345
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
346
- post<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
347
- middleware: [...M];
348
- }, // Using tuple spread pattern
349
- handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
350
- // Make it readonly for type inference
351
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
352
- post<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
353
- post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
354
- put<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
355
- bodySchema: BodySchema;
444
+ paramsSchema?: ParamsSchema;
445
+ middleware?: [...M];
446
+ }, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
447
+ head(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
448
+ head<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
356
449
  querySchema?: QuerySchema;
357
- middleware: [...M];
358
- }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
359
- // Make it readonly for type inference
360
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
361
- put<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
362
- bodySchema: BodySchema;
363
- middleware: [...M];
364
- }, // Using tuple spread pattern
365
- handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
366
- // Make it readonly for type inference
367
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
368
- put<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
369
- middleware: [...M];
370
- }, // Using tuple spread pattern
371
- handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
372
- // Make it readonly for type inference
373
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
374
- put<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
375
- put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
376
- patch<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
377
- bodySchema: BodySchema;
450
+ paramsSchema?: ParamsSchema;
451
+ middleware?: [...M];
452
+ }, handler: SchemaRouteHandler<string, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
453
+ all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
454
+ all<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
455
+ bodySchema?: BodySchema;
378
456
  querySchema?: QuerySchema;
379
- middleware: [...M];
380
- }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
381
- // Make it readonly for type inference
382
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
383
- patch<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
384
- bodySchema: BodySchema;
385
- middleware: [...M];
386
- }, // Using tuple spread pattern
387
- handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
388
- // Make it readonly for type inference
389
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
390
- patch<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
391
- middleware: [...M];
392
- }, // Using tuple spread pattern
393
- handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
394
- // Make it readonly for type inference
395
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
396
- patch<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
397
- patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
398
- delete<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
399
- querySchema: QuerySchema;
400
- middleware: [...M];
401
- }, // Using tuple spread pattern
402
- handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
403
- // Make it readonly for type inference
404
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
405
- delete<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
406
- querySchema: QuerySchema;
407
- }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
408
- delete<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
409
- delete<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
410
- middleware: [...M];
411
- }, // Using tuple spread pattern
412
- handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
413
- // Make it readonly for type inference
414
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
415
- delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
416
- options<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
417
- querySchema: QuerySchema;
418
- middleware: [...M];
419
- }, // Using tuple spread pattern
420
- handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
421
- // Make it readonly for type inference
422
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
423
- options<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
424
- querySchema: QuerySchema;
425
- }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
426
- options<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
427
- options<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
428
- middleware: [...M];
429
- }, // Using tuple spread pattern
430
- handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
431
- // Make it readonly for type inference
432
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
433
- options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
434
- head<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
435
- querySchema: QuerySchema;
436
- middleware: [...M];
437
- }, // Using tuple spread pattern
438
- handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
439
- // Make it readonly for type inference
440
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
441
- head<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
442
- querySchema: QuerySchema;
443
- }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
444
- head<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
445
- head<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
446
- middleware: [...M];
447
- }, // Using tuple spread pattern
448
- handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
449
- // Make it readonly for type inference
450
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
451
- head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
452
- all<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
453
- bodySchema: BodySchema;
457
+ paramsSchema?: ParamsSchema;
458
+ middleware?: [...M];
459
+ }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
460
+ all(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
461
+ all<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
462
+ bodySchema?: BodySchema;
454
463
  querySchema?: QuerySchema;
455
- middleware: [...M];
456
- }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
457
- // Make it readonly for type inference
458
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
459
- all<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
460
- bodySchema: BodySchema;
461
- middleware: [...M];
462
- }, // Using tuple spread pattern
463
- handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
464
- // Make it readonly for type inference
465
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
466
- all<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
467
- querySchema: QuerySchema;
468
- middleware: [...M];
469
- }, // Using tuple spread pattern
470
- handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
471
- // Make it readonly for type inference
472
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
473
- all<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
474
- all<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
475
- middleware: [...M];
476
- }, // Using tuple spread pattern
477
- handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
478
- // Make it readonly for type inference
479
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
480
- all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
464
+ paramsSchema?: ParamsSchema;
465
+ middleware?: [...M];
466
+ }, handler: SchemaRouteHandler<string, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
481
467
  private registerRoute;
482
468
  private createBodyValidationMiddleware;
469
+ private createParamsValidationMiddleware;
483
470
  private createQueryValidationMiddleware;
484
471
  }
485
472
  /**
@@ -584,4 +571,4 @@ type RouterDocEntry = TypedRouter<any, any> | {
584
571
  */
585
572
  declare function createDocs(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): express.Router & express.RequestHandler;
586
573
  //#endregion
587
- export { AdditionalLocals, AdditionalReqProps, AnyStandardSchema, DocsOptions, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteOptions, RouterConfig, RouterDocEntry, SafeParseResult, SchemaRequest, SchemaRouteHandler, TypedMiddleware, TypedRouter, createDocs, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, inferJsonSchema, isSchemaError, parseSchema, safeParseSchema };
574
+ export { AdditionalLocals, AdditionalReqProps, AnyStandardSchema, DocsOptions, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteOptions, RouterConfig, RouterDocEntry, SafeParseResult, SchemaLike, SchemaRequest, SchemaRouteHandler, TypedMiddleware, TypedRouter, createDocs, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, inferJsonSchema, isSchemaError, parseSchema, safeParseSchema };
@@ -1,8 +1,14 @@
1
1
  import express, { NextFunction, Request, Response } from "express";
2
2
  import { StandardSchemaV1 } from "@standard-schema/spec";
3
-
4
3
  //#region src/schema-router.d.ts
5
4
  type AnyStandardSchema = StandardSchemaV1<any, any>;
5
+ type SchemaLike = AnyStandardSchema | {
6
+ safeParse: (...args: any[]) => any;
7
+ } | {
8
+ parse: (...args: any[]) => any;
9
+ } | {
10
+ validate: (...args: any[]) => any;
11
+ };
6
12
  type InferOutput<T> = T extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<T> : unknown;
7
13
  type InferInput<T> = T extends StandardSchemaV1 ? StandardSchemaV1.InferInput<T> : unknown;
8
14
  type InferFromSafeParse<T> = T extends {
@@ -43,28 +49,36 @@ declare function isSchemaError(error: unknown): error is {
43
49
  * - Optional parameters: /posts/:year/:month? → { year: string; month?: string }
44
50
  * - Wildcard parameters: /files/* → { "0": string }
45
51
  * - Multiple wildcards: /a/star/b/star → { "0": string; "1": string }
52
+ *
53
+ * A path containing a named regex capture group — `(?<id>...)` — is treated
54
+ * as a raw regex pattern instead: /^\/legacy\/(?<id>\d+)$/ → { id: string }.
55
+ * This syntax never appears in Express's own path syntax, so detecting it is
56
+ * unambiguous; registerRoute converts the string to a real RegExp at
57
+ * runtime so Express matches it as one.
46
58
  */
47
- type ExtractRouteParams<Path extends string> = string extends Path ? Record<string, string> : ExtractParams<Path>;
59
+ type ExtractRouteParams<Path extends string> = string extends Path ? Record<string, string> : Path extends `${infer _Before}(?<${infer _Name}>${infer _Rest}` ? ExtractRegexGroupParams<Path> : ExtractParams<Path>;
60
+ type ExtractRegexGroupParams<S extends string> = S extends `${infer _Before}(?<${infer Name}>${infer _Rest}` ? { [K in Name]: string; } & ExtractRegexGroupParams<RemoveFirstRegexGroup<S>> : {};
61
+ type RemoveFirstRegexGroup<S extends string> = S extends `${infer _Before}(?<${infer _Name}>${infer After}` ? After extends `${infer _Inner})${infer Rest}` ? Rest : "" : "";
48
62
  /**
49
63
  * Main parameter extraction logic - enhanced for Express 5 support with recursion depth limit
50
64
  */
51
- type ExtractParams<Path extends string> = Path extends `${infer Before}{${infer OptionalContent}}${infer After}` ? ExtractOptionalSegment<OptionalContent> & ExtractParams<`${Before}${After}`> : Path extends `${infer _Before}:${infer Rest}` ? ExtractSingleParam<Rest> & ExtractParams<RemoveFirstParam<Path>> : Path extends `${infer _Before}*${infer Name}/${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`/${After}`> : { [K in Name]: string[] } & ExtractParams<`/${After}`> : Path extends `${infer _Before}*${infer Name}-${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`-${After}`> : { [K in Name]: string[] } & ExtractParams<`-${After}`> : Path extends `${infer _Before}*${infer Name}.${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`.${After}`> : { [K in Name]: string[] } & ExtractParams<`.${After}`> : Path extends `${infer _Before}*${infer Name}#${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`#${After}`> : { [K in Name]: string[] } & ExtractParams<`#${After}`> : Path extends `${infer _Before}*${infer Name}:${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`:${After}`> : { [K in Name]: string[] } & ExtractParams<`:${After}`> : Path extends `${infer _Before}*${infer Name}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<``> : { [K in Name]: string[] } & ExtractParams<``> : Path extends `${infer _Before}*${infer After}` ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<After> : {};
65
+ type ExtractParams<Path extends string> = Path extends `${infer Before}{${infer OptionalContent}}${infer After}` ? ExtractOptionalSegment<OptionalContent> & ExtractParams<`${Before}${After}`> : Path extends `${infer _Before}:${infer Rest}` ? ExtractSingleParam<Rest> & ExtractParams<RemoveFirstParam<Path>> : Path extends `${infer _Before}*${infer Name}/${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`/${After}`> : { [K in Name]: string[]; } & ExtractParams<`/${After}`> : Path extends `${infer _Before}*${infer Name}-${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`-${After}`> : { [K in Name]: string[]; } & ExtractParams<`-${After}`> : Path extends `${infer _Before}*${infer Name}.${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`.${After}`> : { [K in Name]: string[]; } & ExtractParams<`.${After}`> : Path extends `${infer _Before}*${infer Name}#${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`#${After}`> : { [K in Name]: string[]; } & ExtractParams<`#${After}`> : Path extends `${infer _Before}*${infer Name}:${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`:${After}`> : { [K in Name]: string[]; } & ExtractParams<`:${After}`> : Path extends `${infer _Before}*${infer Name}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<``> : { [K in Name]: string[]; } & ExtractParams<``> : Path extends `${infer _Before}*${infer After}` ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<After> : {};
52
66
  /**
53
67
  * Extract parameters from Express 5 optional segments in braces
54
68
  * Handles patterns like {/:param}, {.:ext}, {/optional/:param}
55
69
  */
56
- type ExtractOptionalSegment<Content extends string> = Content extends `*${infer Name}` ? Name extends "" ? {} : { [K in Name]?: string[] } : Content extends `/${infer Rest}` ? Rest extends `*${infer Name}` ? Name extends "" ? {} : { [K in Name]?: string[] } : Rest extends `:${infer R}` ? ExtractOptionalParam<R> : {} : Content extends `/:${infer Rest}` ? ExtractOptionalParam<Rest> : Content extends `.:${infer Rest}` ? ExtractOptionalParam<Rest> : Content extends `${infer _Path}:${infer Rest}` ? ExtractOptionalParam<Rest> : {};
70
+ type ExtractOptionalSegment<Content extends string> = Content extends `*${infer Name}` ? Name extends "" ? {} : { [K in Name]?: string[]; } : Content extends `/${infer Rest}` ? Rest extends `*${infer Name}` ? Name extends "" ? {} : { [K in Name]?: string[]; } : Rest extends `:${infer R}` ? ExtractOptionalParam<R> : {} : Content extends `/:${infer Rest}` ? ExtractOptionalParam<Rest> : Content extends `.:${infer Rest}` ? ExtractOptionalParam<Rest> : Content extends `${infer _Path}:${infer Rest}` ? ExtractOptionalParam<Rest> : {};
57
71
  /**
58
72
  * Extract a single optional parameter from brace content
59
73
  */
60
- type ExtractOptionalParam<Rest extends string> = Rest extends `${infer ParamName}/${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}-${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}.${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}` ? { [K in ParamName]?: string } : {};
74
+ type ExtractOptionalParam<Rest extends string> = Rest extends `${infer ParamName}/${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}-${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}.${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}` ? { [K in ParamName]?: string; } : {};
61
75
  /**
62
76
  * Extract a single parameter name from the rest of the path
63
77
  * Enhanced to handle Express 5 patterns and optional parameters correctly
64
78
  * Special handling for consecutive parameters like :from-:to
65
79
  * Order matters: regex constraints must be handled before repeating parameters
66
80
  */
67
- type ExtractSingleParam<Rest extends string> = Rest extends `${infer ParamName}(${infer _Constraint})${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}-:${infer _NextParam}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}.:${infer _NextParam}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}?/${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}?-${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}?.${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}?#${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}?:${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}/${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}-${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}.${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}#${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}:${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}+${infer _After}` ? { [K in ParamName]: string[] } : Rest extends `${infer ParamName}*${infer _After}` ? { [K in ParamName]?: string[] } : Rest extends `${infer ParamName}?${infer _After}` ? { [K in ParamName]?: string } : Rest extends string ? Rest extends "" ? {} : Rest extends `${infer ParamName}?` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}+` ? { [K in ParamName]: string[] } : Rest extends `${infer ParamName}*` ? { [K in ParamName]?: string[] } : { [K in Rest]: string } : {};
81
+ type ExtractSingleParam<Rest extends string> = Rest extends `${infer ParamName}(${infer _Constraint})${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}-:${infer _NextParam}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}.:${infer _NextParam}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}?/${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}?-${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}?.${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}?#${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}?:${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}/${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}-${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}.${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}#${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}:${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}+${infer _After}` ? { [K in ParamName]: string[]; } : Rest extends `${infer ParamName}*${infer _After}` ? { [K in ParamName]?: string[]; } : Rest extends `${infer ParamName}?${infer _After}` ? { [K in ParamName]?: string; } : Rest extends string ? Rest extends "" ? {} : Rest extends `${infer ParamName}?` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}+` ? { [K in ParamName]: string[]; } : Rest extends `${infer ParamName}*` ? { [K in ParamName]?: string[]; } : { [K in Rest]: string; } : {};
68
82
  /**
69
83
  * Remove the first parameter from path to continue parsing
70
84
  * Enhanced to handle Express 5 patterns and optional parameters
@@ -100,24 +114,39 @@ type RequestOnlyMiddleware<TReq extends Record<string, any>> = TypedMiddleware<T
100
114
  type LocalsOnlyMiddleware<TLocals extends Record<string, any>> = TypedMiddleware<{}, TLocals>;
101
115
  type InferMiddlewareProps<T extends readonly TypedMiddleware<any, any>[]> = T extends readonly [infer First, ...infer Rest] ? First extends TypedMiddleware<infer FirstReq, any> ? Rest extends readonly TypedMiddleware<any, any>[] ? FirstReq & InferMiddlewareProps<Rest> : FirstReq : {} : {};
102
116
  type InferMiddlewareLocals<T extends readonly TypedMiddleware<any, any>[]> = T extends readonly [infer First, ...infer Rest] ? First extends TypedMiddleware<any, infer FirstLocals> ? Rest extends readonly TypedMiddleware<any, any>[] ? FirstLocals & InferMiddlewareLocals<Rest> : FirstLocals : {} : {};
103
- type SchemaRequest<Path extends string = string, BodySchema extends AnyStandardSchema | unknown = unknown, QuerySchema extends AnyStandardSchema | unknown = unknown, MiddlewareProps extends Record<string, any> = {}> = Omit<Request, "params" | "query" | "body"> & {
104
- params: ExtractRouteParams<Path>;
105
- body: BodySchema extends unknown ? InferSchemaOutput<BodySchema> : unknown;
106
- query: QuerySchema extends unknown ? InferSchemaOutput<QuerySchema> : unknown;
117
+ type SchemaRequest<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}, ParamsSchema extends SchemaLike | undefined = undefined> = Omit<Request, "params" | "query" | "body"> & {
118
+ params: ParamsSchema extends undefined ? ExtractRouteParams<Path> : InferSchemaOutput<ParamsSchema>;
119
+ body: BodySchema extends undefined ? unknown : InferSchemaOutput<BodySchema>;
120
+ query: QuerySchema extends undefined ? unknown : InferSchemaOutput<QuerySchema>;
107
121
  } & MiddlewareProps;
108
- type SchemaRouteHandler<Path extends string = string, BodySchema extends AnyStandardSchema | unknown = unknown, QuerySchema extends AnyStandardSchema | unknown = unknown, MiddlewareProps extends Record<string, any> = {}, ResponseLocals extends Record<string, any> = {}> = (req: SchemaRequest<Path, BodySchema, QuerySchema, MiddlewareProps>, res: Response<any, ResponseLocals>, next?: NextFunction) => void | undefined | Promise<void | undefined> | Response | Promise<Response> | Promise<Response | undefined>;
122
+ type SchemaRouteHandler<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}, ResponseLocals extends Record<string, any> = {}, ParamsSchema extends SchemaLike | undefined = undefined> = (req: SchemaRequest<Path, BodySchema, QuerySchema, MiddlewareProps, ParamsSchema>, res: Response<any, ResponseLocals>, next?: NextFunction) => void | undefined | Promise<void | undefined> | Response | Promise<Response> | Promise<Response | undefined>;
109
123
  /**
110
124
  * Options for defining a typed route, including schemas and middleware.
111
125
  *
112
126
  * @template BodySchema - Schema for request body validation.
113
127
  * @template QuerySchema - Schema for query parameter validation.
128
+ * @template ParamsSchema - Schema for route param validation.
114
129
  * @property bodySchema - Optional schema for validating the request body.
115
130
  * @property querySchema - Optional schema for validating the query string.
131
+ * @property paramsSchema - Optional schema for validating route params.
116
132
  * @property middleware - Optional array of TypedMiddleware for this route.
117
133
  */
118
- interface RouteOptions<BodySchema extends AnyStandardSchema | unknown = unknown, QuerySchema extends AnyStandardSchema | unknown = unknown> {
134
+ interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined> {
119
135
  bodySchema?: BodySchema;
136
+ /**
137
+ * Every value here arrives as a string, never a real boolean/number —
138
+ * `z.coerce.boolean()` treats `"false"` as truthy. Use a text-aware parser
139
+ * like `z.stringbool()` for boolean fields.
140
+ * @see https://github.com/Mini-Sylar/express-typed-router#gotchas
141
+ */
120
142
  querySchema?: QuerySchema;
143
+ /**
144
+ * Overrides the inferred `req.params` type with this schema's output —
145
+ * useful for coercing a numeric-looking param (`z.coerce.number()`) since
146
+ * Express never converts params from strings on its own. Same
147
+ * string-arrival caveat as `querySchema` applies; see its docs above.
148
+ */
149
+ paramsSchema?: ParamsSchema;
121
150
  middleware?: TypedMiddleware<any, any>[];
122
151
  tags?: string[];
123
152
  description?: string;
@@ -126,8 +155,16 @@ interface RouteOptions<BodySchema extends AnyStandardSchema | unknown = unknown,
126
155
  responseSchema?: AnyStandardSchema;
127
156
  /** Exclude this route from the generated OpenAPI spec entirely. */
128
157
  hidden?: boolean;
158
+ /**
159
+ * Only used when `path` is a `RegExp`. A readable stand-in path (e.g.
160
+ * `/legacy/:id`) for the OpenAPI doc, since one can't be derived from a
161
+ * `RegExp` value. Defaults to `regex.toString()`. Doc-only — `req.params`
162
+ * types as `Record<string, string>` for RegExp routes unless overridden
163
+ * by `paramsSchema`.
164
+ */
165
+ pathExample?: string;
129
166
  }
130
- type DocMeta = Pick<RouteOptions<unknown, unknown>, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden">;
167
+ type DocMeta = Pick<RouteOptions, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden" | "pathExample">;
131
168
  type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "all";
132
169
  interface DocsOptions {
133
170
  title?: string;
@@ -184,15 +221,18 @@ interface DocsOptions {
184
221
  }
185
222
  interface RouteMetadata {
186
223
  method: HttpMethod;
187
- path: string;
188
- bodySchema?: AnyStandardSchema;
189
- querySchema?: AnyStandardSchema;
190
- tags?: string[];
191
- description?: string;
192
- summary?: string;
193
- deprecated?: boolean;
194
- responseSchema?: AnyStandardSchema;
195
- hidden?: boolean;
224
+ path: string | RegExp;
225
+ /** Doc-only path override for RegExp routes — see RouteOptions.pathExample. */
226
+ pathExample?: string | undefined;
227
+ bodySchema?: AnyStandardSchema | undefined;
228
+ querySchema?: AnyStandardSchema | undefined;
229
+ paramsSchema?: AnyStandardSchema | undefined;
230
+ tags?: string[] | undefined;
231
+ description?: string | undefined;
232
+ summary?: string | undefined;
233
+ deprecated?: boolean | undefined;
234
+ responseSchema?: AnyStandardSchema | undefined;
235
+ hidden?: boolean | undefined;
196
236
  responseSamples: Map<number, {
197
237
  schema: Record<string, any>;
198
238
  example?: unknown;
@@ -235,14 +275,14 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
235
275
  * and/or adds properties to response.locals
236
276
  */
237
277
  /**
238
- * Add typed middleware to the router.
239
- * This middleware will apply to all routes defined after this call.
240
- *
241
- * @template TReq - Type extensions for the request object
242
- * @template TLocals - Type extensions for response.locals
243
- * @param middleware - The typed middleware function
244
- * @returns A new router instance with updated types
245
- */
278
+ * Add typed middleware to the router.
279
+ * This middleware will apply to all routes defined after this call.
280
+ *
281
+ * @template TReq - Type extensions for the request object
282
+ * @template TLocals - Type extensions for response.locals
283
+ * @param middleware - The typed middleware function
284
+ * @returns A new router instance with updated types
285
+ */
246
286
  useMiddleware<TReq extends Record<string, any> = {}, TLocals extends Record<string, any> = {}>(middleware: TypedMiddleware<TReq, TLocals>): TypedRouter<Req & TReq, Locals & TLocals>;
247
287
  /**
248
288
  * Get the underlying Express router, typed as a RequestHandler so it can be
@@ -318,168 +358,115 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
318
358
  * // GET /docs/openapi.json → raw OpenAPI 3.1 spec
319
359
  */
320
360
  docs(options?: DocsOptions): express.Router & express.RequestHandler;
321
- get<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
322
- get<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
323
- get<Path extends string, Middleware extends readonly TypedMiddleware<any, any>[]>(path: Path, options: {
324
- middleware: Middleware;
325
- }, handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<Middleware>, Locals & InferMiddlewareLocals<Middleware>>): TypedRouter<Req, Locals>;
326
- get<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: RouteOptions<BodySchema, QuerySchema> & {
327
- middleware: [...M];
328
- }, // Using tuple spread pattern
329
- handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
330
- // Make it readonly for type inference
331
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
332
- post<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
333
- bodySchema: BodySchema;
361
+ get<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
362
+ get<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
363
+ bodySchema?: BodySchema;
364
+ querySchema?: QuerySchema;
365
+ paramsSchema?: ParamsSchema;
366
+ middleware?: [...M];
367
+ }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
368
+ get(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
369
+ get<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
370
+ bodySchema?: BodySchema;
371
+ querySchema?: QuerySchema;
372
+ paramsSchema?: ParamsSchema;
373
+ middleware?: [...M];
374
+ }, handler: SchemaRouteHandler<string, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
375
+ post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
376
+ post<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
377
+ bodySchema?: BodySchema;
378
+ querySchema?: QuerySchema;
379
+ paramsSchema?: ParamsSchema;
380
+ middleware?: [...M];
381
+ }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
382
+ post(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
383
+ post<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
384
+ bodySchema?: BodySchema;
385
+ querySchema?: QuerySchema;
386
+ paramsSchema?: ParamsSchema;
387
+ middleware?: [...M];
388
+ }, handler: SchemaRouteHandler<string, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
389
+ put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
390
+ put<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
391
+ bodySchema?: BodySchema;
392
+ querySchema?: QuerySchema;
393
+ paramsSchema?: ParamsSchema;
394
+ middleware?: [...M];
395
+ }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
396
+ put(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
397
+ put<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
398
+ bodySchema?: BodySchema;
399
+ querySchema?: QuerySchema;
400
+ paramsSchema?: ParamsSchema;
401
+ middleware?: [...M];
402
+ }, handler: SchemaRouteHandler<string, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
403
+ patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
404
+ patch<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
405
+ bodySchema?: BodySchema;
406
+ querySchema?: QuerySchema;
407
+ paramsSchema?: ParamsSchema;
408
+ middleware?: [...M];
409
+ }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
410
+ patch(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
411
+ patch<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
412
+ bodySchema?: BodySchema;
413
+ querySchema?: QuerySchema;
414
+ paramsSchema?: ParamsSchema;
415
+ middleware?: [...M];
416
+ }, handler: SchemaRouteHandler<string, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
417
+ delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
418
+ delete<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
419
+ querySchema?: QuerySchema;
420
+ paramsSchema?: ParamsSchema;
421
+ middleware?: [...M];
422
+ }, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
423
+ delete(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
424
+ delete<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
425
+ querySchema?: QuerySchema;
426
+ paramsSchema?: ParamsSchema;
427
+ middleware?: [...M];
428
+ }, handler: SchemaRouteHandler<string, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
429
+ options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
430
+ options<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
431
+ querySchema?: QuerySchema;
432
+ paramsSchema?: ParamsSchema;
433
+ middleware?: [...M];
434
+ }, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
435
+ options(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
436
+ options<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
437
+ querySchema?: QuerySchema;
438
+ paramsSchema?: ParamsSchema;
439
+ middleware?: [...M];
440
+ }, handler: SchemaRouteHandler<string, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
441
+ head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
442
+ head<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
334
443
  querySchema?: QuerySchema;
335
- middleware: [...M];
336
- }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
337
- // Make it readonly for type inference
338
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
339
- post<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
340
- bodySchema: BodySchema;
341
- middleware: [...M];
342
- }, // Using tuple spread pattern
343
- handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
344
- // Make it readonly for type inference
345
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
346
- post<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
347
- middleware: [...M];
348
- }, // Using tuple spread pattern
349
- handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
350
- // Make it readonly for type inference
351
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
352
- post<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
353
- post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
354
- put<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
355
- bodySchema: BodySchema;
444
+ paramsSchema?: ParamsSchema;
445
+ middleware?: [...M];
446
+ }, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
447
+ head(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
448
+ head<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
356
449
  querySchema?: QuerySchema;
357
- middleware: [...M];
358
- }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
359
- // Make it readonly for type inference
360
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
361
- put<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
362
- bodySchema: BodySchema;
363
- middleware: [...M];
364
- }, // Using tuple spread pattern
365
- handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
366
- // Make it readonly for type inference
367
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
368
- put<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
369
- middleware: [...M];
370
- }, // Using tuple spread pattern
371
- handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
372
- // Make it readonly for type inference
373
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
374
- put<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
375
- put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
376
- patch<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
377
- bodySchema: BodySchema;
450
+ paramsSchema?: ParamsSchema;
451
+ middleware?: [...M];
452
+ }, handler: SchemaRouteHandler<string, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
453
+ all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
454
+ all<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
455
+ bodySchema?: BodySchema;
378
456
  querySchema?: QuerySchema;
379
- middleware: [...M];
380
- }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
381
- // Make it readonly for type inference
382
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
383
- patch<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
384
- bodySchema: BodySchema;
385
- middleware: [...M];
386
- }, // Using tuple spread pattern
387
- handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
388
- // Make it readonly for type inference
389
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
390
- patch<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
391
- middleware: [...M];
392
- }, // Using tuple spread pattern
393
- handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
394
- // Make it readonly for type inference
395
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
396
- patch<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
397
- patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
398
- delete<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
399
- querySchema: QuerySchema;
400
- middleware: [...M];
401
- }, // Using tuple spread pattern
402
- handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
403
- // Make it readonly for type inference
404
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
405
- delete<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
406
- querySchema: QuerySchema;
407
- }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
408
- delete<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
409
- delete<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
410
- middleware: [...M];
411
- }, // Using tuple spread pattern
412
- handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
413
- // Make it readonly for type inference
414
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
415
- delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
416
- options<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
417
- querySchema: QuerySchema;
418
- middleware: [...M];
419
- }, // Using tuple spread pattern
420
- handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
421
- // Make it readonly for type inference
422
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
423
- options<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
424
- querySchema: QuerySchema;
425
- }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
426
- options<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
427
- options<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
428
- middleware: [...M];
429
- }, // Using tuple spread pattern
430
- handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
431
- // Make it readonly for type inference
432
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
433
- options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
434
- head<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
435
- querySchema: QuerySchema;
436
- middleware: [...M];
437
- }, // Using tuple spread pattern
438
- handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
439
- // Make it readonly for type inference
440
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
441
- head<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
442
- querySchema: QuerySchema;
443
- }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
444
- head<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
445
- head<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
446
- middleware: [...M];
447
- }, // Using tuple spread pattern
448
- handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
449
- // Make it readonly for type inference
450
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
451
- head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
452
- all<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
453
- bodySchema: BodySchema;
457
+ paramsSchema?: ParamsSchema;
458
+ middleware?: [...M];
459
+ }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
460
+ all(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
461
+ all<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
462
+ bodySchema?: BodySchema;
454
463
  querySchema?: QuerySchema;
455
- middleware: [...M];
456
- }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
457
- // Make it readonly for type inference
458
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
459
- all<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
460
- bodySchema: BodySchema;
461
- middleware: [...M];
462
- }, // Using tuple spread pattern
463
- handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
464
- // Make it readonly for type inference
465
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
466
- all<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
467
- querySchema: QuerySchema;
468
- middleware: [...M];
469
- }, // Using tuple spread pattern
470
- handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
471
- // Make it readonly for type inference
472
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
473
- all<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
474
- all<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
475
- middleware: [...M];
476
- }, // Using tuple spread pattern
477
- handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
478
- // Make it readonly for type inference
479
- Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
480
- all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
464
+ paramsSchema?: ParamsSchema;
465
+ middleware?: [...M];
466
+ }, handler: SchemaRouteHandler<string, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
481
467
  private registerRoute;
482
468
  private createBodyValidationMiddleware;
469
+ private createParamsValidationMiddleware;
483
470
  private createQueryValidationMiddleware;
484
471
  }
485
472
  /**
@@ -584,4 +571,4 @@ type RouterDocEntry = TypedRouter<any, any> | {
584
571
  */
585
572
  declare function createDocs(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): express.Router & express.RequestHandler;
586
573
  //#endregion
587
- export { AdditionalLocals, AdditionalReqProps, AnyStandardSchema, DocsOptions, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteOptions, RouterConfig, RouterDocEntry, SafeParseResult, SchemaRequest, SchemaRouteHandler, TypedMiddleware, TypedRouter, createDocs, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, inferJsonSchema, isSchemaError, parseSchema, safeParseSchema };
574
+ export { AdditionalLocals, AdditionalReqProps, AnyStandardSchema, DocsOptions, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteOptions, RouterConfig, RouterDocEntry, SafeParseResult, SchemaLike, SchemaRequest, SchemaRouteHandler, TypedMiddleware, TypedRouter, createDocs, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, inferJsonSchema, isSchemaError, parseSchema, safeParseSchema };
@@ -1,12 +1,12 @@
1
- import e from"express";import{SchemaError as t}from"@standard-schema/utils";function n(e,n){let r=e;if(r&&r[`~standard`]&&typeof r[`~standard`].validate==`function`){let e=r[`~standard`].validate(n);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new t(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function r(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function i(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}const a=Function(`m`,`return import(m)`);let o,s;async function c(e){try{o??=await a(`module`),s??=await a(`url`);let t=globalThis.process?.cwd?.()??``,n=o.createRequire(s.pathToFileURL(t+`/`).href).resolve(e);return a(s.pathToFileURL(n).href)}catch{return a(e)}}const l=new WeakMap;function u(e){return e.replace(/\{([^{}]*)\}/g,`$1`).replace(/:([A-Za-z0-9_]+)(?:\([^)]*\))?[?+*]?/g,`{$1}`).replace(/\/{2,}/g,`/`)}function d(e){return[...e.replace(/\{([^{}]*)\}/g,`$1`).matchAll(/:([A-Za-z0-9_]+)/g)].map(e=>e[1])}function f(e){return e.replace(/[{}]/g,``).split(`/`).filter(Boolean).find(e=>!e.startsWith(`:`)&&!e.startsWith(`*`))??`default`}function p(e,t){let n=t.replace(/[{}]/g,``).split(`/`).filter(e=>e&&!e.startsWith(`:`)&&!e.startsWith(`*`)),r=n[n.length-1]??`resource`;return`${{get:`Get`,post:`Create`,put:`Update`,patch:`Patch`,delete:`Delete`,head:`Head`,options:`Options`}[e]??e} ${r}`}async function m(e){let t=l.get(e);if(t)return t;let n=await h(e);return l.set(e,n),n}async function h(e){if(typeof e.toJsonSchema==`function`)try{return e.toJsonSchema()}catch{}let t=e[`~standard`]?.vendor;if(t===`zod`){try{let t=await c(`zod`);if(typeof t.toJSONSchema==`function`)return t.toJSONSchema(e)}catch{}try{let t=await c(`zod-to-json-schema`),n=t.zodToJsonSchema??t.default?.zodToJsonSchema;if(typeof n==`function`)return n(e)}catch{}}if(t===`valibot`)try{let t=await c(`@valibot/to-json-schema`),n=t.toJsonSchema??t.default?.toJsonSchema;if(typeof n==`function`)return n(e)}catch{}if(t===`effect`)try{let t=await c(`effect`),n=t.JSONSchema?.make??t.default?.JSONSchema?.make;if(typeof n==`function`)return n(e)}catch{}return{}}function g(e){return _(e,0,new WeakSet)}function _(e,t,n){if(e==null)return{type:`null`};if(t>=12)return{};if(e instanceof Date)return{type:`string`,format:`date-time`};if(typeof e==`bigint`)return{type:`integer`};if(typeof e==`object`&&typeof e.toJSON==`function`)return _(e.toJSON(),t,n);if(Array.isArray(e)){if(e.length===0||n.has(e))return{type:`array`,items:{}};n.add(e);let r=Math.min(e.length,20),i=_(e[0],t+1,n);for(let a=1;a<r;a++)i=b(i,_(e[a],t+1,n));return n.delete(e),{type:`array`,items:i}}switch(typeof e){case`string`:return{type:`string`};case`boolean`:return{type:`boolean`};case`number`:return Number.isFinite(e)?{type:Number.isInteger(e)?`integer`:`number`}:{type:`null`};case`object`:{if(n.has(e))return{type:`object`};n.add(e);let r={},i=[];for(let[a,o]of Object.entries(e))typeof o==`function`||o===void 0||(r[a]=_(o,t+1,n),i.push(a));n.delete(e);let a={type:`object`,properties:r};return i.length&&(a.required=i),a}default:return{}}}function v(e){return!e||e.type===void 0?new Set:new Set(Array.isArray(e.type)?e.type:[e.type])}function y(e){e.has(`integer`)&&e.has(`number`)&&e.delete(`integer`);let t=[...e];if(t.length!==0)return t.length===1?t[0]:t}function b(e,t){if(!e||Object.keys(e).length===0)return t??{};if(!t||Object.keys(t).length===0)return e??{};let n=new Set([...v(e),...v(t)]),r={},i=y(n);if(i!==void 0&&(r.type=i),n.has(`object`)&&(e.properties||t.properties)){let n=e.properties??{},i=t.properties??{},a={};for(let e of new Set([...Object.keys(n),...Object.keys(i)]))a[e]=b(n[e],i[e]);r.properties=a;let o=e.required??[],s=t.required??[],c=o.filter(e=>s.includes(e));c.length&&(r.required=c)}if(n.has(`array`)){let n=b(e.items,t.items);n&&Object.keys(n).length&&(r.items=n)}return r}function x(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}const S=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function C(e,t,n){return`<!doctype html>
1
+ import e from"express";import{SchemaError as t}from"@standard-schema/utils";function n(e,n){let r=e;if(r&&r[`~standard`]&&typeof r[`~standard`].validate==`function`){let e=r[`~standard`].validate(n);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new t(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function r(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function i(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}const a=Function(`m`,`return import(m)`);let o,s;async function c(e){o??=await a(`module`),s??=await a(`url`);let t=[],n=globalThis.process?.argv?.[1];n&&t.push(s.pathToFileURL(n).href);let r=globalThis.process?.cwd?.()??``;r&&t.push(s.pathToFileURL(r+`/`).href);for(let n of t)try{let t=o.createRequire(n).resolve(e);return await a(s.pathToFileURL(t).href)}catch{}return a(e)}const l=new WeakMap,u=/\(\?<[^>]+>/;function d(e){return typeof e==`string`&&u.test(e)?new RegExp(e):e}function f(e){if(typeof e.path==`string`)return e.path;if(e.pathExample)return e.pathExample;let t=0;return e.path.source.replace(/\\\//g,`/`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function p(e){return e.replace(/\{([^{}]*)\}/g,`$1`).replace(/:([A-Za-z0-9_]+)(?:\([^)]*\))?[?+*]?/g,`{$1}`).replace(/\(\?<([A-Za-z0-9_]+)>[^)]*\)/g,`{$1}`).replace(/^\^|\$$/g,``).replace(/\/{2,}/g,`/`)}function m(e){let t=e.replace(/\{([^{}]*)\}/g,`$1`);return[...t.matchAll(/:([A-Za-z0-9_]+)/g),...t.matchAll(/\(\?<([A-Za-z0-9_]+)>/g)].map(e=>e[1])}function h(e){return e.startsWith(`:`)||e.startsWith(`*`)||e.includes(`(?<`)}function g(e){return e.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(Boolean).find(e=>!h(e))??`default`}function _(e,t){let n=t.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(e=>e&&!h(e)),r=n[n.length-1]??`resource`;return`${{get:`Get`,post:`Create`,put:`Update`,patch:`Patch`,delete:`Delete`,head:`Head`,options:`Options`}[e]??e} ${r}`}async function v(e){let t=l.get(e);if(t)return t;let n=await y(e);return l.set(e,n),n}async function y(e){if(typeof e.toJsonSchema==`function`)try{return e.toJsonSchema()}catch{}let t=e[`~standard`]?.vendor;if(t===`zod`){try{let t=await c(`zod`);if(typeof t.toJSONSchema==`function`)return t.toJSONSchema(e)}catch{}try{let t=await c(`zod-to-json-schema`),n=t.zodToJsonSchema??t.default?.zodToJsonSchema;if(typeof n==`function`)return n(e)}catch{}}if(t===`valibot`)try{let t=await c(`@valibot/to-json-schema`),n=t.toJsonSchema??t.default?.toJsonSchema;if(typeof n==`function`)return n(e)}catch{}if(t===`effect`)try{let t=await c(`effect`),n=t.JSONSchema?.make??t.default?.JSONSchema?.make;if(typeof n==`function`)return n(e)}catch{}return{}}function b(e){return x(e,0,new WeakSet)}function x(e,t,n){if(e==null)return{type:`null`};if(t>=12)return{};if(e instanceof Date)return{type:`string`,format:`date-time`};if(typeof e==`bigint`)return{type:`integer`};if(typeof e==`object`&&typeof e.toJSON==`function`)return x(e.toJSON(),t,n);if(Array.isArray(e)){if(e.length===0||n.has(e))return{type:`array`,items:{}};n.add(e);let r=Math.min(e.length,20),i=x(e[0],t+1,n);for(let a=1;a<r;a++)i=w(i,x(e[a],t+1,n));return n.delete(e),{type:`array`,items:i}}switch(typeof e){case`string`:return{type:`string`};case`boolean`:return{type:`boolean`};case`number`:return Number.isFinite(e)?{type:Number.isInteger(e)?`integer`:`number`}:{type:`null`};case`object`:{if(n.has(e))return{type:`object`};n.add(e);let r={},i=[];for(let[a,o]of Object.entries(e))typeof o!=`function`&&o!==void 0&&(r[a]=x(o,t+1,n),i.push(a));n.delete(e);let a={type:`object`,properties:r};return i.length&&(a.required=i),a}default:return{}}}function S(e){return!e||e.type===void 0?new Set:new Set(Array.isArray(e.type)?e.type:[e.type])}function C(e){e.has(`integer`)&&e.has(`number`)&&e.delete(`integer`);let t=[...e];if(t.length!==0)return t.length===1?t[0]:t}function w(e,t){if(!e||Object.keys(e).length===0)return t??{};if(!t||Object.keys(t).length===0)return e??{};let n=new Set([...S(e),...S(t)]),r={},i=C(n);if(i!==void 0&&(r.type=i),n.has(`object`)&&(e.properties||t.properties)){let n=e.properties??{},i=t.properties??{},a={};for(let e of new Set([...Object.keys(n),...Object.keys(i)]))a[e]=w(n[e],i[e]);r.properties=a;let o=e.required??[],s=t.required??[],c=o.filter(e=>s.includes(e));c.length&&(r.required=c)}if(n.has(`array`)){let n=w(e.items,t.items);n&&Object.keys(n).length&&(r.items=n)}return r}function T(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}const E=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function D(e,t,n){return`<!doctype html>
2
2
  <html>
3
3
  <head>
4
- <title>${x(e)}</title>
4
+ <title>${T(e)}</title>
5
5
  <meta charset="utf-8" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1" />
7
7
  </head>
8
8
  <body>
9
- <script id="api-reference" data-url="${x(t)}"><\/script>
10
- <script src="${x(n)}"><\/script>
9
+ <script id="api-reference" data-url="${T(t)}"><\/script>
10
+ <script src="${T(n)}"><\/script>
11
11
  </body>
12
- </html>`}async function w(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={};for(let[e,n]of t.responseSamples){let t={schema:n.schema};n.example!==void 0&&(t.example=n.example),a[String(e)]={description:e<400?`Success`:`Error`,content:{"application/json":t}}}if(t.responseSchema){let e=await m(t.responseSchema);if(e&&Object.keys(e).length){let n=`200`;for(let e of t.responseSamples.keys())if(e>=200&&e<300){n=String(e);break}a[n]={description:`Success`,content:{"application/json":{schema:e}}}}}Object.keys(a).length===0&&(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 T=new WeakMap;var E=class t{router;routes=[];mountedRouters=[];sampleMode=`off`;scheduleSpecWrite;constructor(){this.router=e.Router(),T.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.trackMounted(i,e),e.getRouter();let n=T.get(e);return n&&this.trackMounted(i,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.trackMounted(e,n)}else this.router.use(e.getRouter()),this.trackMounted(``,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]}enableSampling(e=`redacted`,t,n=new Set){if(!n.has(this)){n.add(this),this.sampleMode=e,this.scheduleSpecWrite=t;for(let{router:r}of this.mountedRouters)r.enableSampling(e,t,n)}}trackMounted(e,t){this.mountedRouters.some(n=>n.router===t&&n.prefix===e)||(this.mountedRouters.push({prefix:e,router:t}),this.sampleMode!==`off`&&t.enableSampling(this.sampleMode,this.scheduleSpecWrite))}hydrateResponses(e,t=``,n=new Set){if(n.has(this))return;n.add(this);let r=e?.paths??{};for(let e of this.routes){let n=r[u(t+e.path)]?.[e.method]?.responses;if(n)for(let t of Object.keys(n)){let r=Number(t);if(Number.isNaN(r)||e.responseSamples.has(r))continue;let i=n[t]?.content?.[`application/json`];i?.schema&&e.responseSamples.set(r,i.example===void 0?{schema:i.schema}:{schema:i.schema,example:i.example})}}for(let{prefix:r,router:i}of this.mountedRouters)i.hydrateResponses(e,t+r,n)}docs(t={}){let n=e.Router(),r;if(t.specOutputPath){let e=t.specOutputPath,n=!1,i=!1,o=async()=>{if(n){i=!0;return}n=!0;try{let n=await w(this.getRouteMetadata(),t),r=await a(`fs/promises`),i=e.replace(/[/\\][^/\\]*$/,``);i&&i!==e&&await r.mkdir(i,{recursive:!0}).catch(()=>{});let o=`${e}.${globalThis.process?.pid??`0`}.tmp`;await r.writeFile(o,JSON.stringify(n,null,2),`utf8`),await r.rename(o,e)}catch{}finally{n=!1,i&&(i=!1,o())}},s;r=()=>{s&&clearTimeout(s),s=setTimeout(o,300),s.unref?.()},setImmediate(async()=>{try{let t=await(await a(`fs/promises`)).readFile(e,`utf8`).catch(()=>null);if(t)try{this.hydrateResponses(JSON.parse(t))}catch{}}catch{}await o()})}return t.sampleResponses!==!1&&this.enableSampling(t.sampleResponses===`live`?`live`:`redacted`,r),n.get(`/openapi.json`,async(e,n)=>{try{let e=await w(this.getRouteMetadata(),t);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??S;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(C(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);let o=0,s=(e,t)=>{let n=e.statusCode,r=g(t),i=a.responseSamples.get(n),o=i?b(i.schema,r):r,s=this.sampleMode===`live`?i?.example??t:void 0,c=!i||JSON.stringify(i.schema)!==JSON.stringify(o);a.responseSamples.set(n,{schema:o,example:s}),c&&this.scheduleSpecWrite?.()};return this.router[e](t,(e,t,n)=>{if(this.sampleMode===`off`||a.hidden||o>=50||a.responseSamples.size>=10){n();return}o++;let r=t.json;t.json=function(e){return s(t,e),r.call(this,e)};let i=t.send;t.send=function(e){return typeof e==`object`&&e&&!Buffer.isBuffer(e)&&s(t,e),i.call(this,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 D(){return new E}function O(e){let t=new E;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function k(...e){let t=new E;for(let n of e)t=t.useMiddleware(n);return t}function A(t,n={}){let r=(Array.isArray(t)?t:[t]).map(e=>`prefix`in e?e:{prefix:``,router:e});if(n.sampleResponses!==!1){let e=n.sampleResponses===`live`?`live`:`redacted`;for(let{router:t}of r)t.enableSampling(e)}let i=e.Router();return i.get(`/openapi.json`,async(e,t)=>{try{let e=await w(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??S;t.setHeader(`Content-Type`,`text/html; charset=utf-8`),t.send(C(n.title??`API`,r,i))}),i}export{E as TypedRouter,A as createDocs,D as createTypedRouter,O as createTypedRouterWithConfig,k as createTypedRouterWithMiddleware,g as inferJsonSchema,i as isSchemaError,n as parseSchema,r as safeParseSchema};
12
+ </html>`}async function O(e,t){let n=Object.create(null);for(let t of e){if(t.method===`all`||t.hidden)continue;let e=f(t),r=p(e);n[r]||(n[r]={});let i;if(t.paramsSchema){let e=await v(t.paramsSchema),n=e.properties??{},r=e.required??[];i=Object.entries(n).map(([e,t])=>({name:e,in:`path`,required:r.includes(e),schema:t}))}else i=m(e).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await v(t.querySchema),n=e.properties??{},r=e.required??[];for(let[e,t]of Object.entries(n))i.push({name:e,in:`query`,required:r.includes(e),schema:t})}let a={summary:t.summary??_(t.method,e),tags:t.tags??[g(e)],parameters:i};t.description&&(a.description=t.description),t.deprecated&&(a.deprecated=!0),t.bodySchema&&(a.requestBody={required:!0,content:{"application/json":{schema:await v(t.bodySchema)}}});let o={};for(let[e,n]of t.responseSamples){let t={schema:n.schema};n.example!==void 0&&(t.example=n.example),o[String(e)]={description:e<400?`Success`:`Error`,content:{"application/json":t}}}if(t.responseSchema){let e=await v(t.responseSchema);if(e&&Object.keys(e).length){let n=`200`;for(let e of t.responseSamples.keys())if(e>=200&&e<300){n=String(e);break}o[n]={description:`Success`,content:{"application/json":{schema:e}}}}}Object.keys(o).length===0&&(o[200]={description:`Success`}),a.responses=o,n[r][t.method]=a}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 k=new WeakMap;var A=class t{router;routes=[];mountedRouters=[];sampleMode=`off`;scheduleSpecWrite;constructor(){this.router=e.Router(),k.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.trackMounted(i,e),e.getRouter();let n=k.get(e);return n&&this.trackMounted(i,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.trackMounted(e,n)}else this.router.use(e.getRouter()),this.trackMounted(``,e);return this}getRouteMetadata(){let e=this.mountedRouters.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:e+f(t)}})));return[...this.routes,...e]}enableSampling(e=`redacted`,t,n=new Set){if(!n.has(this)){n.add(this),this.sampleMode=e,this.scheduleSpecWrite=t;for(let{router:r}of this.mountedRouters)r.enableSampling(e,t,n)}}trackMounted(e,t){this.mountedRouters.some(n=>n.router===t&&n.prefix===e)||(this.mountedRouters.push({prefix:e,router:t}),this.sampleMode!==`off`&&t.enableSampling(this.sampleMode,this.scheduleSpecWrite))}hydrateResponses(e,t=``,n=new Set){if(n.has(this))return;n.add(this);let r=e?.paths??{};for(let e of this.routes){let n=r[p(t+f(e))]?.[e.method]?.responses;if(n)for(let t of Object.keys(n)){let r=Number(t);if(Number.isNaN(r)||e.responseSamples.has(r))continue;let i=n[t]?.content?.[`application/json`];i?.schema&&e.responseSamples.set(r,i.example===void 0?{schema:i.schema}:{schema:i.schema,example:i.example})}}for(let{prefix:r,router:i}of this.mountedRouters)i.hydrateResponses(e,t+r,n)}docs(t={}){let n=e.Router(),r;if(t.specOutputPath){let e=t.specOutputPath,n=!1,i=!1,o=async()=>{if(n){i=!0;return}n=!0;try{let n=await O(this.getRouteMetadata(),t),r=await a(`fs/promises`),i=e.replace(/[/\\][^/\\]*$/,``);i&&i!==e&&await r.mkdir(i,{recursive:!0}).catch(()=>{});let o=globalThis.process?.pid??`0`,s=`${e}.${o}.tmp`;await r.writeFile(s,JSON.stringify(n,null,2),`utf8`),await r.rename(s,e)}catch{}finally{n=!1,i&&(i=!1,o())}},s;r=()=>{s&&clearTimeout(s),s=setTimeout(o,300),s.unref?.()},setImmediate(async()=>{try{let t=await(await a(`fs/promises`)).readFile(e,`utf8`).catch(()=>null);if(t)try{this.hydrateResponses(JSON.parse(t))}catch{}}catch{}await o()})}return t.sampleResponses!==!1&&this.enableSampling(t.sampleResponses===`live`?`live`:`redacted`,r),n.get(`/openapi.json`,async(e,n)=>{try{let e=await O(this.getRouteMetadata(),t);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??E;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(D(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.paramsSchema=e.paramsSchema,a.tags=e.tags,a.description=e.description,a.summary=e.summary,a.deprecated=e.deprecated,a.responseSchema=e.responseSchema,a.hidden=e.hidden,a.pathExample=e.pathExample,e.middleware&&i.push(...e.middleware),e.bodySchema&&i.push(this.createBodyValidationMiddleware(e.bodySchema)),e.querySchema&&i.push(this.createQueryValidationMiddleware(e.querySchema)),e.paramsSchema&&i.push(this.createParamsValidationMiddleware(e.paramsSchema)),i.push(r)}else i.push(n);let o=0,s=(e,t)=>{let n=e.statusCode,r=b(t),i=a.responseSamples.get(n),o=i?w(i.schema,r):r,s=this.sampleMode===`live`?i?.example??t:void 0,c=!i||JSON.stringify(i.schema)!==JSON.stringify(o);a.responseSamples.set(n,{schema:o,example:s}),c&&this.scheduleSpecWrite?.()};return this.router[e](d(t),(e,t,n)=>{if(this.sampleMode===`off`||a.hidden||o>=50||a.responseSamples.size>=10){n();return}o++;let r=t.json;t.json=function(e){return s(t,e),r.call(this,e)};let i=t.send;t.send=function(e){return typeof e==`object`&&e&&!Buffer.isBuffer(e)&&s(t,e),i.call(this,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)}}}createParamsValidationMiddleware(e){return async(t,n,a)=>{try{let i=r(e,t.params),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.params=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 j(){return new A}function M(e){let t=new A;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function N(...e){let t=new A;for(let n of e)t=t.useMiddleware(n);return t}function P(t,n={}){let r=(Array.isArray(t)?t:[t]).map(e=>`prefix`in e?e:{prefix:``,router:e});if(n.sampleResponses!==!1){let e=n.sampleResponses===`live`?`live`:`redacted`;for(let{router:t}of r)t.enableSampling(e)}let i=e.Router();return i.get(`/openapi.json`,async(e,t)=>{try{let e=await O(r.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:e+f(t)}}))),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??E;t.setHeader(`Content-Type`,`text/html; charset=utf-8`),t.send(D(n.title??`API`,r,i))}),i}export{A as TypedRouter,P as createDocs,j as createTypedRouter,M as createTypedRouterWithConfig,N as createTypedRouterWithMiddleware,b as inferJsonSchema,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.9.3",
3
+ "version": "1.9.5",
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",
@@ -75,14 +75,14 @@
75
75
  "@standard-schema/utils": "^0.3.0"
76
76
  },
77
77
  "devDependencies": {
78
- "@semantic-release/changelog": "^6.0.3",
79
- "@semantic-release/git": "^10.0.1",
78
+ "@semantic-release/changelog": "^7.0.0",
79
+ "@semantic-release/git": "^11.0.1",
80
80
  "@types/express": "^5.0.6",
81
81
  "conventional-changelog-conventionalcommits": "^9.3.1",
82
82
  "rimraf": "^6.1.3",
83
- "semantic-release": "^25.0.3",
84
- "tsdown": "^0.22.1",
85
- "typescript": "^5.9.2"
83
+ "semantic-release": "^25.0.9",
84
+ "tsdown": "^0.22.14",
85
+ "typescript": "^5.9.3"
86
86
  },
87
87
  "engines": {
88
88
  "node": ">=18.0.0"