@minisylar/express-typed-router 1.6.2 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,315 @@ 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 — pass TypedRouter instances directly, no .getRouter() needed
229
+ * export const v1Routes = createTypedRouter()
230
+ *
231
+ * v1Routes.use('/products', productRoutes) // tracked ✓
232
+ * v1Routes.use('/profile', profileRoutes) // tracked ✓
233
+ * v1Routes.use('/', callbackRouter) // plain Express, also works
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<any, any>>): TypedRouter<Req, Locals>;
239
+ use(...handlers: Array<express.RequestHandler | express.Router | TypedRouter<any, any>>): 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>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
359
+ delete<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
252
360
  middleware: [...M];
253
- },
254
- // Using tuple spread pattern
255
- handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
361
+ }, // Using tuple spread pattern
362
+ handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
256
363
  // 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: {
364
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
365
+ delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
366
+ options<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
261
367
  querySchema: QuerySchema;
262
368
  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
369
+ }, // Using tuple spread pattern
370
+ handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
267
371
  // Make it readonly for type inference
268
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
372
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
269
373
  options<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
270
374
  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: {
375
+ }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
376
+ options<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
377
+ options<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
273
378
  middleware: [...M];
274
- },
275
- // Using tuple spread pattern
276
- handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
379
+ }, // Using tuple spread pattern
380
+ handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
277
381
  // 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: {
382
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
383
+ options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
384
+ head<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
282
385
  querySchema: QuerySchema;
283
386
  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
387
+ }, // Using tuple spread pattern
388
+ handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
288
389
  // Make it readonly for type inference
289
- RouterLocals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<RouterMiddlewareProps, RouterLocals>;
390
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
290
391
  head<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
291
392
  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: {
393
+ }, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
394
+ head<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
395
+ head<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
294
396
  middleware: [...M];
295
- },
296
- // Using tuple spread pattern
297
- handler: SchemaRouteHandler<Path, unknown, unknown, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
397
+ }, // Using tuple spread pattern
398
+ handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
298
399
  // 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: {
400
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
401
+ head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
402
+ all<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
303
403
  bodySchema: BodySchema;
304
404
  querySchema?: QuerySchema;
305
405
  middleware: [...M];
306
- }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
307
- // Make it readonly for type inference
406
+ }, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
308
407
  // 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: {
408
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
409
+ all<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
311
410
  bodySchema: BodySchema;
312
411
  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
412
+ }, // Using tuple spread pattern
413
+ handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
317
414
  // 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: {
415
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
416
+ all<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
320
417
  querySchema: QuerySchema;
321
418
  middleware: [...M];
322
- },
323
- // Using tuple spread pattern
324
- handler: SchemaRouteHandler<Path, unknown, QuerySchema, RouterMiddlewareProps & InferMiddlewareProps<readonly [...M]>,
419
+ }, // Using tuple spread pattern
420
+ handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
325
421
  // 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: {
422
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
423
+ 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>;
424
+ all<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
330
425
  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
426
+ }, // Using tuple spread pattern
427
+ handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
335
428
  // 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>;
429
+ Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
430
+ all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
338
431
  private registerRoute;
339
432
  private createBodyValidationMiddleware;
340
433
  private createQueryValidationMiddleware;
@@ -359,7 +452,7 @@ declare class TypedRouter<RouterMiddlewareProps extends Record<string, any> = {}
359
452
  * const app = express();
360
453
  * app.use('/api', router.getRouter());
361
454
  */
362
- declare function createTypedRouter<RouterMiddlewareProps extends Record<string, any> = {}, RouterLocals extends Record<string, any> = {}>(): TypedRouter<RouterMiddlewareProps, RouterLocals>;
455
+ declare function createTypedRouter<Req extends Record<string, any> = AdditionalReqProps, Locals extends Record<string, any> = AdditionalLocals>(): TypedRouter<Req, Locals>;
363
456
  /**
364
457
  * Configuration options for createTypedRouterWithConfig.
365
458
  *
@@ -387,7 +480,7 @@ interface RouterConfig {
387
480
  * }
388
481
  * });
389
482
  */
390
- declare function createTypedRouterWithConfig<RouterMiddlewareProps extends Record<string, any> = {}, RouterLocals extends Record<string, any> = {}>(config?: RouterConfig): TypedRouter<RouterMiddlewareProps, RouterLocals>;
483
+ declare function createTypedRouterWithConfig<Req extends Record<string, any> = AdditionalReqProps, Locals extends Record<string, any> = AdditionalLocals>(config?: RouterConfig): TypedRouter<Req, Locals>;
391
484
  /**
392
485
  * Create a new typed router with pre-configured middleware.
393
486
  *
@@ -402,5 +495,43 @@ declare function createTypedRouterWithConfig<RouterMiddlewareProps extends Recor
402
495
  * const router = createTypedRouterWithMiddleware(authMiddleware, loggingMiddleware);
403
496
  */
