@minisylar/express-typed-router 1.9.3 → 1.9.4
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 +2 -0
- package/dist/schema-router.cjs +2 -2
- package/dist/schema-router.d.cts +75 -184
- package/dist/schema-router.d.mts +75 -184
- package/dist/schema-router.mjs +2 -2
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @minisylar/express-typed-router
|
|
2
2
|
|
|
3
|
+
[](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.
|
package/dist/schema-router.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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,
|
|
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){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,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`)}const A=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function j(e,t,n){return`<!doctype html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
4
|
<title>${k(e)}</title>
|
|
@@ -9,4 +9,4 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.
|
|
|
9
9
|
<script id="api-reference" data-url="${k(t)}"><\/script>
|
|
10
10
|
<script src="${k(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
|
|
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=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 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;
|
package/dist/schema-router.d.cts
CHANGED
|
@@ -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 {
|
|
@@ -48,23 +54,23 @@ type ExtractRouteParams<Path extends string> = string extends Path ? Record<stri
|
|
|
48
54
|
/**
|
|
49
55
|
* Main parameter extraction logic - enhanced for Express 5 support with recursion depth limit
|
|
50
56
|
*/
|
|
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> : {};
|
|
57
|
+
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
58
|
/**
|
|
53
59
|
* Extract parameters from Express 5 optional segments in braces
|
|
54
60
|
* Handles patterns like {/:param}, {.:ext}, {/optional/:param}
|
|
55
61
|
*/
|
|
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> : {};
|
|
62
|
+
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
63
|
/**
|
|
58
64
|
* Extract a single optional parameter from brace content
|
|
59
65
|
*/
|
|
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 } : {};
|
|
66
|
+
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
67
|
/**
|
|
62
68
|
* Extract a single parameter name from the rest of the path
|
|
63
69
|
* Enhanced to handle Express 5 patterns and optional parameters correctly
|
|
64
70
|
* Special handling for consecutive parameters like :from-:to
|
|
65
71
|
* Order matters: regex constraints must be handled before repeating parameters
|
|
66
72
|
*/
|
|
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 } : {};
|
|
73
|
+
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
74
|
/**
|
|
69
75
|
* Remove the first parameter from path to continue parsing
|
|
70
76
|
* Enhanced to handle Express 5 patterns and optional parameters
|
|
@@ -100,12 +106,12 @@ type RequestOnlyMiddleware<TReq extends Record<string, any>> = TypedMiddleware<T
|
|
|
100
106
|
type LocalsOnlyMiddleware<TLocals extends Record<string, any>> = TypedMiddleware<{}, TLocals>;
|
|
101
107
|
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
108
|
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
|
|
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"> & {
|
|
104
110
|
params: ExtractRouteParams<Path>;
|
|
105
|
-
body: BodySchema extends
|
|
106
|
-
query: QuerySchema extends
|
|
111
|
+
body: BodySchema extends undefined ? unknown : InferSchemaOutput<BodySchema>;
|
|
112
|
+
query: QuerySchema extends undefined ? unknown : InferSchemaOutput<QuerySchema>;
|
|
107
113
|
} & MiddlewareProps;
|
|
108
|
-
type SchemaRouteHandler<Path extends string = string, BodySchema extends
|
|
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>;
|
|
109
115
|
/**
|
|
110
116
|
* Options for defining a typed route, including schemas and middleware.
|
|
111
117
|
*
|
|
@@ -115,7 +121,7 @@ type SchemaRouteHandler<Path extends string = string, BodySchema extends AnyStan
|
|
|
115
121
|
* @property querySchema - Optional schema for validating the query string.
|
|
116
122
|
* @property middleware - Optional array of TypedMiddleware for this route.
|
|
117
123
|
*/
|
|
118
|
-
interface RouteOptions<BodySchema extends
|
|
124
|
+
interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined> {
|
|
119
125
|
bodySchema?: BodySchema;
|
|
120
126
|
querySchema?: QuerySchema;
|
|
121
127
|
middleware?: TypedMiddleware<any, any>[];
|
|
@@ -127,7 +133,7 @@ interface RouteOptions<BodySchema extends AnyStandardSchema | unknown = unknown,
|
|
|
127
133
|
/** Exclude this route from the generated OpenAPI spec entirely. */
|
|
128
134
|
hidden?: boolean;
|
|
129
135
|
}
|
|
130
|
-
type DocMeta = Pick<RouteOptions
|
|
136
|
+
type DocMeta = Pick<RouteOptions, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden">;
|
|
131
137
|
type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "all";
|
|
132
138
|
interface DocsOptions {
|
|
133
139
|
title?: string;
|
|
@@ -185,14 +191,14 @@ interface DocsOptions {
|
|
|
185
191
|
interface RouteMetadata {
|
|
186
192
|
method: HttpMethod;
|
|
187
193
|
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;
|
|
194
|
+
bodySchema?: AnyStandardSchema | undefined;
|
|
195
|
+
querySchema?: AnyStandardSchema | undefined;
|
|
196
|
+
tags?: string[] | undefined;
|
|
197
|
+
description?: string | undefined;
|
|
198
|
+
summary?: string | undefined;
|
|
199
|
+
deprecated?: boolean | undefined;
|
|
200
|
+
responseSchema?: AnyStandardSchema | undefined;
|
|
201
|
+
hidden?: boolean | undefined;
|
|
196
202
|
responseSamples: Map<number, {
|
|
197
203
|
schema: Record<string, any>;
|
|
198
204
|
example?: unknown;
|
|
@@ -235,14 +241,14 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
|
|
|
235
241
|
* and/or adds properties to response.locals
|
|
236
242
|
*/
|
|
237
243
|
/**
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
* Add typed middleware to the router.
|
|
245
|
+
* This middleware will apply to all routes defined after this call.
|
|
246
|
+
*
|
|
247
|
+
* @template TReq - Type extensions for the request object
|
|
248
|
+
* @template TLocals - Type extensions for response.locals
|
|
249
|
+
* @param middleware - The typed middleware function
|
|
250
|
+
* @returns A new router instance with updated types
|
|
251
|
+
*/
|
|
246
252
|
useMiddleware<TReq extends Record<string, any> = {}, TLocals extends Record<string, any> = {}>(middleware: TypedMiddleware<TReq, TLocals>): TypedRouter<Req & TReq, Locals & TLocals>;
|
|
247
253
|
/**
|
|
248
254
|
* Get the underlying Express router, typed as a RequestHandler so it can be
|
|
@@ -318,166 +324,51 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
|
|
|
318
324
|
* // GET /docs/openapi.json → raw OpenAPI 3.1 spec
|
|
319
325
|
*/
|
|
320
326
|
docs(options?: DocsOptions): express.Router & express.RequestHandler;
|
|
321
|
-
get<Path extends string>(path: Path, handler: SchemaRouteHandler<Path,
|
|
322
|
-
get<Path extends string, BodySchema extends
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
327
|
+
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 & {
|
|
329
|
+
bodySchema?: BodySchema;
|
|
330
|
+
querySchema?: QuerySchema;
|
|
331
|
+
middleware?: [...M];
|
|
332
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
333
|
+
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 & {
|
|
335
|
+
bodySchema?: BodySchema;
|
|
336
|
+
querySchema?: QuerySchema;
|
|
337
|
+
middleware?: [...M];
|
|
338
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
339
|
+
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 & {
|
|
341
|
+
bodySchema?: BodySchema;
|
|
342
|
+
querySchema?: QuerySchema;
|
|
343
|
+
middleware?: [...M];
|
|
344
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
345
|
+
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 & {
|
|
347
|
+
bodySchema?: BodySchema;
|
|
348
|
+
querySchema?: QuerySchema;
|
|
349
|
+
middleware?: [...M];
|
|
350
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
351
|
+
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 & {
|
|
334
353
|
querySchema?: QuerySchema;
|
|
335
|
-
middleware
|
|
336
|
-
}, handler: SchemaRouteHandler<Path,
|
|
337
|
-
|
|
338
|
-
|
|
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;
|
|
354
|
+
middleware?: [...M];
|
|
355
|
+
}, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
356
|
+
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 & {
|
|
356
358
|
querySchema?: QuerySchema;
|
|
357
|
-
middleware
|
|
358
|
-
}, handler: SchemaRouteHandler<Path,
|
|
359
|
-
|
|
360
|
-
|
|
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;
|
|
359
|
+
middleware?: [...M];
|
|
360
|
+
}, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
361
|
+
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 & {
|
|
378
363
|
querySchema?: QuerySchema;
|
|
379
|
-
middleware
|
|
380
|
-
}, handler: SchemaRouteHandler<Path,
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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;
|
|
364
|
+
middleware?: [...M];
|
|
365
|
+
}, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
366
|
+
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 & {
|
|
368
|
+
bodySchema?: BodySchema;
|
|
454
369
|
querySchema?: QuerySchema;
|
|
455
|
-
middleware
|
|
456
|
-
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>,
|
|
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>;
|
|
370
|
+
middleware?: [...M];
|
|
371
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
481
372
|
private registerRoute;
|
|
482
373
|
private createBodyValidationMiddleware;
|
|
483
374
|
private createQueryValidationMiddleware;
|
|
@@ -584,4 +475,4 @@ type RouterDocEntry = TypedRouter<any, any> | {
|
|
|
584
475
|
*/
|
|
585
476
|
declare function createDocs(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): express.Router & express.RequestHandler;
|
|
586
477
|
//#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 };
|
|
478
|
+
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 };
|
package/dist/schema-router.d.mts
CHANGED
|
@@ -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 {
|
|
@@ -48,23 +54,23 @@ type ExtractRouteParams<Path extends string> = string extends Path ? Record<stri
|
|
|
48
54
|
/**
|
|
49
55
|
* Main parameter extraction logic - enhanced for Express 5 support with recursion depth limit
|
|
50
56
|
*/
|
|
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> : {};
|
|
57
|
+
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
58
|
/**
|
|
53
59
|
* Extract parameters from Express 5 optional segments in braces
|
|
54
60
|
* Handles patterns like {/:param}, {.:ext}, {/optional/:param}
|
|
55
61
|
*/
|
|
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> : {};
|
|
62
|
+
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
63
|
/**
|
|
58
64
|
* Extract a single optional parameter from brace content
|
|
59
65
|
*/
|
|
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 } : {};
|
|
66
|
+
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
67
|
/**
|
|
62
68
|
* Extract a single parameter name from the rest of the path
|
|
63
69
|
* Enhanced to handle Express 5 patterns and optional parameters correctly
|
|
64
70
|
* Special handling for consecutive parameters like :from-:to
|
|
65
71
|
* Order matters: regex constraints must be handled before repeating parameters
|
|
66
72
|
*/
|
|
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 } : {};
|
|
73
|
+
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
74
|
/**
|
|
69
75
|
* Remove the first parameter from path to continue parsing
|
|
70
76
|
* Enhanced to handle Express 5 patterns and optional parameters
|
|
@@ -100,12 +106,12 @@ type RequestOnlyMiddleware<TReq extends Record<string, any>> = TypedMiddleware<T
|
|
|
100
106
|
type LocalsOnlyMiddleware<TLocals extends Record<string, any>> = TypedMiddleware<{}, TLocals>;
|
|
101
107
|
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
108
|
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
|
|
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"> & {
|
|
104
110
|
params: ExtractRouteParams<Path>;
|
|
105
|
-
body: BodySchema extends
|
|
106
|
-
query: QuerySchema extends
|
|
111
|
+
body: BodySchema extends undefined ? unknown : InferSchemaOutput<BodySchema>;
|
|
112
|
+
query: QuerySchema extends undefined ? unknown : InferSchemaOutput<QuerySchema>;
|
|
107
113
|
} & MiddlewareProps;
|
|
108
|
-
type SchemaRouteHandler<Path extends string = string, BodySchema extends
|
|
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>;
|
|
109
115
|
/**
|
|
110
116
|
* Options for defining a typed route, including schemas and middleware.
|
|
111
117
|
*
|
|
@@ -115,7 +121,7 @@ type SchemaRouteHandler<Path extends string = string, BodySchema extends AnyStan
|
|
|
115
121
|
* @property querySchema - Optional schema for validating the query string.
|
|
116
122
|
* @property middleware - Optional array of TypedMiddleware for this route.
|
|
117
123
|
*/
|
|
118
|
-
interface RouteOptions<BodySchema extends
|
|
124
|
+
interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined> {
|
|
119
125
|
bodySchema?: BodySchema;
|
|
120
126
|
querySchema?: QuerySchema;
|
|
121
127
|
middleware?: TypedMiddleware<any, any>[];
|
|
@@ -127,7 +133,7 @@ interface RouteOptions<BodySchema extends AnyStandardSchema | unknown = unknown,
|
|
|
127
133
|
/** Exclude this route from the generated OpenAPI spec entirely. */
|
|
128
134
|
hidden?: boolean;
|
|
129
135
|
}
|
|
130
|
-
type DocMeta = Pick<RouteOptions
|
|
136
|
+
type DocMeta = Pick<RouteOptions, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden">;
|
|
131
137
|
type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "all";
|
|
132
138
|
interface DocsOptions {
|
|
133
139
|
title?: string;
|
|
@@ -185,14 +191,14 @@ interface DocsOptions {
|
|
|
185
191
|
interface RouteMetadata {
|
|
186
192
|
method: HttpMethod;
|
|
187
193
|
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;
|
|
194
|
+
bodySchema?: AnyStandardSchema | undefined;
|
|
195
|
+
querySchema?: AnyStandardSchema | undefined;
|
|
196
|
+
tags?: string[] | undefined;
|
|
197
|
+
description?: string | undefined;
|
|
198
|
+
summary?: string | undefined;
|
|
199
|
+
deprecated?: boolean | undefined;
|
|
200
|
+
responseSchema?: AnyStandardSchema | undefined;
|
|
201
|
+
hidden?: boolean | undefined;
|
|
196
202
|
responseSamples: Map<number, {
|
|
197
203
|
schema: Record<string, any>;
|
|
198
204
|
example?: unknown;
|
|
@@ -235,14 +241,14 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
|
|
|
235
241
|
* and/or adds properties to response.locals
|
|
236
242
|
*/
|
|
237
243
|
/**
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
* Add typed middleware to the router.
|
|
245
|
+
* This middleware will apply to all routes defined after this call.
|
|
246
|
+
*
|
|
247
|
+
* @template TReq - Type extensions for the request object
|
|
248
|
+
* @template TLocals - Type extensions for response.locals
|
|
249
|
+
* @param middleware - The typed middleware function
|
|
250
|
+
* @returns A new router instance with updated types
|
|
251
|
+
*/
|
|
246
252
|
useMiddleware<TReq extends Record<string, any> = {}, TLocals extends Record<string, any> = {}>(middleware: TypedMiddleware<TReq, TLocals>): TypedRouter<Req & TReq, Locals & TLocals>;
|
|
247
253
|
/**
|
|
248
254
|
* Get the underlying Express router, typed as a RequestHandler so it can be
|
|
@@ -318,166 +324,51 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
|
|
|
318
324
|
* // GET /docs/openapi.json → raw OpenAPI 3.1 spec
|
|
319
325
|
*/
|
|
320
326
|
docs(options?: DocsOptions): express.Router & express.RequestHandler;
|
|
321
|
-
get<Path extends string>(path: Path, handler: SchemaRouteHandler<Path,
|
|
322
|
-
get<Path extends string, BodySchema extends
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
327
|
+
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 & {
|
|
329
|
+
bodySchema?: BodySchema;
|
|
330
|
+
querySchema?: QuerySchema;
|
|
331
|
+
middleware?: [...M];
|
|
332
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
333
|
+
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 & {
|
|
335
|
+
bodySchema?: BodySchema;
|
|
336
|
+
querySchema?: QuerySchema;
|
|
337
|
+
middleware?: [...M];
|
|
338
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
339
|
+
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 & {
|
|
341
|
+
bodySchema?: BodySchema;
|
|
342
|
+
querySchema?: QuerySchema;
|
|
343
|
+
middleware?: [...M];
|
|
344
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
345
|
+
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 & {
|
|
347
|
+
bodySchema?: BodySchema;
|
|
348
|
+
querySchema?: QuerySchema;
|
|
349
|
+
middleware?: [...M];
|
|
350
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
351
|
+
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 & {
|
|
334
353
|
querySchema?: QuerySchema;
|
|
335
|
-
middleware
|
|
336
|
-
}, handler: SchemaRouteHandler<Path,
|
|
337
|
-
|
|
338
|
-
|
|
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;
|
|
354
|
+
middleware?: [...M];
|
|
355
|
+
}, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
356
|
+
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 & {
|
|
356
358
|
querySchema?: QuerySchema;
|
|
357
|
-
middleware
|
|
358
|
-
}, handler: SchemaRouteHandler<Path,
|
|
359
|
-
|
|
360
|
-
|
|
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;
|
|
359
|
+
middleware?: [...M];
|
|
360
|
+
}, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
361
|
+
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 & {
|
|
378
363
|
querySchema?: QuerySchema;
|
|
379
|
-
middleware
|
|
380
|
-
}, handler: SchemaRouteHandler<Path,
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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;
|
|
364
|
+
middleware?: [...M];
|
|
365
|
+
}, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
366
|
+
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 & {
|
|
368
|
+
bodySchema?: BodySchema;
|
|
454
369
|
querySchema?: QuerySchema;
|
|
455
|
-
middleware
|
|
456
|
-
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>,
|
|
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>;
|
|
370
|
+
middleware?: [...M];
|
|
371
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
481
372
|
private registerRoute;
|
|
482
373
|
private createBodyValidationMiddleware;
|
|
483
374
|
private createQueryValidationMiddleware;
|
|
@@ -584,4 +475,4 @@ type RouterDocEntry = TypedRouter<any, any> | {
|
|
|
584
475
|
*/
|
|
585
476
|
declare function createDocs(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): express.Router & express.RequestHandler;
|
|
586
477
|
//#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 };
|
|
478
|
+
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 };
|
package/dist/schema-router.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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
|
|
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,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`)}const S=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function C(e,t,n){return`<!doctype html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
4
|
<title>${x(e)}</title>
|
|
@@ -9,4 +9,4 @@ import e from"express";import{SchemaError as t}from"@standard-schema/utils";func
|
|
|
9
9
|
<script id="api-reference" data-url="${x(t)}"><\/script>
|
|
10
10
|
<script src="${x(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
|
|
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=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 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};
|
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.4",
|
|
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": "^
|
|
79
|
-
"@semantic-release/git": "^
|
|
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.
|
|
84
|
-
"tsdown": "^0.22.
|
|
85
|
-
"typescript": "^5.9.
|
|
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"
|