@minisylar/express-typed-router 1.9.2 → 1.9.4
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 +18 -8
- package/dist/schema-router.cjs +5 -5
- package/dist/schema-router.d.cts +76 -184
- package/dist/schema-router.d.mts +76 -184
- package/dist/schema-router.mjs +5 -5
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @minisylar/express-typed-router
|
|
2
2
|
|
|
3
|
+
[](https://github.com/sponsors/Mini-Sylar)
|
|
4
|
+
|
|
3
5
|
A strongly typed Express router with **Standard Schema** validation, automatic type inference, and ✨ OpenAPI docs.
|
|
4
6
|
|
|
5
7
|
Define routes once, infer `params` / `body` / `query`, and generate a clean API spec for docs or client generation.
|
|
@@ -17,7 +19,7 @@ Define routes once, infer `params` / `body` / `query`, and generate a clean API
|
|
|
17
19
|
- **✨ OpenAPI docs** — generated from routes, schemas, and captured responses
|
|
18
20
|
- **Schema-agnostic** — any Standard Schema-compatible validator (Zod, Yup, Valibot, Arktype, Joi...)
|
|
19
21
|
- **Express 4 & 5** — common patterns supported
|
|
20
|
-
- **Client-friendly output** — generate `api.
|
|
22
|
+
- **Client-friendly output** — generate `api.d.ts` and build any client wrapper
|
|
21
23
|
|
|
22
24
|
---
|
|
23
25
|
|
|
@@ -266,7 +268,13 @@ app.use("/docs", api.docs({ title: "My API", version: "1.0.0", sampleResponses:
|
|
|
266
268
|
|
|
267
269
|
> Exclude an individual sensitive route from docs with `hidden: true` in its route options — works in any mode.
|
|
268
270
|
|
|
269
|
-
|
|
271
|
+
**How it persists:** schemas fill in as traffic flows. They're held in memory and, when `specOutputPath` is set, written to the file (debounced) as new shapes are observed. On startup the library **reloads** the existing file, so a restart doesn't reset what was already learned — the file is the durable store.
|
|
272
|
+
|
|
273
|
+
**Gotchas:**
|
|
274
|
+
|
|
275
|
+
- **Reset accumulated schemas** — inference is merge-only, so a field you _remove_ from a response lingers in the docs. To clear it, delete `openapi.json` and let it rebuild from current traffic.
|
|
276
|
+
- **`responseSchema` beats inference** — declare it on routes you want guaranteed-correct (and leak-proof); it overrides whatever traffic suggests.
|
|
277
|
+
- **`res.jsonp()` isn't captured** — only `res.json` and `res.send`. JSONP responses won't get an inferred schema (declare `responseSchema` if you need one).
|
|
270
278
|
|
|
271
279
|
### Schema library support for docs
|
|
272
280
|
|
|
@@ -315,7 +323,7 @@ app.use(
|
|
|
315
323
|
"scripts": {
|
|
316
324
|
"dev": "run-p dev:server dev:types",
|
|
317
325
|
"dev:server": "node --watch src/server.ts",
|
|
318
|
-
"dev:types": "nodemon -L --watch openapi.json --exec \"openapi-typescript ./openapi.json -o ./api.
|
|
326
|
+
"dev:types": "nodemon -L --watch openapi.json --exec \"openapi-typescript ./openapi.json -o ./api.d.ts\""
|
|
319
327
|
},
|
|
320
328
|
"devDependencies": {
|
|
321
329
|
"openapi-typescript": "^7.0.0",
|
|
@@ -334,21 +342,23 @@ npm run dev
|
|
|
334
342
|
That's it — one command runs everything in parallel:
|
|
335
343
|
|
|
336
344
|
- `dev:server` — runs your server with `node --watch` (Node 18.11+; no `tsx` needed on Node 23.6+). On every save the server restarts and the library **rewrites `openapi.json` automatically**.
|
|
337
|
-
- `dev:types` — `nodemon` watches `openapi.json` and regenerates `api.
|
|
345
|
+
- `dev:types` — `nodemon` watches `openapi.json` and regenerates `api.d.ts` whenever it changes.
|
|
338
346
|
|
|
339
347
|
Edit a route, save, and your client types update on their own.
|
|
340
348
|
|
|
341
349
|
> **Why `nodemon -L`?** On Windows, native file watchers miss in-place file writes — the `-L` flag forces polling so the regen reliably fires. On macOS/Linux you can drop it.
|
|
342
350
|
|
|
343
|
-
> Add `openapi.json` and `api.
|
|
351
|
+
> Add `openapi.json` and `api.d.ts` to `.gitignore` — both are generated.
|
|
352
|
+
|
|
353
|
+
**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.
|
|
344
354
|
|
|
345
|
-
**
|
|
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.
|
|
346
356
|
|
|
347
357
|
### Use with `openapi-fetch`
|
|
348
358
|
|
|
349
359
|
```ts
|
|
350
360
|
import createClient from "openapi-fetch";
|
|
351
|
-
import type { paths } from "./api
|
|
361
|
+
import type { paths } from "./api";
|
|
352
362
|
|
|
353
363
|
const client = createClient<paths>({ baseUrl: "http://localhost:3000/api" });
|
|
354
364
|
|
|
@@ -367,7 +377,7 @@ const { data: user } = await client.POST("/users", {
|
|
|
367
377
|
If you prefer not to add `openapi-fetch`, use the generated types directly with standard `fetch`:
|
|
368
378
|
|
|
369
379
|
```ts
|
|
370
|
-
import type { paths } from "./api
|
|
380
|
+
import type { paths } from "./api";
|
|
371
381
|
|
|
372
382
|
type Body<
|
|
373
383
|
P extends keyof paths,
|
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,
|
|
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){try{m??=await p(`module`),h??=await p(`url`);let t=globalThis.process?.cwd?.()??``,n=m.createRequire(h.pathToFileURL(t+`/`).href).resolve(e);return p(h.pathToFileURL(n).href)}catch{return p(e)}}const _=new WeakMap;function v(e){return e.replace(/\{([^{}]*)\}/g,`$1`).replace(/:([A-Za-z0-9_]+)(?:\([^)]*\))?[?+*]?/g,`{$1}`).replace(/\/{2,}/g,`/`)}function y(e){return[...e.replace(/\{([^{}]*)\}/g,`$1`).matchAll(/:([A-Za-z0-9_]+)/g)].map(e=>e[1])}function b(e){return e.replace(/[{}]/g,``).split(`/`).filter(Boolean).find(e=>!e.startsWith(`:`)&&!e.startsWith(`*`))??`default`}function x(e,t){let n=t.replace(/[{}]/g,``).split(`/`).filter(e=>e&&!e.startsWith(`:`)&&!e.startsWith(`*`)),r=n[n.length-1]??`resource`;return`${{get:`Get`,post:`Create`,put:`Update`,patch:`Patch`,delete:`Delete`,head:`Head`,options:`Options`}[e]??e} ${r}`}async function S(e){let t=_.get(e);if(t)return t;let n=await C(e);return _.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 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 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)continue;let e=v(t.path);n[e]||(n[e]={});let r=y(t.path).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await S(t.querySchema),n=e.properties??{},i=e.required??[];for(let[e,t]of Object.entries(n))r.push({name:e,in:`query`,required:i.includes(e),schema:t})}let i={summary:t.summary??x(t.method,t.path),tags:t.tags??[b(t.path)],parameters:r};t.description&&(i.description=t.description),t.deprecated&&(i.deprecated=!0),t.bodySchema&&(i.requestBody={required:!0,content:{"application/json":{schema:await S(t.bodySchema)}}});let a={};for(let[e,n]of t.responseSamples){let t={schema:n.schema};n.example!==void 0&&(t.example=n.example),a[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}a[n]={description:`Success`,content:{"application/json":{schema:e}}}}}Object.keys(a).length===0&&(a[200]={description:`Success`}),i.responses=a,n[e][t.method]=i}return{openapi:`3.1.0`,info:{title:t.title??`API`,version:t.version??`1.0.0`,...t.description?{description:t.description}:{}},...t.servers?{servers:t.servers}:{},paths:n}}const N=new WeakMap;var P=class e{router;routes=[];mountedRouters=[];sampleMode=`off`;scheduleSpecWrite;constructor(){this.router=c.default.Router(),N.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=N.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,path:e+t.path})));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[v(t+e.path)]?.[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 M(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 M(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??A;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(j(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.tags=e.tags,a.description=e.description,a.summary=e.summary,a.deprecated=e.deprecated,a.responseSchema=e.responseSchema,a.hidden=e.hidden,e.middleware&&i.push(...e.middleware),e.bodySchema&&i.push(this.createBodyValidationMiddleware(e.bodySchema)),e.querySchema&&i.push(this.createQueryValidationMiddleware(e.querySchema)),i.push(r)}else i.push(n);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](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)}}}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 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(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 M(n.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,path:e+t.path}))),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??A;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(j(t.title??`API`,r,i))}),r}exports.TypedRouter=P,exports.createDocs=R,exports.createTypedRouter=F,exports.createTypedRouterWithConfig=I,exports.createTypedRouterWithMiddleware=L,exports.inferJsonSchema=w,exports.isSchemaError=f,exports.parseSchema=u,exports.safeParseSchema=d;
|
package/dist/schema-router.d.cts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import express, { NextFunction, Request, Response } from "express";
|
|
2
2
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
3
|
-
|
|
4
3
|
//#region src/schema-router.d.ts
|
|
5
4
|
type AnyStandardSchema = StandardSchemaV1<any, any>;
|
|
5
|
+
type SchemaLike = AnyStandardSchema | {
|
|
6
|
+
safeParse: (...args: any[]) => any;
|
|
7
|
+
} | {
|
|
8
|
+
parse: (...args: any[]) => any;
|
|
9
|
+
} | {
|
|
10
|
+
validate: (...args: any[]) => any;
|
|
11
|
+
};
|
|
6
12
|
type InferOutput<T> = T extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<T> : unknown;
|
|
7
13
|
type InferInput<T> = T extends StandardSchemaV1 ? StandardSchemaV1.InferInput<T> : unknown;
|
|
8
14
|
type InferFromSafeParse<T> = T extends {
|
|
@@ -48,23 +54,23 @@ type ExtractRouteParams<Path extends string> = string extends Path ? Record<stri
|
|
|
48
54
|
/**
|
|
49
55
|
* Main parameter extraction logic - enhanced for Express 5 support with recursion depth limit
|
|
50
56
|
*/
|
|
51
|
-
type ExtractParams<Path extends string> = Path extends `${infer Before}{${infer OptionalContent}}${infer After}` ? ExtractOptionalSegment<OptionalContent> & ExtractParams<`${Before}${After}`> : Path extends `${infer _Before}:${infer Rest}` ? ExtractSingleParam<Rest> & ExtractParams<RemoveFirstParam<Path>> : Path extends `${infer _Before}*${infer Name}/${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`/${After}`> : { [K in Name]: string[] } & ExtractParams<`/${After}`> : Path extends `${infer _Before}*${infer Name}-${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`-${After}`> : { [K in Name]: string[] } & ExtractParams<`-${After}`> : Path extends `${infer _Before}*${infer Name}.${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`.${After}`> : { [K in Name]: string[] } & ExtractParams<`.${After}`> : Path extends `${infer _Before}*${infer Name}#${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`#${After}`> : { [K in Name]: string[] } & ExtractParams<`#${After}`> : Path extends `${infer _Before}*${infer Name}:${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`:${After}`> : { [K in Name]: string[] } & ExtractParams<`:${After}`> : Path extends `${infer _Before}*${infer Name}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<``> : { [K in Name]: string[] } & ExtractParams<``> : Path extends `${infer _Before}*${infer After}` ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<After> : {};
|
|
57
|
+
type ExtractParams<Path extends string> = Path extends `${infer Before}{${infer OptionalContent}}${infer After}` ? ExtractOptionalSegment<OptionalContent> & ExtractParams<`${Before}${After}`> : Path extends `${infer _Before}:${infer Rest}` ? ExtractSingleParam<Rest> & ExtractParams<RemoveFirstParam<Path>> : Path extends `${infer _Before}*${infer Name}/${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`/${After}`> : { [K in Name]: string[]; } & ExtractParams<`/${After}`> : Path extends `${infer _Before}*${infer Name}-${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`-${After}`> : { [K in Name]: string[]; } & ExtractParams<`-${After}`> : Path extends `${infer _Before}*${infer Name}.${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`.${After}`> : { [K in Name]: string[]; } & ExtractParams<`.${After}`> : Path extends `${infer _Before}*${infer Name}#${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`#${After}`> : { [K in Name]: string[]; } & ExtractParams<`#${After}`> : Path extends `${infer _Before}*${infer Name}:${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`:${After}`> : { [K in Name]: string[]; } & ExtractParams<`:${After}`> : Path extends `${infer _Before}*${infer Name}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<``> : { [K in Name]: string[]; } & ExtractParams<``> : Path extends `${infer _Before}*${infer After}` ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<After> : {};
|
|
52
58
|
/**
|
|
53
59
|
* Extract parameters from Express 5 optional segments in braces
|
|
54
60
|
* Handles patterns like {/:param}, {.:ext}, {/optional/:param}
|
|
55
61
|
*/
|
|
56
|
-
type ExtractOptionalSegment<Content extends string> = Content extends `*${infer Name}` ? Name extends "" ? {} : { [K in Name]?: string[] } : Content extends `/${infer Rest}` ? Rest extends `*${infer Name}` ? Name extends "" ? {} : { [K in Name]?: string[] } : Rest extends `:${infer R}` ? ExtractOptionalParam<R> : {} : Content extends `/:${infer Rest}` ? ExtractOptionalParam<Rest> : Content extends `.:${infer Rest}` ? ExtractOptionalParam<Rest> : Content extends `${infer _Path}:${infer Rest}` ? ExtractOptionalParam<Rest> : {};
|
|
62
|
+
type ExtractOptionalSegment<Content extends string> = Content extends `*${infer Name}` ? Name extends "" ? {} : { [K in Name]?: string[]; } : Content extends `/${infer Rest}` ? Rest extends `*${infer Name}` ? Name extends "" ? {} : { [K in Name]?: string[]; } : Rest extends `:${infer R}` ? ExtractOptionalParam<R> : {} : Content extends `/:${infer Rest}` ? ExtractOptionalParam<Rest> : Content extends `.:${infer Rest}` ? ExtractOptionalParam<Rest> : Content extends `${infer _Path}:${infer Rest}` ? ExtractOptionalParam<Rest> : {};
|
|
57
63
|
/**
|
|
58
64
|
* Extract a single optional parameter from brace content
|
|
59
65
|
*/
|
|
60
|
-
type ExtractOptionalParam<Rest extends string> = Rest extends `${infer ParamName}/${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}-${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}.${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}` ? { [K in ParamName]?: string } : {};
|
|
66
|
+
type ExtractOptionalParam<Rest extends string> = Rest extends `${infer ParamName}/${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}-${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}.${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}` ? { [K in ParamName]?: string; } : {};
|
|
61
67
|
/**
|
|
62
68
|
* Extract a single parameter name from the rest of the path
|
|
63
69
|
* Enhanced to handle Express 5 patterns and optional parameters correctly
|
|
64
70
|
* Special handling for consecutive parameters like :from-:to
|
|
65
71
|
* Order matters: regex constraints must be handled before repeating parameters
|
|
66
72
|
*/
|
|
67
|
-
type ExtractSingleParam<Rest extends string> = Rest extends `${infer ParamName}(${infer _Constraint})${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}-:${infer _NextParam}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}.:${infer _NextParam}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}?/${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}?-${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}?.${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}?#${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}?:${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}/${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}-${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}.${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}#${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}:${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}+${infer _After}` ? { [K in ParamName]: string[] } : Rest extends `${infer ParamName}*${infer _After}` ? { [K in ParamName]?: string[] } : Rest extends `${infer ParamName}?${infer _After}` ? { [K in ParamName]?: string } : Rest extends string ? Rest extends "" ? {} : Rest extends `${infer ParamName}?` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}+` ? { [K in ParamName]: string[] } : Rest extends `${infer ParamName}*` ? { [K in ParamName]?: string[] } : { [K in Rest]: string } : {};
|
|
73
|
+
type ExtractSingleParam<Rest extends string> = Rest extends `${infer ParamName}(${infer _Constraint})${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}-:${infer _NextParam}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}.:${infer _NextParam}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}?/${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}?-${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}?.${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}?#${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}?:${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}/${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}-${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}.${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}#${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}:${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}+${infer _After}` ? { [K in ParamName]: string[]; } : Rest extends `${infer ParamName}*${infer _After}` ? { [K in ParamName]?: string[]; } : Rest extends `${infer ParamName}?${infer _After}` ? { [K in ParamName]?: string; } : Rest extends string ? Rest extends "" ? {} : Rest extends `${infer ParamName}?` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}+` ? { [K in ParamName]: string[]; } : Rest extends `${infer ParamName}*` ? { [K in ParamName]?: string[]; } : { [K in Rest]: string; } : {};
|
|
68
74
|
/**
|
|
69
75
|
* Remove the first parameter from path to continue parsing
|
|
70
76
|
* Enhanced to handle Express 5 patterns and optional parameters
|
|
@@ -100,12 +106,12 @@ type RequestOnlyMiddleware<TReq extends Record<string, any>> = TypedMiddleware<T
|
|
|
100
106
|
type LocalsOnlyMiddleware<TLocals extends Record<string, any>> = TypedMiddleware<{}, TLocals>;
|
|
101
107
|
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 : {} : {};
|
|
102
108
|
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 : {} : {};
|
|
103
|
-
type SchemaRequest<Path extends string = string, BodySchema extends
|
|
109
|
+
type SchemaRequest<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}> = Omit<Request, "params" | "query" | "body"> & {
|
|
104
110
|
params: ExtractRouteParams<Path>;
|
|
105
|
-
body: BodySchema extends
|
|
106
|
-
query: QuerySchema extends
|
|
111
|
+
body: BodySchema extends undefined ? unknown : InferSchemaOutput<BodySchema>;
|
|
112
|
+
query: QuerySchema extends undefined ? unknown : InferSchemaOutput<QuerySchema>;
|
|
107
113
|
} & MiddlewareProps;
|
|
108
|
-
type SchemaRouteHandler<Path extends string = string, BodySchema extends
|
|
114
|
+
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> = {}> = (req: SchemaRequest<Path, BodySchema, QuerySchema, MiddlewareProps>, res: Response<any, ResponseLocals>, next?: NextFunction) => void | undefined | Promise<void | undefined> | Response | Promise<Response> | Promise<Response | undefined>;
|
|
109
115
|
/**
|
|
110
116
|
* Options for defining a typed route, including schemas and middleware.
|
|
111
117
|
*
|
|
@@ -115,7 +121,7 @@ type SchemaRouteHandler<Path extends string = string, BodySchema extends AnyStan
|
|
|
115
121
|
* @property querySchema - Optional schema for validating the query string.
|
|
116
122
|
* @property middleware - Optional array of TypedMiddleware for this route.
|
|
117
123
|
*/
|
|
118
|
-
interface RouteOptions<BodySchema extends
|
|
124
|
+
interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined> {
|
|
119
125
|
bodySchema?: BodySchema;
|
|
120
126
|
querySchema?: QuerySchema;
|
|
121
127
|
middleware?: TypedMiddleware<any, any>[];
|
|
@@ -127,7 +133,7 @@ interface RouteOptions<BodySchema extends AnyStandardSchema | unknown = unknown,
|
|
|
127
133
|
/** Exclude this route from the generated OpenAPI spec entirely. */
|
|
128
134
|
hidden?: boolean;
|
|
129
135
|
}
|
|
130
|
-
type DocMeta = Pick<RouteOptions
|
|
136
|
+
type DocMeta = Pick<RouteOptions, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden">;
|
|
131
137
|
type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "all";
|
|
132
138
|
interface DocsOptions {
|
|
133
139
|
title?: string;
|
|
@@ -185,19 +191,20 @@ interface DocsOptions {
|
|
|
185
191
|
interface RouteMetadata {
|
|
186
192
|
method: HttpMethod;
|
|
187
193
|
path: string;
|
|
188
|
-
bodySchema?: AnyStandardSchema;
|
|
189
|
-
querySchema?: AnyStandardSchema;
|
|
190
|
-
tags?: string[];
|
|
191
|
-
description?: string;
|
|
192
|
-
summary?: string;
|
|
193
|
-
deprecated?: boolean;
|
|
194
|
-
responseSchema?: AnyStandardSchema;
|
|
195
|
-
hidden?: boolean;
|
|
194
|
+
bodySchema?: AnyStandardSchema | undefined;
|
|
195
|
+
querySchema?: AnyStandardSchema | undefined;
|
|
196
|
+
tags?: string[] | undefined;
|
|
197
|
+
description?: string | undefined;
|
|
198
|
+
summary?: string | undefined;
|
|
199
|
+
deprecated?: boolean | undefined;
|
|
200
|
+
responseSchema?: AnyStandardSchema | undefined;
|
|
201
|
+
hidden?: boolean | undefined;
|
|
196
202
|
responseSamples: Map<number, {
|
|
197
203
|
schema: Record<string, any>;
|
|
198
204
|
example?: unknown;
|
|
199
205
|
}>;
|
|
200
206
|
}
|
|
207
|
+
declare function inferJsonSchema(value: unknown): Record<string, any>;
|
|
201
208
|
/**
|
|
202
209
|
* Extra properties that middleware has added to the Express `req` object.
|
|
203
210
|
*
|
|
@@ -234,14 +241,14 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
|
|
|
234
241
|
* and/or adds properties to response.locals
|
|
235
242
|
*/
|
|
236
243
|
/**
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
244
|
+
* Add typed middleware to the router.
|
|
245
|
+
* This middleware will apply to all routes defined after this call.
|
|
246
|
+
*
|
|
247
|
+
* @template TReq - Type extensions for the request object
|
|
248
|
+
* @template TLocals - Type extensions for response.locals
|
|
249
|
+
* @param middleware - The typed middleware function
|
|
250
|
+
* @returns A new router instance with updated types
|
|
251
|
+
*/
|
|
245
252
|
useMiddleware<TReq extends Record<string, any> = {}, TLocals extends Record<string, any> = {}>(middleware: TypedMiddleware<TReq, TLocals>): TypedRouter<Req & TReq, Locals & TLocals>;
|
|
246
253
|
/**
|
|
247
254
|
* Get the underlying Express router, typed as a RequestHandler so it can be
|
|
@@ -317,166 +324,51 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
|
|
|
317
324
|
* // GET /docs/openapi.json → raw OpenAPI 3.1 spec
|
|
318
325
|
*/
|
|
319
326
|
docs(options?: DocsOptions): express.Router & express.RequestHandler;
|
|
320
|
-
get<Path extends string>(path: Path, handler: SchemaRouteHandler<Path,
|
|
321
|
-
get<Path extends string, BodySchema extends
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
327
|
+
get<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
328
|
+
get<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
329
|
+
bodySchema?: BodySchema;
|
|
330
|
+
querySchema?: QuerySchema;
|
|
331
|
+
middleware?: [...M];
|
|
332
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
333
|
+
post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
334
|
+
post<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
335
|
+
bodySchema?: BodySchema;
|
|
336
|
+
querySchema?: QuerySchema;
|
|
337
|
+
middleware?: [...M];
|
|
338
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
339
|
+
put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
340
|
+
put<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
341
|
+
bodySchema?: BodySchema;
|
|
342
|
+
querySchema?: QuerySchema;
|
|
343
|
+
middleware?: [...M];
|
|
344
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
345
|
+
patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
346
|
+
patch<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
347
|
+
bodySchema?: BodySchema;
|
|
348
|
+
querySchema?: QuerySchema;
|
|
349
|
+
middleware?: [...M];
|
|
350
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
351
|
+
delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
352
|
+
delete<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
333
353
|
querySchema?: QuerySchema;
|
|
334
|
-
middleware
|
|
335
|
-
}, handler: SchemaRouteHandler<Path,
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
post<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
339
|
-
bodySchema: BodySchema;
|
|
340
|
-
middleware: [...M];
|
|
341
|
-
}, // Using tuple spread pattern
|
|
342
|
-
handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
343
|
-
// Make it readonly for type inference
|
|
344
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
345
|
-
post<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
346
|
-
middleware: [...M];
|
|
347
|
-
}, // Using tuple spread pattern
|
|
348
|
-
handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
349
|
-
// Make it readonly for type inference
|
|
350
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
351
|
-
post<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
|
|
352
|
-
post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
353
|
-
put<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
354
|
-
bodySchema: BodySchema;
|
|
354
|
+
middleware?: [...M];
|
|
355
|
+
}, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
356
|
+
options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
357
|
+
options<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
355
358
|
querySchema?: QuerySchema;
|
|
356
|
-
middleware
|
|
357
|
-
}, handler: SchemaRouteHandler<Path,
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
put<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
361
|
-
bodySchema: BodySchema;
|
|
362
|
-
middleware: [...M];
|
|
363
|
-
}, // Using tuple spread pattern
|
|
364
|
-
handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
365
|
-
// Make it readonly for type inference
|
|
366
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
367
|
-
put<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
368
|
-
middleware: [...M];
|
|
369
|
-
}, // Using tuple spread pattern
|
|
370
|
-
handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
371
|
-
// Make it readonly for type inference
|
|
372
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
373
|
-
put<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
|
|
374
|
-
put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
375
|
-
patch<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
376
|
-
bodySchema: BodySchema;
|
|
359
|
+
middleware?: [...M];
|
|
360
|
+
}, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
361
|
+
head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
362
|
+
head<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
377
363
|
querySchema?: QuerySchema;
|
|
378
|
-
middleware
|
|
379
|
-
}, handler: SchemaRouteHandler<Path,
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
bodySchema: BodySchema;
|
|
384
|
-
middleware: [...M];
|
|
385
|
-
}, // Using tuple spread pattern
|
|
386
|
-
handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
387
|
-
// Make it readonly for type inference
|
|
388
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
389
|
-
patch<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
390
|
-
middleware: [...M];
|
|
391
|
-
}, // Using tuple spread pattern
|
|
392
|
-
handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
393
|
-
// Make it readonly for type inference
|
|
394
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
395
|
-
patch<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
|
|
396
|
-
patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
397
|
-
delete<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
398
|
-
querySchema: QuerySchema;
|
|
399
|
-
middleware: [...M];
|
|
400
|
-
}, // Using tuple spread pattern
|
|
401
|
-
handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
402
|
-
// Make it readonly for type inference
|
|
403
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
404
|
-
delete<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
|
|
405
|
-
querySchema: QuerySchema;
|
|
406
|
-
}, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
|
|
407
|
-
delete<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
408
|
-
delete<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
409
|
-
middleware: [...M];
|
|
410
|
-
}, // Using tuple spread pattern
|
|
411
|
-
handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
412
|
-
// Make it readonly for type inference
|
|
413
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
414
|
-
delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
415
|
-
options<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
416
|
-
querySchema: QuerySchema;
|
|
417
|
-
middleware: [...M];
|
|
418
|
-
}, // Using tuple spread pattern
|
|
419
|
-
handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
420
|
-
// Make it readonly for type inference
|
|
421
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
422
|
-
options<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
|
|
423
|
-
querySchema: QuerySchema;
|
|
424
|
-
}, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
|
|
425
|
-
options<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
426
|
-
options<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
427
|
-
middleware: [...M];
|
|
428
|
-
}, // Using tuple spread pattern
|
|
429
|
-
handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
430
|
-
// Make it readonly for type inference
|
|
431
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
432
|
-
options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
433
|
-
head<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
434
|
-
querySchema: QuerySchema;
|
|
435
|
-
middleware: [...M];
|
|
436
|
-
}, // Using tuple spread pattern
|
|
437
|
-
handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
438
|
-
// Make it readonly for type inference
|
|
439
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
440
|
-
head<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
|
|
441
|
-
querySchema: QuerySchema;
|
|
442
|
-
}, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
|
|
443
|
-
head<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
444
|
-
head<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
445
|
-
middleware: [...M];
|
|
446
|
-
}, // Using tuple spread pattern
|
|
447
|
-
handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
448
|
-
// Make it readonly for type inference
|
|
449
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
450
|
-
head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
451
|
-
all<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
452
|
-
bodySchema: BodySchema;
|
|
364
|
+
middleware?: [...M];
|
|
365
|
+
}, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
366
|
+
all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
367
|
+
all<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
368
|
+
bodySchema?: BodySchema;
|
|
453
369
|
querySchema?: QuerySchema;
|
|
454
|
-
middleware
|
|
455
|
-
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>,
|
|
456
|
-
// Make it readonly for type inference
|
|
457
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
458
|
-
all<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
459
|
-
bodySchema: BodySchema;
|
|
460
|
-
middleware: [...M];
|
|
461
|
-
}, // Using tuple spread pattern
|
|
462
|
-
handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
463
|
-
// Make it readonly for type inference
|
|
464
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
465
|
-
all<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
466
|
-
querySchema: QuerySchema;
|
|
467
|
-
middleware: [...M];
|
|
468
|
-
}, // Using tuple spread pattern
|
|
469
|
-
handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
470
|
-
// Make it readonly for type inference
|
|
471
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
472
|
-
all<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
|
|
473
|
-
all<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
474
|
-
middleware: [...M];
|
|
475
|
-
}, // Using tuple spread pattern
|
|
476
|
-
handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
477
|
-
// Make it readonly for type inference
|
|
478
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
479
|
-
all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
370
|
+
middleware?: [...M];
|
|
371
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
480
372
|
private registerRoute;
|
|
481
373
|
private createBodyValidationMiddleware;
|
|
482
374
|
private createQueryValidationMiddleware;
|
|
@@ -583,4 +475,4 @@ type RouterDocEntry = TypedRouter<any, any> | {
|
|
|
583
475
|
*/
|
|
584
476
|
declare function createDocs(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): express.Router & express.RequestHandler;
|
|
585
477
|
//#endregion
|
|
586
|
-
export { AdditionalLocals, AdditionalReqProps, AnyStandardSchema, DocsOptions, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteOptions, RouterConfig, RouterDocEntry, SafeParseResult, SchemaRequest, SchemaRouteHandler, TypedMiddleware, TypedRouter, createDocs, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, isSchemaError, parseSchema, safeParseSchema };
|
|
478
|
+
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 };
|
package/dist/schema-router.d.mts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import express, { NextFunction, Request, Response } from "express";
|
|
2
2
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
3
|
-
|
|
4
3
|
//#region src/schema-router.d.ts
|
|
5
4
|
type AnyStandardSchema = StandardSchemaV1<any, any>;
|
|
5
|
+
type SchemaLike = AnyStandardSchema | {
|
|
6
|
+
safeParse: (...args: any[]) => any;
|
|
7
|
+
} | {
|
|
8
|
+
parse: (...args: any[]) => any;
|
|
9
|
+
} | {
|
|
10
|
+
validate: (...args: any[]) => any;
|
|
11
|
+
};
|
|
6
12
|
type InferOutput<T> = T extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<T> : unknown;
|
|
7
13
|
type InferInput<T> = T extends StandardSchemaV1 ? StandardSchemaV1.InferInput<T> : unknown;
|
|
8
14
|
type InferFromSafeParse<T> = T extends {
|
|
@@ -48,23 +54,23 @@ type ExtractRouteParams<Path extends string> = string extends Path ? Record<stri
|
|
|
48
54
|
/**
|
|
49
55
|
* Main parameter extraction logic - enhanced for Express 5 support with recursion depth limit
|
|
50
56
|
*/
|
|
51
|
-
type ExtractParams<Path extends string> = Path extends `${infer Before}{${infer OptionalContent}}${infer After}` ? ExtractOptionalSegment<OptionalContent> & ExtractParams<`${Before}${After}`> : Path extends `${infer _Before}:${infer Rest}` ? ExtractSingleParam<Rest> & ExtractParams<RemoveFirstParam<Path>> : Path extends `${infer _Before}*${infer Name}/${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`/${After}`> : { [K in Name]: string[] } & ExtractParams<`/${After}`> : Path extends `${infer _Before}*${infer Name}-${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`-${After}`> : { [K in Name]: string[] } & ExtractParams<`-${After}`> : Path extends `${infer _Before}*${infer Name}.${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`.${After}`> : { [K in Name]: string[] } & ExtractParams<`.${After}`> : Path extends `${infer _Before}*${infer Name}#${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`#${After}`> : { [K in Name]: string[] } & ExtractParams<`#${After}`> : Path extends `${infer _Before}*${infer Name}:${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<`:${After}`> : { [K in Name]: string[] } & ExtractParams<`:${After}`> : Path extends `${infer _Before}*${infer Name}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<``> : { [K in Name]: string[] } & ExtractParams<``> : Path extends `${infer _Before}*${infer After}` ? { [K in CountWildcards<_Before, "0">]: string } & ExtractParams<After> : {};
|
|
57
|
+
type ExtractParams<Path extends string> = Path extends `${infer Before}{${infer OptionalContent}}${infer After}` ? ExtractOptionalSegment<OptionalContent> & ExtractParams<`${Before}${After}`> : Path extends `${infer _Before}:${infer Rest}` ? ExtractSingleParam<Rest> & ExtractParams<RemoveFirstParam<Path>> : Path extends `${infer _Before}*${infer Name}/${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`/${After}`> : { [K in Name]: string[]; } & ExtractParams<`/${After}`> : Path extends `${infer _Before}*${infer Name}-${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`-${After}`> : { [K in Name]: string[]; } & ExtractParams<`-${After}`> : Path extends `${infer _Before}*${infer Name}.${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`.${After}`> : { [K in Name]: string[]; } & ExtractParams<`.${After}`> : Path extends `${infer _Before}*${infer Name}#${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`#${After}`> : { [K in Name]: string[]; } & ExtractParams<`#${After}`> : Path extends `${infer _Before}*${infer Name}:${infer After}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<`:${After}`> : { [K in Name]: string[]; } & ExtractParams<`:${After}`> : Path extends `${infer _Before}*${infer Name}` ? Name extends "" ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<``> : { [K in Name]: string[]; } & ExtractParams<``> : Path extends `${infer _Before}*${infer After}` ? { [K in CountWildcards<_Before, "0">]: string; } & ExtractParams<After> : {};
|
|
52
58
|
/**
|
|
53
59
|
* Extract parameters from Express 5 optional segments in braces
|
|
54
60
|
* Handles patterns like {/:param}, {.:ext}, {/optional/:param}
|
|
55
61
|
*/
|
|
56
|
-
type ExtractOptionalSegment<Content extends string> = Content extends `*${infer Name}` ? Name extends "" ? {} : { [K in Name]?: string[] } : Content extends `/${infer Rest}` ? Rest extends `*${infer Name}` ? Name extends "" ? {} : { [K in Name]?: string[] } : Rest extends `:${infer R}` ? ExtractOptionalParam<R> : {} : Content extends `/:${infer Rest}` ? ExtractOptionalParam<Rest> : Content extends `.:${infer Rest}` ? ExtractOptionalParam<Rest> : Content extends `${infer _Path}:${infer Rest}` ? ExtractOptionalParam<Rest> : {};
|
|
62
|
+
type ExtractOptionalSegment<Content extends string> = Content extends `*${infer Name}` ? Name extends "" ? {} : { [K in Name]?: string[]; } : Content extends `/${infer Rest}` ? Rest extends `*${infer Name}` ? Name extends "" ? {} : { [K in Name]?: string[]; } : Rest extends `:${infer R}` ? ExtractOptionalParam<R> : {} : Content extends `/:${infer Rest}` ? ExtractOptionalParam<Rest> : Content extends `.:${infer Rest}` ? ExtractOptionalParam<Rest> : Content extends `${infer _Path}:${infer Rest}` ? ExtractOptionalParam<Rest> : {};
|
|
57
63
|
/**
|
|
58
64
|
* Extract a single optional parameter from brace content
|
|
59
65
|
*/
|
|
60
|
-
type ExtractOptionalParam<Rest extends string> = Rest extends `${infer ParamName}/${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}-${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}.${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}` ? { [K in ParamName]?: string } : {};
|
|
66
|
+
type ExtractOptionalParam<Rest extends string> = Rest extends `${infer ParamName}/${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}-${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}.${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}` ? { [K in ParamName]?: string; } : {};
|
|
61
67
|
/**
|
|
62
68
|
* Extract a single parameter name from the rest of the path
|
|
63
69
|
* Enhanced to handle Express 5 patterns and optional parameters correctly
|
|
64
70
|
* Special handling for consecutive parameters like :from-:to
|
|
65
71
|
* Order matters: regex constraints must be handled before repeating parameters
|
|
66
72
|
*/
|
|
67
|
-
type ExtractSingleParam<Rest extends string> = Rest extends `${infer ParamName}(${infer _Constraint})${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}-:${infer _NextParam}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}.:${infer _NextParam}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}?/${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}?-${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}?.${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}?#${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}?:${infer _After}` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}/${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}-${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}.${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}#${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}:${infer _After}` ? { [K in ParamName]: string } : Rest extends `${infer ParamName}+${infer _After}` ? { [K in ParamName]: string[] } : Rest extends `${infer ParamName}*${infer _After}` ? { [K in ParamName]?: string[] } : Rest extends `${infer ParamName}?${infer _After}` ? { [K in ParamName]?: string } : Rest extends string ? Rest extends "" ? {} : Rest extends `${infer ParamName}?` ? { [K in ParamName]?: string } : Rest extends `${infer ParamName}+` ? { [K in ParamName]: string[] } : Rest extends `${infer ParamName}*` ? { [K in ParamName]?: string[] } : { [K in Rest]: string } : {};
|
|
73
|
+
type ExtractSingleParam<Rest extends string> = Rest extends `${infer ParamName}(${infer _Constraint})${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}-:${infer _NextParam}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}.:${infer _NextParam}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}?/${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}?-${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}?.${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}?#${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}?:${infer _After}` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}/${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}-${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}.${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}#${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}:${infer _After}` ? { [K in ParamName]: string; } : Rest extends `${infer ParamName}+${infer _After}` ? { [K in ParamName]: string[]; } : Rest extends `${infer ParamName}*${infer _After}` ? { [K in ParamName]?: string[]; } : Rest extends `${infer ParamName}?${infer _After}` ? { [K in ParamName]?: string; } : Rest extends string ? Rest extends "" ? {} : Rest extends `${infer ParamName}?` ? { [K in ParamName]?: string; } : Rest extends `${infer ParamName}+` ? { [K in ParamName]: string[]; } : Rest extends `${infer ParamName}*` ? { [K in ParamName]?: string[]; } : { [K in Rest]: string; } : {};
|
|
68
74
|
/**
|
|
69
75
|
* Remove the first parameter from path to continue parsing
|
|
70
76
|
* Enhanced to handle Express 5 patterns and optional parameters
|
|
@@ -100,12 +106,12 @@ type RequestOnlyMiddleware<TReq extends Record<string, any>> = TypedMiddleware<T
|
|
|
100
106
|
type LocalsOnlyMiddleware<TLocals extends Record<string, any>> = TypedMiddleware<{}, TLocals>;
|
|
101
107
|
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 : {} : {};
|
|
102
108
|
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 : {} : {};
|
|
103
|
-
type SchemaRequest<Path extends string = string, BodySchema extends
|
|
109
|
+
type SchemaRequest<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}> = Omit<Request, "params" | "query" | "body"> & {
|
|
104
110
|
params: ExtractRouteParams<Path>;
|
|
105
|
-
body: BodySchema extends
|
|
106
|
-
query: QuerySchema extends
|
|
111
|
+
body: BodySchema extends undefined ? unknown : InferSchemaOutput<BodySchema>;
|
|
112
|
+
query: QuerySchema extends undefined ? unknown : InferSchemaOutput<QuerySchema>;
|
|
107
113
|
} & MiddlewareProps;
|
|
108
|
-
type SchemaRouteHandler<Path extends string = string, BodySchema extends
|
|
114
|
+
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> = {}> = (req: SchemaRequest<Path, BodySchema, QuerySchema, MiddlewareProps>, res: Response<any, ResponseLocals>, next?: NextFunction) => void | undefined | Promise<void | undefined> | Response | Promise<Response> | Promise<Response | undefined>;
|
|
109
115
|
/**
|
|
110
116
|
* Options for defining a typed route, including schemas and middleware.
|
|
111
117
|
*
|
|
@@ -115,7 +121,7 @@ type SchemaRouteHandler<Path extends string = string, BodySchema extends AnyStan
|
|
|
115
121
|
* @property querySchema - Optional schema for validating the query string.
|
|
116
122
|
* @property middleware - Optional array of TypedMiddleware for this route.
|
|
117
123
|
*/
|
|
118
|
-
interface RouteOptions<BodySchema extends
|
|
124
|
+
interface RouteOptions<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined> {
|
|
119
125
|
bodySchema?: BodySchema;
|
|
120
126
|
querySchema?: QuerySchema;
|
|
121
127
|
middleware?: TypedMiddleware<any, any>[];
|
|
@@ -127,7 +133,7 @@ interface RouteOptions<BodySchema extends AnyStandardSchema | unknown = unknown,
|
|
|
127
133
|
/** Exclude this route from the generated OpenAPI spec entirely. */
|
|
128
134
|
hidden?: boolean;
|
|
129
135
|
}
|
|
130
|
-
type DocMeta = Pick<RouteOptions
|
|
136
|
+
type DocMeta = Pick<RouteOptions, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden">;
|
|
131
137
|
type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "all";
|
|
132
138
|
interface DocsOptions {
|
|
133
139
|
title?: string;
|
|
@@ -185,19 +191,20 @@ interface DocsOptions {
|
|
|
185
191
|
interface RouteMetadata {
|
|
186
192
|
method: HttpMethod;
|
|
187
193
|
path: string;
|
|
188
|
-
bodySchema?: AnyStandardSchema;
|
|
189
|
-
querySchema?: AnyStandardSchema;
|
|
190
|
-
tags?: string[];
|
|
191
|
-
description?: string;
|
|
192
|
-
summary?: string;
|
|
193
|
-
deprecated?: boolean;
|
|
194
|
-
responseSchema?: AnyStandardSchema;
|
|
195
|
-
hidden?: boolean;
|
|
194
|
+
bodySchema?: AnyStandardSchema | undefined;
|
|
195
|
+
querySchema?: AnyStandardSchema | undefined;
|
|
196
|
+
tags?: string[] | undefined;
|
|
197
|
+
description?: string | undefined;
|
|
198
|
+
summary?: string | undefined;
|
|
199
|
+
deprecated?: boolean | undefined;
|
|
200
|
+
responseSchema?: AnyStandardSchema | undefined;
|
|
201
|
+
hidden?: boolean | undefined;
|
|
196
202
|
responseSamples: Map<number, {
|
|
197
203
|
schema: Record<string, any>;
|
|
198
204
|
example?: unknown;
|
|
199
205
|
}>;
|
|
200
206
|
}
|
|
207
|
+
declare function inferJsonSchema(value: unknown): Record<string, any>;
|
|
201
208
|
/**
|
|
202
209
|
* Extra properties that middleware has added to the Express `req` object.
|
|
203
210
|
*
|
|
@@ -234,14 +241,14 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
|
|
|
234
241
|
* and/or adds properties to response.locals
|
|
235
242
|
*/
|
|
236
243
|
/**
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
244
|
+
* Add typed middleware to the router.
|
|
245
|
+
* This middleware will apply to all routes defined after this call.
|
|
246
|
+
*
|
|
247
|
+
* @template TReq - Type extensions for the request object
|
|
248
|
+
* @template TLocals - Type extensions for response.locals
|
|
249
|
+
* @param middleware - The typed middleware function
|
|
250
|
+
* @returns A new router instance with updated types
|
|
251
|
+
*/
|
|
245
252
|
useMiddleware<TReq extends Record<string, any> = {}, TLocals extends Record<string, any> = {}>(middleware: TypedMiddleware<TReq, TLocals>): TypedRouter<Req & TReq, Locals & TLocals>;
|
|
246
253
|
/**
|
|
247
254
|
* Get the underlying Express router, typed as a RequestHandler so it can be
|
|
@@ -317,166 +324,51 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
|
|
|
317
324
|
* // GET /docs/openapi.json → raw OpenAPI 3.1 spec
|
|
318
325
|
*/
|
|
319
326
|
docs(options?: DocsOptions): express.Router & express.RequestHandler;
|
|
320
|
-
get<Path extends string>(path: Path, handler: SchemaRouteHandler<Path,
|
|
321
|
-
get<Path extends string, BodySchema extends
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
327
|
+
get<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
328
|
+
get<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
329
|
+
bodySchema?: BodySchema;
|
|
330
|
+
querySchema?: QuerySchema;
|
|
331
|
+
middleware?: [...M];
|
|
332
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
333
|
+
post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
334
|
+
post<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
335
|
+
bodySchema?: BodySchema;
|
|
336
|
+
querySchema?: QuerySchema;
|
|
337
|
+
middleware?: [...M];
|
|
338
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
339
|
+
put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
340
|
+
put<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
341
|
+
bodySchema?: BodySchema;
|
|
342
|
+
querySchema?: QuerySchema;
|
|
343
|
+
middleware?: [...M];
|
|
344
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
345
|
+
patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
346
|
+
patch<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
347
|
+
bodySchema?: BodySchema;
|
|
348
|
+
querySchema?: QuerySchema;
|
|
349
|
+
middleware?: [...M];
|
|
350
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
351
|
+
delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
352
|
+
delete<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
333
353
|
querySchema?: QuerySchema;
|
|
334
|
-
middleware
|
|
335
|
-
}, handler: SchemaRouteHandler<Path,
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
post<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
339
|
-
bodySchema: BodySchema;
|
|
340
|
-
middleware: [...M];
|
|
341
|
-
}, // Using tuple spread pattern
|
|
342
|
-
handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
343
|
-
// Make it readonly for type inference
|
|
344
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
345
|
-
post<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
346
|
-
middleware: [...M];
|
|
347
|
-
}, // Using tuple spread pattern
|
|
348
|
-
handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
349
|
-
// Make it readonly for type inference
|
|
350
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
351
|
-
post<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
|
|
352
|
-
post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
353
|
-
put<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
354
|
-
bodySchema: BodySchema;
|
|
354
|
+
middleware?: [...M];
|
|
355
|
+
}, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
356
|
+
options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
357
|
+
options<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
355
358
|
querySchema?: QuerySchema;
|
|
356
|
-
middleware
|
|
357
|
-
}, handler: SchemaRouteHandler<Path,
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
put<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
361
|
-
bodySchema: BodySchema;
|
|
362
|
-
middleware: [...M];
|
|
363
|
-
}, // Using tuple spread pattern
|
|
364
|
-
handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
365
|
-
// Make it readonly for type inference
|
|
366
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
367
|
-
put<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
368
|
-
middleware: [...M];
|
|
369
|
-
}, // Using tuple spread pattern
|
|
370
|
-
handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
371
|
-
// Make it readonly for type inference
|
|
372
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
373
|
-
put<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
|
|
374
|
-
put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
375
|
-
patch<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
376
|
-
bodySchema: BodySchema;
|
|
359
|
+
middleware?: [...M];
|
|
360
|
+
}, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
361
|
+
head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
362
|
+
head<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
377
363
|
querySchema?: QuerySchema;
|
|
378
|
-
middleware
|
|
379
|
-
}, handler: SchemaRouteHandler<Path,
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
bodySchema: BodySchema;
|
|
384
|
-
middleware: [...M];
|
|
385
|
-
}, // Using tuple spread pattern
|
|
386
|
-
handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
387
|
-
// Make it readonly for type inference
|
|
388
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
389
|
-
patch<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
390
|
-
middleware: [...M];
|
|
391
|
-
}, // Using tuple spread pattern
|
|
392
|
-
handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
393
|
-
// Make it readonly for type inference
|
|
394
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
395
|
-
patch<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
|
|
396
|
-
patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
397
|
-
delete<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
398
|
-
querySchema: QuerySchema;
|
|
399
|
-
middleware: [...M];
|
|
400
|
-
}, // Using tuple spread pattern
|
|
401
|
-
handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
402
|
-
// Make it readonly for type inference
|
|
403
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
404
|
-
delete<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
|
|
405
|
-
querySchema: QuerySchema;
|
|
406
|
-
}, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
|
|
407
|
-
delete<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
408
|
-
delete<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
409
|
-
middleware: [...M];
|
|
410
|
-
}, // Using tuple spread pattern
|
|
411
|
-
handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
412
|
-
// Make it readonly for type inference
|
|
413
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
414
|
-
delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
415
|
-
options<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
416
|
-
querySchema: QuerySchema;
|
|
417
|
-
middleware: [...M];
|
|
418
|
-
}, // Using tuple spread pattern
|
|
419
|
-
handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
420
|
-
// Make it readonly for type inference
|
|
421
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
422
|
-
options<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
|
|
423
|
-
querySchema: QuerySchema;
|
|
424
|
-
}, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
|
|
425
|
-
options<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
426
|
-
options<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
427
|
-
middleware: [...M];
|
|
428
|
-
}, // Using tuple spread pattern
|
|
429
|
-
handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
430
|
-
// Make it readonly for type inference
|
|
431
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
432
|
-
options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
433
|
-
head<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
434
|
-
querySchema: QuerySchema;
|
|
435
|
-
middleware: [...M];
|
|
436
|
-
}, // Using tuple spread pattern
|
|
437
|
-
handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
438
|
-
// Make it readonly for type inference
|
|
439
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
440
|
-
head<Path extends string, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: {
|
|
441
|
-
querySchema: QuerySchema;
|
|
442
|
-
}, handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
|
|
443
|
-
head<Path extends string>(path: Path, options: DocMeta, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
444
|
-
head<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
445
|
-
middleware: [...M];
|
|
446
|
-
}, // Using tuple spread pattern
|
|
447
|
-
handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
448
|
-
// Make it readonly for type inference
|
|
449
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
450
|
-
head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
451
|
-
all<Path extends string, BodySchema extends AnyStandardSchema, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
452
|
-
bodySchema: BodySchema;
|
|
364
|
+
middleware?: [...M];
|
|
365
|
+
}, handler: SchemaRouteHandler<Path, undefined, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
366
|
+
all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
367
|
+
all<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
368
|
+
bodySchema?: BodySchema;
|
|
453
369
|
querySchema?: QuerySchema;
|
|
454
|
-
middleware
|
|
455
|
-
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>,
|
|
456
|
-
// Make it readonly for type inference
|
|
457
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
458
|
-
all<Path extends string, BodySchema extends AnyStandardSchema, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
459
|
-
bodySchema: BodySchema;
|
|
460
|
-
middleware: [...M];
|
|
461
|
-
}, // Using tuple spread pattern
|
|
462
|
-
handler: SchemaRouteHandler<Path, BodySchema, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
463
|
-
// Make it readonly for type inference
|
|
464
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
465
|
-
all<Path extends string, QuerySchema extends AnyStandardSchema | unknown, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
466
|
-
querySchema: QuerySchema;
|
|
467
|
-
middleware: [...M];
|
|
468
|
-
}, // Using tuple spread pattern
|
|
469
|
-
handler: SchemaRouteHandler<Path, unknown, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
470
|
-
// Make it readonly for type inference
|
|
471
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
472
|
-
all<Path extends string, BodySchema extends AnyStandardSchema | unknown, QuerySchema extends AnyStandardSchema | unknown>(path: Path, options: RouteOptions<BodySchema, QuerySchema>, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req, Locals>): TypedRouter<Req, Locals>;
|
|
473
|
-
all<Path extends string, M extends TypedMiddleware<any, any>[]>(path: Path, options: DocMeta & {
|
|
474
|
-
middleware: [...M];
|
|
475
|
-
}, // Using tuple spread pattern
|
|
476
|
-
handler: SchemaRouteHandler<Path, unknown, unknown, Req & InferMiddlewareProps<readonly [...M]>, // Make it readonly for type inference
|
|
477
|
-
// Make it readonly for type inference
|
|
478
|
-
Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
479
|
-
all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, unknown, unknown, Req, Locals>): TypedRouter<Req, Locals>;
|
|
370
|
+
middleware?: [...M];
|
|
371
|
+
}, handler: SchemaRouteHandler<Path, BodySchema, QuerySchema, Req & InferMiddlewareProps<readonly [...M]>, Locals & InferMiddlewareLocals<readonly [...M]>>): TypedRouter<Req, Locals>;
|
|
480
372
|
private registerRoute;
|
|
481
373
|
private createBodyValidationMiddleware;
|
|
482
374
|
private createQueryValidationMiddleware;
|
|
@@ -583,4 +475,4 @@ type RouterDocEntry = TypedRouter<any, any> | {
|
|
|
583
475
|
*/
|
|
584
476
|
declare function createDocs(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): express.Router & express.RequestHandler;
|
|
585
477
|
//#endregion
|
|
586
|
-
export { AdditionalLocals, AdditionalReqProps, AnyStandardSchema, DocsOptions, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteOptions, RouterConfig, RouterDocEntry, SafeParseResult, SchemaRequest, SchemaRouteHandler, TypedMiddleware, TypedRouter, createDocs, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, isSchemaError, parseSchema, safeParseSchema };
|
|
478
|
+
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 };
|
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){try{o??=await a(`module`),s??=await a(`url`);let t=globalThis.process?.cwd?.()??``,n=o.createRequire(s.pathToFileURL(t+`/`).href).resolve(e);return a(s.pathToFileURL(n).href)}catch{return a(e)}}const l=new WeakMap;function u(e){return e.replace(/\{([^{}]*)\}/g,`$1`).replace(/:([A-Za-z0-9_]+)(?:\([^)]*\))?[?+*]?/g,`{$1}`).replace(/\/{2,}/g,`/`)}function d(e){return[...e.replace(/\{([^{}]*)\}/g,`$1`).matchAll(/:([A-Za-z0-9_]+)/g)].map(e=>e[1])}function f(e){return e.replace(/[{}]/g,``).split(`/`).filter(Boolean).find(e=>!e.startsWith(`:`)&&!e.startsWith(`*`))??`default`}function p(e,t){let n=t.replace(/[{}]/g,``).split(`/`).filter(e=>e&&!e.startsWith(`:`)&&!e.startsWith(`*`)),r=n[n.length-1]??`resource`;return`${{get:`Get`,post:`Create`,put:`Update`,patch:`Patch`,delete:`Delete`,head:`Head`,options:`Options`}[e]??e} ${r}`}async function m(e){let t=l.get(e);if(t)return t;let n=await h(e);return l.set(e,n),n}async function h(e){if(typeof e.toJsonSchema==`function`)try{return e.toJsonSchema()}catch{}let t=e[`~standard`]?.vendor;if(t===`zod`){try{let t=await c(`zod`);if(typeof t.toJSONSchema==`function`)return t.toJSONSchema(e)}catch{}try{let t=await c(`zod-to-json-schema`),n=t.zodToJsonSchema??t.default?.zodToJsonSchema;if(typeof n==`function`)return n(e)}catch{}}if(t===`valibot`)try{let t=await c(`@valibot/to-json-schema`),n=t.toJsonSchema??t.default?.toJsonSchema;if(typeof n==`function`)return n(e)}catch{}if(t===`effect`)try{let t=await c(`effect`),n=t.JSONSchema?.make??t.default?.JSONSchema?.make;if(typeof n==`function`)return n(e)}catch{}return{}}function g(e){if(e==null)return{type:`null`};if(Array.isArray(e)){if(e.length===0)return{type:`array`,items:{}};let
|
|
1
|
+
import e from"express";import{SchemaError as t}from"@standard-schema/utils";function n(e,n){let r=e;if(r&&r[`~standard`]&&typeof r[`~standard`].validate==`function`){let e=r[`~standard`].validate(n);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new t(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function r(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function i(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}const a=Function(`m`,`return import(m)`);let o,s;async function c(e){try{o??=await a(`module`),s??=await a(`url`);let t=globalThis.process?.cwd?.()??``,n=o.createRequire(s.pathToFileURL(t+`/`).href).resolve(e);return a(s.pathToFileURL(n).href)}catch{return a(e)}}const l=new WeakMap;function u(e){return e.replace(/\{([^{}]*)\}/g,`$1`).replace(/:([A-Za-z0-9_]+)(?:\([^)]*\))?[?+*]?/g,`{$1}`).replace(/\/{2,}/g,`/`)}function d(e){return[...e.replace(/\{([^{}]*)\}/g,`$1`).matchAll(/:([A-Za-z0-9_]+)/g)].map(e=>e[1])}function f(e){return e.replace(/[{}]/g,``).split(`/`).filter(Boolean).find(e=>!e.startsWith(`:`)&&!e.startsWith(`*`))??`default`}function p(e,t){let n=t.replace(/[{}]/g,``).split(`/`).filter(e=>e&&!e.startsWith(`:`)&&!e.startsWith(`*`)),r=n[n.length-1]??`resource`;return`${{get:`Get`,post:`Create`,put:`Update`,patch:`Patch`,delete:`Delete`,head:`Head`,options:`Options`}[e]??e} ${r}`}async function m(e){let t=l.get(e);if(t)return t;let n=await h(e);return l.set(e,n),n}async function h(e){if(typeof e.toJsonSchema==`function`)try{return e.toJsonSchema()}catch{}let t=e[`~standard`]?.vendor;if(t===`zod`){try{let t=await c(`zod`);if(typeof t.toJSONSchema==`function`)return t.toJSONSchema(e)}catch{}try{let t=await c(`zod-to-json-schema`),n=t.zodToJsonSchema??t.default?.zodToJsonSchema;if(typeof n==`function`)return n(e)}catch{}}if(t===`valibot`)try{let t=await c(`@valibot/to-json-schema`),n=t.toJsonSchema??t.default?.toJsonSchema;if(typeof n==`function`)return n(e)}catch{}if(t===`effect`)try{let t=await c(`effect`),n=t.JSONSchema?.make??t.default?.JSONSchema?.make;if(typeof n==`function`)return n(e)}catch{}return{}}function g(e){return _(e,0,new WeakSet)}function _(e,t,n){if(e==null)return{type:`null`};if(t>=12)return{};if(e instanceof Date)return{type:`string`,format:`date-time`};if(typeof e==`bigint`)return{type:`integer`};if(typeof e==`object`&&typeof e.toJSON==`function`)return _(e.toJSON(),t,n);if(Array.isArray(e)){if(e.length===0||n.has(e))return{type:`array`,items:{}};n.add(e);let r=Math.min(e.length,20),i=_(e[0],t+1,n);for(let a=1;a<r;a++)i=b(i,_(e[a],t+1,n));return n.delete(e),{type:`array`,items:i}}switch(typeof e){case`string`:return{type:`string`};case`boolean`:return{type:`boolean`};case`number`:return Number.isFinite(e)?{type:Number.isInteger(e)?`integer`:`number`}:{type:`null`};case`object`:{if(n.has(e))return{type:`object`};n.add(e);let r={},i=[];for(let[a,o]of Object.entries(e))typeof o!=`function`&&o!==void 0&&(r[a]=_(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 v(e){return!e||e.type===void 0?new Set:new Set(Array.isArray(e.type)?e.type:[e.type])}function y(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 b(e,t){if(!e||Object.keys(e).length===0)return t??{};if(!t||Object.keys(t).length===0)return e??{};let n=new Set([...v(e),...v(t)]),r={},i=y(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]=b(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=b(e.items,t.items);n&&Object.keys(n).length&&(r.items=n)}return r}function x(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`)}const S=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function C(e,t,n){return`<!doctype html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
|
-
<title>${
|
|
4
|
+
<title>${x(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="${x(t)}"><\/script>
|
|
10
|
+
<script src="${x(n)}"><\/script>
|
|
11
11
|
</body>
|
|
12
|
-
</html>`}async function
|
|
12
|
+
</html>`}async function w(e,t){let n=Object.create(null);for(let t of e){if(t.method===`all`||t.hidden)continue;let e=u(t.path);n[e]||(n[e]={});let r=d(t.path).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await m(t.querySchema),n=e.properties??{},i=e.required??[];for(let[e,t]of Object.entries(n))r.push({name:e,in:`query`,required:i.includes(e),schema:t})}let i={summary:t.summary??p(t.method,t.path),tags:t.tags??[f(t.path)],parameters:r};t.description&&(i.description=t.description),t.deprecated&&(i.deprecated=!0),t.bodySchema&&(i.requestBody={required:!0,content:{"application/json":{schema:await m(t.bodySchema)}}});let a={};for(let[e,n]of t.responseSamples){let t={schema:n.schema};n.example!==void 0&&(t.example=n.example),a[String(e)]={description:e<400?`Success`:`Error`,content:{"application/json":t}}}if(t.responseSchema){let e=await m(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}a[n]={description:`Success`,content:{"application/json":{schema:e}}}}}Object.keys(a).length===0&&(a[200]={description:`Success`}),i.responses=a,n[e][t.method]=i}return{openapi:`3.1.0`,info:{title:t.title??`API`,version:t.version??`1.0.0`,...t.description?{description:t.description}:{}},...t.servers?{servers:t.servers}:{},paths:n}}const T=new WeakMap;var E=class t{router;routes=[];mountedRouters=[];sampleMode=`off`;scheduleSpecWrite;constructor(){this.router=e.Router(),T.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=T.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,path:e+t.path})));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[u(t+e.path)]?.[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 w(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 w(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??S;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(C(t.title??`API`,r,i))}),n}get(e,t,n){return this.registerRoute(`get`,e,t,n)}post(e,t,n){return this.registerRoute(`post`,e,t,n)}put(e,t,n){return this.registerRoute(`put`,e,t,n)}patch(e,t,n){return this.registerRoute(`patch`,e,t,n)}delete(e,t,n){return this.registerRoute(`delete`,e,t,n)}options(e,t,n){return this.registerRoute(`options`,e,t,n)}head(e,t,n){return this.registerRoute(`head`,e,t,n)}all(e,t,n){return this.registerRoute(`all`,e,t,n)}registerRoute(e,t,n,r){let i=[],a={method:e,path:t,responseSamples:new Map};if(this.routes.push(a),typeof n==`object`){let e=n;a.bodySchema=e.bodySchema,a.querySchema=e.querySchema,a.tags=e.tags,a.description=e.description,a.summary=e.summary,a.deprecated=e.deprecated,a.responseSchema=e.responseSchema,a.hidden=e.hidden,e.middleware&&i.push(...e.middleware),e.bodySchema&&i.push(this.createBodyValidationMiddleware(e.bodySchema)),e.querySchema&&i.push(this.createQueryValidationMiddleware(e.querySchema)),i.push(r)}else i.push(n);let o=0,s=(e,t)=>{let n=e.statusCode,r=g(t),i=a.responseSamples.get(n),o=i?b(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](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)}}}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 D(){return new E}function O(e){let t=new E;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function k(...e){let t=new E;for(let n of e)t=t.useMiddleware(n);return t}function A(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 w(r.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,path:e+t.path}))),n);t.json(e)}catch(e){t.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),i.get(`/`,(e,t)=>{let r=`${e.baseUrl}/openapi.json`,i=n.cdnUrl??S;t.setHeader(`Content-Type`,`text/html; charset=utf-8`),t.send(C(n.title??`API`,r,i))}),i}export{E as TypedRouter,A as createDocs,D as createTypedRouter,O as createTypedRouterWithConfig,k as createTypedRouterWithMiddleware,g 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.4",
|
|
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",
|
|
@@ -75,14 +75,14 @@
|
|
|
75
75
|
"@standard-schema/utils": "^0.3.0"
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
|
-
"@semantic-release/changelog": "^
|
|
79
|
-
"@semantic-release/git": "^
|
|
78
|
+
"@semantic-release/changelog": "^7.0.0",
|
|
79
|
+
"@semantic-release/git": "^11.0.1",
|
|
80
80
|
"@types/express": "^5.0.6",
|
|
81
81
|
"conventional-changelog-conventionalcommits": "^9.3.1",
|
|
82
82
|
"rimraf": "^6.1.3",
|
|
83
|
-
"semantic-release": "^25.0.
|
|
84
|
-
"tsdown": "^0.22.
|
|
85
|
-
"typescript": "^5.9.
|
|
83
|
+
"semantic-release": "^25.0.9",
|
|
84
|
+
"tsdown": "^0.22.14",
|
|
85
|
+
"typescript": "^5.9.3"
|
|
86
86
|
},
|
|
87
87
|
"engines": {
|
|
88
88
|
"node": ">=18.0.0"
|