@minisylar/express-typed-router 1.9.8 → 1.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -127,6 +127,182 @@ router.get("/static/*", handler); // { "0": string }
127
127
 
128
128
  ---
129
129
 
130
+ ## Validation failure hooks
131
+
132
+ `bodySchema`/`querySchema`/`paramsSchema` already reject a bad request with a 400. Hooks let you react to that failure, and for three of them, change what happens next. There are two kinds:
133
+
134
+ - **`onValidationFailure`** (route or global) — a side effect only: logging, metrics, alerts. It never changes the response.
135
+ - **`onBodyValidationFailure`, `onQueryValidationFailure`, `onParamsValidationFailure`** — one per schema. These *can* change the response: keep the default 400, send something else, or let the request through anyway. Covered below.
136
+
137
+ ### The `info` object
138
+
139
+ Every hook is called with one object:
140
+
141
+ | field | type | what it is |
142
+ | --- | --- | --- |
143
+ | `source` | `"body" \| "query" \| "params"` | which schema failed — only on `onValidationFailure`; the schema-specific hooks already know from their own name |
144
+ | `error` | `string` | short error message |
145
+ | `details` | that schema's own issue shape once narrowed | the validation issues themselves. On the route's `onValidationFailure`, narrow on `source` first (it's a discriminated union). On the global `onValidationFailure`, always `any[]`, it isn't tied to one route's schemas. |
146
+ | `method` | `string` | the route's HTTP method, e.g. `"post"` |
147
+ | `path` | `string` | the route's path, readable even for a `RegExp` route |
148
+ | `req` | Express `Request` | the request; also sees this route's `middleware`-added properties (route-level hooks) or the router's `useMiddleware()`-added properties (global), see below |
149
+ | `res` | Express `Response` | the response — schema-specific hooks only, that's how they change it |
150
+ | `next` | Express `NextFunction` | continues the request — schema-specific hooks only |
151
+
152
+ ### Just reacting to a failure
153
+
154
+ ```ts
155
+ router.post(
156
+ "/users",
157
+ {
158
+ bodySchema: UserSchema,
159
+ querySchema: QuerySchema,
160
+ hooks: {
161
+ onValidationFailure: (info) => {
162
+ if (info.source === "body") {
163
+ logger.warn({ issues: info.details }, "user body validation failed"); // UserSchema's own issue shape
164
+ } else if (info.source === "query") {
165
+ logger.warn({ issues: info.details }, "user query validation failed"); // QuerySchema's own issue shape
166
+ }
167
+ },
168
+ },
169
+ },
170
+ handler,
171
+ );
172
+
173
+ // Or globally, for every route on the router — details is always any[] here,
174
+ // a route's own onValidationFailure above is the one that narrows per schema.
175
+ createTypedRouter().onValidationFailure(({ source, method, path }) => metrics.increment(source));
176
+ createTypedRouterWithConfig({ hooks: { onValidationFailure: (info) => metrics.increment(info.source) } });
177
+ ```
178
+
179
+ Narrowing on `info.source` is what gives you the precise `details` type per branch; unnarrowed, it's whatever's common to all three. The 400 is sent regardless of what this hook does. It can be `async`; a throw or rejection is swallowed, the response never waits on it. `router.onValidationFailure()` can be called any time, read fresh per request, and picks up whatever `.useMiddleware()` already added by that point in the chain, no cast needed:
180
+
181
+ ```ts
182
+ const router = createTypedRouter()
183
+ .useMiddleware(requireAuth) // adds req.userId
184
+ .onValidationFailure((info) => {
185
+ metrics.increment(`validation_failure.${info.source}`, { userId: info.req.userId });
186
+ });
187
+ ```
188
+
189
+ `createTypedRouterWithConfig<Req>({ hooks: { onValidationFailure } })` works the same way, `Req` just has to be spelled out explicitly since there's no `.useMiddleware()` call for it to infer from:
190
+
191
+ ```ts
192
+ const router = createTypedRouterWithConfig<{ userId: string }>({
193
+ hooks: { onValidationFailure: (info) => metrics.increment(info.source, { userId: info.req.userId }) },
194
+ });
195
+ ```
196
+
197
+ The global hook doesn't inherit into a `.mount()`ed sub-router, pass the same function to each router if you want it everywhere.
198
+
199
+ The route's own `onValidationFailure` gets this too, same as its schema-specific siblings, from that route's `middleware` option:
200
+
201
+ ```ts
202
+ router.post(
203
+ "/orders",
204
+ {
205
+ bodySchema: OrderSchema,
206
+ middleware: [requireAuth], // adds req.userId
207
+ hooks: {
208
+ onValidationFailure: (info) => logger.warn({ userId: info.req.userId, source: info.source }, "validation failed"),
209
+ },
210
+ },
211
+ handler,
212
+ );
213
+ ```
214
+
215
+ ### Changing the response
216
+
217
+ `onBodyValidationFailure`, `onQueryValidationFailure`, `onParamsValidationFailure` are typed to that one schema (`details` is its own issue shape, not a blanket `any[]`), and the router waits for them before responding:
218
+
219
+ ```ts
220
+ import { defaultValidationHandler } from "@minisylar/express-typed-router";
221
+ // ^ the library's own default 400 response, exported so a hook can run a
222
+ // side effect and still fall back to it, instead of re-implementing it.
223
+
224
+ router.post(
225
+ "/orders",
226
+ {
227
+ bodySchema: OrderSchema,
228
+ hooks: {
229
+ onBodyValidationFailure: (info) => {
230
+ logger.warn({ issues: info.details }, "order validation failed");
231
+ return defaultValidationHandler(info); // side effect above, still the normal 400
232
+ },
233
+ },
234
+ },
235
+ handler,
236
+ );
237
+ ```
238
+
239
+ What a hook does decides the outcome:
240
+
241
+ | does this | result |
242
+ | --- | --- |
243
+ | nothing | default 400, same as if there were no hook |
244
+ | `defaultValidationHandler(info)` | default 400, after whatever the hook did first |
245
+ | `info.res.status(422).json(...)` | that response instead |
246
+ | `info.next()` | request continues unvalidated; `req.body` stays typed as `OrderSchema`'s output either way, you're trusting it yourself |
247
+
248
+ ### Order
249
+
250
+ A request only ever fails one schema, whichever is checked first (body, then query, then params). So on the `/users` route above, a request with both an invalid body *and* an invalid query string still only fails on `body`, query is never even checked:
251
+
252
+ - `onBodyValidationFailure` fires (if set)
253
+ - `onValidationFailure` fires once, with `source: "body"` — not twice, and `onQueryValidationFailure` does not fire
254
+ - the global `onValidationFailure` fires once, same `source: "body"`
255
+
256
+ Fix the body and resend, and *then* you'd see the query failure. Whichever hooks apply to that one failure run in this order: the schema-specific one, then the route's `onValidationFailure`, then the global one.
257
+
258
+ ### Reading middleware-added properties
259
+
260
+ A route's own `middleware` (see [Middleware typing](#middleware-typing) below) runs before validation, and what it adds to `req` is visible, typed, inside a schema-specific hook too, no cast needed, as long as the hook is written inline:
261
+
262
+ ```ts
263
+ router.post(
264
+ "/orders",
265
+ {
266
+ bodySchema: OrderSchema,
267
+ middleware: [requireAuth], // adds req.userId
268
+ hooks: {
269
+ onBodyValidationFailure: (info) => {
270
+ if (info.req.userId === "trusted-internal-service") return info.next();
271
+ info.res.status(422).json({ error: "Invalid order payload" });
272
+ },
273
+ },
274
+ },
275
+ handler,
276
+ );
277
+ ```
278
+
279
+ A hook pulled out into a standalone `const` can't know which route's `middleware` it'll be attached to, so it falls back to plain `req` there, see reuse below.
280
+
281
+ ### Reuse outside the route
282
+
283
+ `SchemaValidationFailureHook<S>` / `ValidationFailureHook` are exported, so a hook doesn't have to be written inline:
284
+
285
+ ```ts
286
+ const logUserBodyFailure: SchemaValidationFailureHook<typeof UserSchema> = ({ details }) => {
287
+ logger.warn({ issues: details }, "user body validation failed");
288
+ };
289
+
290
+ router.post("/users", { bodySchema: UserSchema, hooks: { onBodyValidationFailure: logUserBodyFailure } }, handler);
291
+ router.put("/users/:id", { bodySchema: UserSchema, hooks: { onBodyValidationFailure: logUserBodyFailure } }, handler);
292
+ ```
293
+
294
+ ### At a glance
295
+
296
+ | hook | fires when | can change the response | `details` |
297
+ | --- | --- | --- | --- |
298
+ | `onBodyValidationFailure` | `bodySchema` rejects | yes | `bodySchema`'s own issue shape |
299
+ | `onQueryValidationFailure` | `querySchema` rejects | yes | `querySchema`'s own issue shape |
300
+ | `onParamsValidationFailure` | `paramsSchema` rejects | yes | `paramsSchema`'s own issue shape |
301
+ | `onValidationFailure` (route option) | any of the three, on this route | no | matching schema's issue shape, once narrowed on `source` |
302
+ | `onValidationFailure` (`router.onValidationFailure()` / `createTypedRouterWithConfig`) | any of the three, on any route on this router | no | `any[]` |
303
+
304
+ ---
305
+
130
306
  ## Middleware typing
131
307
 
132
308
  Declare what a middleware adds to `req`, and that type flows into every handler that uses it.
@@ -661,14 +837,19 @@ Use a schema that actually parses the text:
661
837
  | ---------------------------------------- | ------------------------------------------------ |
662
838
  | `createTypedRouter()` | Create a router |
663
839
  | `createTypedRouterWithMiddleware(...mw)` | Create a router pre-configured with middleware |
664
- | `createTypedRouterWithConfig(config)` | Create a router with custom error handling |
840
+ | `createTypedRouterWithConfig(config)` | Create a router with an error handler and/or global config |
665
841
  | `router.useMiddleware(mw)` | Add typed global middleware (returns new router) |
842
+ | `router.onValidationFailure(hook)` | Set the global validation failure hook (returns same router) |
666
843
  | `router.use(prefix, subRouter)` | Mount a sub-router |
667
844
  | `router.getRouter()` | Get the underlying Express router |
668
845
  | `router.docs(options)` | Get the docs + OpenAPI spec router |
669
846
  | `generateOpenApiSpec(routers, options)` | Build the spec object with no server involved |
670
847
  | `TypedMiddleware<T>` | Type helper for middleware that extends `req` |
671
848
  | `defineMiddleware(...mw)` | Keep a middleware array's tuple type when reused |
849
+ | `SchemaValidationFailureHook<S>` | Type a reusable `onBodyValidationFailure`/`onQueryValidationFailure`/`onParamsValidationFailure` outside the route |
850
+ | `RouteValidationFailureHook<Body, Query, Params>` | Type a reusable route-level `onValidationFailure` outside the route, `details` still narrows on `source` |
851
+ | `ValidationFailureHook` | Type a reusable global `onValidationFailure` outside the route |
852
+ | `defaultValidationHandler(info)` | The library's own default 400 response, callable from a schema-specific hook |
672
853
 
673
854
  ---
674
855
 
@@ -1,12 +1,12 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));let c=require("express");c=s(c,1);let l=require("@standard-schema/utils");function u(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`){let e=n[`~standard`].validate(t);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new l.SchemaError(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function d(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function f(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}function p(...e){return e}const m=Function(`m`,`return import(m)`);let h,g;async function _(e){h??=await m(`module`),g??=await m(`url`);let t=[],n=globalThis.process?.argv?.[1];n&&t.push(g.pathToFileURL(n).href);let r=globalThis.process?.cwd?.()??``;r&&t.push(g.pathToFileURL(r+`/`).href);for(let n of t)try{let t=h.createRequire(n).resolve(e);return await m(g.pathToFileURL(t).href)}catch{}return m(e)}const v=new WeakMap,y=/\(\?<[^>]+>/;function b(e){return typeof e==`string`&&y.test(e)?new RegExp(e):e}function x(e){if(typeof e.path==`string`)return e.path;if(e.pathExample)return e.pathExample;let t=0;return e.path.source.replace(/\\\//g,`/`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function S(e){let t=[],n=0,r=0,i=!1,a=!1;for(let o=0;o<e.length;o++){let s=e[o];if(a){a=!1;continue}if(s===`\\`){a=!0;continue}if(s===`[`){i=!0;continue}if(s===`]`){i=!1;continue}i||(s===`(`?r++:s===`)`?r=Math.max(0,r-1):s===`|`&&r===0&&(t.push(e.slice(n,o)),n=o+1))}return t.push(e.slice(n)),t.length>1?t:[e]}function C(e){if(!e.startsWith(`(`)||!e.endsWith(`)`))return e;let t=0,n=!1,r=!1;for(let i=0;i<e.length;i++){let a=e[i];if(r){r=!1;continue}if(a===`\\`){r=!0;continue}if(a===`[`)n=!0;else if(a===`]`)n=!1;else if(!n&&a===`(`)t++;else if(!n&&a===`)`&&(t--,t===0&&i!==e.length-1))return e}return e.slice(1,-1).replace(/^\?:/,``)}function w(e){let t=0;return C(e).replace(/^\^|\$$/g,``).replace(/\\\//g,`/`).replace(/\.\*|\.\+/g,`/:path`).replace(/\/?\?$/,``).replace(/\(\?<([A-Za-z0-9_]+)>[^()]*\)/g,`:$1`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function T(e){if(typeof e.path==`string`||e.pathExample)return[x(e)];let t=S(e.path.source);return t.length===1?[x(e)]:t.map(w)}function E(e){return e.replace(/\{([^{}]*)\}/g,`$1`).replace(/:([A-Za-z0-9_]+)(?:\([^)]*\))?[?+*]?/g,`{$1}`).replace(/\(\?<([A-Za-z0-9_]+)>[^)]*\)/g,`{$1}`).replace(/^\^|\$$/g,``).replace(/\/{2,}/g,`/`)}function D(e){let t=e.replace(/\{([^{}]*)\}/g,`$1`);return[...t.matchAll(/:([A-Za-z0-9_]+)/g),...t.matchAll(/\(\?<([A-Za-z0-9_]+)>/g)].map(e=>e[1])}function O(e){return e.startsWith(`:`)||e.startsWith(`*`)||e.includes(`(?<`)}function k(e){return e.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(Boolean).find(e=>!O(e))??`default`}function A(e,t){let n=t.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(e=>e&&!O(e)),r=n[n.length-1]??`resource`;return`${{get:`Get`,post:`Create`,put:`Update`,patch:`Patch`,delete:`Delete`,head:`Head`,options:`Options`}[e]??e} ${r}`}async function j(e){let t=v.get(e);if(t)return t;let n=await M(e);return v.set(e,n),n}async function M(e){if(typeof e.toJsonSchema==`function`)try{return e.toJsonSchema()}catch{}let t=e[`~standard`]?.vendor;if(t===`zod`){try{let t=await _(`zod`);if(typeof t.toJSONSchema==`function`)return t.toJSONSchema(e)}catch{}try{let t=await _(`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 _(`@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 _(`effect`),n=t.JSONSchema?.make??t.default?.JSONSchema?.make;if(typeof n==`function`)return n(e)}catch{}return{}}function N(e){return P(e,0,new WeakSet)}function P(e,t,n){if(e==null)return{type:`null`};if(t>=12)return{};if(e instanceof Date)return{type:`string`,format:`date-time`};if(typeof e==`bigint`)return{type:`integer`};if(typeof e==`object`&&typeof e.toJSON==`function`)return P(e.toJSON(),t,n);if(Array.isArray(e)){if(e.length===0||n.has(e))return{type:`array`,items:{}};n.add(e);let r=Math.min(e.length,20),i=P(e[0],t+1,n);for(let a=1;a<r;a++)i=L(i,P(e[a],t+1,n));return n.delete(e),{type:`array`,items:i}}switch(typeof e){case`string`:return{type:`string`};case`boolean`:return{type:`boolean`};case`number`:return Number.isFinite(e)?{type:Number.isInteger(e)?`integer`:`number`}:{type:`null`};case`object`:{if(n.has(e))return{type:`object`};n.add(e);let r={},i=[];for(let[a,o]of Object.entries(e))typeof o!=`function`&&o!==void 0&&(r[a]=P(o,t+1,n),i.push(a));n.delete(e);let a={type:`object`,properties:r};return i.length&&(a.required=i),a}default:return{}}}function F(e){return!e||e.type===void 0?new Set:new Set(Array.isArray(e.type)?e.type:[e.type])}function I(e){e.has(`integer`)&&e.has(`number`)&&e.delete(`integer`);let t=[...e];if(t.length!==0)return t.length===1?t[0]:t}function L(e,t){if(!e||Object.keys(e).length===0)return t??{};if(!t||Object.keys(t).length===0)return e??{};let n=new Set([...F(e),...F(t)]),r={},i=I(n);if(i!==void 0&&(r.type=i),n.has(`object`)&&(e.properties||t.properties)){let n=e.properties??{},i=t.properties??{},a={};for(let e of new Set([...Object.keys(n),...Object.keys(i)]))a[e]=L(n[e],i[e]);r.properties=a;let o=e.required??[],s=t.required??[],c=o.filter(e=>s.includes(e));c.length&&(r.required=c)}if(n.has(`array`)){let n=L(e.items,t.items);n&&Object.keys(n).length&&(r.items=n)}return r}function R(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}const z=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function B(e,t,n){return`<!doctype html>
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));let c=require("express");c=s(c,1);let l=require("@standard-schema/utils");function u(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`){let e=n[`~standard`].validate(t);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new l.SchemaError(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function d(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function f(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}function p(...e){return e}function m(e){e.res.status(400).json({error:`Validation failed`,details:e.details})}function h(e,t){if(e)try{e(t)?.catch?.(()=>{})}catch{}}const g=Function(`m`,`return import(m)`);let _,v;async function y(e){_??=await g(`module`),v??=await g(`url`);let t=[],n=globalThis.process?.argv?.[1];n&&t.push(v.pathToFileURL(n).href);let r=globalThis.process?.cwd?.()??``;r&&t.push(v.pathToFileURL(r+`/`).href);for(let n of t)try{let t=_.createRequire(n).resolve(e);return await g(v.pathToFileURL(t).href)}catch{}return g(e)}const b=new WeakMap,x=/\(\?<[^>]+>/;function S(e){return typeof e==`string`&&x.test(e)?new RegExp(e):e}function C(e){let t=Array.isArray(e.pathExample)?e.pathExample[0]:e.pathExample;if(t)return t;if(typeof e.path==`string`)return e.path;let n=0;return e.path.source.replace(/\\\//g,`/`).replace(/\((?!\?)[^()]*\)/g,()=>`:${n++}`)}function w(e){let t=[],n=0,r=0,i=!1,a=!1;for(let o=0;o<e.length;o++){let s=e[o];if(a){a=!1;continue}if(s===`\\`){a=!0;continue}if(s===`[`){i=!0;continue}if(s===`]`){i=!1;continue}i||(s===`(`?r++:s===`)`?r=Math.max(0,r-1):s===`|`&&r===0&&(t.push(e.slice(n,o)),n=o+1))}return t.push(e.slice(n)),t.length>1?t:[e]}function T(e){if(!e.startsWith(`(`)||!e.endsWith(`)`))return e;let t=0,n=!1,r=!1;for(let i=0;i<e.length;i++){let a=e[i];if(r){r=!1;continue}if(a===`\\`){r=!0;continue}if(a===`[`)n=!0;else if(a===`]`)n=!1;else if(!n&&a===`(`)t++;else if(!n&&a===`)`&&(t--,t===0&&i!==e.length-1))return e}return e.slice(1,-1).replace(/^\?:/,``)}function E(e){let t=0;return T(e).replace(/^\^|\$$/g,``).replace(/\\\//g,`/`).replace(/\.\*|\.\+/g,`/:path`).replace(/\/?\?$/,``).replace(/\(\?<([A-Za-z0-9_]+)>[^()]*\)/g,`:$1`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function D(e){if(Array.isArray(e.pathExample)&&e.pathExample.length>0)return e.pathExample;if(typeof e.path==`string`||e.pathExample)return[C(e)];let t=w(e.path.source);return t.length===1?[C(e)]:t.map(E)}function O(e){let t=e.replace(/\{([^{}]*)\}/g,`$1`).replace(/:([A-Za-z0-9_]+)(?:\([^)]*\))?[?+*]?/g,`{$1}`).replace(/\(\?<([A-Za-z0-9_]+)>[^)]*\)/g,`{$1}`).replace(/\*([A-Za-z0-9_]+)/g,`{$1}`).replace(/^\^|\$$/g,``),n=0;return t.replace(/\*/g,()=>`{${n++}}`).replace(/\/{2,}/g,`/`)}function k(e){let t=e.replace(/\{([^{}]*)\}/g,`$1`).replace(/^\^|\$$/g,``),n=[...t.matchAll(/:([A-Za-z0-9_]+)/g),...t.matchAll(/\(\?<([A-Za-z0-9_]+)>/g),...t.matchAll(/\*([A-Za-z0-9_]+)/g)].map(e=>e[1]),r=(t.replace(/\*[A-Za-z0-9_]+/g,``).match(/\*/g)??[]).length;for(let e=0;e<r;e++)n.push(String(e));return n}function A(e){return e.startsWith(`:`)||e.includes(`*`)||e.includes(`(?<`)}function j(e){return e.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(Boolean).find(e=>!A(e))??`default`}function M(e,t){let n=t.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(e=>e&&!A(e)),r=n[n.length-1]??`resource`;return`${{get:`Get`,post:`Create`,put:`Update`,patch:`Patch`,delete:`Delete`,head:`Head`,options:`Options`}[e]??e} ${r}`}async function N(e){let t=b.get(e);if(t)return t;let n=await P(e);return b.set(e,n),n}async function P(e){if(typeof e.toJsonSchema==`function`)try{return e.toJsonSchema()}catch{}let t=e[`~standard`]?.vendor;if(t===`zod`){try{let t=await y(`zod`);if(typeof t.toJSONSchema==`function`)return t.toJSONSchema(e)}catch{}try{let t=await y(`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 y(`@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 y(`effect`),n=t.JSONSchema?.make??t.default?.JSONSchema?.make;if(typeof n==`function`)return n(e)}catch{}return{}}function F(e){return I(e,0,new WeakSet)}function I(e,t,n){if(e==null)return{type:`null`};if(t>=12)return{};if(e instanceof Date)return{type:`string`,format:`date-time`};if(typeof e==`bigint`)return{type:`integer`};if(typeof e==`object`&&typeof e.toJSON==`function`)return I(e.toJSON(),t,n);if(Array.isArray(e)){if(e.length===0||n.has(e))return{type:`array`,items:{}};n.add(e);let r=Math.min(e.length,20),i=I(e[0],t+1,n);for(let a=1;a<r;a++)i=z(i,I(e[a],t+1,n));return n.delete(e),{type:`array`,items:i}}switch(typeof e){case`string`:return{type:`string`};case`boolean`:return{type:`boolean`};case`number`:return Number.isFinite(e)?{type:Number.isInteger(e)?`integer`:`number`}:{type:`null`};case`object`:{if(n.has(e))return{type:`object`};n.add(e);let r={},i=[];for(let[a,o]of Object.entries(e))typeof o!=`function`&&o!==void 0&&(r[a]=I(o,t+1,n),i.push(a));n.delete(e);let a={type:`object`,properties:r};return i.length&&(a.required=i),a}default:return{}}}function L(e){return!e||e.type===void 0?new Set:new Set(Array.isArray(e.type)?e.type:[e.type])}function R(e){e.has(`integer`)&&e.has(`number`)&&e.delete(`integer`);let t=[...e];if(t.length!==0)return t.length===1?t[0]:t}function z(e,t){if(!e||Object.keys(e).length===0)return t??{};if(!t||Object.keys(t).length===0)return e??{};let n=new Set([...L(e),...L(t)]),r={},i=R(n);if(i!==void 0&&(r.type=i),n.has(`object`)&&(e.properties||t.properties)){let n=e.properties??{},i=t.properties??{},a={};for(let e of new Set([...Object.keys(n),...Object.keys(i)]))a[e]=z(n[e],i[e]);r.properties=a;let o=e.required??[],s=t.required??[],c=o.filter(e=>s.includes(e));c.length&&(r.required=c)}if(n.has(`array`)){let n=z(e.items,t.items);n&&Object.keys(n).length&&(r.items=n)}return r}function B(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}const V=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function H(e,t,n){return`<!doctype html>
2
2
  <html>
3
3
  <head>
4
- <title>${R(e)}</title>
4
+ <title>${B(e)}</title>
5
5
  <meta charset="utf-8" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1" />
7
7
  </head>
8
8
  <body>
9
- <script id="api-reference" data-url="${R(t)}"><\/script>
10
- <script src="${R(n)}"><\/script>
9
+ <script id="api-reference" data-url="${B(t)}"><\/script>
10
+ <script src="${B(n)}"><\/script>
11
11
  </body>
12
- </html>`}async function V(e,t){let n=Object.create(null);for(let t of e)if(!(t.method===`all`||t.hidden))for(let e of T(t)){let r=E(e);n[r]||(n[r]={});let i;if(t.paramsSchema){let e=await j(t.paramsSchema),n=e.properties??{},r=e.required??[];i=Object.entries(n).map(([e,t])=>({name:e,in:`path`,required:r.includes(e),schema:t}))}else i=D(e).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await j(t.querySchema),n=e.properties??{},r=e.required??[];for(let[e,t]of Object.entries(n))i.push({name:e,in:`query`,required:r.includes(e),schema:t})}let a={summary:t.summary??A(t.method,e),tags:t.tags??[k(e)],parameters:i};t.description&&(a.description=t.description),t.deprecated&&(a.deprecated=!0),t.bodySchema&&(a.requestBody={required:!0,content:{"application/json":{schema:await j(t.bodySchema)}}});let o={};for(let[e,n]of t.responseSamples){let t={schema:n.schema};n.example!==void 0&&(t.example=n.example),o[String(e)]={description:e<400?`Success`:`Error`,content:{"application/json":t}}}if(t.responseSchema){let e=await j(t.responseSchema);if(e&&Object.keys(e).length){let n=`200`;for(let e of t.responseSamples.keys())if(e>=200&&e<300){n=String(e);break}o[n]={description:`Success`,content:{"application/json":{schema:e}}}}}Object.keys(o).length===0&&(o[200]={description:`Success`}),a.responses=o,n[r][t.method]=a}return{openapi:`3.1.0`,info:{title:t.title??`API`,version:t.version??`1.0.0`,...t.description?{description:t.description}:{}},...t.servers?{servers:t.servers}:{},paths:n}}const H=new WeakMap;var U=class e{router;routes=[];mountedRouters=[];sampleMode=`off`;scheduleSpecWrite;constructor(){this.router=c.default.Router(),H.set(this.router,this)}useMiddleware(e){return this.router.use(e),this}getRouter(){return this.router}use(t,...n){let r=typeof t==`string`,i=r?t:``,a=(r?n:[t,...n]).map(t=>{if(t instanceof e)return this.trackMounted(i,t),t.getRouter();let n=H.get(t);return n&&this.trackMounted(i,n),t});return r?this.router.use(t,...a):this.router.use(...a),this}mount(e,t){if(typeof e==`string`){let n=t;this.router.use(e,n.getRouter()),this.trackMounted(e,n)}else this.router.use(e.getRouter()),this.trackMounted(``,e);return this}getRouteMetadata(){let e=this.mountedRouters.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:e+x(t)}})));return[...this.routes,...e]}enableSampling(e=`redacted`,t,n=new Set){if(!n.has(this)){n.add(this),this.sampleMode=e,this.scheduleSpecWrite=t;for(let{router:r}of this.mountedRouters)r.enableSampling(e,t,n)}}trackMounted(e,t){this.mountedRouters.some(n=>n.router===t&&n.prefix===e)||(this.mountedRouters.push({prefix:e,router:t}),this.sampleMode!==`off`&&t.enableSampling(this.sampleMode,this.scheduleSpecWrite))}hydrateResponses(e,t=``,n=new Set){if(n.has(this))return;n.add(this);let r=e?.paths??{};for(let e of this.routes)for(let n of T(e)){let i=r[E(t+n)]?.[e.method]?.responses;if(i)for(let t of Object.keys(i)){let n=Number(t);if(Number.isNaN(n)||e.responseSamples.has(n))continue;let r=i[t]?.content?.[`application/json`];r?.schema&&e.responseSamples.set(n,r.example===void 0?{schema:r.schema}:{schema:r.schema,example:r.example})}}for(let{prefix:r,router:i}of this.mountedRouters)i.hydrateResponses(e,t+r,n)}docs(e={}){let t=c.default.Router(),n;if(e.specOutputPath){let t=e.specOutputPath,r=!1,i=!1,a=async()=>{if(r){i=!0;return}r=!0;try{let n=await V(this.getRouteMetadata(),e),r=await m(`fs/promises`),i=t.replace(/[/\\][^/\\]*$/,``);i&&i!==t&&await r.mkdir(i,{recursive:!0}).catch(()=>{});let a=globalThis.process?.pid??`0`,o=`${t}.${a}.tmp`;await r.writeFile(o,JSON.stringify(n,null,2),`utf8`),await r.rename(o,t)}catch{}finally{r=!1,i&&(i=!1,a())}},o;n=()=>{o&&clearTimeout(o),o=setTimeout(a,300),o.unref?.()},setImmediate(async()=>{try{let e=await(await m(`fs/promises`)).readFile(t,`utf8`).catch(()=>null);if(e)try{this.hydrateResponses(JSON.parse(e))}catch{}}catch{}await a()})}return e.sampleResponses!==!1&&this.enableSampling(e.sampleResponses===`live`?`live`:`redacted`,n),t.get(`/openapi.json`,async(t,n)=>{try{let t=await V(this.getRouteMetadata(),e);n.json(t)}catch(e){n.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),t.get(`/`,(t,n)=>{let r=`${t.baseUrl}/openapi.json`,i=e.cdnUrl??z;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(B(e.title??`API`,r,i))}),t}get(e,t,n){return this.registerRoute(`get`,e,t,n)}post(e,t,n){return this.registerRoute(`post`,e,t,n)}put(e,t,n){return this.registerRoute(`put`,e,t,n)}patch(e,t,n){return this.registerRoute(`patch`,e,t,n)}delete(e,t,n){return this.registerRoute(`delete`,e,t,n)}options(e,t,n){return this.registerRoute(`options`,e,t,n)}head(e,t,n){return this.registerRoute(`head`,e,t,n)}all(e,t,n){return this.registerRoute(`all`,e,t,n)}registerRoute(e,t,n,r){let i=[],a={method:e,path:t,responseSamples:new Map};if(this.routes.push(a),typeof n==`object`){let e=n;a.bodySchema=e.bodySchema,a.querySchema=e.querySchema,a.paramsSchema=e.paramsSchema,a.tags=e.tags,a.description=e.description,a.summary=e.summary,a.deprecated=e.deprecated,a.responseSchema=e.responseSchema,a.hidden=e.hidden,a.pathExample=e.pathExample,e.middleware&&i.push(...e.middleware),e.bodySchema&&i.push(this.createBodyValidationMiddleware(e.bodySchema)),e.querySchema&&i.push(this.createQueryValidationMiddleware(e.querySchema)),e.paramsSchema&&i.push(this.createParamsValidationMiddleware(e.paramsSchema)),i.push(r)}else i.push(n);let o=0,s=(e,t)=>{let n=e.statusCode,r=N(t),i=a.responseSamples.get(n),o=i?L(i.schema,r):r,s=this.sampleMode===`live`?i?.example??t:void 0,c=!i||JSON.stringify(i.schema)!==JSON.stringify(o);a.responseSamples.set(n,{schema:o,example:s}),c&&this.scheduleSpecWrite?.()};return this.router[e](b(t),(e,t,n)=>{if(this.sampleMode===`off`||a.hidden||o>=50||a.responseSamples.size>=10){n();return}o++;let r=t.json;t.json=function(e){return s(t,e),r.call(this,e)};let i=t.send;t.send=function(e){return typeof e==`object`&&e&&!Buffer.isBuffer(e)&&s(t,e),i.call(this,e)},n()},...i),this}createBodyValidationMiddleware(e){return async(t,n,r)=>{try{let i=d(e,t.body),a=i&&typeof i.then==`function`?await i:i;if(a&&`issues`in a&&a.issues){n.status(400).json({error:`Validation failed`,details:a.errors||a.issues});return}t.body=a&&`value`in a?a.value:a,r()}catch(e){f(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):r(e)}}}createParamsValidationMiddleware(e){return async(t,n,r)=>{try{let i=d(e,t.params),a=i&&typeof i.then==`function`?await i:i;if(a&&`issues`in a&&a.issues){n.status(400).json({error:`Validation failed`,details:a.errors||a.issues});return}t.params=a&&`value`in a?a.value:a,r()}catch(e){f(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):r(e)}}}createQueryValidationMiddleware(e){return async(t,n,r)=>{try{let i=d(e,t.query),a=i&&typeof i.then==`function`?await i:i;if(a&&`issues`in a&&a.issues){n.status(400).json({error:`Validation failed`,details:a.errors||a.issues});return}let o=a&&`value`in a?a.value:a;Object.defineProperty(t,"query",{value:o,writable:!1,enumerable:!0,configurable:!0}),r()}catch(e){f(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):r(e)}}}};function W(){return new U}function G(e){let t=new U;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function K(...e){let t=new U;for(let n of e)t=t.useMiddleware(n);return t}function q(e){return(Array.isArray(e)?e:[e]).map(e=>`prefix`in e?e:{prefix:``,router:e}).flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:e+x(t)}})))}async function J(e,t={}){return V(q(e),t)}function Y(e,t={}){if(t.sampleResponses!==!1){let n=t.sampleResponses===`live`?`live`:`redacted`,r=Array.isArray(e)?e:[e];for(let e of r)(`prefix`in e?e.router:e).enableSampling(n)}let n=c.default.Router();return n.get(`/openapi.json`,async(n,r)=>{try{let n=await J(e,t);r.json(n)}catch(e){r.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),n.get(`/`,(e,n)=>{let r=`${e.baseUrl}/openapi.json`,i=t.cdnUrl??z;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(B(t.title??`API`,r,i))}),n}exports.TypedRouter=U,exports.createDocs=Y,exports.createTypedRouter=W,exports.createTypedRouterWithConfig=G,exports.createTypedRouterWithMiddleware=K,exports.defineMiddleware=p,exports.generateOpenApiSpec=J,exports.inferJsonSchema=N,exports.isSchemaError=f,exports.parseSchema=u,exports.safeParseSchema=d;
12
+ </html>`}async function U(e,t){let n=Object.create(null);for(let t of e)if(!(t.method===`all`||t.hidden))for(let e of D(t)){let r=O(e);n[r]||(n[r]={});let i;if(t.paramsSchema){let e=await N(t.paramsSchema),n=e.properties??{},r=e.required??[];i=Object.entries(n).map(([e,t])=>({name:e,in:`path`,required:r.includes(e),schema:t}))}else i=k(e).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await N(t.querySchema),n=e.properties??{},r=e.required??[];for(let[e,t]of Object.entries(n))i.push({name:e,in:`query`,required:r.includes(e),schema:t})}let a={summary:t.summary??M(t.method,e),tags:t.tags??[j(e)],parameters:i};t.description&&(a.description=t.description),t.deprecated&&(a.deprecated=!0),t.bodySchema&&(a.requestBody={required:!0,content:{"application/json":{schema:await N(t.bodySchema)}}});let o={};for(let[e,n]of t.responseSamples){let t={schema:n.schema};n.example!==void 0&&(t.example=n.example),o[String(e)]={description:e<400?`Success`:`Error`,content:{"application/json":t}}}if(t.responseSchema){let e=await N(t.responseSchema);if(e&&Object.keys(e).length){let n=`200`;for(let e of t.responseSamples.keys())if(e>=200&&e<300){n=String(e);break}o[n]={description:`Success`,content:{"application/json":{schema:e}}}}}Object.keys(o).length===0&&(o[200]={description:`Success`}),a.responses=o,n[r][t.method]=a}return{openapi:`3.1.0`,info:{title:t.title??`API`,version:t.version??`1.0.0`,...t.description?{description:t.description}:{}},...t.servers?{servers:t.servers}:{},paths:n}}const W=new WeakMap;var G=class e{router;routes=[];mountedRouters=[];sampleMode=`off`;scheduleSpecWrite;globalValidationFailureHook;constructor(){this.router=c.default.Router(),W.set(this.router,this)}onValidationFailure(e){return this.globalValidationFailureHook=e,this}useMiddleware(e){return this.router.use(e),this}getRouter(){return this.router}use(t,...n){let r=typeof t==`string`,i=r?t:``,a=(r?n:[t,...n]).map(t=>{if(t instanceof e)return this.trackMounted(i,t),t.getRouter();let n=W.get(t);return n&&this.trackMounted(i,n),t});return r?this.router.use(t,...a):this.router.use(...a),this}mount(e,t){if(typeof e==`string`){let n=t;this.router.use(e,n.getRouter()),this.trackMounted(e,n)}else this.router.use(e.getRouter()),this.trackMounted(``,e);return this}getRouteMetadata(){let e=this.mountedRouters.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:D(t).map(t=>e+t)}})));return[...this.routes,...e]}enableSampling(e=`redacted`,t,n=new Set){if(!n.has(this)){n.add(this),this.sampleMode=e,this.scheduleSpecWrite=t;for(let{router:r}of this.mountedRouters)r.enableSampling(e,t,n)}}trackMounted(e,t){this.mountedRouters.some(n=>n.router===t&&n.prefix===e)||(this.mountedRouters.push({prefix:e,router:t}),this.sampleMode!==`off`&&t.enableSampling(this.sampleMode,this.scheduleSpecWrite))}hydrateResponses(e,t=``,n=new Set){if(n.has(this))return;n.add(this);let r=e?.paths??{};for(let e of this.routes)for(let n of D(e)){let i=r[O(t+n)]?.[e.method]?.responses;if(i)for(let t of Object.keys(i)){let n=Number(t);if(Number.isNaN(n)||e.responseSamples.has(n))continue;let r=i[t]?.content?.[`application/json`];r?.schema&&e.responseSamples.set(n,r.example===void 0?{schema:r.schema}:{schema:r.schema,example:r.example})}}for(let{prefix:r,router:i}of this.mountedRouters)i.hydrateResponses(e,t+r,n)}docs(e={}){let t=c.default.Router(),n;if(e.specOutputPath){let t=e.specOutputPath,r=!1,i=!1,a=async()=>{if(r){i=!0;return}r=!0;try{let n=await U(this.getRouteMetadata(),e),r=await g(`fs/promises`),i=t.replace(/[/\\][^/\\]*$/,``);i&&i!==t&&await r.mkdir(i,{recursive:!0}).catch(()=>{});let a=globalThis.process?.pid??`0`,o=`${t}.${a}.tmp`;await r.writeFile(o,JSON.stringify(n,null,2),`utf8`),await r.rename(o,t)}catch{}finally{r=!1,i&&(i=!1,a())}},o;n=()=>{o&&clearTimeout(o),o=setTimeout(a,300),o.unref?.()},setImmediate(async()=>{try{let e=await(await g(`fs/promises`)).readFile(t,`utf8`).catch(()=>null);if(e)try{this.hydrateResponses(JSON.parse(e))}catch{}}catch{}await a()})}return e.sampleResponses!==!1&&this.enableSampling(e.sampleResponses===`live`?`live`:`redacted`,n),t.get(`/openapi.json`,async(t,n)=>{try{let t=await U(this.getRouteMetadata(),e);n.json(t)}catch(e){n.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),t.get(`/`,(t,n)=>{let r=`${t.baseUrl}/openapi.json`,i=e.cdnUrl??V;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(H(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 o=n;a.bodySchema=o.bodySchema,a.querySchema=o.querySchema,a.paramsSchema=o.paramsSchema,a.tags=o.tags,a.description=o.description,a.summary=o.summary,a.deprecated=o.deprecated,a.responseSchema=o.responseSchema,a.hidden=o.hidden,a.pathExample=o.pathExample,o.middleware&&i.push(...o.middleware);let s=o.hooks?.onValidationFailure,c=o.hooks?.onBodyValidationFailure,l=o.hooks?.onQueryValidationFailure,u=o.hooks?.onParamsValidationFailure;o.bodySchema&&i.push(this.createBodyValidationMiddleware(o.bodySchema,e,t,o.pathExample,s,c)),o.querySchema&&i.push(this.createQueryValidationMiddleware(o.querySchema,e,t,o.pathExample,s,l)),o.paramsSchema&&i.push(this.createParamsValidationMiddleware(o.paramsSchema,e,t,o.pathExample,s,u)),i.push(r)}else i.push(n);let o=0,s=(e,t)=>{let n=e.statusCode,r=F(t),i=a.responseSamples.get(n),o=i?z(i.schema,r):r,s=this.sampleMode===`live`?i?.example??t:void 0,c=!i||JSON.stringify(i.schema)!==JSON.stringify(o);a.responseSamples.set(n,{schema:o,example:s}),c&&this.scheduleSpecWrite?.()};return this.router[e](S(t),(e,t,n)=>{if(this.sampleMode===`off`||a.hidden||o>=50||a.responseSamples.size>=10){n();return}o++;let r=t.json;t.json=function(e){return s(t,e),r.call(this,e)};let i=t.send;t.send=function(e){return typeof e==`object`&&e&&!Buffer.isBuffer(e)&&s(t,e),i.call(this,e)},n()},...i),this}notifyValidationFailure(e,t,n,r,i,a,o){if(!o&&!this.globalValidationFailureHook)return;let s={source:e,error:t,details:n,method:r,path:i,req:a};h(o,s),h(this.globalValidationFailureHook,s)}async runSchemaValidationHook(e,t,n,r,i,a,o,s){if(!e)return!1;let c=!1,l={error:t,details:n,method:r,path:i,req:a,res:o,next:((...e)=>{c=!0,s(...e)})};try{await e(l)}catch{}return c||o.headersSent}createBodyValidationMiddleware(e,t,n,r,i,a){return async(o,s,c)=>{try{let l=d(e,o.body),u=l&&typeof l.then==`function`?await l:l;if(u&&`issues`in u&&u.issues){let e=u.errors||u.issues,l=C({path:n,pathExample:r}),d=await this.runSchemaValidationHook(a,`Validation failed`,e,t,l,o,s,c);this.notifyValidationFailure(`body`,`Validation failed`,e,t,l,o,i),d||m({res:s,details:e});return}o.body=u&&`value`in u?u.value:u,c()}catch(e){if(f(e)){let l=e.errors||e.issues,u=C({path:n,pathExample:r}),d=await this.runSchemaValidationHook(a,`Validation failed`,l,t,u,o,s,c);this.notifyValidationFailure(`body`,`Validation failed`,l,t,u,o,i),d||m({res:s,details:l})}else c(e)}}}createParamsValidationMiddleware(e,t,n,r,i,a){return async(o,s,c)=>{try{let l=d(e,o.params),u=l&&typeof l.then==`function`?await l:l;if(u&&`issues`in u&&u.issues){let e=u.errors||u.issues,l=C({path:n,pathExample:r}),d=await this.runSchemaValidationHook(a,`Validation failed`,e,t,l,o,s,c);this.notifyValidationFailure(`params`,`Validation failed`,e,t,l,o,i),d||m({res:s,details:e});return}o.params=u&&`value`in u?u.value:u,c()}catch(e){if(f(e)){let l=e.errors||e.issues,u=C({path:n,pathExample:r}),d=await this.runSchemaValidationHook(a,`Validation failed`,l,t,u,o,s,c);this.notifyValidationFailure(`params`,`Validation failed`,l,t,u,o,i),d||m({res:s,details:l})}else c(e)}}}createQueryValidationMiddleware(e,t,n,r,i,a){return async(o,s,c)=>{try{let l=d(e,o.query),u=l&&typeof l.then==`function`?await l:l;if(u&&`issues`in u&&u.issues){let e=u.errors||u.issues,l=C({path:n,pathExample:r}),d=await this.runSchemaValidationHook(a,`Validation failed`,e,t,l,o,s,c);this.notifyValidationFailure(`query`,`Validation failed`,e,t,l,o,i),d||m({res:s,details:e});return}let f=u&&`value`in u?u.value:u;Object.defineProperty(o,"query",{value:f,writable:!1,enumerable:!0,configurable:!0}),c()}catch(e){if(f(e)){let l=e.errors||e.issues,u=C({path:n,pathExample:r}),d=await this.runSchemaValidationHook(a,`Validation failed`,l,t,u,o,s,c);this.notifyValidationFailure(`query`,`Validation failed`,l,t,u,o,i),d||m({res:s,details:l})}else c(e)}}}};function K(){return new G}function q(e){let t=new G;return e?.errorHandler&&t.getRouter().use(e.errorHandler),e?.hooks?.onValidationFailure&&t.onValidationFailure(e.hooks.onValidationFailure),t}function J(...e){let t=new G;for(let n of e)t=t.useMiddleware(n);return t}function Y(e){return(Array.isArray(e)?e:[e]).map(e=>`prefix`in e?e:{prefix:``,router:e}).flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:D(t).map(t=>e+t)}})))}async function X(e,t={}){return U(Y(e),t)}function Z(e,t={}){if(t.sampleResponses!==!1){let n=t.sampleResponses===`live`?`live`:`redacted`,r=Array.isArray(e)?e:[e];for(let e of r)(`prefix`in e?e.router:e).enableSampling(n)}let n=c.default.Router();return n.get(`/openapi.json`,async(n,r)=>{try{let n=await X(e,t);r.json(n)}catch(e){r.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),n.get(`/`,(e,n)=>{let r=`${e.baseUrl}/openapi.json`,i=t.cdnUrl??V;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(H(t.title??`API`,r,i))}),n}exports.TypedRouter=G,exports.createDocs=Z,exports.createTypedRouter=K,exports.createTypedRouterWithConfig=q,exports.createTypedRouterWithMiddleware=J,exports.defaultValidationHandler=m,exports.defineMiddleware=p,exports.generateOpenApiSpec=X,exports.inferJsonSchema=F,exports.isSchemaError=f,exports.parseSchema=u,exports.safeParseSchema=d;
@@ -143,6 +143,112 @@ type SchemaRouteHandler<Path extends string = string, BodySchema extends SchemaL
143
143
  * @property paramsSchema - Optional schema for validating route params.
144
144
  * @property middleware - Optional array of TypedMiddleware for this route.
145
145
  */
146
+ /** Which validated part of the request failed. */
147
+ type ValidationFailureSource = "body" | "query" | "params";
148
+ /**
149
+ * Passed to a validation failure hook when bodySchema/querySchema/paramsSchema
150
+ * rejects a request. `req` includes whatever `.useMiddleware()` added, when
151
+ * set via `router.onValidationFailure()` (that method's `Req` type parameter
152
+ * is the router's own, already widened by any `.useMiddleware()` calls
153
+ * before it in the chain). Not the case for `createTypedRouterWithConfig`'s
154
+ * `hooks.onValidationFailure`, called before any middleware exists.
155
+ */
156
+ interface ValidationFailureInfo<MiddlewareProps extends Record<string, any> = {}> {
157
+ source: ValidationFailureSource;
158
+ error: string;
159
+ details: any[];
160
+ method: HttpMethod;
161
+ path: string;
162
+ req: Request & MiddlewareProps;
163
+ }
164
+ /**
165
+ * Called when validation fails, in addition to the 400 response that's
166
+ * already sent, for side effects like metrics/logging/alerting, not for
167
+ * changing the response (see the schema-specific hooks in RouteHooks for
168
+ * that). May be async (e.g. to log to an external service); the response is sent
169
+ * without waiting for it either way. Both a synchronous throw and a rejected
170
+ * promise are caught and ignored, so a broken hook can't take down request
171
+ * handling or surface as an unhandled rejection.
172
+ */
173
+ type ValidationFailureHook<MiddlewareProps extends Record<string, any> = {}> = (info: ValidationFailureInfo<MiddlewareProps>) => void | Promise<void>;
174
+ type SchemaValidationDetails<S> = S extends AnyStandardSchema ? readonly StandardSchemaV1.Issue[] : any[];
175
+ /**
176
+ * Passed to a route's onValidationFailure. A discriminated union on `source`
177
+ * (unlike {@link ValidationFailureInfo}, which is only ever `any[]`): narrow
178
+ * on `source` and `details` is that schema's own issue shape.
179
+ */
180
+ type RouteValidationFailureInfo<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}> = {
181
+ source: "body";
182
+ error: string;
183
+ details: SchemaValidationDetails<BodySchema>;
184
+ method: HttpMethod;
185
+ path: string;
186
+ req: Request & MiddlewareProps;
187
+ } | {
188
+ source: "query";
189
+ error: string;
190
+ details: SchemaValidationDetails<QuerySchema>;
191
+ method: HttpMethod;
192
+ path: string;
193
+ req: Request & MiddlewareProps;
194
+ } | {
195
+ source: "params";
196
+ error: string;
197
+ details: SchemaValidationDetails<ParamsSchema>;
198
+ method: HttpMethod;
199
+ path: string;
200
+ req: Request & MiddlewareProps;
201
+ };
202
+ type RouteValidationFailureHook<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}> = (info: RouteValidationFailureInfo<BodySchema, QuerySchema, ParamsSchema, MiddlewareProps>) => void | Promise<void>;
203
+ /**
204
+ * Passed to a schema-specific validation failure hook. Do nothing and the
205
+ * default 400 still fires. Call `next()` to continue with the raw,
206
+ * unvalidated value, or respond via `res` yourself (e.g. a custom status).
207
+ * `req` includes whatever the route's own `middleware` option adds, same as
208
+ * the handler's `req`.
209
+ */
210
+ interface SchemaValidationFailureInfo<S extends SchemaLike | undefined, MiddlewareProps extends Record<string, any> = {}> {
211
+ error: string;
212
+ details: SchemaValidationDetails<S>;
213
+ method: HttpMethod;
214
+ path: string;
215
+ req: Request & MiddlewareProps;
216
+ res: Response;
217
+ next: NextFunction;
218
+ }
219
+ type SchemaValidationFailureHook<S extends SchemaLike | undefined, MiddlewareProps extends Record<string, any> = {}> = (info: SchemaValidationFailureInfo<S, MiddlewareProps>) => void | Promise<void>;
220
+ /** The default 400 response for a validation failure. Call it from a schema-specific hook to log first and still fall back to the normal response. */
221
+ declare function defaultValidationHandler(info: {
222
+ res: Response;
223
+ details: unknown;
224
+ }): void;
225
+ /**
226
+ * Router-wide validation failure hook. Schema-specific hooks aren't
227
+ * available here: a router spans many routes, each with its own (possibly
228
+ * different) bodySchema/querySchema/paramsSchema, so there's no single
229
+ * schema to type this against.
230
+ */
231
+ interface RouterHooks<Req extends Record<string, any> = {}> {
232
+ /** Runs for every route on this router, after any per-route hooks below. */
233
+ onValidationFailure?: ValidationFailureHook<Req>;
234
+ }
235
+ /**
236
+ * Per-route lifecycle hooks, grouped under one `hooks` option so adding a
237
+ * future hook doesn't grow the flat option list. `onBodyValidationFailure`/
238
+ * `onQueryValidationFailure`/`onParamsValidationFailure` are typed to that
239
+ * route's own schema; `onValidationFailure` covers all three sources with
240
+ * `details: any[]`, for narrowing on `source` yourself instead.
241
+ */
242
+ interface RouteHooks<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}> {
243
+ /** Runs when bodySchema, querySchema, or paramsSchema rejects the request. Fires after the schema-specific hook for that one, before the router's global hook. Can't change the response, use the schema-specific hooks for that; good for side effects (metrics, logging, alerts) across all three sources at once. `details` is typed to the matching schema once you narrow on `source`; `req` includes this route's `middleware` props, same as the schema-specific hooks below. */
244
+ onValidationFailure?: RouteValidationFailureHook<BodySchema, QuerySchema, ParamsSchema, MiddlewareProps>;
245
+ /** Runs when bodySchema rejects. `details` typed to bodySchema, `req` includes this route's `middleware` props. Do nothing for the default 400, call `next()` to continue with the raw body, or respond via `res` yourself. */
246
+ onBodyValidationFailure?: SchemaValidationFailureHook<BodySchema, MiddlewareProps>;
247
+ /** Runs when querySchema rejects. `details` typed to querySchema, `req` includes this route's `middleware` props. Do nothing for the default 400, call `next()` to continue with the raw query, or respond via `res` yourself. */
248
+ onQueryValidationFailure?: SchemaValidationFailureHook<QuerySchema, MiddlewareProps>;
249
+ /** Runs when paramsSchema rejects. `details` typed to paramsSchema, `req` includes this route's `middleware` props. Do nothing for the default 400, call `next()` to continue with the raw params, or respond via `res` yourself. */
250
+ onParamsValidationFailure?: SchemaValidationFailureHook<ParamsSchema, MiddlewareProps>;
251
+ }
146
252
  interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined> {
147
253
  bodySchema?: BodySchema;
148
254
  /**
@@ -159,6 +265,8 @@ interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, Qu
159
265
  * string-arrival caveat as `querySchema` applies; see its docs above.
160
266
  */
161
267
  paramsSchema?: ParamsSchema;
268
+ /** hooks.onValidationFailure runs in addition to the router's global hook (set via createTypedRouterWithConfig or router.onValidationFailure()), if both are set. */
269
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema>;
162
270
  middleware?: TypedMiddleware<any, any>[];
163
271
  tags?: string[];
164
272
  description?: string;
@@ -168,13 +276,14 @@ interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, Qu
168
276
  /** Exclude this route from the generated OpenAPI spec entirely. */
169
277
  hidden?: boolean;
170
278
  /**
171
- * Only used when `path` is a `RegExp`. A readable stand-in path (e.g.
172
- * `/legacy/:id`) for the OpenAPI doc, since one can't be derived from a
173
- * `RegExp` value. Defaults to `regex.toString()`. Doc-only `req.params`
174
- * types as `Record<string, string>` for RegExp routes unless overridden
175
- * by `paramsSchema`.
279
+ * Stand-in path(s) for the OpenAPI doc. Required for a `RegExp` route
280
+ * (defaults to `regex.toString()` otherwise); optional for a string
281
+ * route, to override an ambiguous auto-converted path. An array documents
282
+ * the route under multiple paths at once (e.g. a wildcard that's commonly
283
+ * hit a few different ways), one spec entry per element. Doc-only,
284
+ * doesn't affect `req.params`.
176
285
  */
177
- pathExample?: string;
286
+ pathExample?: string | string[];
178
287
  }
