@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 +182 -1
- package/dist/schema-router.cjs +5 -5
- package/dist/schema-router.d.cts +154 -11
- package/dist/schema-router.d.mts +154 -11
- package/dist/schema-router.mjs +5 -5
- package/package.json +1 -1
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
|
|
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
|
|
package/dist/schema-router.cjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));let c=require("express");c=s(c,1);let l=require("@standard-schema/utils");function u(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`){let e=n[`~standard`].validate(t);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new l.SchemaError(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function d(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function f(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}function p(...e){return e}const
|
|
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,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`)}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>${
|
|
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="${
|
|
10
|
-
<script src="${
|
|
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;
|
package/dist/schema-router.d.cts
CHANGED
|
@@ -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
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
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
|
|
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 };
|
package/dist/schema-router.d.mts
CHANGED
|
@@ -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
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
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
|
|
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 };
|
package/dist/schema-router.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import e from"express";import{SchemaError as t}from"@standard-schema/utils";function n(e,n){let r=e;if(r&&r[`~standard`]&&typeof r[`~standard`].validate==`function`){let e=r[`~standard`].validate(n);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new t(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function r(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function i(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}function a(...e){return e}const
|
|
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,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`)}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>${
|
|
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="${
|
|
10
|
-
<script src="${
|
|
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.
|
|
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",
|