@minisylar/express-typed-router 1.9.4 → 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 +34 -0
- package/dist/schema-router.cjs +5 -5
- package/dist/schema-router.d.cts +119 -23
- package/dist/schema-router.d.mts +119 -23
- package/dist/schema-router.mjs +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -493,6 +493,40 @@ app.use("/docs", api.docs({ title: "My API", version: "1.0.0" }));
|
|
|
493
493
|
|
|
494
494
|
---
|
|
495
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
|
+
|
|
496
530
|
## API surface
|
|
497
531
|
|
|
498
532
|
| | |
|
package/dist/schema-router.cjs
CHANGED
|
@@ -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,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){
|
|
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,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`)}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>${
|
|
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="${
|
|
10
|
-
<script src="${
|
|
9
|
+
<script id="api-reference" data-url="${N(t)}"><\/script>
|
|
10
|
+
<script src="${N(n)}"><\/script>
|
|
11
11
|
</body>
|
|
12
|
-
</html>`}async function
|
|
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;
|
package/dist/schema-router.d.cts
CHANGED
|
@@ -49,8 +49,16 @@ declare function isSchemaError(error: unknown): error is {
|
|
|
49
49
|
* - Optional parameters: /posts/:year/:month? → { year: string; month?: string }
|
|
50
50
|
* - Wildcard parameters: /files/* → { "0": string }
|
|
51
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.
|
|
52
58
|
*/
|
|
53
|
-
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 : "" : "";
|
|
54
62
|
/**
|
|
55
63
|
* Main parameter extraction logic - enhanced for Express 5 support with recursion depth limit
|
|
56
64
|
*/
|
|
@@ -106,24 +114,39 @@ type RequestOnlyMiddleware<TReq extends Record<string, any>> = TypedMiddleware<T
|
|
|
106
114
|
type LocalsOnlyMiddleware<TLocals extends Record<string, any>> = TypedMiddleware<{}, TLocals>;
|
|
107
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 : {} : {};
|
|
108
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 : {} : {};
|
|
109
|
-
type SchemaRequest<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}> = Omit<Request, "params" | "query" | "body"> & {
|
|
110
|
-
params: ExtractRouteParams<Path>;
|
|
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>;
|
|
111
119
|
body: BodySchema extends undefined ? unknown : InferSchemaOutput<BodySchema>;
|
|
112
120
|
query: QuerySchema extends undefined ? unknown : InferSchemaOutput<QuerySchema>;
|
|
113
121
|
} & MiddlewareProps;
|
|
114
|
-
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> = {}> = (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>;
|
|
115
123
|
/**
|
|
116
124
|
* Options for defining a typed route, including schemas and middleware.
|
|
117
125
|
*
|
|
118
126
|
* @template BodySchema - Schema for request body validation.
|
|
119
127
|
* @template QuerySchema - Schema for query parameter validation.
|
|
128
|
+
* @template ParamsSchema - Schema for route param validation.
|
|
120
129
|
* @property bodySchema - Optional schema for validating the request body.
|
|
121
130
|
* @property querySchema - Optional schema for validating the query string.
|
|
131
|
+
* @property paramsSchema - Optional schema for validating route params.
|
|
122
132
|
* @property middleware - Optional array of TypedMiddleware for this route.
|
|
123
133
|
*/
|
|
124
|
-
interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined> {
|
|
134
|
+
interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined> {
|
|
125
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
|
+
*/
|
|
126
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;
|
|
127
150
|
middleware?: TypedMiddleware<any, any>[];
|
|
128
151
|
tags?: string[];
|
|
129
152
|
description?: string;
|
|
@@ -132,8 +155,16 @@ interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, Qu
|
|
|
132
155
|
responseSchema?: AnyStandardSchema;
|
|
133
156
|
/** Exclude this route from the generated OpenAPI spec entirely. */
|
|
134
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;
|
|
135
166
|
}
|
|
136
|
-
type DocMeta = Pick<RouteOptions, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden">;
|
|
167
|
+
type DocMeta = Pick<RouteOptions, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden" | "pathExample">;
|
|
137
168
|
type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "all";
|
|
138
169
|
interface DocsOptions {
|
|
139
170
|
title?: string;
|
|
@@ -190,9 +221,12 @@ interface DocsOptions {
|
|
|
190
221
|
}
|
|
191
222
|
interface RouteMetadata {
|
|
192
223
|
method: HttpMethod;
|
|
193
|
-
path: string;
|
|
224
|
+
path: string | RegExp;
|
|
225
|
+
/** Doc-only path override for RegExp routes — see RouteOptions.pathExample. */
|
|
226
|
+
pathExample?: string | undefined;
|
|
194
227
|
bodySchema?: AnyStandardSchema | undefined;
|
|
195
228
|
querySchema?: AnyStandardSchema | undefined;
|
|
229
|
+
paramsSchema?: AnyStandardSchema | undefined;
|
|
196
230
|
tags?: string[] | undefined;
|
|
197
231
|
description?: string | undefined;
|
|
198
232
|
summary?: string | undefined;
|
|
@@ -325,52 +359,114 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
|
|
|
325
359
|
*/
|
|
326
360
|
docs(options?: DocsOptions): express.Router & express.RequestHandler;
|
|
327
361
|
get<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
328
|
-
get<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
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 & {
|
|
329
363
|
bodySchema?: BodySchema;
|
|
330
364
|
querySchema?: QuerySchema;
|
|
365
|
+
paramsSchema?: ParamsSchema;
|
|
331
366
|
middleware?: [...M];
|
|
332
|
-
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...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>;
|
|
333
375
|
post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
334
|
-
post<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
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 & {
|
|
335
377
|
bodySchema?: BodySchema;
|
|
336
378
|
querySchema?: QuerySchema;
|
|
379
|
+
paramsSchema?: ParamsSchema;
|
|
337
380
|
middleware?: [...M];
|
|
338
|
-
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...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>;
|
|
339
389
|
put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
340
|
-
put<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
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 & {
|
|
341
398
|
bodySchema?: BodySchema;
|
|
342
399
|
querySchema?: QuerySchema;
|
|
400
|
+
paramsSchema?: ParamsSchema;
|
|
343
401
|
middleware?: [...M];
|
|
344
|
-
}, handler: SchemaRouteHandler<
|
|
402
|
+
}, handler: SchemaRouteHandler<string, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
|
|
345
403
|
patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
346
|
-
patch<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
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 & {
|
|
347
405
|
bodySchema?: BodySchema;
|
|
348
406
|
querySchema?: QuerySchema;
|
|
407
|
+
paramsSchema?: ParamsSchema;
|
|
349
408
|
middleware?: [...M];
|
|
350
|
-
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...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>;
|
|
351
417
|
delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
352
|
-
delete<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
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 & {
|
|
353
425
|
querySchema?: QuerySchema;
|
|
426
|
+
paramsSchema?: ParamsSchema;
|
|
354
427
|
middleware?: [...M];
|
|
355
|
-
}, handler: SchemaRouteHandler<
|
|
428
|
+
}, handler: SchemaRouteHandler<string, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
|
|
356
429
|
options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
357
|
-
options<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
430
|
+
options<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
358
431
|
querySchema?: QuerySchema;
|
|
432
|
+
paramsSchema?: ParamsSchema;
|
|
359
433
|
middleware?: [...M];
|
|
360
|
-
}, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...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>;
|
|
361
441
|
head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
362
|
-
head<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
442
|
+
head<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
443
|
+
querySchema?: QuerySchema;
|
|
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 & {
|
|
363
449
|
querySchema?: QuerySchema;
|
|
450
|
+
paramsSchema?: ParamsSchema;
|
|
364
451
|
middleware?: [...M];
|
|
365
|
-
}, handler: SchemaRouteHandler<
|
|
452
|
+
}, handler: SchemaRouteHandler<string, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
|
|
366
453
|
all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
367
|
-
all<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
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;
|
|
456
|
+
querySchema?: QuerySchema;
|
|
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 & {
|
|
368
462
|
bodySchema?: BodySchema;
|
|
369
463
|
querySchema?: QuerySchema;
|
|
464
|
+
paramsSchema?: ParamsSchema;
|
|
370
465
|
middleware?: [...M];
|
|
371
|
-
}, handler: SchemaRouteHandler<
|
|
466
|
+
}, handler: SchemaRouteHandler<string, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
|
|
372
467
|
private registerRoute;
|
|
373
468
|
private createBodyValidationMiddleware;
|
|
469
|
+
private createParamsValidationMiddleware;
|
|
374
470
|
private createQueryValidationMiddleware;
|
|
375
471
|
}
|
|
376
472
|
/**
|
package/dist/schema-router.d.mts
CHANGED
|
@@ -49,8 +49,16 @@ declare function isSchemaError(error: unknown): error is {
|
|
|
49
49
|
* - Optional parameters: /posts/:year/:month? → { year: string; month?: string }
|
|
50
50
|
* - Wildcard parameters: /files/* → { "0": string }
|
|
51
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.
|
|
52
58
|
*/
|
|
53
|
-
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 : "" : "";
|
|
54
62
|
/**
|
|
55
63
|
* Main parameter extraction logic - enhanced for Express 5 support with recursion depth limit
|
|
56
64
|
*/
|
|
@@ -106,24 +114,39 @@ type RequestOnlyMiddleware<TReq extends Record<string, any>> = TypedMiddleware<T
|
|
|
106
114
|
type LocalsOnlyMiddleware<TLocals extends Record<string, any>> = TypedMiddleware<{}, TLocals>;
|
|
107
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 : {} : {};
|
|
108
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 : {} : {};
|
|
109
|
-
type SchemaRequest<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}> = Omit<Request, "params" | "query" | "body"> & {
|
|
110
|
-
params: ExtractRouteParams<Path>;
|
|
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>;
|
|
111
119
|
body: BodySchema extends undefined ? unknown : InferSchemaOutput<BodySchema>;
|
|
112
120
|
query: QuerySchema extends undefined ? unknown : InferSchemaOutput<QuerySchema>;
|
|
113
121
|
} & MiddlewareProps;
|
|
114
|
-
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> = {}> = (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>;
|
|
115
123
|
/**
|
|
116
124
|
* Options for defining a typed route, including schemas and middleware.
|
|
117
125
|
*
|
|
118
126
|
* @template BodySchema - Schema for request body validation.
|
|
119
127
|
* @template QuerySchema - Schema for query parameter validation.
|
|
128
|
+
* @template ParamsSchema - Schema for route param validation.
|
|
120
129
|
* @property bodySchema - Optional schema for validating the request body.
|
|
121
130
|
* @property querySchema - Optional schema for validating the query string.
|
|
131
|
+
* @property paramsSchema - Optional schema for validating route params.
|
|
122
132
|
* @property middleware - Optional array of TypedMiddleware for this route.
|
|
123
133
|
*/
|
|
124
|
-
interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined> {
|
|
134
|
+
interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined> {
|
|
125
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
|
+
*/
|
|
126
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;
|
|
127
150
|
middleware?: TypedMiddleware<any, any>[];
|
|
128
151
|
tags?: string[];
|
|
129
152
|
description?: string;
|
|
@@ -132,8 +155,16 @@ interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, Qu
|
|
|
132
155
|
responseSchema?: AnyStandardSchema;
|
|
133
156
|
/** Exclude this route from the generated OpenAPI spec entirely. */
|
|
134
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;
|
|
135
166
|
}
|
|
136
|
-
type DocMeta = Pick<RouteOptions, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden">;
|
|
167
|
+
type DocMeta = Pick<RouteOptions, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden" | "pathExample">;
|
|
137
168
|
type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "all";
|
|
138
169
|
interface DocsOptions {
|
|
139
170
|
title?: string;
|
|
@@ -190,9 +221,12 @@ interface DocsOptions {
|
|
|
190
221
|
}
|
|
191
222
|
interface RouteMetadata {
|
|
192
223
|
method: HttpMethod;
|
|
193
|
-
path: string;
|
|
224
|
+
path: string | RegExp;
|
|
225
|
+
/** Doc-only path override for RegExp routes — see RouteOptions.pathExample. */
|
|
226
|
+
pathExample?: string | undefined;
|
|
194
227
|
bodySchema?: AnyStandardSchema | undefined;
|
|
195
228
|
querySchema?: AnyStandardSchema | undefined;
|
|
229
|
+
paramsSchema?: AnyStandardSchema | undefined;
|
|
196
230
|
tags?: string[] | undefined;
|
|
197
231
|
description?: string | undefined;
|
|
198
232
|
summary?: string | undefined;
|
|
@@ -325,52 +359,114 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
|
|
|
325
359
|
*/
|
|
326
360
|
docs(options?: DocsOptions): express.Router & express.RequestHandler;
|
|
327
361
|
get<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
328
|
-
get<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
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 & {
|
|
329
363
|
bodySchema?: BodySchema;
|
|
330
364
|
querySchema?: QuerySchema;
|
|
365
|
+
paramsSchema?: ParamsSchema;
|
|
331
366
|
middleware?: [...M];
|
|
332
|
-
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...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>;
|
|
333
375
|
post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
334
|
-
post<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
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 & {
|
|
335
377
|
bodySchema?: BodySchema;
|
|
336
378
|
querySchema?: QuerySchema;
|
|
379
|
+
paramsSchema?: ParamsSchema;
|
|
337
380
|
middleware?: [...M];
|
|
338
|
-
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...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>;
|
|
339
389
|
put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
340
|
-
put<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
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 & {
|
|
341
398
|
bodySchema?: BodySchema;
|
|
342
399
|
querySchema?: QuerySchema;
|
|
400
|
+
paramsSchema?: ParamsSchema;
|
|
343
401
|
middleware?: [...M];
|
|
344
|
-
}, handler: SchemaRouteHandler<
|
|
402
|
+
}, handler: SchemaRouteHandler<string, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
|
|
345
403
|
patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
346
|
-
patch<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
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 & {
|
|
347
405
|
bodySchema?: BodySchema;
|
|
348
406
|
querySchema?: QuerySchema;
|
|
407
|
+
paramsSchema?: ParamsSchema;
|
|
349
408
|
middleware?: [...M];
|
|
350
|
-
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...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>;
|
|
351
417
|
delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
352
|
-
delete<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
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 & {
|
|
353
425
|
querySchema?: QuerySchema;
|
|
426
|
+
paramsSchema?: ParamsSchema;
|
|
354
427
|
middleware?: [...M];
|
|
355
|
-
}, handler: SchemaRouteHandler<
|
|
428
|
+
}, handler: SchemaRouteHandler<string, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
|
|
356
429
|
options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
357
|
-
options<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
430
|
+
options<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
358
431
|
querySchema?: QuerySchema;
|
|
432
|
+
paramsSchema?: ParamsSchema;
|
|
359
433
|
middleware?: [...M];
|
|
360
|
-
}, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...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>;
|
|
361
441
|
head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
362
|
-
head<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
442
|
+
head<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
443
|
+
querySchema?: QuerySchema;
|
|
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 & {
|
|
363
449
|
querySchema?: QuerySchema;
|
|
450
|
+
paramsSchema?: ParamsSchema;
|
|
364
451
|
middleware?: [...M];
|
|
365
|
-
}, handler: SchemaRouteHandler<
|
|
452
|
+
}, handler: SchemaRouteHandler<string, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
|
|
366
453
|
all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
367
|
-
all<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
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;
|
|
456
|
+
querySchema?: QuerySchema;
|
|
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 & {
|
|
368
462
|
bodySchema?: BodySchema;
|
|
369
463
|
querySchema?: QuerySchema;
|
|
464
|
+
paramsSchema?: ParamsSchema;
|
|
370
465
|
middleware?: [...M];
|
|
371
|
-
}, handler: SchemaRouteHandler<
|
|
466
|
+
}, handler: SchemaRouteHandler<string, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>, ParamsSchema>): TypedRouter<Req, Locals>;
|
|
372
467
|
private registerRoute;
|
|
373
468
|
private createBodyValidationMiddleware;
|
|
469
|
+
private createParamsValidationMiddleware;
|
|
374
470
|
private createQueryValidationMiddleware;
|
|
375
471
|
}
|
|
376
472
|
/**
|
package/dist/schema-router.mjs
CHANGED
|
@@ -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){
|
|
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,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`)}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>${
|
|
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="${
|
|
10
|
-
<script src="${
|
|
9
|
+
<script id="api-reference" data-url="${T(t)}"><\/script>
|
|
10
|
+
<script src="${T(n)}"><\/script>
|
|
11
11
|
</body>
|
|
12
|
-
</html>`}async function
|
|
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
|
+
"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",
|