179
288
  /**
180
289
  * The route options accepted by {@link InferSchemaHandler}.
@@ -262,8 +371,8 @@ interface DocsOptions {
262
371
  interface RouteMetadata {
263
372
  method: HttpMethod;
264
373
  path: string | RegExp;
265
- /** Doc-only path override for RegExp routes — see RouteOptions.pathExample. */
266
- pathExample?: string | undefined;
374
+ /** Doc-only path override — see RouteOptions.pathExample. */
375
+ pathExample?: string | string[] | undefined;
267
376
  bodySchema?: AnyStandardSchema | undefined;
268
377
  querySchema?: AnyStandardSchema | undefined;
269
378
  paramsSchema?: AnyStandardSchema | undefined;
@@ -309,7 +418,21 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
309
418
  private mountedRouters;
310
419
  private sampleMode;
311
420
  private scheduleSpecWrite?;
421
+ private globalValidationFailureHook?;
312
422
  constructor();
423
+ /**
424
+ * Set the global validation failure hook. Called for every route
425
+ * registered directly on this router when bodySchema/querySchema/
426
+ * paramsSchema rejects a request. Works the same whether the router came
427
+ * from createTypedRouter() or createTypedRouterWithConfig({ onValidationFailure }),
428
+ * so you're not locked into the config-taking factory just to add this later.
429
+ *
430
+ * @example
431
+ * const router = createTypedRouter().onValidationFailure((info) => {
432
+ * logger.warn(info, 'request validation failed');
433
+ * });
434
+ */
435
+ onValidationFailure(hook: ValidationFailureHook<Req>): TypedRouter<Req, Locals>;
313
436
  /**
314
437
  * Add typed middleware that extends the request with additional properties
315
438
  * and/or adds properties to response.locals
@@ -403,6 +526,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
403
526
  bodySchema?: BodySchema;
404
527
  querySchema?: QuerySchema;
405
528
  paramsSchema?: ParamsSchema;
529
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
406
530
  middleware?: [...M];
407
531
  }, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
408
532
  get(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -410,6 +534,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
410
534
  bodySchema?: BodySchema;
411
535
  querySchema?: QuerySchema;
412
536
  paramsSchema?: ParamsSchema;
537
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
413
538
  middleware?: [...M];
414
539
  }, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
415
540
  post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -417,6 +542,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
417
542
  bodySchema?: BodySchema;
418
543
  querySchema?: QuerySchema;
419
544
  paramsSchema?: ParamsSchema;
545
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
420
546
  middleware?: [...M];
421
547
  }, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
422
548
  post(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -424,6 +550,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
424
550
  bodySchema?: BodySchema;
425
551
  querySchema?: QuerySchema;
426
552
  paramsSchema?: ParamsSchema;
553
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
427
554
  middleware?: [...M];
428
555
  }, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
429
556
  put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -431,6 +558,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
431
558
  bodySchema?: BodySchema;
432
559
  querySchema?: QuerySchema;
433
560
  paramsSchema?: ParamsSchema;
561
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
434
562
  middleware?: [...M];
435
563
  }, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
436
564
  put(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -438,6 +566,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
438
566
  bodySchema?: BodySchema;
439
567
  querySchema?: QuerySchema;
440
568
  paramsSchema?: ParamsSchema;
569
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
441
570
  middleware?: [...M];
442
571
  }, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
443
572
  patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -445,6 +574,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
445
574
  bodySchema?: BodySchema;
446
575
  querySchema?: QuerySchema;
447
576
  paramsSchema?: ParamsSchema;
577
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
448
578
  middleware?: [...M];
449
579
  }, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
450
580
  patch(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -452,42 +582,49 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
452
582
  bodySchema?: BodySchema;
453
583
  querySchema?: QuerySchema;
454
584
  paramsSchema?: ParamsSchema;
585
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
455
586
  middleware?: [...M];
456
587
  }, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
457
588
  delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
458
589
  delete<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
459
590
  querySchema?: QuerySchema;
460
591
  paramsSchema?: ParamsSchema;
592
+ hooks?: RouteHooks<undefined, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
461
593
  middleware?: [...M];
462
594
  }, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
463
595
  delete(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
464
596
  delete<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
465
597
  querySchema?: QuerySchema;
466
598
  paramsSchema?: ParamsSchema;
599
+ hooks?: RouteHooks<undefined, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
467
600
  middleware?: [...M];
468
601
  }, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
469
602
  options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
470
603
  options<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
471
604
  querySchema?: QuerySchema;
472
605
  paramsSchema?: ParamsSchema;
606
+ hooks?: RouteHooks<undefined, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
473
607
  middleware?: [...M];
474
608
  }, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
475
609
  options(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
476
610
  options<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
477
611
  querySchema?: QuerySchema;
478
612
  paramsSchema?: ParamsSchema;
613
+ hooks?: RouteHooks<undefined, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
479
614
  middleware?: [...M];
480
615
  }, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
481
616
  head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
482
617
  head<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
483
618
  querySchema?: QuerySchema;
484
619
  paramsSchema?: ParamsSchema;
620
+ hooks?: RouteHooks<undefined, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
485
621
  middleware?: [...M];
486
622
  }, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
487
623
  head(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
488
624
  head<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
489
625
  querySchema?: QuerySchema;
490
626
  paramsSchema?: ParamsSchema;
627
+ hooks?: RouteHooks<undefined, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
491
628
  middleware?: [...M];
492
629
  }, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
493
630
  all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -495,6 +632,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
495
632
  bodySchema?: BodySchema;
496
633
  querySchema?: QuerySchema;
497
634
  paramsSchema?: ParamsSchema;
635
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
498
636
  middleware?: [...M];
499
637
  }, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
500
638
  all(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -502,9 +640,12 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
502
640
  bodySchema?: BodySchema;
503
641
  querySchema?: QuerySchema;
504
642
  paramsSchema?: ParamsSchema;
643
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
505
644
  middleware?: [...M];
506
645
  }, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
507
646
  private registerRoute;
647
+ private notifyValidationFailure;
648
+ private runSchemaValidationHook;
508
649
  private createBodyValidationMiddleware;
509
650
  private createParamsValidationMiddleware;
510
651
  private createQueryValidationMiddleware;
@@ -535,10 +676,12 @@ declare function createTypedRouter<Req extends Record<string, any> = AdditionalR
535
676
  *
536
677
  * @property validateInput - (Future) Whether to enable global input validation.
537
678
  * @property errorHandler - Optional global error handler middleware for the router.
679
+ * @property hooks.onValidationFailure - Called for every validation failure on this router, in addition to any per-route hook.
538
680
  */
