@minisylar/express-typed-router 1.9.5 → 1.9.7
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 +74 -11
- package/dist/schema-router.cjs +5 -5
- package/dist/schema-router.d.cts +48 -20
- package/dist/schema-router.d.mts +48 -20
- package/dist/schema-router.mjs +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -173,6 +173,44 @@ router.get("/admin", { middleware: [requireAdmin] }, (req, res) => {
|
|
|
173
173
|
|
|
174
174
|
> **Note:** `useMiddleware` returns a new router instance. Use method chaining or capture the return value — see [Common Patterns](#common-patterns).
|
|
175
175
|
|
|
176
|
+
### Reusable route handlers
|
|
177
|
+
|
|
178
|
+
Use `InferSchemaHandler` when the same typed handler is registered for multiple
|
|
179
|
+
routes. Pass the same route options as the route; middleware can be supplied as
|
|
180
|
+
one middleware type or as a tuple.
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
import {
|
|
184
|
+
InferSchemaHandler,
|
|
185
|
+
type TypedMiddleware,
|
|
186
|
+
} from "@minisylar/express-typed-router";
|
|
187
|
+
|
|
188
|
+
const auth: TypedMiddleware<{ userId: string }> = (req, _res, next) => {
|
|
189
|
+
req.userId = "user-123";
|
|
190
|
+
next();
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
type WebhookHandler = InferSchemaHandler<{
|
|
194
|
+
bodySchema: typeof WebhookSchema;
|
|
195
|
+
middleware: typeof auth;
|
|
196
|
+
}>;
|
|
197
|
+
|
|
198
|
+
const consolidatedHandler: WebhookHandler = (req, res) => {
|
|
199
|
+
res.json({ receivedBy: req.userId, event: req.body });
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
router.post(
|
|
203
|
+
"/webhooks/events/*path",
|
|
204
|
+
{ bodySchema: WebhookSchema, middleware: [auth] },
|
|
205
|
+
consolidatedHandler,
|
|
206
|
+
);
|
|
207
|
+
router.post(
|
|
208
|
+
"/hooks/event",
|
|
209
|
+
{ bodySchema: WebhookSchema, middleware: [auth] },
|
|
210
|
+
consolidatedHandler,
|
|
211
|
+
);
|
|
212
|
+
```
|
|
213
|
+
|
|
176
214
|
---
|
|
177
215
|
|
|
178
216
|
## ✨ OpenAPI and docs
|
|
@@ -227,6 +265,21 @@ app.use("/docs", api.docs({ title: "My API", version: "1.0.0" }));
|
|
|
227
265
|
// Discovers all sub-routers and merges routes with correct prefixes
|
|
228
266
|
```
|
|
229
267
|
|
|
268
|
+
**Regex route docs** — simple top-level alternatives are expanded into
|
|
269
|
+
separate OpenAPI paths automatically:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
router.post(
|
|
273
|
+
/(\/webhooks\/events.*)|(\/hooks\/event\/?$)/,
|
|
274
|
+
{ bodySchema: WebhookSchema, middleware: [auth] },
|
|
275
|
+
consolidatedHandler,
|
|
276
|
+
);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
This produces `/webhooks/events/{path}` and `/hooks/event` in the spec.
|
|
280
|
+
For complex regular expressions, provide `pathExample` as a documentation
|
|
281
|
+
stand-in; runtime matching is unaffected.
|
|
282
|
+
|
|
230
283
|
### Response schemas from live traffic
|
|
231
284
|
|
|
232
285
|
You don't have to declare what your routes return. The library **observes real responses** (`res.json` / `res.send`), **infers a JSON Schema** from them, and **merges across samples** — so it learns field types, which fields are nullable, and which are optional. This drives both the docs UI and `openapi-typescript` (real response types instead of `unknown`).
|
|
@@ -249,21 +302,27 @@ By default this runs in **redacted** mode: only the shape is kept, never the val
|
|
|
249
302
|
|
|
250
303
|
Control it with `sampleResponses`:
|
|
251
304
|
|
|
252
|
-
| Value
|
|
253
|
-
|
|
254
|
-
| `true` _(default)_ | **Redacted** — infer schema only. Real values discarded at capture time. Safe to expose.
|
|
255
|
-
| `"live"`
|
|
256
|
-
| `false`
|
|
305
|
+
| Value | Behavior |
|
|
306
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
307
|
+
| `true` _(default)_ | **Redacted** — infer schema only. Real values discarded at capture time. Safe to expose. |
|
|
308
|
+
| `"live"` | Infer schema **and** attach one real captured response as an example. ⚠️ Examples contain actual data — use only for trusted/internal docs. |
|
|
309
|
+
| `false` | Don't observe responses at all. |
|
|
257
310
|
|
|
258
311
|
```ts
|
|
259
312
|
// Safe default — schema only, no real data
|
|
260
313
|
app.use("/docs", api.docs({ title: "My API", version: "1.0.0" }));
|
|
261
314
|
|
|
262
315
|
// Show real example payloads (internal docs only)
|
|
263
|
-
app.use(
|
|
316
|
+
app.use(
|
|
317
|
+
"/docs",
|
|
318
|
+
api.docs({ title: "My API", version: "1.0.0", sampleResponses: "live" }),
|
|
319
|
+
);
|
|
264
320
|
|
|
265
321
|
// Disable entirely
|
|
266
|
-
app.use(
|
|
322
|
+
app.use(
|
|
323
|
+
"/docs",
|
|
324
|
+
api.docs({ title: "My API", version: "1.0.0", sampleResponses: false }),
|
|
325
|
+
);
|
|
267
326
|
```
|
|
268
327
|
|
|
269
328
|
> Exclude an individual sensitive route from docs with `hidden: true` in its route options — works in any mode.
|
|
@@ -352,7 +411,7 @@ Edit a route, save, and your client types update on their own.
|
|
|
352
411
|
|
|
353
412
|
**Prefer to keep it manual?** Skip `nodemon` and `npm-run-all2` entirely — just run the server with `node --watch src/server.ts` and regenerate types on demand with `openapi-typescript ./openapi.json -o ./api.d.ts` whenever you change your API.
|
|
354
413
|
|
|
355
|
-
> ⚠️ **Avoid a restart loop.** Write the generated `api.d.ts` **outside** the path your server watcher restarts on (or add it to the watcher's ignore list). If your server watches `*.ts` in `src/` and you output the types
|
|
414
|
+
> ⚠️ **Avoid a restart loop.** Write the generated `api.d.ts` **outside** the path your server watcher restarts on (or add it to the watcher's ignore list). If your server watches `*.ts` in `src/` and you output the types _into_ `src/`, you get: type-gen writes `api.d.ts` → server restarts → spec rewrites → type-gen runs again → ♻️. Putting it in a separate folder (e.g. `shared/`, `generated/`) avoids this.
|
|
356
415
|
|
|
357
416
|
### Use with `openapi-fetch`
|
|
358
417
|
|
|
@@ -497,7 +556,7 @@ app.use("/docs", api.docs({ title: "My API", version: "1.0.0" }));
|
|
|
497
556
|
|
|
498
557
|
### `querySchema` booleans: `req.query.flag` is text, not a real boolean
|
|
499
558
|
|
|
500
|
-
Query strings have no wire format for booleans — `?flag=false` arrives at your schema as the
|
|
559
|
+
Query strings have no wire format for booleans — `?flag=false` arrives at your schema as the _string_ `"false"`, no matter what the client intended. This isn't specific to this library; it's true of every Express app, but it trips people up specifically when picking a schema for `querySchema`:
|
|
501
560
|
|
|
502
561
|
```ts
|
|
503
562
|
router.get(
|
|
@@ -512,7 +571,9 @@ router.get(
|
|
|
512
571
|
The obvious fix has its own trap:
|
|
513
572
|
|
|
514
573
|
```ts
|
|
515
|
-
{
|
|
574
|
+
{
|
|
575
|
+
querySchema: z.object({ flag: z.coerce.boolean() });
|
|
576
|
+
} // ❌ silently wrong
|
|
516
577
|
```
|
|
517
578
|
|
|
518
579
|
`z.coerce.boolean()` does plain JS `Boolean(value)` — and `Boolean("false")` is `true`, because any non-empty string is truthy in JavaScript. `?flag=false` stops erroring and instead silently becomes `true`.
|
|
@@ -520,7 +581,9 @@ The obvious fix has its own trap:
|
|
|
520
581
|
Use a schema that actually parses the text:
|
|
521
582
|
|
|
522
583
|
```ts
|
|
523
|
-
{
|
|
584
|
+
{
|
|
585
|
+
querySchema: z.object({ flag: z.stringbool() });
|
|
586
|
+
} // ✅ zod v4+
|
|
524
587
|
```
|
|
525
588
|
|
|
526
589
|
`z.stringbool()` parses `"true"`/`"false"` (and a few common variants) into the correct boolean. This isn't an express-typed-router limitation to work around — the router runs whatever schema you give it exactly as written; the fix is picking the right schema primitive, not something the router could safely guess on your behalf.
|
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)}const p=Function(`m`,`return import(m)`);let m,h;async function g(e){m??=await p(`module`),h??=await p(`url`);let t=[],n=globalThis.process?.argv?.[1];n&&t.push(h.pathToFileURL(n).href);let r=globalThis.process?.cwd?.()??``;r&&t.push(h.pathToFileURL(r+`/`).href);for(let n of t)try{let t=m.createRequire(n).resolve(e);return await p(h.pathToFileURL(t).href)}catch{}return p(e)}const _=new WeakMap,v=/\(\?<[^>]+>/;function y(e){return typeof e==`string`&&v.test(e)?new RegExp(e):e}function b(e){if(typeof e.path==`string`)return e.path;if(e.pathExample)return e.pathExample;let t=0;return e.path.source.replace(/\\\//g,`/`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function x(e){return e.replace(/\{([^{}]*)\}/g,`$1`).replace(/:([A-Za-z0-9_]+)(?:\([^)]*\))?[?+*]?/g,`{$1}`).replace(/\(\?<([A-Za-z0-9_]+)>[^)]*\)/g,`{$1}`).replace(/^\^|\$$/g,``).replace(/\/{2,}/g,`/`)}function
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));let c=require("express");c=s(c,1);let l=require("@standard-schema/utils");function u(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`){let e=n[`~standard`].validate(t);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new l.SchemaError(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function d(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function f(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}const p=Function(`m`,`return import(m)`);let m,h;async function g(e){m??=await p(`module`),h??=await p(`url`);let t=[],n=globalThis.process?.argv?.[1];n&&t.push(h.pathToFileURL(n).href);let r=globalThis.process?.cwd?.()??``;r&&t.push(h.pathToFileURL(r+`/`).href);for(let n of t)try{let t=m.createRequire(n).resolve(e);return await p(h.pathToFileURL(t).href)}catch{}return p(e)}const _=new WeakMap,v=/\(\?<[^>]+>/;function y(e){return typeof e==`string`&&v.test(e)?new RegExp(e):e}function b(e){if(typeof e.path==`string`)return e.path;if(e.pathExample)return e.pathExample;let t=0;return e.path.source.replace(/\\\//g,`/`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function x(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 S(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 C(e){let t=0;return S(e).replace(/^\^|\$$/g,``).replace(/\\\//g,`/`).replace(/\.\*|\.\+/g,`/:path`).replace(/\/?\?$/,``).replace(/\(\?<([A-Za-z0-9_]+)>[^()]*\)/g,`:$1`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function w(e){if(typeof e.path==`string`||e.pathExample)return[b(e)];let t=x(e.path.source);return t.length===1?[b(e)]:t.map(C)}function T(e){return e.replace(/\{([^{}]*)\}/g,`$1`).replace(/:([A-Za-z0-9_]+)(?:\([^)]*\))?[?+*]?/g,`{$1}`).replace(/\(\?<([A-Za-z0-9_]+)>[^)]*\)/g,`{$1}`).replace(/^\^|\$$/g,``).replace(/\/{2,}/g,`/`)}function E(e){let t=e.replace(/\{([^{}]*)\}/g,`$1`);return[...t.matchAll(/:([A-Za-z0-9_]+)/g),...t.matchAll(/\(\?<([A-Za-z0-9_]+)>/g)].map(e=>e[1])}function D(e){return e.startsWith(`:`)||e.startsWith(`*`)||e.includes(`(?<`)}function O(e){return e.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(Boolean).find(e=>!D(e))??`default`}function k(e,t){let n=t.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(e=>e&&!D(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 A(e){let t=_.get(e);if(t)return t;let n=await j(e);return _.set(e,n),n}async function j(e){if(typeof e.toJsonSchema==`function`)try{return e.toJsonSchema()}catch{}let t=e[`~standard`]?.vendor;if(t===`zod`){try{let t=await g(`zod`);if(typeof t.toJSONSchema==`function`)return t.toJSONSchema(e)}catch{}try{let t=await g(`zod-to-json-schema`),n=t.zodToJsonSchema??t.default?.zodToJsonSchema;if(typeof n==`function`)return n(e)}catch{}}if(t===`valibot`)try{let t=await g(`@valibot/to-json-schema`),n=t.toJsonSchema??t.default?.toJsonSchema;if(typeof n==`function`)return n(e)}catch{}if(t===`effect`)try{let t=await g(`effect`),n=t.JSONSchema?.make??t.default?.JSONSchema?.make;if(typeof n==`function`)return n(e)}catch{}return{}}function M(e){return N(e,0,new WeakSet)}function N(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 N(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=N(e[0],t+1,n);for(let a=1;a<r;a++)i=I(i,N(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]=N(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 P(e){return!e||e.type===void 0?new Set:new Set(Array.isArray(e.type)?e.type:[e.type])}function F(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 I(e,t){if(!e||Object.keys(e).length===0)return t??{};if(!t||Object.keys(t).length===0)return e??{};let n=new Set([...P(e),...P(t)]),r={},i=F(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]=I(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=I(e.items,t.items);n&&Object.keys(n).length&&(r.items=n)}return r}function L(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`)}const R=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function z(e,t,n){return`<!doctype html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
|
-
<title>${
|
|
4
|
+
<title>${L(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="${L(t)}"><\/script>
|
|
10
|
+
<script src="${L(n)}"><\/script>
|
|
11
11
|
</body>
|
|
12
|
-
</html>`}async function
|
|
12
|
+
</html>`}async function B(e,t){let n=Object.create(null);for(let t of e)if(!(t.method===`all`||t.hidden))for(let e of w(t)){let r=T(e);n[r]||(n[r]={});let i;if(t.paramsSchema){let e=await A(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=E(e).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await A(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??k(t.method,e),tags:t.tags??[O(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 A(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 A(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 V=new WeakMap;var H=class e{router;routes=[];mountedRouters=[];sampleMode=`off`;scheduleSpecWrite;constructor(){this.router=c.default.Router(),V.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=V.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+b(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 w(e)){let i=r[T(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 B(this.getRouteMetadata(),e),r=await p(`fs/promises`),i=t.replace(/[/\\][^/\\]*$/,``);i&&i!==t&&await r.mkdir(i,{recursive:!0}).catch(()=>{});let a=globalThis.process?.pid??`0`,o=`${t}.${a}.tmp`;await r.writeFile(o,JSON.stringify(n,null,2),`utf8`),await r.rename(o,t)}catch{}finally{r=!1,i&&(i=!1,a())}},o;n=()=>{o&&clearTimeout(o),o=setTimeout(a,300),o.unref?.()},setImmediate(async()=>{try{let e=await(await p(`fs/promises`)).readFile(t,`utf8`).catch(()=>null);if(e)try{this.hydrateResponses(JSON.parse(e))}catch{}}catch{}await a()})}return e.sampleResponses!==!1&&this.enableSampling(e.sampleResponses===`live`?`live`:`redacted`,n),t.get(`/openapi.json`,async(t,n)=>{try{let t=await B(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??R;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(z(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=M(t),i=a.responseSamples.get(n),o=i?I(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](y(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 U(){return new H}function W(e){let t=new H;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function G(...e){let t=new H;for(let n of e)t=t.useMiddleware(n);return t}function K(e,t={}){let n=(Array.isArray(e)?e:[e]).map(e=>`prefix`in e?e:{prefix:``,router:e});if(t.sampleResponses!==!1){let e=t.sampleResponses===`live`?`live`:`redacted`;for(let{router:t}of n)t.enableSampling(e)}let r=c.default.Router();return r.get(`/openapi.json`,async(e,r)=>{try{let e=await B(n.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:e+b(t)}}))),t);r.json(e)}catch(e){r.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),r.get(`/`,(e,n)=>{let r=`${e.baseUrl}/openapi.json`,i=t.cdnUrl??R;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(z(t.title??`API`,r,i))}),r}exports.TypedRouter=H,exports.createDocs=K,exports.createTypedRouter=U,exports.createTypedRouterWithConfig=W,exports.createTypedRouterWithMiddleware=G,exports.inferJsonSchema=M,exports.isSchemaError=f,exports.parseSchema=u,exports.safeParseSchema=d;
|
package/dist/schema-router.d.cts
CHANGED
|
@@ -114,12 +114,12 @@ type RequestOnlyMiddleware<TReq extends Record<string, any>> = TypedMiddleware<T
|
|
|
114
114
|
type LocalsOnlyMiddleware<TLocals extends Record<string, any>> = TypedMiddleware<{}, TLocals>;
|
|
115
115
|
type InferMiddlewareProps<T extends readonly TypedMiddleware<any, any>[]> = T extends readonly [infer First, ...infer Rest] ? First extends TypedMiddleware<infer FirstReq, any> ? Rest extends readonly TypedMiddleware<any, any>[] ? FirstReq & InferMiddlewareProps<Rest> : FirstReq : {} : {};
|
|
116
116
|
type InferMiddlewareLocals<T extends readonly TypedMiddleware<any, any>[]> = T extends readonly [infer First, ...infer Rest] ? First extends TypedMiddleware<any, infer FirstLocals> ? Rest extends readonly TypedMiddleware<any, any>[] ? FirstLocals & InferMiddlewareLocals<Rest> : FirstLocals : {} : {};
|
|
117
|
-
type SchemaRequest<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}, ParamsSchema extends SchemaLike | undefined = undefined> = Omit<Request, "params" | "query" | "body"> & {
|
|
118
|
-
params: ParamsSchema extends undefined ? ExtractRouteParams<Path> : InferSchemaOutput<ParamsSchema
|
|
117
|
+
type SchemaRequest<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}, ParamsSchema extends SchemaLike | undefined = undefined, ParamsOverride extends Record<string, any> | undefined = undefined> = Omit<Request, "params" | "query" | "body"> & {
|
|
118
|
+
params: ParamsOverride extends undefined ? ParamsSchema extends undefined ? ExtractRouteParams<Path> : InferSchemaOutput<ParamsSchema> : ParamsOverride;
|
|
119
119
|
body: BodySchema extends undefined ? unknown : InferSchemaOutput<BodySchema>;
|
|
120
120
|
query: QuerySchema extends undefined ? unknown : InferSchemaOutput<QuerySchema>;
|
|
121
121
|
} & MiddlewareProps;
|
|
122
|
-
type SchemaRouteHandler<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}, ResponseLocals extends Record<string, any> = {}, ParamsSchema extends SchemaLike | undefined = undefined> = (req: SchemaRequest<Path, BodySchema, QuerySchema, MiddlewareProps, ParamsSchema>, res: Response<any, ResponseLocals>, next?: NextFunction) => void | undefined | Promise<void | undefined> | Response | Promise<Response> | Promise<Response | undefined>;
|
|
122
|
+
type SchemaRouteHandler<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}, ResponseLocals extends Record<string, any> = {}, ParamsSchema extends SchemaLike | undefined = undefined, ParamsOverride extends Record<string, any> | undefined = undefined> = (req: SchemaRequest<Path, BodySchema, QuerySchema, MiddlewareProps, ParamsSchema, ParamsOverride>, res: Response<any, ResponseLocals>, next?: NextFunction) => void | undefined | Promise<void | undefined> | Response | Promise<Response> | Promise<Response | undefined>;
|
|
123
123
|
/**
|
|
124
124
|
* Options for defining a typed route, including schemas and middleware.
|
|
125
125
|
*
|
|
@@ -164,6 +164,34 @@ interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, Qu
|
|
|
164
164
|
*/
|
|
165
165
|
pathExample?: string;
|
|
166
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* The route options accepted by {@link InferSchemaHandler}.
|
|
169
|
+
*
|
|
170
|
+
* This is derived from {@link RouteOptions}, so adding a route option keeps
|
|
171
|
+
* this helper's accepted shape in sync automatically. Middleware may be
|
|
172
|
+
* written as a single middleware type for reusable handlers, or as the tuple
|
|
173
|
+
* passed to a route.
|
|
174
|
+
*/
|
|
175
|
+
type InferSchemaHandlerOptions = Partial<Omit<RouteOptions<any, any, any>, "middleware">> & {
|
|
176
|
+
middleware?: TypedMiddleware<any, any> | readonly TypedMiddleware<any, any>[];
|
|
177
|
+
};
|
|
178
|
+
type NormalizeHandlerMiddleware<Middleware> = Middleware extends TypedMiddleware<any, any> ? readonly [Middleware] : Middleware extends readonly TypedMiddleware<any, any>[] ? Middleware : readonly [];
|
|
179
|
+
type InferHandlerOption<Options extends InferSchemaHandlerOptions, Key extends "bodySchema" | "querySchema" | "paramsSchema" | "middleware"> = Key extends keyof Options ? Options[Key] : undefined;
|
|
180
|
+
/**
|
|
181
|
+
* Infer a reusable route handler from the same options passed to a route.
|
|
182
|
+
*
|
|
183
|
+
* The path is intentionally `string` because the handler can be registered
|
|
184
|
+
* for multiple paths. Use `paramsSchema` when those paths share a validated
|
|
185
|
+
* params shape that should be reflected in the handler type.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* type ListenHandler = InferSchemaHandler<{
|
|
189
|
+
* bodySchema: typeof ListenSchema;
|
|
190
|
+
* middleware: typeof auth;
|
|
191
|
+
* }>;
|
|
192
|
+
*/
|
|
193
|
+
type InferSchemaHandler<Options extends InferSchemaHandlerOptions = {}> = SchemaRouteHandler<string, InferHandlerOption<Options, "bodySchema">, InferHandlerOption<Options, "querySchema">, InferMiddlewareProps<NormalizeHandlerMiddleware<InferHandlerOption<Options, "middleware">>>, InferMiddlewareLocals<NormalizeHandlerMiddleware<InferHandlerOption<Options, "middleware">>>, InferHandlerOption<Options, "paramsSchema">, Record<string, string | string[] | undefined>>;
|
|
194
|
+
type RouteHandlerFromOptions<Path extends string, BodySchema extends SchemaLike | undefined, QuerySchema extends SchemaLike | undefined, ParamsSchema extends SchemaLike | undefined, RouterReq extends Record<string, any>, RouterLocals extends Record<string, any>, M extends TypedMiddleware<any, any>[]> = SchemaRouteHandler<Path, NoInfer<BodySchema>, NoInfer<QuerySchema>, RouterReq & InferMiddlewareProps<readonly [...M]>, RouterLocals & InferMiddlewareLocals<readonly [...M]>, NoInfer<ParamsSchema>>;
|
|
167
195
|
type DocMeta = Pick<RouteOptions, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden" | "pathExample">;
|
|
168
196
|
type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "all";
|
|
169
197
|
interface DocsOptions {
|
|
@@ -364,106 +392,106 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
|
|
|
364
392
|
querySchema?: QuerySchema;
|
|
365
393
|
paramsSchema?: ParamsSchema;
|
|
366
394
|
middleware?: [...M];
|
|
367
|
-
}, handler:
|
|
395
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
368
396
|
get(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
369
397
|
get<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
370
398
|
bodySchema?: BodySchema;
|
|
371
399
|
querySchema?: QuerySchema;
|
|
372
400
|
paramsSchema?: ParamsSchema;
|
|
373
401
|
middleware?: [...M];
|
|
374
|
-
}, handler:
|
|
402
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
375
403
|
post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
376
404
|
post<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
377
405
|
bodySchema?: BodySchema;
|
|
378
406
|
querySchema?: QuerySchema;
|
|
379
407
|
paramsSchema?: ParamsSchema;
|
|
380
408
|
middleware?: [...M];
|
|
381
|
-
}, handler:
|
|
409
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
382
410
|
post(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
383
411
|
post<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
384
412
|
bodySchema?: BodySchema;
|
|
385
413
|
querySchema?: QuerySchema;
|
|
386
414
|
paramsSchema?: ParamsSchema;
|
|
387
415
|
middleware?: [...M];
|
|
388
|
-
}, handler:
|
|
416
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
389
417
|
put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
390
418
|
put<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
391
419
|
bodySchema?: BodySchema;
|
|
392
420
|
querySchema?: QuerySchema;
|
|
393
421
|
paramsSchema?: ParamsSchema;
|
|
394
422
|
middleware?: [...M];
|
|
395
|
-
}, handler:
|
|
423
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
396
424
|
put(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
397
425
|
put<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
398
426
|
bodySchema?: BodySchema;
|
|
399
427
|
querySchema?: QuerySchema;
|
|
400
428
|
paramsSchema?: ParamsSchema;
|
|
401
429
|
middleware?: [...M];
|
|
402
|
-
}, handler:
|
|
430
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
403
431
|
patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
404
432
|
patch<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
405
433
|
bodySchema?: BodySchema;
|
|
406
434
|
querySchema?: QuerySchema;
|
|
407
435
|
paramsSchema?: ParamsSchema;
|
|
408
436
|
middleware?: [...M];
|
|
409
|
-
}, handler:
|
|
437
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
410
438
|
patch(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
411
439
|
patch<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
412
440
|
bodySchema?: BodySchema;
|
|
413
441
|
querySchema?: QuerySchema;
|
|
414
442
|
paramsSchema?: ParamsSchema;
|
|
415
443
|
middleware?: [...M];
|
|
416
|
-
}, handler:
|
|
444
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
417
445
|
delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
418
446
|
delete<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
419
447
|
querySchema?: QuerySchema;
|
|
420
448
|
paramsSchema?: ParamsSchema;
|
|
421
449
|
middleware?: [...M];
|
|
422
|
-
}, handler:
|
|
450
|
+
}, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
423
451
|
delete(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
424
452
|
delete<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
425
453
|
querySchema?: QuerySchema;
|
|
426
454
|
paramsSchema?: ParamsSchema;
|
|
427
455
|
middleware?: [...M];
|
|
428
|
-
}, handler:
|
|
456
|
+
}, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
429
457
|
options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
430
458
|
options<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
431
459
|
querySchema?: QuerySchema;
|
|
432
460
|
paramsSchema?: ParamsSchema;
|
|
433
461
|
middleware?: [...M];
|
|
434
|
-
}, handler:
|
|
462
|
+
}, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
435
463
|
options(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
436
464
|
options<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
437
465
|
querySchema?: QuerySchema;
|
|
438
466
|
paramsSchema?: ParamsSchema;
|
|
439
467
|
middleware?: [...M];
|
|
440
|
-
}, handler:
|
|
468
|
+
}, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
441
469
|
head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
442
470
|
head<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
443
471
|
querySchema?: QuerySchema;
|
|
444
472
|
paramsSchema?: ParamsSchema;
|
|
445
473
|
middleware?: [...M];
|
|
446
|
-
}, handler:
|
|
474
|
+
}, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
447
475
|
head(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
448
476
|
head<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
449
477
|
querySchema?: QuerySchema;
|
|
450
478
|
paramsSchema?: ParamsSchema;
|
|
451
479
|
middleware?: [...M];
|
|
452
|
-
}, handler:
|
|
480
|
+
}, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
453
481
|
all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
454
482
|
all<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
455
483
|
bodySchema?: BodySchema;
|
|
456
484
|
querySchema?: QuerySchema;
|
|
457
485
|
paramsSchema?: ParamsSchema;
|
|
458
486
|
middleware?: [...M];
|
|
459
|
-
}, handler:
|
|
487
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
460
488
|
all(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
461
489
|
all<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
462
490
|
bodySchema?: BodySchema;
|
|
463
491
|
querySchema?: QuerySchema;
|
|
464
492
|
paramsSchema?: ParamsSchema;
|
|
465
493
|
middleware?: [...M];
|
|
466
|
-
}, handler:
|
|
494
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
467
495
|
private registerRoute;
|
|
468
496
|
private createBodyValidationMiddleware;
|
|
469
497
|
private createParamsValidationMiddleware;
|
|
@@ -571,4 +599,4 @@ type RouterDocEntry = TypedRouter<any, any> | {
|
|
|
571
599
|
*/
|
|
572
600
|
declare function createDocs(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): express.Router & express.RequestHandler;
|
|
573
601
|
//#endregion
|
|
574
|
-
export { AdditionalLocals, AdditionalReqProps, AnyStandardSchema, DocsOptions, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteOptions, RouterConfig, RouterDocEntry, SafeParseResult, SchemaLike, SchemaRequest, SchemaRouteHandler, TypedMiddleware, TypedRouter, createDocs, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, inferJsonSchema, isSchemaError, parseSchema, safeParseSchema };
|
|
602
|
+
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, inferJsonSchema, isSchemaError, parseSchema, safeParseSchema };
|
package/dist/schema-router.d.mts
CHANGED
|
@@ -114,12 +114,12 @@ type RequestOnlyMiddleware<TReq extends Record<string, any>> = TypedMiddleware<T
|
|
|
114
114
|
type LocalsOnlyMiddleware<TLocals extends Record<string, any>> = TypedMiddleware<{}, TLocals>;
|
|
115
115
|
type InferMiddlewareProps<T extends readonly TypedMiddleware<any, any>[]> = T extends readonly [infer First, ...infer Rest] ? First extends TypedMiddleware<infer FirstReq, any> ? Rest extends readonly TypedMiddleware<any, any>[] ? FirstReq & InferMiddlewareProps<Rest> : FirstReq : {} : {};
|
|
116
116
|
type InferMiddlewareLocals<T extends readonly TypedMiddleware<any, any>[]> = T extends readonly [infer First, ...infer Rest] ? First extends TypedMiddleware<any, infer FirstLocals> ? Rest extends readonly TypedMiddleware<any, any>[] ? FirstLocals & InferMiddlewareLocals<Rest> : FirstLocals : {} : {};
|
|
117
|
-
type SchemaRequest<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}, ParamsSchema extends SchemaLike | undefined = undefined> = Omit<Request, "params" | "query" | "body"> & {
|
|
118
|
-
params: ParamsSchema extends undefined ? ExtractRouteParams<Path> : InferSchemaOutput<ParamsSchema
|
|
117
|
+
type SchemaRequest<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}, ParamsSchema extends SchemaLike | undefined = undefined, ParamsOverride extends Record<string, any> | undefined = undefined> = Omit<Request, "params" | "query" | "body"> & {
|
|
118
|
+
params: ParamsOverride extends undefined ? ParamsSchema extends undefined ? ExtractRouteParams<Path> : InferSchemaOutput<ParamsSchema> : ParamsOverride;
|
|
119
119
|
body: BodySchema extends undefined ? unknown : InferSchemaOutput<BodySchema>;
|
|
120
120
|
query: QuerySchema extends undefined ? unknown : InferSchemaOutput<QuerySchema>;
|
|
121
121
|
} & MiddlewareProps;
|
|
122
|
-
type SchemaRouteHandler<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}, ResponseLocals extends Record<string, any> = {}, ParamsSchema extends SchemaLike | undefined = undefined> = (req: SchemaRequest<Path, BodySchema, QuerySchema, MiddlewareProps, ParamsSchema>, res: Response<any, ResponseLocals>, next?: NextFunction) => void | undefined | Promise<void | undefined> | Response | Promise<Response> | Promise<Response | undefined>;
|
|
122
|
+
type SchemaRouteHandler<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}, ResponseLocals extends Record<string, any> = {}, ParamsSchema extends SchemaLike | undefined = undefined, ParamsOverride extends Record<string, any> | undefined = undefined> = (req: SchemaRequest<Path, BodySchema, QuerySchema, MiddlewareProps, ParamsSchema, ParamsOverride>, res: Response<any, ResponseLocals>, next?: NextFunction) => void | undefined | Promise<void | undefined> | Response | Promise<Response> | Promise<Response | undefined>;
|
|
123
123
|
/**
|
|
124
124
|
* Options for defining a typed route, including schemas and middleware.
|
|
125
125
|
*
|
|
@@ -164,6 +164,34 @@ interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, Qu
|
|
|
164
164
|
*/
|
|
165
165
|
pathExample?: string;
|
|
166
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* The route options accepted by {@link InferSchemaHandler}.
|
|
169
|
+
*
|
|
170
|
+
* This is derived from {@link RouteOptions}, so adding a route option keeps
|
|
171
|
+
* this helper's accepted shape in sync automatically. Middleware may be
|
|
172
|
+
* written as a single middleware type for reusable handlers, or as the tuple
|
|
173
|
+
* passed to a route.
|
|
174
|
+
*/
|
|
175
|
+
type InferSchemaHandlerOptions = Partial<Omit<RouteOptions<any, any, any>, "middleware">> & {
|
|
176
|
+
middleware?: TypedMiddleware<any, any> | readonly TypedMiddleware<any, any>[];
|
|
177
|
+
};
|
|
178
|
+
type NormalizeHandlerMiddleware<Middleware> = Middleware extends TypedMiddleware<any, any> ? readonly [Middleware] : Middleware extends readonly TypedMiddleware<any, any>[] ? Middleware : readonly [];
|
|
179
|
+
type InferHandlerOption<Options extends InferSchemaHandlerOptions, Key extends "bodySchema" | "querySchema" | "paramsSchema" | "middleware"> = Key extends keyof Options ? Options[Key] : undefined;
|
|
180
|
+
/**
|
|
181
|
+
* Infer a reusable route handler from the same options passed to a route.
|
|
182
|
+
*
|
|
183
|
+
* The path is intentionally `string` because the handler can be registered
|
|
184
|
+
* for multiple paths. Use `paramsSchema` when those paths share a validated
|
|
185
|
+
* params shape that should be reflected in the handler type.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* type ListenHandler = InferSchemaHandler<{
|
|
189
|
+
* bodySchema: typeof ListenSchema;
|
|
190
|
+
* middleware: typeof auth;
|
|
191
|
+
* }>;
|
|
192
|
+
*/
|
|
193
|
+
type InferSchemaHandler<Options extends InferSchemaHandlerOptions = {}> = SchemaRouteHandler<string, InferHandlerOption<Options, "bodySchema">, InferHandlerOption<Options, "querySchema">, InferMiddlewareProps<NormalizeHandlerMiddleware<InferHandlerOption<Options, "middleware">>>, InferMiddlewareLocals<NormalizeHandlerMiddleware<InferHandlerOption<Options, "middleware">>>, InferHandlerOption<Options, "paramsSchema">, Record<string, string | string[] | undefined>>;
|
|
194
|
+
type RouteHandlerFromOptions<Path extends string, BodySchema extends SchemaLike | undefined, QuerySchema extends SchemaLike | undefined, ParamsSchema extends SchemaLike | undefined, RouterReq extends Record<string, any>, RouterLocals extends Record<string, any>, M extends TypedMiddleware<any, any>[]> = SchemaRouteHandler<Path, NoInfer<BodySchema>, NoInfer<QuerySchema>, RouterReq & InferMiddlewareProps<readonly [...M]>, RouterLocals & InferMiddlewareLocals<readonly [...M]>, NoInfer<ParamsSchema>>;
|
|
167
195
|
type DocMeta = Pick<RouteOptions, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden" | "pathExample">;
|
|
168
196
|
type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "all";
|
|
169
197
|
interface DocsOptions {
|
|
@@ -364,106 +392,106 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
|
|
|
364
392
|
querySchema?: QuerySchema;
|
|
365
393
|
paramsSchema?: ParamsSchema;
|
|
366
394
|
middleware?: [...M];
|
|
367
|
-
}, handler:
|
|
395
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
368
396
|
get(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
369
397
|
get<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
370
398
|
bodySchema?: BodySchema;
|
|
371
399
|
querySchema?: QuerySchema;
|
|
372
400
|
paramsSchema?: ParamsSchema;
|
|
373
401
|
middleware?: [...M];
|
|
374
|
-
}, handler:
|
|
402
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
375
403
|
post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
376
404
|
post<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
377
405
|
bodySchema?: BodySchema;
|
|
378
406
|
querySchema?: QuerySchema;
|
|
379
407
|
paramsSchema?: ParamsSchema;
|
|
380
408
|
middleware?: [...M];
|
|
381
|
-
}, handler:
|
|
409
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
382
410
|
post(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
383
411
|
post<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
384
412
|
bodySchema?: BodySchema;
|
|
385
413
|
querySchema?: QuerySchema;
|
|
386
414
|
paramsSchema?: ParamsSchema;
|
|
387
415
|
middleware?: [...M];
|
|
388
|
-
}, handler:
|
|
416
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
389
417
|
put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
390
418
|
put<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
391
419
|
bodySchema?: BodySchema;
|
|
392
420
|
querySchema?: QuerySchema;
|
|
393
421
|
paramsSchema?: ParamsSchema;
|
|
394
422
|
middleware?: [...M];
|
|
395
|
-
}, handler:
|
|
423
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
396
424
|
put(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
397
425
|
put<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
398
426
|
bodySchema?: BodySchema;
|
|
399
427
|
querySchema?: QuerySchema;
|
|
400
428
|
paramsSchema?: ParamsSchema;
|
|
401
429
|
middleware?: [...M];
|
|
402
|
-
}, handler:
|
|
430
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
403
431
|
patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
404
432
|
patch<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
405
433
|
bodySchema?: BodySchema;
|
|
406
434
|
querySchema?: QuerySchema;
|
|
407
435
|
paramsSchema?: ParamsSchema;
|
|
408
436
|
middleware?: [...M];
|
|
409
|
-
}, handler:
|
|
437
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
410
438
|
patch(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
411
439
|
patch<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
412
440
|
bodySchema?: BodySchema;
|
|
413
441
|
querySchema?: QuerySchema;
|
|
414
442
|
paramsSchema?: ParamsSchema;
|
|
415
443
|
middleware?: [...M];
|
|
416
|
-
}, handler:
|
|
444
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
417
445
|
delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
418
446
|
delete<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
419
447
|
querySchema?: QuerySchema;
|
|
420
448
|
paramsSchema?: ParamsSchema;
|
|
421
449
|
middleware?: [...M];
|
|
422
|
-
}, handler:
|
|
450
|
+
}, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
423
451
|
delete(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
424
452
|
delete<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
425
453
|
querySchema?: QuerySchema;
|
|
426
454
|
paramsSchema?: ParamsSchema;
|
|
427
455
|
middleware?: [...M];
|
|
428
|
-
}, handler:
|
|
456
|
+
}, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
429
457
|
options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
430
458
|
options<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
431
459
|
querySchema?: QuerySchema;
|
|
432
460
|
paramsSchema?: ParamsSchema;
|
|
433
461
|
middleware?: [...M];
|
|
434
|
-
}, handler:
|
|
462
|
+
}, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
435
463
|
options(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
436
464
|
options<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
437
465
|
querySchema?: QuerySchema;
|
|
438
466
|
paramsSchema?: ParamsSchema;
|
|
439
467
|
middleware?: [...M];
|
|
440
|
-
}, handler:
|
|
468
|
+
}, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
441
469
|
head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
442
470
|
head<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
443
471
|
querySchema?: QuerySchema;
|
|
444
472
|
paramsSchema?: ParamsSchema;
|
|
445
473
|
middleware?: [...M];
|
|
446
|
-
}, handler:
|
|
474
|
+
}, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
447
475
|
head(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
448
476
|
head<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
449
477
|
querySchema?: QuerySchema;
|
|
450
478
|
paramsSchema?: ParamsSchema;
|
|
451
479
|
middleware?: [...M];
|
|
452
|
-
}, handler:
|
|
480
|
+
}, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
453
481
|
all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
454
482
|
all<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
455
483
|
bodySchema?: BodySchema;
|
|
456
484
|
querySchema?: QuerySchema;
|
|
457
485
|
paramsSchema?: ParamsSchema;
|
|
458
486
|
middleware?: [...M];
|
|
459
|
-
}, handler:
|
|
487
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
460
488
|
all(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
461
489
|
all<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
462
490
|
bodySchema?: BodySchema;
|
|
463
491
|
querySchema?: QuerySchema;
|
|
464
492
|
paramsSchema?: ParamsSchema;
|
|
465
493
|
middleware?: [...M];
|
|
466
|
-
}, handler:
|
|
494
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
467
495
|
private registerRoute;
|
|
468
496
|
private createBodyValidationMiddleware;
|
|
469
497
|
private createParamsValidationMiddleware;
|
|
@@ -571,4 +599,4 @@ type RouterDocEntry = TypedRouter<any, any> | {
|
|
|
571
599
|
*/
|
|
572
600
|
declare function createDocs(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): express.Router & express.RequestHandler;
|
|
573
601
|
//#endregion
|
|
574
|
-
export { AdditionalLocals, AdditionalReqProps, AnyStandardSchema, DocsOptions, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteOptions, RouterConfig, RouterDocEntry, SafeParseResult, SchemaLike, SchemaRequest, SchemaRouteHandler, TypedMiddleware, TypedRouter, createDocs, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, inferJsonSchema, isSchemaError, parseSchema, safeParseSchema };
|
|
602
|
+
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, 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)}const a=Function(`m`,`return import(m)`);let o,s;async function c(e){o??=await a(`module`),s??=await a(`url`);let t=[],n=globalThis.process?.argv?.[1];n&&t.push(s.pathToFileURL(n).href);let r=globalThis.process?.cwd?.()??``;r&&t.push(s.pathToFileURL(r+`/`).href);for(let n of t)try{let t=o.createRequire(n).resolve(e);return await a(s.pathToFileURL(t).href)}catch{}return a(e)}const l=new WeakMap,u=/\(\?<[^>]+>/;function d(e){return typeof e==`string`&&u.test(e)?new RegExp(e):e}function f(e){if(typeof e.path==`string`)return e.path;if(e.pathExample)return e.pathExample;let t=0;return e.path.source.replace(/\\\//g,`/`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function p(e){return e.replace(/\{([^{}]*)\}/g,`$1`).replace(/:([A-Za-z0-9_]+)(?:\([^)]*\))?[?+*]?/g,`{$1}`).replace(/\(\?<([A-Za-z0-9_]+)>[^)]*\)/g,`{$1}`).replace(/^\^|\$$/g,``).replace(/\/{2,}/g,`/`)}function
|
|
1
|
+
import e from"express";import{SchemaError as t}from"@standard-schema/utils";function n(e,n){let r=e;if(r&&r[`~standard`]&&typeof r[`~standard`].validate==`function`){let e=r[`~standard`].validate(n);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new t(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function r(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function i(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}const a=Function(`m`,`return import(m)`);let o,s;async function c(e){o??=await a(`module`),s??=await a(`url`);let t=[],n=globalThis.process?.argv?.[1];n&&t.push(s.pathToFileURL(n).href);let r=globalThis.process?.cwd?.()??``;r&&t.push(s.pathToFileURL(r+`/`).href);for(let n of t)try{let t=o.createRequire(n).resolve(e);return await a(s.pathToFileURL(t).href)}catch{}return a(e)}const l=new WeakMap,u=/\(\?<[^>]+>/;function d(e){return typeof e==`string`&&u.test(e)?new RegExp(e):e}function f(e){if(typeof e.path==`string`)return e.path;if(e.pathExample)return e.pathExample;let t=0;return e.path.source.replace(/\\\//g,`/`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function p(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 m(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 h(e){let t=0;return m(e).replace(/^\^|\$$/g,``).replace(/\\\//g,`/`).replace(/\.\*|\.\+/g,`/:path`).replace(/\/?\?$/,``).replace(/\(\?<([A-Za-z0-9_]+)>[^()]*\)/g,`:$1`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function g(e){if(typeof e.path==`string`||e.pathExample)return[f(e)];let t=p(e.path.source);return t.length===1?[f(e)]:t.map(h)}function _(e){return e.replace(/\{([^{}]*)\}/g,`$1`).replace(/:([A-Za-z0-9_]+)(?:\([^)]*\))?[?+*]?/g,`{$1}`).replace(/\(\?<([A-Za-z0-9_]+)>[^)]*\)/g,`{$1}`).replace(/^\^|\$$/g,``).replace(/\/{2,}/g,`/`)}function v(e){let t=e.replace(/\{([^{}]*)\}/g,`$1`);return[...t.matchAll(/:([A-Za-z0-9_]+)/g),...t.matchAll(/\(\?<([A-Za-z0-9_]+)>/g)].map(e=>e[1])}function y(e){return e.startsWith(`:`)||e.startsWith(`*`)||e.includes(`(?<`)}function b(e){return e.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(Boolean).find(e=>!y(e))??`default`}function x(e,t){let n=t.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(e=>e&&!y(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 S(e){let t=l.get(e);if(t)return t;let n=await C(e);return l.set(e,n),n}async function C(e){if(typeof e.toJsonSchema==`function`)try{return e.toJsonSchema()}catch{}let t=e[`~standard`]?.vendor;if(t===`zod`){try{let t=await c(`zod`);if(typeof t.toJSONSchema==`function`)return t.toJSONSchema(e)}catch{}try{let t=await c(`zod-to-json-schema`),n=t.zodToJsonSchema??t.default?.zodToJsonSchema;if(typeof n==`function`)return n(e)}catch{}}if(t===`valibot`)try{let t=await c(`@valibot/to-json-schema`),n=t.toJsonSchema??t.default?.toJsonSchema;if(typeof n==`function`)return n(e)}catch{}if(t===`effect`)try{let t=await c(`effect`),n=t.JSONSchema?.make??t.default?.JSONSchema?.make;if(typeof n==`function`)return n(e)}catch{}return{}}function w(e){return T(e,0,new WeakSet)}function T(e,t,n){if(e==null)return{type:`null`};if(t>=12)return{};if(e instanceof Date)return{type:`string`,format:`date-time`};if(typeof e==`bigint`)return{type:`integer`};if(typeof e==`object`&&typeof e.toJSON==`function`)return T(e.toJSON(),t,n);if(Array.isArray(e)){if(e.length===0||n.has(e))return{type:`array`,items:{}};n.add(e);let r=Math.min(e.length,20),i=T(e[0],t+1,n);for(let a=1;a<r;a++)i=O(i,T(e[a],t+1,n));return n.delete(e),{type:`array`,items:i}}switch(typeof e){case`string`:return{type:`string`};case`boolean`:return{type:`boolean`};case`number`:return Number.isFinite(e)?{type:Number.isInteger(e)?`integer`:`number`}:{type:`null`};case`object`:{if(n.has(e))return{type:`object`};n.add(e);let r={},i=[];for(let[a,o]of Object.entries(e))typeof o!=`function`&&o!==void 0&&(r[a]=T(o,t+1,n),i.push(a));n.delete(e);let a={type:`object`,properties:r};return i.length&&(a.required=i),a}default:return{}}}function E(e){return!e||e.type===void 0?new Set:new Set(Array.isArray(e.type)?e.type:[e.type])}function D(e){e.has(`integer`)&&e.has(`number`)&&e.delete(`integer`);let t=[...e];if(t.length!==0)return t.length===1?t[0]:t}function O(e,t){if(!e||Object.keys(e).length===0)return t??{};if(!t||Object.keys(t).length===0)return e??{};let n=new Set([...E(e),...E(t)]),r={},i=D(n);if(i!==void 0&&(r.type=i),n.has(`object`)&&(e.properties||t.properties)){let n=e.properties??{},i=t.properties??{},a={};for(let e of new Set([...Object.keys(n),...Object.keys(i)]))a[e]=O(n[e],i[e]);r.properties=a;let o=e.required??[],s=t.required??[],c=o.filter(e=>s.includes(e));c.length&&(r.required=c)}if(n.has(`array`)){let n=O(e.items,t.items);n&&Object.keys(n).length&&(r.items=n)}return r}function k(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`)}const A=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function j(e,t,n){return`<!doctype html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
|
-
<title>${
|
|
4
|
+
<title>${k(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="${k(t)}"><\/script>
|
|
10
|
+
<script src="${k(n)}"><\/script>
|
|
11
11
|
</body>
|
|
12
|
-
</html>`}async function
|
|
12
|
+
</html>`}async function M(e,t){let n=Object.create(null);for(let t of e)if(!(t.method===`all`||t.hidden))for(let e of g(t)){let r=_(e);n[r]||(n[r]={});let i;if(t.paramsSchema){let e=await S(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=v(e).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await S(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??x(t.method,e),tags:t.tags??[b(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 S(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 S(t.responseSchema);if(e&&Object.keys(e).length){let n=`200`;for(let e of t.responseSamples.keys())if(e>=200&&e<300){n=String(e);break}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 N=new WeakMap;var P=class t{router;routes=[];mountedRouters=[];sampleMode=`off`;scheduleSpecWrite;constructor(){this.router=e.Router(),N.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=N.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+f(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 g(e)){let i=r[_(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,o=async()=>{if(n){i=!0;return}n=!0;try{let n=await M(this.getRouteMetadata(),t),r=await a(`fs/promises`),i=e.replace(/[/\\][^/\\]*$/,``);i&&i!==e&&await r.mkdir(i,{recursive:!0}).catch(()=>{});let o=globalThis.process?.pid??`0`,s=`${e}.${o}.tmp`;await r.writeFile(s,JSON.stringify(n,null,2),`utf8`),await r.rename(s,e)}catch{}finally{n=!1,i&&(i=!1,o())}},s;r=()=>{s&&clearTimeout(s),s=setTimeout(o,300),s.unref?.()},setImmediate(async()=>{try{let t=await(await a(`fs/promises`)).readFile(e,`utf8`).catch(()=>null);if(t)try{this.hydrateResponses(JSON.parse(t))}catch{}}catch{}await o()})}return t.sampleResponses!==!1&&this.enableSampling(t.sampleResponses===`live`?`live`:`redacted`,r),n.get(`/openapi.json`,async(e,n)=>{try{let e=await M(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??A;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(j(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=w(t),i=a.responseSamples.get(n),o=i?O(i.schema,r):r,s=this.sampleMode===`live`?i?.example??t:void 0,c=!i||JSON.stringify(i.schema)!==JSON.stringify(o);a.responseSamples.set(n,{schema:o,example:s}),c&&this.scheduleSpecWrite?.()};return this.router[e](d(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 F(){return new P}function I(e){let t=new P;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function L(...e){let t=new P;for(let n of e)t=t.useMiddleware(n);return t}function R(t,n={}){let r=(Array.isArray(t)?t:[t]).map(e=>`prefix`in e?e:{prefix:``,router:e});if(n.sampleResponses!==!1){let e=n.sampleResponses===`live`?`live`:`redacted`;for(let{router:t}of r)t.enableSampling(e)}let i=e.Router();return i.get(`/openapi.json`,async(e,t)=>{try{let e=await M(r.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:e+f(t)}}))),n);t.json(e)}catch(e){t.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),i.get(`/`,(e,t)=>{let r=`${e.baseUrl}/openapi.json`,i=n.cdnUrl??A;t.setHeader(`Content-Type`,`text/html; charset=utf-8`),t.send(j(n.title??`API`,r,i))}),i}export{P as TypedRouter,R as createDocs,F as createTypedRouter,I as createTypedRouterWithConfig,L as createTypedRouterWithMiddleware,w 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.7",
|
|
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",
|