@minisylar/express-typed-router 1.6.2 → 1.7.0

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.
@@ -1 +1,12 @@
1
- var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`express`)),l=s(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{let e=n.parse(t);return{value:e}}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)}var p=class{router;constructor(){this.router=c.default.Router()}useMiddleware(e){return this.router.use(e),this}getRouter(){return this.router}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=[];if(typeof n==`object`){let e=n;e.middleware&&i.push(...e.middleware),e.bodySchema&&i.push(this.createBodyValidationMiddleware(e.bodySchema)),e.querySchema&&i.push(this.createQueryValidationMiddleware(e.querySchema)),i.push(r)}else i.push(n);return this.router[e](t,...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 m(){return new p}function h(e){let t=new p;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function g(...e){let t=new p;for(let n of e)t=t.useMiddleware(n);return t}exports.createTypedRouter=m,exports.createTypedRouterWithConfig=h,exports.createTypedRouterWithMiddleware=g,exports.isSchemaError=f,exports.parseSchema=u,exports.safeParseSchema=d;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require("express");c=s(c,1);let l=require("@standard-schema/utils");function u(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`){let e=n[`~standard`].validate(t);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new l.SchemaError(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function d(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function f(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}const p=Function(`m`,`return import(m)`);let m,h;async function g(e){try{m??=await p(`module`),h??=await p(`url`);let t=globalThis.process?.cwd?.()??``,n=m.createRequire(h.pathToFileURL(t+`/`).href).resolve(e);return p(h.pathToFileURL(n).href)}catch{return p(e)}}const _=new WeakMap;function v(e){return e.replace(/:([^/?*.()]+)\??/g,`{$1}`)}function y(e){return[...e.matchAll(/:([^/?*.()]+)\??/g)].map(e=>e[1])}function b(e){let t=e.split(`/`).filter(Boolean)[0];return t&&!t.startsWith(`:`)?t:`default`}function x(e,t){let n=t.split(`/`).filter(e=>e&&!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 e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}const T=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function E(e,t,n){return`<!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>${w(e)}</title>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ </head>
8
+ <body>
9
+ <script id="api-reference" data-url="${w(t)}"><\/script>
10
+ <script src="${w(n)}"><\/script>
11
+ </body>
12
+ </html>`}async function D(e,t){let n=Object.create(null);for(let t of e){if(t.method===`all`||t.hidden)continue;let e=v(t.path);n[e]||(n[e]={});let r=y(t.path).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await S(t.querySchema),n=e.properties??{},i=e.required??[];for(let[e,t]of Object.entries(n))r.push({name:e,in:`query`,required:i.includes(e),schema:t})}let i={summary:t.summary??x(t.method,t.path),tags:t.tags??[b(t.path)],parameters:r};t.description&&(i.description=t.description),t.deprecated&&(i.deprecated=!0),t.bodySchema&&(i.requestBody={required:!0,content:{"application/json":{schema:await S(t.bodySchema)}}});let a={};if(t.responseSamples.size>0)for(let[e,n]of t.responseSamples)a[String(e)]={description:e<400?`Success`:`Error`,content:{"application/json":{example:n}}};else a[200]={description:`Success`};i.responses=a,n[e][t.method]=i}return{openapi:`3.1.0`,info:{title:t.title??`API`,version:t.version??`1.0.0`,...t.description?{description:t.description}:{}},...t.servers?{servers:t.servers}:{},paths:n}}const O=new WeakMap;var k=class{router;routes=[];mountedRouters=[];constructor(){this.router=c.default.Router(),O.set(this.router,this)}useMiddleware(e){return this.router.use(e),this}getRouter(){return this.router}use(e,...t){let n=typeof e==`string`,r=n?e:``,i=n?t:[e,...t];for(let e of i){let t=O.get(e);t&&this.mountedRouters.push({prefix:r,router:t})}return n?this.router.use(e,...i):this.router.use(...i),this}mount(e,t){if(typeof e==`string`){let n=t;this.router.use(e,n.getRouter()),this.mountedRouters.push({prefix:e,router:n})}else this.router.use(e.getRouter()),this.mountedRouters.push({prefix:``,router:e});return this}getRouteMetadata(){let e=this.mountedRouters.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,path:e+t.path})));return[...this.routes,...e]}docs(e={}){let t=c.default.Router();return t.get(`/openapi.json`,async(t,n)=>{try{let t=await D(this.getRouteMetadata(),e);e.specOutputPath&&await(await p(`fs/promises`)).writeFile(e.specOutputPath,JSON.stringify(t,null,2),`utf8`).catch(()=>{}),n.json(t)}catch(e){n.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),t.get(`/`,(t,n)=>{let r=`${t.baseUrl}/openapi.json`,i=e.cdnUrl??T;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(E(e.title??`API`,r,i))}),t}get(e,t,n){return this.registerRoute(`get`,e,t,n)}post(e,t,n){return this.registerRoute(`post`,e,t,n)}put(e,t,n){return this.registerRoute(`put`,e,t,n)}patch(e,t,n){return this.registerRoute(`patch`,e,t,n)}delete(e,t,n){return this.registerRoute(`delete`,e,t,n)}options(e,t,n){return this.registerRoute(`options`,e,t,n)}head(e,t,n){return this.registerRoute(`head`,e,t,n)}all(e,t,n){return this.registerRoute(`all`,e,t,n)}registerRoute(e,t,n,r){let i=[],a={method:e,path:t,responseSamples:new Map};if(this.routes.push(a),typeof n==`object`){let e=n;a.bodySchema=e.bodySchema,a.querySchema=e.querySchema,a.tags=e.tags,a.description=e.description,a.summary=e.summary,a.deprecated=e.deprecated,a.responseSchema=e.responseSchema,a.hidden=e.hidden,e.middleware&&i.push(...e.middleware),e.bodySchema&&i.push(this.createBodyValidationMiddleware(e.bodySchema)),e.querySchema&&i.push(this.createQueryValidationMiddleware(e.querySchema)),i.push(r)}else i.push(n);return this.router[e](t,(e,t,n)=>{if(a.hidden||a.responseSamples.size>=10){n();return}let r=t.json;t.json=e=>(a.responseSamples.has(t.statusCode)||a.responseSamples.set(t.statusCode,e),r.call(t,e)),n()},...i),this}createBodyValidationMiddleware(e){return async(t,n,r)=>{try{let i=d(e,t.body),a=i&&typeof i.then==`function`?await i:i;if(a&&`issues`in a&&a.issues){n.status(400).json({error:`Validation failed`,details:a.errors||a.issues});return}t.body=a&&`value`in a?a.value:a,r()}catch(e){f(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):r(e)}}}createQueryValidationMiddleware(e){return async(t,n,r)=>{try{let i=d(e,t.query),a=i&&typeof i.then==`function`?await i:i;if(a&&`issues`in a&&a.issues){n.status(400).json({error:`Validation failed`,details:a.errors||a.issues});return}let o=a&&`value`in a?a.value:a;Object.defineProperty(t,"query",{value:o,writable:!1,enumerable:!0,configurable:!0}),r()}catch(e){f(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):r(e)}}}};function A(){return new k}function j(e){let t=new k;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function M(...e){let t=new k;for(let n of e)t=t.useMiddleware(n);return t}function N(e,t={}){let n=(Array.isArray(e)?e:[e]).map(e=>`prefix`in e?e:{prefix:``,router:e}),r=c.default.Router();return r.get(`/openapi.json`,async(e,r)=>{try{let e=await D(n.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,path:e+t.path}))),t);r.json(e)}catch(e){r.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),r.get(`/`,(e,n)=>{let r=`${e.baseUrl}/openapi.json`,i=t.cdnUrl??T;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(E(t.title??`API`,r,i))}),r}exports.TypedRouter=k,exports.createDocs=N,exports.createTypedRouter=A,exports.createTypedRouterWithConfig=j,exports.createTypedRouterWithMiddleware=M,exports.isSchemaError=f,exports.parseSchema=u,exports.safeParseSchema=d;
@@ -2,7 +2,6 @@ import express, { NextFunction, Request, Response } from "express";
2
2
  import { StandardSchemaV1 } from "@standard-schema/spec";
3
3
 
4
4
  //#region src/schema-router.d.ts
5
-
6
5
  type AnyStandardSchema = StandardSchemaV1<any, any>;
7
6
  type InferOutput<T> = T extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<T> : unknown;
8
7
  type InferInput<T> = T extends StandardSchemaV1 ? StandardSchemaV1.InferInput<T> : unknown;
@@ -120,221 +119,312 @@ interface RouteOptions<BodySchema extends AnyStandardSchema | unknown = unknown,
120
119
  bodySchema?: BodySchema;
121
120
  querySchema?: QuerySchema;
122
121
  middleware?: TypedMiddleware<any, any>[];
122
+ tags?: string[];
123
+ description?: string;
124
+ summary?: string;
125
+ deprecated?: boolean;
126
+ responseSchema?: AnyStandardSchema;
127
+ /** Exclude this route from the generated OpenAPI spec entirely. */
128
+ hidden?: boolean;
123
129
  }
130
+ type DocMeta = Pick<RouteOptions<unknown, unknown>, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden">;
124
131
  type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "all";
125
- declare class TypedRouter<RouterMiddlewareProps extends Record<string, any> = {}, RouterLocals extends Record<string, any> = {}> {
132
+ interface DocsOptions {
133
+ title?: string;
134
+ version?: string;
135
+ description?: string;
136
+ servers?: Array<{
137
+ url: string;
138
+ description?: string;
139
+ }>;
140
+ /**
141
+ * Override the Scalar CDN URL. Use this to pin a specific version or
142
+ * self-host the Scalar bundle to avoid the external CDN dependency.
143
+ * @default "https://cdn.jsdelivr.net/npm/@scalar/api-reference"
144
+ */
145
+ cdnUrl?: string;
146
+ /**
147
+ * File path to write the OpenAPI spec to whenever it is generated.
148
+ * Enables `openapi-typescript --watch` in development — the tool watches
149
+ * the file and regenerates your client types automatically as routes change.
150
+ *
151
+ * @example
152
+ * // docs options
153
+ * { specOutputPath: './openapi.json' }
154
+ *
155
+ * // then in a separate terminal (or via concurrently in package.json):
156
+ * // npx openapi-typescript ./openapi.json -o ./src/client.d.ts --watch
157
+ */
158
+ specOutputPath?: string;
159
+ }
160
+ interface RouteMetadata {
161
+ method: HttpMethod;
162
+ path: string;
163
+ bodySchema?: AnyStandardSchema;
164
+ querySchema?: AnyStandardSchema;
165
+ tags?: string[];
166
+ description?: string;
167
+ summary?: string;
168
+ deprecated?: boolean;
169
+ responseSchema?: AnyStandardSchema;
170
+ hidden?: boolean;
171
+ responseSamples: Map<number, unknown>;
172
+ }
173
+ /**
174
+ * Extra properties that middleware has added to the Express `req` object.
175
+ *
176
+ * Starts as `{}` (nothing added yet) and widens automatically with every
177
+ * `.useMiddleware()` call. After `router.useMiddleware(authMiddleware)` where
178
+ * `authMiddleware` contributes `{ userId: string }`, this becomes `{ userId: string }`.
179
+ *
180
+ * You will see this type in router hover text — it is the accumulating "req additions" slot.
181
+ */
182
+ type AdditionalReqProps = {};
183
+ /**
184
+ * Extra properties that middleware has added to `res.locals`.
185
+ *
186
+ * Starts as `{}` and widens automatically with every `.useMiddleware()` call,
187
+ * mirroring the `TLocals` parameter of each `TypedMiddleware` you attach.
188
+ */
189
+ type AdditionalLocals = {};
190
+ /**
191
+ * A strongly-typed Express router. The two generic params accumulate as
192
+ * middleware is added via `.useMiddleware()`.
193
+ *
194
+ * @typeParam Req - Extra properties on `req` contributed by middleware. Starts as {@link AdditionalReqProps}.
195
+ * @typeParam Locals - Extra properties on `res.locals` contributed by middleware. Starts as {@link AdditionalLocals}.
196
+ */
197
+ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps, Locals extends Record<string, any> = AdditionalLocals> {
126
198
  private router;
199
+ private routes;
200
+ private mountedRouters;
127
201
  constructor();
128
202
  /**
129
203
  * Add typed middleware that extends the request with additional properties
130
204
  * and/or adds properties to response.locals
131
- */ /**
132
- * Add typed middleware to the router.
133
- * This middleware will apply to all routes defined after this call.
134
- *
135
- * @template TReq - Type extensions for the request object
136
- * @template TLocals - Type extensions for response.locals
137
- * @param middleware - The typed middleware function
138
- * @returns A new router instance with updated types
139
- */
140
- useMiddleware<TReq extends Record<string, any> = {}, TLocals extends Record<string, any> = {}>(middleware: TypedMiddleware<TReq, TLocals>): TypedRouter<RouterMiddlewareProps & TReq, RouterLocals & TLocals>;
205
+ */
206
+ /**
207
+ * Add typed middleware to the router.
208
+ * This middleware will apply to all routes defined after this call.
209
+ *
210
+ * @template TReq - Type extensions for the request object
211
+ * @template TLocals - Type extensions for response.locals
212
+ * @param middleware - The typed middleware function
213
+ * @returns A new router instance with updated types
214
+ */
215
+ useMiddleware<TReq extends Record<string, any> = {}, TLocals extends Record<string, any> = {}>(middleware: TypedMiddleware<TReq, TLocals>): TypedRouter<Req & TReq, Locals & TLocals>;
216
+ /**
217
+ * Get the underlying Express router, typed as a RequestHandler so it can be
218
+ * passed directly to app.use() without a cast in Express 5.
219
+ */
220
+ getRouter(): express.Router & express.RequestHandler;
221
+ /**
222
+ * Mount middleware or a sub-router at an optional path prefix.
223
+ *
224
+ * When passed the result of another TypedRouter's .getRouter(), it is
225
+ * automatically recognised and tracked for .docs() — no extra wiring needed.
226
+ *
227
+ * @example
228
+ * // v1.routes.ts — keep your exact existing pattern, just use TypedRouter
229
+ * export const v1Routes = createTypedRouter()
230
+ *
231
+ * v1Routes.use('/products', productRoutes.getRouter()) // tracked ✓
232
+ * v1Routes.use('/profile', profileRoutes.getRouter()) // tracked ✓
233
+ * v1Routes.use('/', callbackRouter) // plain Express, not tracked
234
+ *
235
+ * app.use('/v1', v1Routes.getRouter())
236
+ * app.use('/docs', v1Routes.docs({ title: 'My API' })) // just works
237
+ */
238
+ use(path: string, ...handlers: Array<express.RequestHandler | express.Router>): TypedRouter<Req, Locals>;
239
+ use(...handlers: Array<express.RequestHandler | express.Router>): TypedRouter<Req, Locals>;
141
240
  /**
142
- * Get the underlying Express router
241
+ * Mount a TypedRouter at a path prefix, registering it both on the Express
242
+ * router and in the docs registry so .docs() picks it up automatically.
243
+ *
244
+ * @example
245
+ * const v1 = createTypedRouter()
246
+ * .mount('/products', productRoutes)
247
+ * .mount('/profile', profileRoutes)
248
+ * .mount('/supplier', supplierRoutes)
249
+ *
250
+ * app.use('/v1', v1.getRouter())
251
+ * app.use('/docs', v1.docs({ title: 'My API' }))
143
252
  */
144
- getRouter(): express.Router;
145
- get<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps, RouterLocals>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
146
- get<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, RouterMiddlewareProps, RouterLocals>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
253
+ mount(prefix: string, router: TypedRouter<any, any>): TypedRouter<Req, Locals>;
254
+ mount(router: TypedRouter<any, any>): TypedRouter<Req, Locals>;
255
+ /**
256
+ * Returns the collected route metadata for this router, including all
257
+ * sub-routers registered via .mount() with their prefixes applied.
258
+ * Used internally by .docs() and by createDocs() for multi-router merging.
259
+ */
260
+ getRouteMetadata(): RouteMetadata[];
261
+ /**
262
+ * Returns an Express router that serves OpenAPI docs.
263
+ * Mount it anywhere on your app — routes are auto-discovered.
264
+ *
265
+ * @example
266
+ * app.use('/docs', router.docs({ title: 'My API', version: '1.0.0' }))
267
+ * // GET /docs → Scalar UI
268
+ * // GET /docs/openapi.json → raw OpenAPI 3.1 spec
269
+ */
270
+ docs(options?: DocsOptions): express.Router & express.RequestHandler;
271
+ get<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
272
+ get<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
147
273
  get<Path extends string, Middleware extends readonly TypedMiddleware<any, any>[]>(path: Path, options: {
148
274
  middleware: Middleware;
149
- }, handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps & InferMiddlewareProps<Middleware>, RouterLocals & InferMiddlewareLocals<Middleware>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
275
+ }, handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<Middleware>, Locals & InferMiddlewareLocals<Middleware>>): TypedRouter<Req, Locals>;
150
276
  get<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: RouteOptions<BodySchema, QuerySchema> & {
151
277
  middleware: [...M];
152
- },
153
- // Using tuple spread pattern
154
- handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
278
+ }, // Using tuple spread pattern
279
+ handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
155
280
  // Make it readonly for type inference
156
- // Make it readonly for type inference
157
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
158
- post<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
281
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
282
+ post<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
159
283
  bodySchema: BodySchema;
160
284
  querySchema?: QuerySchema;
161
285
  middleware: [...M];
162
- }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
163
- // Make it readonly for type inference
286
+ }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
164
287
  // Make it readonly for type inference
165
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
166
- post<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
288
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
289
+ post<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
167
290
  bodySchema: BodySchema;
168
291
  middleware: [...M];
169
- },
170
- // Using tuple spread pattern
171
- handler: SchemaRouteHandler<Path, BodySchema, unknown, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
172
- // Make it readonly for type inference
292
+ }, // Using tuple spread pattern
293
+ handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
173
294
  // Make it readonly for type inference
174
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
175
- post<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
295
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
296
+ post<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
176
297
  middleware: [...M];
177
- },
178
- // Using tuple spread pattern
179
- handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
298
+ }, // Using tuple spread pattern
299
+ handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
180
300
  // Make it readonly for type inference
181
- // Make it readonly for type inference
182
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
183
- post<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, RouterMiddlewareProps, RouterLocals>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
184
- post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps, RouterLocals>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
185
- put<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
301
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
302
+ 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>;
303
+ post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
304
+ put<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
186
305
  bodySchema: BodySchema;
187
306
  querySchema?: QuerySchema;
188
307
  middleware: [...M];
189
- }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
190
- // Make it readonly for type inference
308
+ }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
191
309
  // Make it readonly for type inference
192
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
193
- put<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
310
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
311
+ put<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
194
312
  bodySchema: BodySchema;
195
313
  middleware: [...M];
196
- },
197
- // Using tuple spread pattern
198
- handler: SchemaRouteHandler<Path, BodySchema, unknown, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
314
+ }, // Using tuple spread pattern
315
+ handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
199
316
  // Make it readonly for type inference
200
- // Make it readonly for type inference
201
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
202
- put<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
317
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
318
+ put<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
203
319
  middleware: [...M];
204
- },
205
- // Using tuple spread pattern
206
- handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
207
- // Make it readonly for type inference
320
+ }, // Using tuple spread pattern
321
+ handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
208
322
  // Make it readonly for type inference
209
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
210
- put<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, RouterMiddlewareProps, RouterLocals>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
211
- put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps, RouterLocals>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
212
- patch<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
323
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
324
+ 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>;
325
+ put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
326
+ patch<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
213
327
  bodySchema: BodySchema;
214
328
  querySchema?: QuerySchema;
215
329
  middleware: [...M];
216
- }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
330
+ }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
217
331
  // Make it readonly for type inference
218
- // Make it readonly for type inference
219
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
220
- patch<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
332
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
333
+ patch<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
221
334
  bodySchema: BodySchema;
222
335
  middleware: [...M];
223
- },
224
- // Using tuple spread pattern
225
- handler: SchemaRouteHandler<Path, BodySchema, unknown, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
226
- // Make it readonly for type inference
336
+ }, // Using tuple spread pattern
337
+ handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
227
338
  // Make it readonly for type inference
228
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
229
- patch<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
339
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
340
+ patch<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
230
341
  middleware: [...M];
231
- },
232
- // Using tuple spread pattern
233
- handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
342
+ }, // Using tuple spread pattern
343
+ handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
234
344
  // Make it readonly for type inference
235
- // Make it readonly for type inference
236
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
237
- patch<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, RouterMiddlewareProps, RouterLocals>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
238
- patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps, RouterLocals>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
239
- delete<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
345
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
346
+ 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>;
347
+ patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
348
+ delete<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
240
349
  querySchema: QuerySchema;
241
350
  middleware: [...M];
242
- },
243
- // Using tuple spread pattern
244
- handler: SchemaRouteHandler<Path, unknown, QuerySchema, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
245
- // Make it readonly for type inference
351
+ }, // Using tuple spread pattern
352
+ handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
246
353
  // Make it readonly for type inference
247
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
354
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
248
355
  delete<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
249
356
  querySchema: QuerySchema;
250
- }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, RouterMiddlewareProps, RouterLocals>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
251
- delete<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
357
+ }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
358
+ delete<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
252
359
  middleware: [...M];
253
- },
254
- // Using tuple spread pattern
255
- handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
360
+ }, // Using tuple spread pattern
361
+ handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
256
362
  // Make it readonly for type inference
257
- // Make it readonly for type inference
258
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
259
- delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps, RouterLocals>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
260
- options<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
363
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
364
+ delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
365
+ options<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
261
366
  querySchema: QuerySchema;
262
367
  middleware: [...M];
263
- },
264
- // Using tuple spread pattern
265
- handler: SchemaRouteHandler<Path, unknown, QuerySchema, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
266
- // Make it readonly for type inference
368
+ }, // Using tuple spread pattern
369
+ handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
267
370
  // Make it readonly for type inference
268
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
371
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
269
372
  options<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
270
373
  querySchema: QuerySchema;
271
- }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, RouterMiddlewareProps, RouterLocals>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
272
- options<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
374
+ }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
375
+ options<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
273
376
  middleware: [...M];
274
- },
275
- // Using tuple spread pattern
276
- handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
377
+ }, // Using tuple spread pattern
378
+ handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
277
379
  // Make it readonly for type inference
278
- // Make it readonly for type inference
279
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
280
- options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps, RouterLocals>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
281
- head<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
380
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
381
+ options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
382
+ head<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
282
383
  querySchema: QuerySchema;
283
384
  middleware: [...M];
284
- },
285
- // Using tuple spread pattern
286
- handler: SchemaRouteHandler<Path, unknown, QuerySchema, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
287
- // Make it readonly for type inference
385
+ }, // Using tuple spread pattern
386
+ handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
288
387
  // Make it readonly for type inference
289
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
388
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
290
389
  head<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
291
390
  querySchema: QuerySchema;
292
- }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, RouterMiddlewareProps, RouterLocals>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
293
- head<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
391
+ }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
392
+ head<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
294
393
  middleware: [...M];
295
- },
296
- // Using tuple spread pattern
297
- handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
394
+ }, // Using tuple spread pattern
395
+ handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
298
396
  // Make it readonly for type inference
299
- // Make it readonly for type inference
300
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
301
- head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps, RouterLocals>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
302
- all<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
397
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
398
+ head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
399
+ all<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
303
400
  bodySchema: BodySchema;
304
401
  querySchema?: QuerySchema;
305
402
  middleware: [...M];
306
- }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
307
- // Make it readonly for type inference
403
+ }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
308
404
  // Make it readonly for type inference
309
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
310
- all<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
405
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
406
+ all<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
311
407
  bodySchema: BodySchema;
312
408
  middleware: [...M];
313
- },
314
- // Using tuple spread pattern
315
- handler: SchemaRouteHandler<Path, BodySchema, unknown, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
316
- // Make it readonly for type inference
409
+ }, // Using tuple spread pattern
410
+ handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
317
411
  // Make it readonly for type inference
318
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
319
- all<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
412
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
413
+ all<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
320
414
  querySchema: QuerySchema;
321
415
  middleware: [...M];
322
- },
323
- // Using tuple spread pattern
324
- handler: SchemaRouteHandler<Path, unknown, QuerySchema, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
416
+ }, // Using tuple spread pattern
417
+ handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
325
418
  // Make it readonly for type inference
326
- // Make it readonly for type inference
327
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
328
- all<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, RouterMiddlewareProps, RouterLocals>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
329
- all<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: {
419
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
420
+ 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>;
421
+ all<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
330
422
  middleware: [...M];
331
- },
332
- // Using tuple spread pattern
333
- handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
334
- // Make it readonly for type inference
423
+ }, // Using tuple spread pattern
424
+ handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
335
425
  // Make it readonly for type inference
336
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
337
- all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps, RouterLocals>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
426
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
427
+ all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
338
428
  private registerRoute;
339
429
  private createBodyValidationMiddleware;
340
430
  private createQueryValidationMiddleware;
@@ -359,7 +449,7 @@ declare class TypedRouter<RouterMiddlewareProps extends Record<string, any> = {}
359
449
  * const app = express();
360
450
  * app.use('/api', router.getRouter());
361
451
  */
362
- declare function createTypedRouter<RouterMiddlewareProps extends Record<string, any> = {}, RouterLocals extends Record<string, any> = {}>(): TypedRouter<RouterMiddlewareProps, RouterLocals>;
452
+ declare function createTypedRouter<Req extends Record<string, any> = AdditionalReqProps, Locals extends Record<string, any> = AdditionalLocals>(): TypedRouter<Req, Locals>;
363
453
  /**
364
454
  * Configuration options for createTypedRouterWithConfig.
365
455
  *
@@ -387,7 +477,7 @@ interface RouterConfig {
387
477
  * }
388
478
  * });
389
479
  */
390
- declare function createTypedRouterWithConfig<RouterMiddlewareProps extends Record<string, any> = {}, RouterLocals extends Record<string, any> = {}>(config?: RouterConfig): TypedRouter<RouterMiddlewareProps, RouterLocals>;
480
+ declare function createTypedRouterWithConfig<Req extends Record<string, any> = AdditionalReqProps, Locals extends Record<string, any> = AdditionalLocals>(config?: RouterConfig): TypedRouter<Req, Locals>;
391
481
  /**
392
482
  * Create a new typed router with pre-configured middleware.
393
483
  *
@@ -402,5 +492,43 @@ declare function createTypedRouterWithConfig<RouterMiddlewareProps extends Recor
402
492
  * const router = createTypedRouterWithMiddleware(authMiddleware, loggingMiddleware);
403
493
  */
404
494
  declare function createTypedRouterWithMiddleware<T extends Record<string, any>>(...middleware: TypedMiddleware<any, any>[]): TypedRouter<T>;
495
+ /**
496
+ * An entry for createDocs(). Either a bare TypedRouter (no prefix prepended)
497
+ * or an object with an explicit prefix matching the mount point in app.use().
498
+ *
499
+ * @example
500
+ * // Routes defined as /users/:id — mount prefix prepends /api
501
+ * { prefix: '/api', router: usersRouter }
502
+ *
503
+ * // Routes already include the full path — no prefix needed
504
+ * authRouter
505
+ */
506
+ type RouterDocEntry = TypedRouter<any, any> | {
507
+ prefix: string;
508
+ router: TypedRouter<any, any>;
509
+ };
510
+ /**
511
+ * Create a unified OpenAPI docs endpoint that merges routes from multiple
512
+ * TypedRouter instances. Use this when routes are split across files.
513
+ *
514
+ * @example
515
+ * // users.router.ts — routes like /users, /users/:id
516
+ * export const usersRouter = createTypedRouter();
517
+ *
518
+ * // auth.router.ts — routes like /login, /logout
519
+ * export const authRouter = createTypedRouter();
520
+ *
521
+ * // app.ts
522
+ * app.use('/api', usersRouter.getRouter());
523
+ * app.use('/api', authRouter.getRouter());
524
+ * app.use('/docs', createDocs(
525
+ * [
526
+ * { prefix: '/api', router: usersRouter },
527
+ * { prefix: '/api', router: authRouter },
528
+ * ],
529
+ * { title: 'My API', version: '1.0.0' }
530
+ * ));
531
+ */
532
+ declare function createDocs(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): express.Router & express.RequestHandler;
405
533
  //#endregion
406
- export { AnyStandardSchema, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteOptions, RouterConfig, SafeParseResult, SchemaRequest, SchemaRouteHandler, TypedMiddleware, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, isSchemaError, parseSchema, safeParseSchema };
534
+ export { AdditionalLocals, AdditionalReqProps, AnyStandardSchema, DocsOptions, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteOptions, RouterConfig, RouterDocEntry, SafeParseResult, SchemaRequest, SchemaRouteHandler, TypedMiddleware, TypedRouter, createDocs, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, isSchemaError, parseSchema, safeParseSchema };