539
- interface RouterConfig {
681
+ interface RouterConfig<Req extends Record<string, any> = {}> {
540
682
  validateInput?: boolean;
541
683
  errorHandler?: (error: any, req: Request, res: Response, next: NextFunction) => void;
684
+ hooks?: RouterHooks<Req>;
542
685
  }
543
686
  /**
544
687
  * Create a new typed router with optional configuration.
@@ -557,7 +700,7 @@ interface RouterConfig {
557
700
  * }
558
701
  * });
559
702
  */
560
- declare function createTypedRouterWithConfig<Req extends Record<string, any> = AdditionalReqProps, Locals extends Record<string, any> = AdditionalLocals>(config?: RouterConfig): TypedRouter<Req, Locals>;
703
+ declare function createTypedRouterWithConfig<Req extends Record<string, any> = AdditionalReqProps, Locals extends Record<string, any> = AdditionalLocals>(config?: RouterConfig<Req>): TypedRouter<Req, Locals>;
561
704
  /**
562
705
  * Create a new typed router with pre-configured middleware.
563
706
  *
@@ -600,4 +743,4 @@ type RouterDocEntry = TypedRouter<any, any> | {
600
743
  declare function generateOpenApiSpec(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): Promise<Record<string, any>>;
601
744
  declare function createDocs(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): express.Router & express.RequestHandler;
602
745
  //#endregion
603
- export { AdditionalLocals, AdditionalReqProps, AnyStandardSchema, DocsOptions, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaHandler, InferSchemaHandlerOptions, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteOptions, RouterConfig, RouterDocEntry, SafeParseResult, SchemaLike, SchemaRequest, SchemaRouteHandler, TypedMiddleware, TypedRouter, createDocs, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, defineMiddleware, generateOpenApiSpec, inferJsonSchema, isSchemaError, parseSchema, safeParseSchema };
746
+ export { AdditionalLocals, AdditionalReqProps, AnyStandardSchema, DocsOptions, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaHandler, InferSchemaHandlerOptions, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteHooks, RouteOptions, RouteValidationFailureHook, RouteValidationFailureInfo, RouterConfig, RouterDocEntry, RouterHooks, SafeParseResult, SchemaLike, SchemaRequest, SchemaRouteHandler, SchemaValidationFailureHook, SchemaValidationFailureInfo, TypedMiddleware, TypedRouter, ValidationFailureHook, ValidationFailureInfo, ValidationFailureSource, createDocs, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, defaultValidationHandler, defineMiddleware, generateOpenApiSpec, inferJsonSchema, isSchemaError, parseSchema, safeParseSchema };
@@ -143,6 +143,112 @@ type SchemaRouteHandler<Path extends string = string, BodySchema extends SchemaL
143
143
  * @property paramsSchema - Optional schema for validating route params.
144
144
  * @property middleware - Optional array of TypedMiddleware for this route.
145
145
  */
146
+ /** Which validated part of the request failed. */
147
+ type ValidationFailureSource = "body" | "query" | "params";
148
+ /**
149
+ * Passed to a validation failure hook when bodySchema/querySchema/paramsSchema
150
+ * rejects a request. `req` includes whatever `.useMiddleware()` added, when
151
+ * set via `router.onValidationFailure()` (that method's `Req` type parameter
152
+ * is the router's own, already widened by any `.useMiddleware()` calls
153
+ * before it in the chain). Not the case for `createTypedRouterWithConfig`'s
154
+ * `hooks.onValidationFailure`, called before any middleware exists.
155
+ */
156
+ interface ValidationFailureInfo<MiddlewareProps extends Record<string, any> = {}> {
157
+ source: ValidationFailureSource;
158
+ error: string;
159
+ details: any[];
160
+ method: HttpMethod;
161
+ path: string;
162
+ req: Request & MiddlewareProps;
163
+ }
164
+ /**
165
+ * Called when validation fails, in addition to the 400 response that's
166
+ * already sent, for side effects like metrics/logging/alerting, not for
167
+ * changing the response (see the schema-specific hooks in RouteHooks for
168
+ * that). May be async (e.g. to log to an external service); the response is sent
169
+ * without waiting for it either way. Both a synchronous throw and a rejected
170
+ * promise are caught and ignored, so a broken hook can't take down request
171
+ * handling or surface as an unhandled rejection.
172
+ */
173
+ type ValidationFailureHook<MiddlewareProps extends Record<string, any> = {}> = (info: ValidationFailureInfo<MiddlewareProps>) => void | Promise<void>;
174
+ type SchemaValidationDetails<S> = S extends AnyStandardSchema ? readonly StandardSchemaV1.Issue[] : any[];
175
+ /**
176
+ * Passed to a route's onValidationFailure. A discriminated union on `source`
177
+ * (unlike {@link ValidationFailureInfo}, which is only ever `any[]`): narrow
178
+ * on `source` and `details` is that schema's own issue shape.
179
+ */
180
+ type RouteValidationFailureInfo<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}> = {
181
+ source: "body";
182
+ error: string;
183
+ details: SchemaValidationDetails<BodySchema>;
184
+ method: HttpMethod;
185
+ path: string;
186
+ req: Request & MiddlewareProps;
187
+ } | {
188
+ source: "query";
189
+ error: string;
190
+ details: SchemaValidationDetails<QuerySchema>;
191
+ method: HttpMethod;
192
+ path: string;
193
+ req: Request & MiddlewareProps;
194
+ } | {
195
+ source: "params";
196
+ error: string;
197
+ details: SchemaValidationDetails<ParamsSchema>;
198
+ method: HttpMethod;
199
+ path: string;
200
+ req: Request & MiddlewareProps;
201
+ };
202
+ type RouteValidationFailureHook<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}> = (info: RouteValidationFailureInfo<BodySchema, QuerySchema, ParamsSchema, MiddlewareProps>) => void | Promise<void>;
203
+ /**
204
+ * Passed to a schema-specific validation failure hook. Do nothing and the
205
+ * default 400 still fires. Call `next()` to continue with the raw,
206
+ * unvalidated value, or respond via `res` yourself (e.g. a custom status).
207
+ * `req` includes whatever the route's own `middleware` option adds, same as
208
+ * the handler's `req`.
209
+ */
210
+ interface SchemaValidationFailureInfo<S extends SchemaLike | undefined, MiddlewareProps extends Record<string, any> = {}> {
211
+ error: string;
212
+ details: SchemaValidationDetails<S>;
213
+ method: HttpMethod;
214
+ path: string;
215
+ req: Request & MiddlewareProps;
216
+ res: Response;
217
+ next: NextFunction;
218
+ }
219
+ type SchemaValidationFailureHook<S extends SchemaLike | undefined, MiddlewareProps extends Record<string, any> = {}> = (info: SchemaValidationFailureInfo<S, MiddlewareProps>) => void | Promise<void>;
220
+ /** The default 400 response for a validation failure. Call it from a schema-specific hook to log first and still fall back to the normal response. */
221
+ declare function defaultValidationHandler(info: {
222
+ res: Response;
223
+ details: unknown;
224
+ }): void;
225
+ /**
226
+ * Router-wide validation failure hook. Schema-specific hooks aren't
227
+ * available here: a router spans many routes, each with its own (possibly
228
+ * different) bodySchema/querySchema/paramsSchema, so there's no single
229
+ * schema to type this against.
230
+ */
231
+ interface RouterHooks<Req extends Record<string, any> = {}> {
232
+ /** Runs for every route on this router, after any per-route hooks below. */
233
+ onValidationFailure?: ValidationFailureHook<Req>;
234
+ }
235
+ /**
236
+ * Per-route lifecycle hooks, grouped under one `hooks` option so adding a
237
+ * future hook doesn't grow the flat option list. `onBodyValidationFailure`/
238
+ * `onQueryValidationFailure`/`onParamsValidationFailure` are typed to that
239
+ * route's own schema; `onValidationFailure` covers all three sources with
240
+ * `details: any[]`, for narrowing on `source` yourself instead.
241
+ */
242
+ interface RouteHooks<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}> {
243
+ /** Runs when bodySchema, querySchema, or paramsSchema rejects the request. Fires after the schema-specific hook for that one, before the router's global hook. Can't change the response, use the schema-specific hooks for that; good for side effects (metrics, logging, alerts) across all three sources at once. `details` is typed to the matching schema once you narrow on `source`; `req` includes this route's `middleware` props, same as the schema-specific hooks below. */
244
+ onValidationFailure?: RouteValidationFailureHook<BodySchema, QuerySchema, ParamsSchema, MiddlewareProps>;
245
+ /** Runs when bodySchema rejects. `details` typed to bodySchema, `req` includes this route's `middleware` props. Do nothing for the default 400, call `next()` to continue with the raw body, or respond via `res` yourself. */
246
+ onBodyValidationFailure?: SchemaValidationFailureHook<BodySchema, MiddlewareProps>;
247
+ /** Runs when querySchema rejects. `details` typed to querySchema, `req` includes this route's `middleware` props. Do nothing for the default 400, call `next()` to continue with the raw query, or respond via `res` yourself. */
248
+ onQueryValidationFailure?: SchemaValidationFailureHook<QuerySchema, MiddlewareProps>;
249
+ /** Runs when paramsSchema rejects. `details` typed to paramsSchema, `req` includes this route's `middleware` props. Do nothing for the default 400, call `next()` to continue with the raw params, or respond via `res` yourself. */
250
+ onParamsValidationFailure?: SchemaValidationFailureHook<ParamsSchema, MiddlewareProps>;
251
+ }
146
252
  interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined> {
147
253
  bodySchema?: BodySchema;
148
254
  /**
@@ -159,6 +265,8 @@ interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, Qu
159
265
  * string-arrival caveat as `querySchema` applies; see its docs above.
160
266
  */
161
267
  paramsSchema?: ParamsSchema;
268
+ /** hooks.onValidationFailure runs in addition to the router's global hook (set via createTypedRouterWithConfig or router.onValidationFailure()), if both are set. */
269
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema>;
162
270
  middleware?: TypedMiddleware<any, any>[];
163
271
  tags?: string[];
164
272
  description?: string;
@@ -168,13 +276,14 @@ interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, Qu
168
276
  /** Exclude this route from the generated OpenAPI spec entirely. */
169
277
  hidden?: boolean;
170
278
  /**
171
- * Only used when `path` is a `RegExp`. A readable stand-in path (e.g.
172
- * `/legacy/:id`) for the OpenAPI doc, since one can't be derived from a
173
- * `RegExp` value. Defaults to `regex.toString()`. Doc-only `req.params`
174
- * types as `Record<string, string>` for RegExp routes unless overridden
175
- * by `paramsSchema`.
279
+ * Stand-in path(s) for the OpenAPI doc. Required for a `RegExp` route
280
+ * (defaults to `regex.toString()` otherwise); optional for a string
281
+ * route, to override an ambiguous auto-converted path. An array documents
282
+ * the route under multiple paths at once (e.g. a wildcard that's commonly
283
+ * hit a few different ways), one spec entry per element. Doc-only,
284
+ * doesn't affect `req.params`.
176
285
  */
177
- pathExample?: string;
286
+ pathExample?: string | string[];
178
287
  }