404
497
  declare function createTypedRouterWithMiddleware<T extends Record<string, any>>(...middleware: TypedMiddleware<any, any>[]): TypedRouter<T>;
498
+ /**
499
+ * An entry for createDocs(). Either a bare TypedRouter (no prefix prepended)
500
+ * or an object with an explicit prefix matching the mount point in app.use().
501
+ *
502
+ * @example
503
+ * // Routes defined as /users/:id — mount prefix prepends /api
504
+ * { prefix: '/api', router: usersRouter }
505
+ *
506
+ * // Routes already include the full path — no prefix needed
507
+ * authRouter
508
+ */
509
+ type RouterDocEntry = TypedRouter<any, any> | {
510
+ prefix: string;
511
+ router: TypedRouter<any, any>;
512
+ };
513
+ /**
514
+ * Create a unified OpenAPI docs endpoint that merges routes from multiple
515
+ * TypedRouter instances. Use this when routes are split across files.
516
+ *
517
+ * @example
518
+ * // users.router.ts — routes like /users, /users/:id
519
+ * export const usersRouter = createTypedRouter();
520
+ *
521
+ * // auth.router.ts — routes like /login, /logout
522
+ * export const authRouter = createTypedRouter();
523
+ *
524
+ * // app.ts
525
+ * app.use('/api', usersRouter.getRouter());
526
+ * app.use('/api', authRouter.getRouter());
527
+ * app.use('/docs', createDocs(
528
+ * [
529
+ * { prefix: '/api', router: usersRouter },
530
+ * { prefix: '/api', router: authRouter },
531
+ * ],
532
+ * { title: 'My API', version: '1.0.0' }
533
+ * ));
534
+ */
535
+ declare function createDocs(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): express.Router & express.RequestHandler;
405
536
  //#endregion
