@minisylar/express-typed-router 1.9.5 → 1.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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 | Behavior |
253
- |---|---|
254
- | `true` _(default)_ | **Redacted** — infer schema only. Real values discarded at capture time. Safe to expose. |
255
- | `"live"` | Infer schema **and** attach one real captured response as an example. ⚠️ Examples contain actual data — use only for trusted/internal docs. |
256
- | `false` | Don't observe responses at all. |
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("/docs", api.docs({ title: "My API", version: "1.0.0", sampleResponses: "live" }));
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("/docs", api.docs({ title: "My API", version: "1.0.0", sampleResponses: false }));
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 *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.
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 *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`:
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
- { querySchema: z.object({ flag: z.coerce.boolean() }) } // ❌ silently wrong
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
- { querySchema: z.object({ flag: z.stringbool() }) } // ✅ zod v4+
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.
@@ -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 S(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 C(e){return e.startsWith(`:`)||e.startsWith(`*`)||e.includes(`(?<`)}function w(e){return e.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(Boolean).find(e=>!C(e))??`default`}function T(e,t){let n=t.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(e=>e&&!C(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 E(e){let t=_.get(e);if(t)return t;let n=await D(e);return _.set(e,n),n}async function D(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 O(e){return k(e,0,new WeakSet)}function k(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 k(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=k(e[0],t+1,n);for(let a=1;a<r;a++)i=M(i,k(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]=k(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 A(e){return!e||e.type===void 0?new Set:new Set(Array.isArray(e.type)?e.type:[e.type])}function j(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 M(e,t){if(!e||Object.keys(e).length===0)return t??{};if(!t||Object.keys(t).length===0)return e??{};let n=new Set([...A(e),...A(t)]),r={},i=j(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]=M(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=M(e.items,t.items);n&&Object.keys(n).length&&(r.items=n)}return r}function N(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}const P=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function F(e,t,n){return`<!doctype html>
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));let c=require("express");c=s(c,1);let l=require("@standard-schema/utils");function u(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`){let e=n[`~standard`].validate(t);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new l.SchemaError(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function d(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function f(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}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,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}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>${N(e)}</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="${N(t)}"><\/script>
10
- <script src="${N(n)}"><\/script>
9
+ <script id="api-reference" data-url="${L(t)}"><\/script>
10
+ <script src="${L(n)}"><\/script>
11
11
  </body>
12
- </html>`}async function I(e,t){let n=Object.create(null);for(let t of e){if(t.method===`all`||t.hidden)continue;let e=b(t),r=x(e);n[r]||(n[r]={});let i;if(t.paramsSchema){let e=await E(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=S(e).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await E(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??T(t.method,e),tags:t.tags??[w(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 E(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 E(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 L=new WeakMap;var R=class e{router;routes=[];mountedRouters=[];sampleMode=`off`;scheduleSpecWrite;constructor(){this.router=c.default.Router(),L.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=L.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){let n=r[x(t+b(e))]?.[e.method]?.responses;if(n)for(let t of Object.keys(n)){let r=Number(t);if(Number.isNaN(r)||e.responseSamples.has(r))continue;let i=n[t]?.content?.[`application/json`];i?.schema&&e.responseSamples.set(r,i.example===void 0?{schema:i.schema}:{schema:i.schema,example:i.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 I(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 I(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??P;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(F(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=O(t),i=a.responseSamples.get(n),o=i?M(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 z(){return new R}function B(e){let t=new R;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function V(...e){let t=new R;for(let n of e)t=t.useMiddleware(n);return t}function H(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 I(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??P;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(F(t.title??`API`,r,i))}),r}exports.TypedRouter=R,exports.createDocs=H,exports.createTypedRouter=z,exports.createTypedRouterWithConfig=B,exports.createTypedRouterWithMiddleware=V,exports.inferJsonSchema=O,exports.isSchemaError=f,exports.parseSchema=u,exports.safeParseSchema=d;
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;
@@ -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,33 @@ 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>>;
167
194
  type DocMeta = Pick<RouteOptions, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden" | "pathExample">;
168
195
  type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "all";
169
196
  interface DocsOptions {
@@ -571,4 +598,4 @@ type RouterDocEntry = TypedRouter<any, any> | {
571
598
  */
572
599
  declare function createDocs(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): express.Router & express.RequestHandler;
573
600
  //#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 };
601
+ 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 };
@@ -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,33 @@ 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>>;
167
194
  type DocMeta = Pick<RouteOptions, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden" | "pathExample">;
168
195
  type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "all";
169
196
  interface DocsOptions {
@@ -571,4 +598,4 @@ type RouterDocEntry = TypedRouter<any, any> | {
571
598
  */
572
599
  declare function createDocs(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): express.Router & express.RequestHandler;
573
600
  //#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 };
601
+ 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 };
@@ -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 m(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 h(e){return e.startsWith(`:`)||e.startsWith(`*`)||e.includes(`(?<`)}function g(e){return e.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(Boolean).find(e=>!h(e))??`default`}function _(e,t){let n=t.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(e=>e&&!h(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 v(e){let t=l.get(e);if(t)return t;let n=await y(e);return l.set(e,n),n}async function y(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 b(e){return x(e,0,new WeakSet)}function x(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 x(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=x(e[0],t+1,n);for(let a=1;a<r;a++)i=w(i,x(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]=x(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 S(e){return!e||e.type===void 0?new Set:new Set(Array.isArray(e.type)?e.type:[e.type])}function C(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 w(e,t){if(!e||Object.keys(e).length===0)return t??{};if(!t||Object.keys(t).length===0)return e??{};let n=new Set([...S(e),...S(t)]),r={},i=C(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]=w(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=w(e.items,t.items);n&&Object.keys(n).length&&(r.items=n)}return r}function T(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}const E=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function D(e,t,n){return`<!doctype html>
1
+ import e from"express";import{SchemaError as t}from"@standard-schema/utils";function n(e,n){let r=e;if(r&&r[`~standard`]&&typeof r[`~standard`].validate==`function`){let e=r[`~standard`].validate(n);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new t(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function r(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function i(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}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,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}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>${T(e)}</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="${T(t)}"><\/script>
10
- <script src="${T(n)}"><\/script>
9
+ <script id="api-reference" data-url="${k(t)}"><\/script>
10
+ <script src="${k(n)}"><\/script>
11
11
  </body>
12
- </html>`}async function O(e,t){let n=Object.create(null);for(let t of e){if(t.method===`all`||t.hidden)continue;let e=f(t),r=p(e);n[r]||(n[r]={});let i;if(t.paramsSchema){let e=await v(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=m(e).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await v(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??_(t.method,e),tags:t.tags??[g(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 v(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 v(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 k=new WeakMap;var A=class t{router;routes=[];mountedRouters=[];sampleMode=`off`;scheduleSpecWrite;constructor(){this.router=e.Router(),k.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=k.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){let n=r[p(t+f(e))]?.[e.method]?.responses;if(n)for(let t of Object.keys(n)){let r=Number(t);if(Number.isNaN(r)||e.responseSamples.has(r))continue;let i=n[t]?.content?.[`application/json`];i?.schema&&e.responseSamples.set(r,i.example===void 0?{schema:i.schema}:{schema:i.schema,example:i.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 O(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 O(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??E;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(D(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=b(t),i=a.responseSamples.get(n),o=i?w(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 j(){return new A}function M(e){let t=new A;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function N(...e){let t=new A;for(let n of e)t=t.useMiddleware(n);return t}function P(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 O(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??E;t.setHeader(`Content-Type`,`text/html; charset=utf-8`),t.send(D(n.title??`API`,r,i))}),i}export{A as TypedRouter,P as createDocs,j as createTypedRouter,M as createTypedRouterWithConfig,N as createTypedRouterWithMiddleware,b as inferJsonSchema,i as isSchemaError,n as parseSchema,r as safeParseSchema};
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.5",
3
+ "version": "1.9.6",
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",