179
288
  /**
180
289
  * The route options accepted by {@link InferSchemaHandler}.
@@ -262,8 +371,8 @@ interface DocsOptions {
262
371
  interface RouteMetadata {
263
372
  method: HttpMethod;
264
373
  path: string | RegExp;
265
- /** Doc-only path override for RegExp routes — see RouteOptions.pathExample. */
266
- pathExample?: string | undefined;
374
+ /** Doc-only path override — see RouteOptions.pathExample. */
375
+ pathExample?: string | string[] | undefined;
267
376
  bodySchema?: AnyStandardSchema | undefined;
268
377
  querySchema?: AnyStandardSchema | undefined;
269
378
  paramsSchema?: AnyStandardSchema | undefined;
@@ -309,7 +418,21 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
309
418
  private mountedRouters;
310
419
  private sampleMode;
311
420
  private scheduleSpecWrite?;
421
+ private globalValidationFailureHook?;
312
422
  constructor();
423
+ /**
424
+ * Set the global validation failure hook. Called for every route
425
+ * registered directly on this router when bodySchema/querySchema/
426
+ * paramsSchema rejects a request. Works the same whether the router came
427
+ * from createTypedRouter() or createTypedRouterWithConfig({ onValidationFailure }),
428
+ * so you're not locked into the config-taking factory just to add this later.
429
+ *
430
+ * @example
431
+ * const router = createTypedRouter().onValidationFailure((info) => {
432
+ * logger.warn(info, 'request validation failed');
433
+ * });
434
+ */
435
+ onValidationFailure(hook: ValidationFailureHook<Req>): TypedRouter<Req, Locals>;
313
436
  /**
314
437
  * Add typed middleware that extends the request with additional properties
315
438
  * and/or adds properties to response.locals
@@ -403,6 +526,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
403
526
  bodySchema?: BodySchema;
404
527
  querySchema?: QuerySchema;
405
528
  paramsSchema?: ParamsSchema;
529
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
406
530
  middleware?: [...M];
407
531
  }, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
408
532
  get(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -410,6 +534,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
410
534
  bodySchema?: BodySchema;
411
535
  querySchema?: QuerySchema;
412
536
  paramsSchema?: ParamsSchema;
537
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
413
538
  middleware?: [...M];
414
539
  }, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
415
540
  post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -417,6 +542,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
417
542
  bodySchema?: BodySchema;
418
543
  querySchema?: QuerySchema;
419
544
  paramsSchema?: ParamsSchema;
545
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
420
546
  middleware?: [...M];
421
547
  }, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
422
548
  post(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -424,6 +550,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
424
550
  bodySchema?: BodySchema;
425
551
  querySchema?: QuerySchema;
426
552
  paramsSchema?: ParamsSchema;
553
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
427
554
  middleware?: [...M];
428
555
  }, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
429
556
  put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -431,6 +558,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
431
558
  bodySchema?: BodySchema;
432
559
  querySchema?: QuerySchema;
433
560
  paramsSchema?: ParamsSchema;
561
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
434
562
  middleware?: [...M];
435
563
  }, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
436
564
  put(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -438,6 +566,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
438
566
  bodySchema?: BodySchema;
439
567
  querySchema?: QuerySchema;
440
568
  paramsSchema?: ParamsSchema;
569
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
441
570
  middleware?: [...M];
442
571
  }, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
443
572
  patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -445,6 +574,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
445
574
  bodySchema?: BodySchema;
446
575
  querySchema?: QuerySchema;
447
576
  paramsSchema?: ParamsSchema;
577
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
448
578
  middleware?: [...M];
449
579
  }, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
450
580
  patch(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -452,42 +582,49 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
452
582
  bodySchema?: BodySchema;
453
583
  querySchema?: QuerySchema;
454
584
  paramsSchema?: ParamsSchema;
585
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
455
586
  middleware?: [...M];
456
587
  }, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
457
588
  delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
458
589
  delete<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
459
590
  querySchema?: QuerySchema;
460
591
  paramsSchema?: ParamsSchema;
592
+ hooks?: RouteHooks<undefined, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
461
593
  middleware?: [...M];
462
594
  }, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
463
595
  delete(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
464
596
  delete<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
465
597
  querySchema?: QuerySchema;
466
598
  paramsSchema?: ParamsSchema;
599
+ hooks?: RouteHooks<undefined, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
467
600
  middleware?: [...M];
468
601
  }, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
469
602
  options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
470
603
  options<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
471
604
  querySchema?: QuerySchema;
472
605
  paramsSchema?: ParamsSchema;
606
+ hooks?: RouteHooks<undefined, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
473
607
  middleware?: [...M];
474
608
  }, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
475
609
  options(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
476
610
  options<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
477
611
  querySchema?: QuerySchema;
478
612
  paramsSchema?: ParamsSchema;
613
+ hooks?: RouteHooks<undefined, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
479
614
  middleware?: [...M];
480
615
  }, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
481
616
  head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
482
617
  head<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
483
618
  querySchema?: QuerySchema;
484
619
  paramsSchema?: ParamsSchema;
620
+ hooks?: RouteHooks<undefined, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
485
621
  middleware?: [...M];
486
622
  }, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
487
623
  head(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
488
624
  head<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
489
625
  querySchema?: QuerySchema;
490
626
  paramsSchema?: ParamsSchema;
627
+ hooks?: RouteHooks<undefined, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
491
628
  middleware?: [...M];
492
629
  }, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
493
630
  all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -495,6 +632,7 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
495
632
  bodySchema?: BodySchema;
496
633
  querySchema?: QuerySchema;
497
634
  paramsSchema?: ParamsSchema;
635
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
498
636
  middleware?: [...M];
499
637
  }, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
500
638
  all(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
@@ -502,9 +640,12 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
502
640
  bodySchema?: BodySchema;
503
641
  querySchema?: QuerySchema;
504
642
  paramsSchema?: ParamsSchema;
643
+ hooks?: RouteHooks<BodySchema, QuerySchema, ParamsSchema, InferMiddlewareProps<[...M]>>;
505
644
  middleware?: [...M];
506
645
  }, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
507
646
  private registerRoute;
647
+ private notifyValidationFailure;
648
+ private runSchemaValidationHook;
508
649
  private createBodyValidationMiddleware;
509
650
  private createParamsValidationMiddleware;
510
651
  private createQueryValidationMiddleware;
@@ -535,10 +676,12 @@ declare function createTypedRouter<Req extends Record<string, any> = AdditionalR
535
676
  *
536
677
  * @property validateInput - (Future) Whether to enable global input validation.
537
678
  * @property errorHandler - Optional global error handler middleware for the router.
679
+ * @property hooks.onValidationFailure - Called for every validation failure on this router, in addition to any per-route hook.
538
680
  */