406
- export { AnyStandardSchema, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteOptions, RouterConfig, SafeParseResult, SchemaRequest, SchemaRouteHandler, TypedMiddleware, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, isSchemaError, parseSchema, safeParseSchema };
537
+ 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 };
@@ -0,0 +1,12 @@
1
+ import e from"express";import{SchemaError as t}from"@standard-schema/utils";function n(e,n){let r=e;if(r&&r[`~standard`]&&typeof r[`~standard`].validate==`function`){let e=r[`~standard`].validate(n);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new t(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function r(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function i(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}const a=Function(`m`,`return import(m)`);let o,s;async function c(e){try{o??=await a(`module`),s??=await a(`url`);let t=globalThis.process?.cwd?.()??``,n=o.createRequire(s.pathToFileURL(t+`/`).href).resolve(e);return a(s.pathToFileURL(n).href)}catch{return a(e)}}const l=new WeakMap;function u(e){return e.replace(/:([^/?*.()]+)\??/g,`{$1}`)}function d(e){return[...e.matchAll(/:([^/?*.()]+)\??/g)].map(e=>e[1])}function f(e){let t=e.split(`/`).filter(Boolean)[0];return t&&!t.startsWith(`:`)?t:`default`}function p(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 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.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}const _=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function v(e,t,n){return`<!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>${g(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="${g(t)}"><\/script>
10
+ <script src="${g(n)}"><\/script>
11
+ </body>
12
+ </html>`}async function y(e,t){let n=Object.create(null);for(let t of e){if(t.method===`all`||t.hidden)continue;let e=u(t.path);n[e]||(n[e]={});let r=d(t.path).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await m(t.querySchema),n=e.properties??{},i=e.required??[];for(let[e,t]of Object.entries(n))r.push({name:e,in:`query`,required:i.includes(e),schema:t})}let i={summary:t.summary??p(t.method,t.path),tags:t.tags??[f(t.path)],parameters:r};t.description&&(i.description=t.description),t.deprecated&&(i.deprecated=!0),t.bodySchema&&(i.requestBody={required:!0,content:{"application/json":{schema:await m(t.bodySchema)}}});let a={};if(t.responseSamples.size>0)for(let[e,n]of t.responseSamples)a[String(e)]={description:e<400?`Success`:`Error`,content:{"application/json":{example:n}}};else a[200]={description:`Success`};i.responses=a,n[e][t.method]=i}return{openapi:`3.1.0`,info:{title:t.title??`API`,version:t.version??`1.0.0`,...t.description?{description:t.description}:{}},...t.servers?{servers:t.servers}:{},paths:n}}const b=new WeakMap;var x=class t{router;routes=[];mountedRouters=[];constructor(){this.router=e.Router(),b.set(this.router,this)}useMiddleware(e){return this.router.use(e),this}getRouter(){return this.router}use(e,...n){let r=typeof e==`string`,i=r?e:``,a=(r?n:[e,...n]).map(e=>{if(e instanceof t)return this.mountedRouters.push({prefix:i,router:e}),e.getRouter();let n=b.get(e);return n&&this.mountedRouters.push({prefix:i,router:n}),e});return r?this.router.use(e,...a):this.router.use(...a),this}mount(e,t){if(typeof e==`string`){let n=t;this.router.use(e,n.getRouter()),this.mountedRouters.push({prefix:e,router:n})}else this.router.use(e.getRouter()),this.mountedRouters.push({prefix:``,router:e});return this}getRouteMetadata(){let e=this.mountedRouters.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,path:e+t.path})));return[...this.routes,...e]}docs(t={}){let n=e.Router();return n.get(`/openapi.json`,async(e,n)=>{try{let e=await y(this.getRouteMetadata(),t);t.specOutputPath&&await(await a(`fs/promises`)).writeFile(t.specOutputPath,JSON.stringify(e,null,2),`utf8`).catch(()=>{}),n.json(e)}catch(e){n.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),n.get(`/`,(e,n)=>{let r=`${e.baseUrl}/openapi.json`,i=t.cdnUrl??_;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(v(t.title??`API`,r,i))}),n}get(e,t,n){return this.registerRoute(`get`,e,t,n)}post(e,t,n){return this.registerRoute(`post`,e,t,n)}put(e,t,n){return this.registerRoute(`put`,e,t,n)}patch(e,t,n){return this.registerRoute(`patch`,e,t,n)}delete(e,t,n){return this.registerRoute(`delete`,e,t,n)}options(e,t,n){return this.registerRoute(`options`,e,t,n)}head(e,t,n){return this.registerRoute(`head`,e,t,n)}all(e,t,n){return this.registerRoute(`all`,e,t,n)}registerRoute(e,t,n,r){let i=[],a={method:e,path:t,responseSamples:new Map};if(this.routes.push(a),typeof n==`object`){let e=n;a.bodySchema=e.bodySchema,a.querySchema=e.querySchema,a.tags=e.tags,a.description=e.description,a.summary=e.summary,a.deprecated=e.deprecated,a.responseSchema=e.responseSchema,a.hidden=e.hidden,e.middleware&&i.push(...e.middleware),e.bodySchema&&i.push(this.createBodyValidationMiddleware(e.bodySchema)),e.querySchema&&i.push(this.createQueryValidationMiddleware(e.querySchema)),i.push(r)}else i.push(n);return this.router[e](t,(e,t,n)=>{if(a.hidden||a.responseSamples.size>=10){n();return}let r=t.json;t.json=e=>(a.responseSamples.has(t.statusCode)||a.responseSamples.set(t.statusCode,e),r.call(t,e)),n()},...i),this}createBodyValidationMiddleware(e){return async(t,n,a)=>{try{let i=r(e,t.body),o=i&&typeof i.then==`function`?await i:i;if(o&&`issues`in o&&o.issues){n.status(400).json({error:`Validation failed`,details:o.errors||o.issues});return}t.body=o&&`value`in o?o.value:o,a()}catch(e){i(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):a(e)}}}createQueryValidationMiddleware(e){return async(t,n,a)=>{try{let i=r(e,t.query),o=i&&typeof i.then==`function`?await i:i;if(o&&`issues`in o&&o.issues){n.status(400).json({error:`Validation failed`,details:o.errors||o.issues});return}let s=o&&`value`in o?o.value:o;Object.defineProperty(t,"query",{value:s,writable:!1,enumerable:!0,configurable:!0}),a()}catch(e){i(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):a(e)}}}};function S(){return new x}function C(e){let t=new x;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function w(...e){let t=new x;for(let n of e)t=t.useMiddleware(n);return t}function T(t,n={}){let r=(Array.isArray(t)?t:[t]).map(e=>`prefix`in e?e:{prefix:``,router:e}),i=e.Router();return i.get(`/openapi.json`,async(e,t)=>{try{let e=await y(r.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,path:e+t.path}))),n);t.json(e)}catch(e){t.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),i.get(`/`,(e,t)=>{let r=`${e.baseUrl}/openapi.json`,i=n.cdnUrl??_;t.setHeader(`Content-Type`,`text/html; charset=utf-8`),t.send(v(n.title??`API`,r,i))}),i}export{x as TypedRouter,T as createDocs,S as createTypedRouter,C as createTypedRouterWithConfig,w as createTypedRouterWithMiddleware,i as isSchemaError,n as parseSchema,r as safeParseSchema};