539
- interface RouterConfig {
681
+ interface RouterConfig<Req extends Record<string, any> = {}> {
540
682
  validateInput?: boolean;
541
683
  errorHandler?: (error: any, req: Request, res: Response, next: NextFunction) => void;
684
+ hooks?: RouterHooks<Req>;
542
685
  }
543
686
  /**
544
687
  * Create a new typed router with optional configuration.
@@ -557,7 +700,7 @@ interface RouterConfig {
557
700
  * }
558
701
  * });
559
702
  */
560
- declare function createTypedRouterWithConfig<Req extends Record<string, any> = AdditionalReqProps, Locals extends Record<string, any> = AdditionalLocals>(config?: RouterConfig): TypedRouter<Req, Locals>;
703
+ declare function createTypedRouterWithConfig<Req extends Record<string, any> = AdditionalReqProps, Locals extends Record<string, any> = AdditionalLocals>(config?: RouterConfig<Req>): TypedRouter<Req, Locals>;
561
704
  /**
562
705
  * Create a new typed router with pre-configured middleware.
563
706
  *
@@ -600,4 +743,4 @@ type RouterDocEntry = TypedRouter<any, any> | {
600
743
  declare function generateOpenApiSpec(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): Promise<Record<string, any>>;
601
744
  declare function createDocs(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): express.Router & express.RequestHandler;
602
745
  //#endregion
603
- export { AdditionalLocals, AdditionalReqProps, AnyStandardSchema, DocsOptions, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaHandler, InferSchemaHandlerOptions, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteOptions, RouterConfig, RouterDocEntry, SafeParseResult, SchemaLike, SchemaRequest, SchemaRouteHandler, TypedMiddleware, TypedRouter, createDocs, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, defineMiddleware, generateOpenApiSpec, inferJsonSchema, isSchemaError, parseSchema, safeParseSchema };
746
+ export { AdditionalLocals, AdditionalReqProps, AnyStandardSchema, DocsOptions, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaHandler, InferSchemaHandlerOptions, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteHooks, RouteOptions, RouteValidationFailureHook, RouteValidationFailureInfo, RouterConfig, RouterDocEntry, RouterHooks, SafeParseResult, SchemaLike, SchemaRequest, SchemaRouteHandler, SchemaValidationFailureHook, SchemaValidationFailureInfo, TypedMiddleware, TypedRouter, ValidationFailureHook, ValidationFailureInfo, ValidationFailureSource, createDocs, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, defaultValidationHandler, defineMiddleware, generateOpenApiSpec, inferJsonSchema, isSchemaError, parseSchema, safeParseSchema };
@@ -1,12 +1,12 @@
1
- import e from"express";import{SchemaError as t}from"@standard-schema/utils";function n(e,n){let r=e;if(r&&r[`~standard`]&&typeof r[`~standard`].validate==`function`){let e=r[`~standard`].validate(n);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new t(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function r(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function i(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}function a(...e){return e}const o=Function(`m`,`return import(m)`);let s,c;async function l(e){s??=await o(`module`),c??=await o(`url`);let t=[],n=globalThis.process?.argv?.[1];n&&t.push(c.pathToFileURL(n).href);let r=globalThis.process?.cwd?.()??``;r&&t.push(c.pathToFileURL(r+`/`).href);for(let n of t)try{let t=s.createRequire(n).resolve(e);return await o(c.pathToFileURL(t).href)}catch{}return o(e)}const u=new WeakMap,d=/\(\?<[^>]+>/;function f(e){return typeof e==`string`&&d.test(e)?new RegExp(e):e}function p(e){if(typeof e.path==`string`)return e.path;if(e.pathExample)return e.pathExample;let t=0;return e.path.source.replace(/\\\//g,`/`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function m(e){let t=[],n=0,r=0,i=!1,a=!1;for(let o=0;o<e.length;o++){let s=e[o];if(a){a=!1;continue}if(s===`\\`){a=!0;continue}if(s===`[`){i=!0;continue}if(s===`]`){i=!1;continue}i||(s===`(`?r++:s===`)`?r=Math.max(0,r-1):s===`|`&&r===0&&(t.push(e.slice(n,o)),n=o+1))}return t.push(e.slice(n)),t.length>1?t:[e]}function h(e){if(!e.startsWith(`(`)||!e.endsWith(`)`))return e;let t=0,n=!1,r=!1;for(let i=0;i<e.length;i++){let a=e[i];if(r){r=!1;continue}if(a===`\\`){r=!0;continue}if(a===`[`)n=!0;else if(a===`]`)n=!1;else if(!n&&a===`(`)t++;else if(!n&&a===`)`&&(t--,t===0&&i!==e.length-1))return e}return e.slice(1,-1).replace(/^\?:/,``)}function g(e){let t=0;return h(e).replace(/^\^|\$$/g,``).replace(/\\\//g,`/`).replace(/\.\*|\.\+/g,`/:path`).replace(/\/?\?$/,``).replace(/\(\?<([A-Za-z0-9_]+)>[^()]*\)/g,`:$1`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function _(e){if(typeof e.path==`string`||e.pathExample)return[p(e)];let t=m(e.path.source);return t.length===1?[p(e)]:t.map(g)}function v(e){return e.replace(/\{([^{}]*)\}/g,`$1`).replace(/:([A-Za-z0-9_]+)(?:\([^)]*\))?[?+*]?/g,`{$1}`).replace(/\(\?<([A-Za-z0-9_]+)>[^)]*\)/g,`{$1}`).replace(/^\^|\$$/g,``).replace(/\/{2,}/g,`/`)}function y(e){let t=e.replace(/\{([^{}]*)\}/g,`$1`);return[...t.matchAll(/:([A-Za-z0-9_]+)/g),...t.matchAll(/\(\?<([A-Za-z0-9_]+)>/g)].map(e=>e[1])}function b(e){return e.startsWith(`:`)||e.startsWith(`*`)||e.includes(`(?<`)}function x(e){return e.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(Boolean).find(e=>!b(e))??`default`}function S(e,t){let n=t.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(e=>e&&!b(e)),r=n[n.length-1]??`resource`;return`${{get:`Get`,post:`Create`,put:`Update`,patch:`Patch`,delete:`Delete`,head:`Head`,options:`Options`}[e]??e} ${r}`}async function C(e){let t=u.get(e);if(t)return t;let n=await w(e);return u.set(e,n),n}async function w(e){if(typeof e.toJsonSchema==`function`)try{return e.toJsonSchema()}catch{}let t=e[`~standard`]?.vendor;if(t===`zod`){try{let t=await l(`zod`);if(typeof t.toJSONSchema==`function`)return t.toJSONSchema(e)}catch{}try{let t=await l(`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 l(`@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 l(`effect`),n=t.JSONSchema?.make??t.default?.JSONSchema?.make;if(typeof n==`function`)return n(e)}catch{}return{}}function T(e){return E(e,0,new WeakSet)}function E(e,t,n){if(e==null)return{type:`null`};if(t>=12)return{};if(e instanceof Date)return{type:`string`,format:`date-time`};if(typeof e==`bigint`)return{type:`integer`};if(typeof e==`object`&&typeof e.toJSON==`function`)return E(e.toJSON(),t,n);if(Array.isArray(e)){if(e.length===0||n.has(e))return{type:`array`,items:{}};n.add(e);let r=Math.min(e.length,20),i=E(e[0],t+1,n);for(let a=1;a<r;a++)i=k(i,E(e[a],t+1,n));return n.delete(e),{type:`array`,items:i}}switch(typeof e){case`string`:return{type:`string`};case`boolean`:return{type:`boolean`};case`number`:return Number.isFinite(e)?{type:Number.isInteger(e)?`integer`:`number`}:{type:`null`};case`object`:{if(n.has(e))return{type:`object`};n.add(e);let r={},i=[];for(let[a,o]of Object.entries(e))typeof o!=`function`&&o!==void 0&&(r[a]=E(o,t+1,n),i.push(a));n.delete(e);let a={type:`object`,properties:r};return i.length&&(a.required=i),a}default:return{}}}function D(e){return!e||e.type===void 0?new Set:new Set(Array.isArray(e.type)?e.type:[e.type])}function O(e){e.has(`integer`)&&e.has(`number`)&&e.delete(`integer`);let t=[...e];if(t.length!==0)return t.length===1?t[0]:t}function k(e,t){if(!e||Object.keys(e).length===0)return t??{};if(!t||Object.keys(t).length===0)return e??{};let n=new Set([...D(e),...D(t)]),r={},i=O(n);if(i!==void 0&&(r.type=i),n.has(`object`)&&(e.properties||t.properties)){let n=e.properties??{},i=t.properties??{},a={};for(let e of new Set([...Object.keys(n),...Object.keys(i)]))a[e]=k(n[e],i[e]);r.properties=a;let o=e.required??[],s=t.required??[],c=o.filter(e=>s.includes(e));c.length&&(r.required=c)}if(n.has(`array`)){let n=k(e.items,t.items);n&&Object.keys(n).length&&(r.items=n)}return r}function A(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}const j=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function M(e,t,n){return`<!doctype html>
1
+ import e from"express";import{SchemaError as t}from"@standard-schema/utils";function n(e,n){let r=e;if(r&&r[`~standard`]&&typeof r[`~standard`].validate==`function`){let e=r[`~standard`].validate(n);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new t(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function r(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function i(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}function a(...e){return e}function o(e){e.res.status(400).json({error:`Validation failed`,details:e.details})}function s(e,t){if(e)try{e(t)?.catch?.(()=>{})}catch{}}const c=Function(`m`,`return import(m)`);let l,u;async function d(e){l??=await c(`module`),u??=await c(`url`);let t=[],n=globalThis.process?.argv?.[1];n&&t.push(u.pathToFileURL(n).href);let r=globalThis.process?.cwd?.()??``;r&&t.push(u.pathToFileURL(r+`/`).href);for(let n of t)try{let t=l.createRequire(n).resolve(e);return await c(u.pathToFileURL(t).href)}catch{}return c(e)}const f=new WeakMap,p=/\(\?<[^>]+>/;function m(e){return typeof e==`string`&&p.test(e)?new RegExp(e):e}function h(e){let t=Array.isArray(e.pathExample)?e.pathExample[0]:e.pathExample;if(t)return t;if(typeof e.path==`string`)return e.path;let n=0;return e.path.source.replace(/\\\//g,`/`).replace(/\((?!\?)[^()]*\)/g,()=>`:${n++}`)}function g(e){let t=[],n=0,r=0,i=!1,a=!1;for(let o=0;o<e.length;o++){let s=e[o];if(a){a=!1;continue}if(s===`\\`){a=!0;continue}if(s===`[`){i=!0;continue}if(s===`]`){i=!1;continue}i||(s===`(`?r++:s===`)`?r=Math.max(0,r-1):s===`|`&&r===0&&(t.push(e.slice(n,o)),n=o+1))}return t.push(e.slice(n)),t.length>1?t:[e]}function _(e){if(!e.startsWith(`(`)||!e.endsWith(`)`))return e;let t=0,n=!1,r=!1;for(let i=0;i<e.length;i++){let a=e[i];if(r){r=!1;continue}if(a===`\\`){r=!0;continue}if(a===`[`)n=!0;else if(a===`]`)n=!1;else if(!n&&a===`(`)t++;else if(!n&&a===`)`&&(t--,t===0&&i!==e.length-1))return e}return e.slice(1,-1).replace(/^\?:/,``)}function v(e){let t=0;return _(e).replace(/^\^|\$$/g,``).replace(/\\\//g,`/`).replace(/\.\*|\.\+/g,`/:path`).replace(/\/?\?$/,``).replace(/\(\?<([A-Za-z0-9_]+)>[^()]*\)/g,`:$1`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function y(e){if(Array.isArray(e.pathExample)&&e.pathExample.length>0)return e.pathExample;if(typeof e.path==`string`||e.pathExample)return[h(e)];let t=g(e.path.source);return t.length===1?[h(e)]:t.map(v)}function b(e){let t=e.replace(/\{([^{}]*)\}/g,`$1`).replace(/:([A-Za-z0-9_]+)(?:\([^)]*\))?[?+*]?/g,`{$1}`).replace(/\(\?<([A-Za-z0-9_]+)>[^)]*\)/g,`{$1}`).replace(/\*([A-Za-z0-9_]+)/g,`{$1}`).replace(/^\^|\$$/g,``),n=0;return t.replace(/\*/g,()=>`{${n++}}`).replace(/\/{2,}/g,`/`)}function x(e){let t=e.replace(/\{([^{}]*)\}/g,`$1`).replace(/^\^|\$$/g,``),n=[...t.matchAll(/:([A-Za-z0-9_]+)/g),...t.matchAll(/\(\?<([A-Za-z0-9_]+)>/g),...t.matchAll(/\*([A-Za-z0-9_]+)/g)].map(e=>e[1]),r=(t.replace(/\*[A-Za-z0-9_]+/g,``).match(/\*/g)??[]).length;for(let e=0;e<r;e++)n.push(String(e));return n}function S(e){return e.startsWith(`:`)||e.includes(`*`)||e.includes(`(?<`)}function C(e){return e.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(Boolean).find(e=>!S(e))??`default`}function w(e,t){let n=t.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(e=>e&&!S(e)),r=n[n.length-1]??`resource`;return`${{get:`Get`,post:`Create`,put:`Update`,patch:`Patch`,delete:`Delete`,head:`Head`,options:`Options`}[e]??e} ${r}`}async function T(e){let t=f.get(e);if(t)return t;let n=await E(e);return f.set(e,n),n}async function E(e){if(typeof e.toJsonSchema==`function`)try{return e.toJsonSchema()}catch{}let t=e[`~standard`]?.vendor;if(t===`zod`){try{let t=await d(`zod`);if(typeof t.toJSONSchema==`function`)return t.toJSONSchema(e)}catch{}try{let t=await d(`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 d(`@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 d(`effect`),n=t.JSONSchema?.make??t.default?.JSONSchema?.make;if(typeof n==`function`)return n(e)}catch{}return{}}function D(e){return O(e,0,new WeakSet)}function O(e,t,n){if(e==null)return{type:`null`};if(t>=12)return{};if(e instanceof Date)return{type:`string`,format:`date-time`};if(typeof e==`bigint`)return{type:`integer`};if(typeof e==`object`&&typeof e.toJSON==`function`)return O(e.toJSON(),t,n);if(Array.isArray(e)){if(e.length===0||n.has(e))return{type:`array`,items:{}};n.add(e);let r=Math.min(e.length,20),i=O(e[0],t+1,n);for(let a=1;a<r;a++)i=j(i,O(e[a],t+1,n));return n.delete(e),{type:`array`,items:i}}switch(typeof e){case`string`:return{type:`string`};case`boolean`:return{type:`boolean`};case`number`:return Number.isFinite(e)?{type:Number.isInteger(e)?`integer`:`number`}:{type:`null`};case`object`:{if(n.has(e))return{type:`object`};n.add(e);let r={},i=[];for(let[a,o]of Object.entries(e))typeof o!=`function`&&o!==void 0&&(r[a]=O(o,t+1,n),i.push(a));n.delete(e);let a={type:`object`,properties:r};return i.length&&(a.required=i),a}default:return{}}}function k(e){return!e||e.type===void 0?new Set:new Set(Array.isArray(e.type)?e.type:[e.type])}function A(e){e.has(`integer`)&&e.has(`number`)&&e.delete(`integer`);let t=[...e];if(t.length!==0)return t.length===1?t[0]:t}function j(e,t){if(!e||Object.keys(e).length===0)return t??{};if(!t||Object.keys(t).length===0)return e??{};let n=new Set([...k(e),...k(t)]),r={},i=A(n);if(i!==void 0&&(r.type=i),n.has(`object`)&&(e.properties||t.properties)){let n=e.properties??{},i=t.properties??{},a={};for(let e of new Set([...Object.keys(n),...Object.keys(i)]))a[e]=j(n[e],i[e]);r.properties=a;let o=e.required??[],s=t.required??[],c=o.filter(e=>s.includes(e));c.length&&(r.required=c)}if(n.has(`array`)){let n=j(e.items,t.items);n&&Object.keys(n).length&&(r.items=n)}return r}function M(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}const N=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function P(e,t,n){return`<!doctype html>
2
2
  <html>
3
3
  <head>
4
- <title>${A(e)}</title>
4
+ <title>${M(e)}</title>
5
5
  <meta charset="utf-8" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1" />
7
7
  </head>
8
8
  <body>
9
- <script id="api-reference" data-url="${A(t)}"><\/script>
10
- <script src="${A(n)}"><\/script>
9
+ <script id="api-reference" data-url="${M(t)}"><\/script>
10
+ <script src="${M(n)}"><\/script>
11
11
  </body>
12
- </html>`}async function N(e,t){let n=Object.create(null);for(let t of e)if(!(t.method===`all`||t.hidden))for(let e of _(t)){let r=v(e);n[r]||(n[r]={});let i;if(t.paramsSchema){let e=await C(t.paramsSchema),n=e.properties??{},r=e.required??[];i=Object.entries(n).map(([e,t])=>({name:e,in:`path`,required:r.includes(e),schema:t}))}else i=y(e).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await C(t.querySchema),n=e.properties??{},r=e.required??[];for(let[e,t]of Object.entries(n))i.push({name:e,in:`query`,required:r.includes(e),schema:t})}let a={summary:t.summary??S(t.method,e),tags:t.tags??[x(e)],parameters:i};t.description&&(a.description=t.description),t.deprecated&&(a.deprecated=!0),t.bodySchema&&(a.requestBody={required:!0,content:{"application/json":{schema:await C(t.bodySchema)}}});let o={};for(let[e,n]of t.responseSamples){let t={schema:n.schema};n.example!==void 0&&(t.example=n.example),o[String(e)]={description:e<400?`Success`:`Error`,content:{"application/json":t}}}if(t.responseSchema){let e=await C(t.responseSchema);if(e&&Object.keys(e).length){let n=`200`;for(let e of t.responseSamples.keys())if(e>=200&&e<300){n=String(e);break}o[n]={description:`Success`,content:{"application/json":{schema:e}}}}}Object.keys(o).length===0&&(o[200]={description:`Success`}),a.responses=o,n[r][t.method]=a}return{openapi:`3.1.0`,info:{title:t.title??`API`,version:t.version??`1.0.0`,...t.description?{description:t.description}:{}},...t.servers?{servers:t.servers}:{},paths:n}}const P=new WeakMap;var F=class t{router;routes=[];mountedRouters=[];sampleMode=`off`;scheduleSpecWrite;constructor(){this.router=e.Router(),P.set(this.router,this)}useMiddleware(e){return this.router.use(e),this}getRouter(){return this.router}use(e,...n){let r=typeof e==`string`,i=r?e:``,a=(r?n:[e,...n]).map(e=>{if(e instanceof t)return this.trackMounted(i,e),e.getRouter();let n=P.get(e);return n&&this.trackMounted(i,n),e});return r?this.router.use(e,...a):this.router.use(...a),this}mount(e,t){if(typeof e==`string`){let n=t;this.router.use(e,n.getRouter()),this.trackMounted(e,n)}else this.router.use(e.getRouter()),this.trackMounted(``,e);return this}getRouteMetadata(){let e=this.mountedRouters.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:e+p(t)}})));return[...this.routes,...e]}enableSampling(e=`redacted`,t,n=new Set){if(!n.has(this)){n.add(this),this.sampleMode=e,this.scheduleSpecWrite=t;for(let{router:r}of this.mountedRouters)r.enableSampling(e,t,n)}}trackMounted(e,t){this.mountedRouters.some(n=>n.router===t&&n.prefix===e)||(this.mountedRouters.push({prefix:e,router:t}),this.sampleMode!==`off`&&t.enableSampling(this.sampleMode,this.scheduleSpecWrite))}hydrateResponses(e,t=``,n=new Set){if(n.has(this))return;n.add(this);let r=e?.paths??{};for(let e of this.routes)for(let n of _(e)){let i=r[v(t+n)]?.[e.method]?.responses;if(i)for(let t of Object.keys(i)){let n=Number(t);if(Number.isNaN(n)||e.responseSamples.has(n))continue;let r=i[t]?.content?.[`application/json`];r?.schema&&e.responseSamples.set(n,r.example===void 0?{schema:r.schema}:{schema:r.schema,example:r.example})}}for(let{prefix:r,router:i}of this.mountedRouters)i.hydrateResponses(e,t+r,n)}docs(t={}){let n=e.Router(),r;if(t.specOutputPath){let e=t.specOutputPath,n=!1,i=!1,a=async()=>{if(n){i=!0;return}n=!0;try{let n=await N(this.getRouteMetadata(),t),r=await o(`fs/promises`),i=e.replace(/[/\\][^/\\]*$/,``);i&&i!==e&&await r.mkdir(i,{recursive:!0}).catch(()=>{});let a=globalThis.process?.pid??`0`,s=`${e}.${a}.tmp`;await r.writeFile(s,JSON.stringify(n,null,2),`utf8`),await r.rename(s,e)}catch{}finally{n=!1,i&&(i=!1,a())}},s;r=()=>{s&&clearTimeout(s),s=setTimeout(a,300),s.unref?.()},setImmediate(async()=>{try{let t=await(await o(`fs/promises`)).readFile(e,`utf8`).catch(()=>null);if(t)try{this.hydrateResponses(JSON.parse(t))}catch{}}catch{}await a()})}return t.sampleResponses!==!1&&this.enableSampling(t.sampleResponses===`live`?`live`:`redacted`,r),n.get(`/openapi.json`,async(e,n)=>{try{let e=await N(this.getRouteMetadata(),t);n.json(e)}catch(e){n.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),n.get(`/`,(e,n)=>{let r=`${e.baseUrl}/openapi.json`,i=t.cdnUrl??j;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(M(t.title??`API`,r,i))}),n}get(e,t,n){return this.registerRoute(`get`,e,t,n)}post(e,t,n){return this.registerRoute(`post`,e,t,n)}put(e,t,n){return this.registerRoute(`put`,e,t,n)}patch(e,t,n){return this.registerRoute(`patch`,e,t,n)}delete(e,t,n){return this.registerRoute(`delete`,e,t,n)}options(e,t,n){return this.registerRoute(`options`,e,t,n)}head(e,t,n){return this.registerRoute(`head`,e,t,n)}all(e,t,n){return this.registerRoute(`all`,e,t,n)}registerRoute(e,t,n,r){let i=[],a={method:e,path:t,responseSamples:new Map};if(this.routes.push(a),typeof n==`object`){let e=n;a.bodySchema=e.bodySchema,a.querySchema=e.querySchema,a.paramsSchema=e.paramsSchema,a.tags=e.tags,a.description=e.description,a.summary=e.summary,a.deprecated=e.deprecated,a.responseSchema=e.responseSchema,a.hidden=e.hidden,a.pathExample=e.pathExample,e.middleware&&i.push(...e.middleware),e.bodySchema&&i.push(this.createBodyValidationMiddleware(e.bodySchema)),e.querySchema&&i.push(this.createQueryValidationMiddleware(e.querySchema)),e.paramsSchema&&i.push(this.createParamsValidationMiddleware(e.paramsSchema)),i.push(r)}else i.push(n);let o=0,s=(e,t)=>{let n=e.statusCode,r=T(t),i=a.responseSamples.get(n),o=i?k(i.schema,r):r,s=this.sampleMode===`live`?i?.example??t:void 0,c=!i||JSON.stringify(i.schema)!==JSON.stringify(o);a.responseSamples.set(n,{schema:o,example:s}),c&&this.scheduleSpecWrite?.()};return this.router[e](f(t),(e,t,n)=>{if(this.sampleMode===`off`||a.hidden||o>=50||a.responseSamples.size>=10){n();return}o++;let r=t.json;t.json=function(e){return s(t,e),r.call(this,e)};let i=t.send;t.send=function(e){return typeof e==`object`&&e&&!Buffer.isBuffer(e)&&s(t,e),i.call(this,e)},n()},...i),this}createBodyValidationMiddleware(e){return async(t,n,a)=>{try{let i=r(e,t.body),o=i&&typeof i.then==`function`?await i:i;if(o&&`issues`in o&&o.issues){n.status(400).json({error:`Validation failed`,details:o.errors||o.issues});return}t.body=o&&`value`in o?o.value:o,a()}catch(e){i(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):a(e)}}}createParamsValidationMiddleware(e){return async(t,n,a)=>{try{let i=r(e,t.params),o=i&&typeof i.then==`function`?await i:i;if(o&&`issues`in o&&o.issues){n.status(400).json({error:`Validation failed`,details:o.errors||o.issues});return}t.params=o&&`value`in o?o.value:o,a()}catch(e){i(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):a(e)}}}createQueryValidationMiddleware(e){return async(t,n,a)=>{try{let i=r(e,t.query),o=i&&typeof i.then==`function`?await i:i;if(o&&`issues`in o&&o.issues){n.status(400).json({error:`Validation failed`,details:o.errors||o.issues});return}let s=o&&`value`in o?o.value:o;Object.defineProperty(t,"query",{value:s,writable:!1,enumerable:!0,configurable:!0}),a()}catch(e){i(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):a(e)}}}};function I(){return new F}function L(e){let t=new F;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function R(...e){let t=new F;for(let n of e)t=t.useMiddleware(n);return t}function z(e){return(Array.isArray(e)?e:[e]).map(e=>`prefix`in e?e:{prefix:``,router:e}).flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:e+p(t)}})))}async function B(e,t={}){return N(z(e),t)}function V(t,n={}){if(n.sampleResponses!==!1){let e=n.sampleResponses===`live`?`live`:`redacted`,r=Array.isArray(t)?t:[t];for(let t of r)(`prefix`in t?t.router:t).enableSampling(e)}let r=e.Router();return r.get(`/openapi.json`,async(e,r)=>{try{let e=await B(t,n);r.json(e)}catch(e){r.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),r.get(`/`,(e,t)=>{let r=`${e.baseUrl}/openapi.json`,i=n.cdnUrl??j;t.setHeader(`Content-Type`,`text/html; charset=utf-8`),t.send(M(n.title??`API`,r,i))}),r}export{F as TypedRouter,V as createDocs,I as createTypedRouter,L as createTypedRouterWithConfig,R as createTypedRouterWithMiddleware,a as defineMiddleware,B as generateOpenApiSpec,T as inferJsonSchema,i as isSchemaError,n as parseSchema,r as safeParseSchema};
12
+ </html>`}async function F(e,t){let n=Object.create(null);for(let t of e)if(!(t.method===`all`||t.hidden))for(let e of y(t)){let r=b(e);n[r]||(n[r]={});let i;if(t.paramsSchema){let e=await T(t.paramsSchema),n=e.properties??{},r=e.required??[];i=Object.entries(n).map(([e,t])=>({name:e,in:`path`,required:r.includes(e),schema:t}))}else i=x(e).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await T(t.querySchema),n=e.properties??{},r=e.required??[];for(let[e,t]of Object.entries(n))i.push({name:e,in:`query`,required:r.includes(e),schema:t})}let a={summary:t.summary??w(t.method,e),tags:t.tags??[C(e)],parameters:i};t.description&&(a.description=t.description),t.deprecated&&(a.deprecated=!0),t.bodySchema&&(a.requestBody={required:!0,content:{"application/json":{schema:await T(t.bodySchema)}}});let o={};for(let[e,n]of t.responseSamples){let t={schema:n.schema};n.example!==void 0&&(t.example=n.example),o[String(e)]={description:e<400?`Success`:`Error`,content:{"application/json":t}}}if(t.responseSchema){let e=await T(t.responseSchema);if(e&&Object.keys(e).length){let n=`200`;for(let e of t.responseSamples.keys())if(e>=200&&e<300){n=String(e);break}o[n]={description:`Success`,content:{"application/json":{schema:e}}}}}Object.keys(o).length===0&&(o[200]={description:`Success`}),a.responses=o,n[r][t.method]=a}return{openapi:`3.1.0`,info:{title:t.title??`API`,version:t.version??`1.0.0`,...t.description?{description:t.description}:{}},...t.servers?{servers:t.servers}:{},paths:n}}const I=new WeakMap;var L=class t{router;routes=[];mountedRouters=[];sampleMode=`off`;scheduleSpecWrite;globalValidationFailureHook;constructor(){this.router=e.Router(),I.set(this.router,this)}onValidationFailure(e){return this.globalValidationFailureHook=e,this}useMiddleware(e){return this.router.use(e),this}getRouter(){return this.router}use(e,...n){let r=typeof e==`string`,i=r?e:``,a=(r?n:[e,...n]).map(e=>{if(e instanceof t)return this.trackMounted(i,e),e.getRouter();let n=I.get(e);return n&&this.trackMounted(i,n),e});return r?this.router.use(e,...a):this.router.use(...a),this}mount(e,t){if(typeof e==`string`){let n=t;this.router.use(e,n.getRouter()),this.trackMounted(e,n)}else this.router.use(e.getRouter()),this.trackMounted(``,e);return this}getRouteMetadata(){let e=this.mountedRouters.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:y(t).map(t=>e+t)}})));return[...this.routes,...e]}enableSampling(e=`redacted`,t,n=new Set){if(!n.has(this)){n.add(this),this.sampleMode=e,this.scheduleSpecWrite=t;for(let{router:r}of this.mountedRouters)r.enableSampling(e,t,n)}}trackMounted(e,t){this.mountedRouters.some(n=>n.router===t&&n.prefix===e)||(this.mountedRouters.push({prefix:e,router:t}),this.sampleMode!==`off`&&t.enableSampling(this.sampleMode,this.scheduleSpecWrite))}hydrateResponses(e,t=``,n=new Set){if(n.has(this))return;n.add(this);let r=e?.paths??{};for(let e of this.routes)for(let n of y(e)){let i=r[b(t+n)]?.[e.method]?.responses;if(i)for(let t of Object.keys(i)){let n=Number(t);if(Number.isNaN(n)||e.responseSamples.has(n))continue;let r=i[t]?.content?.[`application/json`];r?.schema&&e.responseSamples.set(n,r.example===void 0?{schema:r.schema}:{schema:r.schema,example:r.example})}}for(let{prefix:r,router:i}of this.mountedRouters)i.hydrateResponses(e,t+r,n)}docs(t={}){let n=e.Router(),r;if(t.specOutputPath){let e=t.specOutputPath,n=!1,i=!1,a=async()=>{if(n){i=!0;return}n=!0;try{let n=await F(this.getRouteMetadata(),t),r=await c(`fs/promises`),i=e.replace(/[/\\][^/\\]*$/,``);i&&i!==e&&await r.mkdir(i,{recursive:!0}).catch(()=>{});let a=globalThis.process?.pid??`0`,o=`${e}.${a}.tmp`;await r.writeFile(o,JSON.stringify(n,null,2),`utf8`),await r.rename(o,e)}catch{}finally{n=!1,i&&(i=!1,a())}},o;r=()=>{o&&clearTimeout(o),o=setTimeout(a,300),o.unref?.()},setImmediate(async()=>{try{let t=await(await c(`fs/promises`)).readFile(e,`utf8`).catch(()=>null);if(t)try{this.hydrateResponses(JSON.parse(t))}catch{}}catch{}await a()})}return t.sampleResponses!==!1&&this.enableSampling(t.sampleResponses===`live`?`live`:`redacted`,r),n.get(`/openapi.json`,async(e,n)=>{try{let e=await F(this.getRouteMetadata(),t);n.json(e)}catch(e){n.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),n.get(`/`,(e,n)=>{let r=`${e.baseUrl}/openapi.json`,i=t.cdnUrl??N;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(P(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 o=n;a.bodySchema=o.bodySchema,a.querySchema=o.querySchema,a.paramsSchema=o.paramsSchema,a.tags=o.tags,a.description=o.description,a.summary=o.summary,a.deprecated=o.deprecated,a.responseSchema=o.responseSchema,a.hidden=o.hidden,a.pathExample=o.pathExample,o.middleware&&i.push(...o.middleware);let s=o.hooks?.onValidationFailure,c=o.hooks?.onBodyValidationFailure,l=o.hooks?.onQueryValidationFailure,u=o.hooks?.onParamsValidationFailure;o.bodySchema&&i.push(this.createBodyValidationMiddleware(o.bodySchema,e,t,o.pathExample,s,c)),o.querySchema&&i.push(this.createQueryValidationMiddleware(o.querySchema,e,t,o.pathExample,s,l)),o.paramsSchema&&i.push(this.createParamsValidationMiddleware(o.paramsSchema,e,t,o.pathExample,s,u)),i.push(r)}else i.push(n);let o=0,s=(e,t)=>{let n=e.statusCode,r=D(t),i=a.responseSamples.get(n),o=i?j(i.schema,r):r,s=this.sampleMode===`live`?i?.example??t:void 0,c=!i||JSON.stringify(i.schema)!==JSON.stringify(o);a.responseSamples.set(n,{schema:o,example:s}),c&&this.scheduleSpecWrite?.()};return this.router[e](m(t),(e,t,n)=>{if(this.sampleMode===`off`||a.hidden||o>=50||a.responseSamples.size>=10){n();return}o++;let r=t.json;t.json=function(e){return s(t,e),r.call(this,e)};let i=t.send;t.send=function(e){return typeof e==`object`&&e&&!Buffer.isBuffer(e)&&s(t,e),i.call(this,e)},n()},...i),this}notifyValidationFailure(e,t,n,r,i,a,o){if(!o&&!this.globalValidationFailureHook)return;let c={source:e,error:t,details:n,method:r,path:i,req:a};s(o,c),s(this.globalValidationFailureHook,c)}async runSchemaValidationHook(e,t,n,r,i,a,o,s){if(!e)return!1;let c=!1,l={error:t,details:n,method:r,path:i,req:a,res:o,next:((...e)=>{c=!0,s(...e)})};try{await e(l)}catch{}return c||o.headersSent}createBodyValidationMiddleware(e,t,n,a,s,c){return async(l,u,d)=>{try{let i=r(e,l.body),f=i&&typeof i.then==`function`?await i:i;if(f&&`issues`in f&&f.issues){let e=f.errors||f.issues,r=h({path:n,pathExample:a}),i=await this.runSchemaValidationHook(c,`Validation failed`,e,t,r,l,u,d);this.notifyValidationFailure(`body`,`Validation failed`,e,t,r,l,s),i||o({res:u,details:e});return}l.body=f&&`value`in f?f.value:f,d()}catch(e){if(i(e)){let r=e.errors||e.issues,i=h({path:n,pathExample:a}),f=await this.runSchemaValidationHook(c,`Validation failed`,r,t,i,l,u,d);this.notifyValidationFailure(`body`,`Validation failed`,r,t,i,l,s),f||o({res:u,details:r})}else d(e)}}}createParamsValidationMiddleware(e,t,n,a,s,c){return async(l,u,d)=>{try{let i=r(e,l.params),f=i&&typeof i.then==`function`?await i:i;if(f&&`issues`in f&&f.issues){let e=f.errors||f.issues,r=h({path:n,pathExample:a}),i=await this.runSchemaValidationHook(c,`Validation failed`,e,t,r,l,u,d);this.notifyValidationFailure(`params`,`Validation failed`,e,t,r,l,s),i||o({res:u,details:e});return}l.params=f&&`value`in f?f.value:f,d()}catch(e){if(i(e)){let r=e.errors||e.issues,i=h({path:n,pathExample:a}),f=await this.runSchemaValidationHook(c,`Validation failed`,r,t,i,l,u,d);this.notifyValidationFailure(`params`,`Validation failed`,r,t,i,l,s),f||o({res:u,details:r})}else d(e)}}}createQueryValidationMiddleware(e,t,n,a,s,c){return async(l,u,d)=>{try{let i=r(e,l.query),f=i&&typeof i.then==`function`?await i:i;if(f&&`issues`in f&&f.issues){let e=f.errors||f.issues,r=h({path:n,pathExample:a}),i=await this.runSchemaValidationHook(c,`Validation failed`,e,t,r,l,u,d);this.notifyValidationFailure(`query`,`Validation failed`,e,t,r,l,s),i||o({res:u,details:e});return}let p=f&&`value`in f?f.value:f;Object.defineProperty(l,"query",{value:p,writable:!1,enumerable:!0,configurable:!0}),d()}catch(e){if(i(e)){let r=e.errors||e.issues,i=h({path:n,pathExample:a}),f=await this.runSchemaValidationHook(c,`Validation failed`,r,t,i,l,u,d);this.notifyValidationFailure(`query`,`Validation failed`,r,t,i,l,s),f||o({res:u,details:r})}else d(e)}}}};function R(){return new L}function z(e){let t=new L;return e?.errorHandler&&t.getRouter().use(e.errorHandler),e?.hooks?.onValidationFailure&&t.onValidationFailure(e.hooks.onValidationFailure),t}function B(...e){let t=new L;for(let n of e)t=t.useMiddleware(n);return t}function V(e){return(Array.isArray(e)?e:[e]).map(e=>`prefix`in e?e:{prefix:``,router:e}).flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:y(t).map(t=>e+t)}})))}async function H(e,t={}){return F(V(e),t)}function U(t,n={}){if(n.sampleResponses!==!1){let e=n.sampleResponses===`live`?`live`:`redacted`,r=Array.isArray(t)?t:[t];for(let t of r)(`prefix`in t?t.router:t).enableSampling(e)}let r=e.Router();return r.get(`/openapi.json`,async(e,r)=>{try{let e=await H(t,n);r.json(e)}catch(e){r.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),r.get(`/`,(e,t)=>{let r=`${e.baseUrl}/openapi.json`,i=n.cdnUrl??N;t.setHeader(`Content-Type`,`text/html; charset=utf-8`),t.send(P(n.title??`API`,r,i))}),r}export{L as TypedRouter,U as createDocs,R as createTypedRouter,z as createTypedRouterWithConfig,B as createTypedRouterWithMiddleware,o as defaultValidationHandler,a as defineMiddleware,H as generateOpenApiSpec,D as inferJsonSchema,i as isSchemaError,n as parseSchema,r as safeParseSchema};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minisylar/express-typed-router",
3
- "version": "1.9.8",
3
+ "version": "1.9.10",
4
4
  "description": "A strongly-typed Express router with Zod validation and automatic type inference for params, body, query, and middleware",
5
5
  "type": "module",
6
6
  "main": "dist/schema-router.cjs",