@minisylar/express-typed-router 1.9.6 → 1.9.8
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 +68 -1
- package/dist/schema-router.cjs +5 -5
- package/dist/schema-router.d.cts +37 -35
- package/dist/schema-router.d.mts +37 -35
- package/dist/schema-router.mjs +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -329,7 +329,32 @@ app.use(
|
|
|
329
329
|
|
|
330
330
|
**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.
|
|
331
331
|
|
|
332
|
-
|
|
332
|
+
### Gotchas
|
|
333
|
+
|
|
334
|
+
- **Keep middleware variables as tuples** - inline middleware arrays preserve
|
|
335
|
+
their tuple type automatically, but assigning the array to a variable widens
|
|
336
|
+
it to `TypedMiddleware[]`. The simplest option is to pass the tuple directly
|
|
337
|
+
in the route's `middleware` object:
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
router.get("/something", { middleware: [a, b, c] }, handler);
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
If you need to reuse the middleware variable with `InferSchemaHandler`, wrap
|
|
344
|
+
it in `defineMiddleware`. It keeps each middleware's specific type instead
|
|
345
|
+
of widening:
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
const middleware = defineMiddleware(a, b, c);
|
|
349
|
+
type Handler = InferSchemaHandler<{ middleware: typeof middleware }>;
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
`as const` works too, if you'd rather not import a helper:
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
const middleware = [a, b, c] as const;
|
|
356
|
+
type Handler = InferSchemaHandler<{ middleware: typeof middleware }>;
|
|
357
|
+
```
|
|
333
358
|
|
|
334
359
|
- **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.
|
|
335
360
|
- **`responseSchema` beats inference** — declare it on routes you want guaranteed-correct (and leak-proof); it overrides whatever traffic suggests.
|
|
@@ -413,6 +438,46 @@ Edit a route, save, and your client types update on their own.
|
|
|
413
438
|
|
|
414
439
|
> ⚠️ **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.
|
|
415
440
|
|
|
441
|
+
### Generate the spec without running a server
|
|
442
|
+
|
|
443
|
+
`specOutputPath` above writes the file as a side effect of starting your app. That's fine for local dev, but in CI you don't want to start a server just to get a file. `generateOpenApiSpec` builds the spec object directly: no Express app, no `app.listen()`, no HTTP request.
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
import { createTypedRouter, generateOpenApiSpec } from "@minisylar/express-typed-router";
|
|
447
|
+
import { writeFile } from "node:fs/promises";
|
|
448
|
+
|
|
449
|
+
const router = createTypedRouter();
|
|
450
|
+
router.get("/users/:id", handler);
|
|
451
|
+
// ... define the rest of your routes
|
|
452
|
+
|
|
453
|
+
const spec = await generateOpenApiSpec(router, { title: "My API", version: "1.0.0" });
|
|
454
|
+
await writeFile("./openapi.json", JSON.stringify(spec, null, 2));
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Run that as a script in CI and feed the output to whatever reads a static spec: `openapi-typescript`, a docs site generator, a linter, anything that takes a `.json` file.
|
|
458
|
+
|
|
459
|
+
It accepts the same router shapes as [`createDocs`](#per-feature-routers-one-doc-endpoint) — a single router, a single prefixed router, or an array mixing either:
|
|
460
|
+
|
|
461
|
+
```ts
|
|
462
|
+
// Single router, no options
|
|
463
|
+
await generateOpenApiSpec(usersRouter);
|
|
464
|
+
|
|
465
|
+
// Single router with a prefix
|
|
466
|
+
await generateOpenApiSpec({ prefix: "/api/users", router: usersRouter });
|
|
467
|
+
|
|
468
|
+
// Multiple routers, all prefixed
|
|
469
|
+
await generateOpenApiSpec([
|
|
470
|
+
{ prefix: "/api/users", router: usersRouter },
|
|
471
|
+
{ prefix: "/api/orders", router: ordersRouter },
|
|
472
|
+
]);
|
|
473
|
+
|
|
474
|
+
// Mixed — some prefixed, some not
|
|
475
|
+
await generateOpenApiSpec([
|
|
476
|
+
usersRouter,
|
|
477
|
+
{ prefix: "/api/orders", router: ordersRouter },
|
|
478
|
+
]);
|
|
479
|
+
```
|
|
480
|
+
|
|
416
481
|
### Use with `openapi-fetch`
|
|
417
482
|
|
|
418
483
|
```ts
|
|
@@ -601,7 +666,9 @@ Use a schema that actually parses the text:
|
|
|
601
666
|
| `router.use(prefix, subRouter)` | Mount a sub-router |
|
|
602
667
|
| `router.getRouter()` | Get the underlying Express router |
|
|
603
668
|
| `router.docs(options)` | Get the docs + OpenAPI spec router |
|
|
669
|
+
| `generateOpenApiSpec(routers, options)` | Build the spec object with no server involved |
|
|
604
670
|
| `TypedMiddleware<T>` | Type helper for middleware that extends `req` |
|
|
671
|
+
| `defineMiddleware(...mw)` | Keep a middleware array's tuple type when reused |
|
|
605
672
|
|
|
606
673
|
---
|
|
607
674
|
|
package/dist/schema-router.cjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));let c=require("express");c=s(c,1);let l=require("@standard-schema/utils");function u(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`){let e=n[`~standard`].validate(t);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new l.SchemaError(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function d(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function f(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}const
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));let c=require("express");c=s(c,1);let l=require("@standard-schema/utils");function u(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`){let e=n[`~standard`].validate(t);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new l.SchemaError(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function d(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function f(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}function p(...e){return e}const m=Function(`m`,`return import(m)`);let h,g;async function _(e){h??=await m(`module`),g??=await m(`url`);let t=[],n=globalThis.process?.argv?.[1];n&&t.push(g.pathToFileURL(n).href);let r=globalThis.process?.cwd?.()??``;r&&t.push(g.pathToFileURL(r+`/`).href);for(let n of t)try{let t=h.createRequire(n).resolve(e);return await m(g.pathToFileURL(t).href)}catch{}return m(e)}const v=new WeakMap,y=/\(\?<[^>]+>/;function b(e){return typeof e==`string`&&y.test(e)?new RegExp(e):e}function x(e){if(typeof e.path==`string`)return e.path;if(e.pathExample)return e.pathExample;let t=0;return e.path.source.replace(/\\\//g,`/`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function S(e){let t=[],n=0,r=0,i=!1,a=!1;for(let o=0;o<e.length;o++){let s=e[o];if(a){a=!1;continue}if(s===`\\`){a=!0;continue}if(s===`[`){i=!0;continue}if(s===`]`){i=!1;continue}i||(s===`(`?r++:s===`)`?r=Math.max(0,r-1):s===`|`&&r===0&&(t.push(e.slice(n,o)),n=o+1))}return t.push(e.slice(n)),t.length>1?t:[e]}function C(e){if(!e.startsWith(`(`)||!e.endsWith(`)`))return e;let t=0,n=!1,r=!1;for(let i=0;i<e.length;i++){let a=e[i];if(r){r=!1;continue}if(a===`\\`){r=!0;continue}if(a===`[`)n=!0;else if(a===`]`)n=!1;else if(!n&&a===`(`)t++;else if(!n&&a===`)`&&(t--,t===0&&i!==e.length-1))return e}return e.slice(1,-1).replace(/^\?:/,``)}function w(e){let t=0;return C(e).replace(/^\^|\$$/g,``).replace(/\\\//g,`/`).replace(/\.\*|\.\+/g,`/:path`).replace(/\/?\?$/,``).replace(/\(\?<([A-Za-z0-9_]+)>[^()]*\)/g,`:$1`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function T(e){if(typeof e.path==`string`||e.pathExample)return[x(e)];let t=S(e.path.source);return t.length===1?[x(e)]:t.map(w)}function E(e){return e.replace(/\{([^{}]*)\}/g,`$1`).replace(/:([A-Za-z0-9_]+)(?:\([^)]*\))?[?+*]?/g,`{$1}`).replace(/\(\?<([A-Za-z0-9_]+)>[^)]*\)/g,`{$1}`).replace(/^\^|\$$/g,``).replace(/\/{2,}/g,`/`)}function D(e){let t=e.replace(/\{([^{}]*)\}/g,`$1`);return[...t.matchAll(/:([A-Za-z0-9_]+)/g),...t.matchAll(/\(\?<([A-Za-z0-9_]+)>/g)].map(e=>e[1])}function O(e){return e.startsWith(`:`)||e.startsWith(`*`)||e.includes(`(?<`)}function k(e){return e.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(Boolean).find(e=>!O(e))??`default`}function A(e,t){let n=t.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(e=>e&&!O(e)),r=n[n.length-1]??`resource`;return`${{get:`Get`,post:`Create`,put:`Update`,patch:`Patch`,delete:`Delete`,head:`Head`,options:`Options`}[e]??e} ${r}`}async function j(e){let t=v.get(e);if(t)return t;let n=await M(e);return v.set(e,n),n}async function M(e){if(typeof e.toJsonSchema==`function`)try{return e.toJsonSchema()}catch{}let t=e[`~standard`]?.vendor;if(t===`zod`){try{let t=await _(`zod`);if(typeof t.toJSONSchema==`function`)return t.toJSONSchema(e)}catch{}try{let t=await _(`zod-to-json-schema`),n=t.zodToJsonSchema??t.default?.zodToJsonSchema;if(typeof n==`function`)return n(e)}catch{}}if(t===`valibot`)try{let t=await _(`@valibot/to-json-schema`),n=t.toJsonSchema??t.default?.toJsonSchema;if(typeof n==`function`)return n(e)}catch{}if(t===`effect`)try{let t=await _(`effect`),n=t.JSONSchema?.make??t.default?.JSONSchema?.make;if(typeof n==`function`)return n(e)}catch{}return{}}function N(e){return P(e,0,new WeakSet)}function P(e,t,n){if(e==null)return{type:`null`};if(t>=12)return{};if(e instanceof Date)return{type:`string`,format:`date-time`};if(typeof e==`bigint`)return{type:`integer`};if(typeof e==`object`&&typeof e.toJSON==`function`)return P(e.toJSON(),t,n);if(Array.isArray(e)){if(e.length===0||n.has(e))return{type:`array`,items:{}};n.add(e);let r=Math.min(e.length,20),i=P(e[0],t+1,n);for(let a=1;a<r;a++)i=L(i,P(e[a],t+1,n));return n.delete(e),{type:`array`,items:i}}switch(typeof e){case`string`:return{type:`string`};case`boolean`:return{type:`boolean`};case`number`:return Number.isFinite(e)?{type:Number.isInteger(e)?`integer`:`number`}:{type:`null`};case`object`:{if(n.has(e))return{type:`object`};n.add(e);let r={},i=[];for(let[a,o]of Object.entries(e))typeof o!=`function`&&o!==void 0&&(r[a]=P(o,t+1,n),i.push(a));n.delete(e);let a={type:`object`,properties:r};return i.length&&(a.required=i),a}default:return{}}}function F(e){return!e||e.type===void 0?new Set:new Set(Array.isArray(e.type)?e.type:[e.type])}function I(e){e.has(`integer`)&&e.has(`number`)&&e.delete(`integer`);let t=[...e];if(t.length!==0)return t.length===1?t[0]:t}function L(e,t){if(!e||Object.keys(e).length===0)return t??{};if(!t||Object.keys(t).length===0)return e??{};let n=new Set([...F(e),...F(t)]),r={},i=I(n);if(i!==void 0&&(r.type=i),n.has(`object`)&&(e.properties||t.properties)){let n=e.properties??{},i=t.properties??{},a={};for(let e of new Set([...Object.keys(n),...Object.keys(i)]))a[e]=L(n[e],i[e]);r.properties=a;let o=e.required??[],s=t.required??[],c=o.filter(e=>s.includes(e));c.length&&(r.required=c)}if(n.has(`array`)){let n=L(e.items,t.items);n&&Object.keys(n).length&&(r.items=n)}return r}function R(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`)}const z=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function B(e,t,n){return`<!doctype html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
|
-
<title>${
|
|
4
|
+
<title>${R(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="${R(t)}"><\/script>
|
|
10
|
+
<script src="${R(n)}"><\/script>
|
|
11
11
|
</body>
|
|
12
|
-
</html>`}async function
|
|
12
|
+
</html>`}async function V(e,t){let n=Object.create(null);for(let t of e)if(!(t.method===`all`||t.hidden))for(let e of T(t)){let r=E(e);n[r]||(n[r]={});let i;if(t.paramsSchema){let e=await j(t.paramsSchema),n=e.properties??{},r=e.required??[];i=Object.entries(n).map(([e,t])=>({name:e,in:`path`,required:r.includes(e),schema:t}))}else i=D(e).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await j(t.querySchema),n=e.properties??{},r=e.required??[];for(let[e,t]of Object.entries(n))i.push({name:e,in:`query`,required:r.includes(e),schema:t})}let a={summary:t.summary??A(t.method,e),tags:t.tags??[k(e)],parameters:i};t.description&&(a.description=t.description),t.deprecated&&(a.deprecated=!0),t.bodySchema&&(a.requestBody={required:!0,content:{"application/json":{schema:await j(t.bodySchema)}}});let o={};for(let[e,n]of t.responseSamples){let t={schema:n.schema};n.example!==void 0&&(t.example=n.example),o[String(e)]={description:e<400?`Success`:`Error`,content:{"application/json":t}}}if(t.responseSchema){let e=await j(t.responseSchema);if(e&&Object.keys(e).length){let n=`200`;for(let e of t.responseSamples.keys())if(e>=200&&e<300){n=String(e);break}o[n]={description:`Success`,content:{"application/json":{schema:e}}}}}Object.keys(o).length===0&&(o[200]={description:`Success`}),a.responses=o,n[r][t.method]=a}return{openapi:`3.1.0`,info:{title:t.title??`API`,version:t.version??`1.0.0`,...t.description?{description:t.description}:{}},...t.servers?{servers:t.servers}:{},paths:n}}const H=new WeakMap;var U=class e{router;routes=[];mountedRouters=[];sampleMode=`off`;scheduleSpecWrite;constructor(){this.router=c.default.Router(),H.set(this.router,this)}useMiddleware(e){return this.router.use(e),this}getRouter(){return this.router}use(t,...n){let r=typeof t==`string`,i=r?t:``,a=(r?n:[t,...n]).map(t=>{if(t instanceof e)return this.trackMounted(i,t),t.getRouter();let n=H.get(t);return n&&this.trackMounted(i,n),t});return r?this.router.use(t,...a):this.router.use(...a),this}mount(e,t){if(typeof e==`string`){let n=t;this.router.use(e,n.getRouter()),this.trackMounted(e,n)}else this.router.use(e.getRouter()),this.trackMounted(``,e);return this}getRouteMetadata(){let e=this.mountedRouters.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:e+x(t)}})));return[...this.routes,...e]}enableSampling(e=`redacted`,t,n=new Set){if(!n.has(this)){n.add(this),this.sampleMode=e,this.scheduleSpecWrite=t;for(let{router:r}of this.mountedRouters)r.enableSampling(e,t,n)}}trackMounted(e,t){this.mountedRouters.some(n=>n.router===t&&n.prefix===e)||(this.mountedRouters.push({prefix:e,router:t}),this.sampleMode!==`off`&&t.enableSampling(this.sampleMode,this.scheduleSpecWrite))}hydrateResponses(e,t=``,n=new Set){if(n.has(this))return;n.add(this);let r=e?.paths??{};for(let e of this.routes)for(let n of T(e)){let i=r[E(t+n)]?.[e.method]?.responses;if(i)for(let t of Object.keys(i)){let n=Number(t);if(Number.isNaN(n)||e.responseSamples.has(n))continue;let r=i[t]?.content?.[`application/json`];r?.schema&&e.responseSamples.set(n,r.example===void 0?{schema:r.schema}:{schema:r.schema,example:r.example})}}for(let{prefix:r,router:i}of this.mountedRouters)i.hydrateResponses(e,t+r,n)}docs(e={}){let t=c.default.Router(),n;if(e.specOutputPath){let t=e.specOutputPath,r=!1,i=!1,a=async()=>{if(r){i=!0;return}r=!0;try{let n=await V(this.getRouteMetadata(),e),r=await m(`fs/promises`),i=t.replace(/[/\\][^/\\]*$/,``);i&&i!==t&&await r.mkdir(i,{recursive:!0}).catch(()=>{});let a=globalThis.process?.pid??`0`,o=`${t}.${a}.tmp`;await r.writeFile(o,JSON.stringify(n,null,2),`utf8`),await r.rename(o,t)}catch{}finally{r=!1,i&&(i=!1,a())}},o;n=()=>{o&&clearTimeout(o),o=setTimeout(a,300),o.unref?.()},setImmediate(async()=>{try{let e=await(await m(`fs/promises`)).readFile(t,`utf8`).catch(()=>null);if(e)try{this.hydrateResponses(JSON.parse(e))}catch{}}catch{}await a()})}return e.sampleResponses!==!1&&this.enableSampling(e.sampleResponses===`live`?`live`:`redacted`,n),t.get(`/openapi.json`,async(t,n)=>{try{let t=await V(this.getRouteMetadata(),e);n.json(t)}catch(e){n.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),t.get(`/`,(t,n)=>{let r=`${t.baseUrl}/openapi.json`,i=e.cdnUrl??z;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(B(e.title??`API`,r,i))}),t}get(e,t,n){return this.registerRoute(`get`,e,t,n)}post(e,t,n){return this.registerRoute(`post`,e,t,n)}put(e,t,n){return this.registerRoute(`put`,e,t,n)}patch(e,t,n){return this.registerRoute(`patch`,e,t,n)}delete(e,t,n){return this.registerRoute(`delete`,e,t,n)}options(e,t,n){return this.registerRoute(`options`,e,t,n)}head(e,t,n){return this.registerRoute(`head`,e,t,n)}all(e,t,n){return this.registerRoute(`all`,e,t,n)}registerRoute(e,t,n,r){let i=[],a={method:e,path:t,responseSamples:new Map};if(this.routes.push(a),typeof n==`object`){let e=n;a.bodySchema=e.bodySchema,a.querySchema=e.querySchema,a.paramsSchema=e.paramsSchema,a.tags=e.tags,a.description=e.description,a.summary=e.summary,a.deprecated=e.deprecated,a.responseSchema=e.responseSchema,a.hidden=e.hidden,a.pathExample=e.pathExample,e.middleware&&i.push(...e.middleware),e.bodySchema&&i.push(this.createBodyValidationMiddleware(e.bodySchema)),e.querySchema&&i.push(this.createQueryValidationMiddleware(e.querySchema)),e.paramsSchema&&i.push(this.createParamsValidationMiddleware(e.paramsSchema)),i.push(r)}else i.push(n);let o=0,s=(e,t)=>{let n=e.statusCode,r=N(t),i=a.responseSamples.get(n),o=i?L(i.schema,r):r,s=this.sampleMode===`live`?i?.example??t:void 0,c=!i||JSON.stringify(i.schema)!==JSON.stringify(o);a.responseSamples.set(n,{schema:o,example:s}),c&&this.scheduleSpecWrite?.()};return this.router[e](b(t),(e,t,n)=>{if(this.sampleMode===`off`||a.hidden||o>=50||a.responseSamples.size>=10){n();return}o++;let r=t.json;t.json=function(e){return s(t,e),r.call(this,e)};let i=t.send;t.send=function(e){return typeof e==`object`&&e&&!Buffer.isBuffer(e)&&s(t,e),i.call(this,e)},n()},...i),this}createBodyValidationMiddleware(e){return async(t,n,r)=>{try{let i=d(e,t.body),a=i&&typeof i.then==`function`?await i:i;if(a&&`issues`in a&&a.issues){n.status(400).json({error:`Validation failed`,details:a.errors||a.issues});return}t.body=a&&`value`in a?a.value:a,r()}catch(e){f(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):r(e)}}}createParamsValidationMiddleware(e){return async(t,n,r)=>{try{let i=d(e,t.params),a=i&&typeof i.then==`function`?await i:i;if(a&&`issues`in a&&a.issues){n.status(400).json({error:`Validation failed`,details:a.errors||a.issues});return}t.params=a&&`value`in a?a.value:a,r()}catch(e){f(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):r(e)}}}createQueryValidationMiddleware(e){return async(t,n,r)=>{try{let i=d(e,t.query),a=i&&typeof i.then==`function`?await i:i;if(a&&`issues`in a&&a.issues){n.status(400).json({error:`Validation failed`,details:a.errors||a.issues});return}let o=a&&`value`in a?a.value:a;Object.defineProperty(t,"query",{value:o,writable:!1,enumerable:!0,configurable:!0}),r()}catch(e){f(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):r(e)}}}};function W(){return new U}function G(e){let t=new U;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function K(...e){let t=new U;for(let n of e)t=t.useMiddleware(n);return t}function q(e){return(Array.isArray(e)?e:[e]).map(e=>`prefix`in e?e:{prefix:``,router:e}).flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:e+x(t)}})))}async function J(e,t={}){return V(q(e),t)}function Y(e,t={}){if(t.sampleResponses!==!1){let n=t.sampleResponses===`live`?`live`:`redacted`,r=Array.isArray(e)?e:[e];for(let e of r)(`prefix`in e?e.router:e).enableSampling(n)}let n=c.default.Router();return n.get(`/openapi.json`,async(n,r)=>{try{let n=await J(e,t);r.json(n)}catch(e){r.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),n.get(`/`,(e,n)=>{let r=`${e.baseUrl}/openapi.json`,i=t.cdnUrl??z;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(B(t.title??`API`,r,i))}),n}exports.TypedRouter=U,exports.createDocs=Y,exports.createTypedRouter=W,exports.createTypedRouterWithConfig=G,exports.createTypedRouterWithMiddleware=K,exports.defineMiddleware=p,exports.generateOpenApiSpec=J,exports.inferJsonSchema=N,exports.isSchemaError=f,exports.parseSchema=u,exports.safeParseSchema=d;
|
package/dist/schema-router.d.cts
CHANGED
|
@@ -112,6 +112,18 @@ type RequestOnlyMiddleware<TReq extends Record<string, any>> = TypedMiddleware<T
|
|
|
112
112
|
* Simplified TypedMiddleware for response locals-only extensions
|
|
113
113
|
*/
|
|
114
114
|
type LocalsOnlyMiddleware<TLocals extends Record<string, any>> = TypedMiddleware<{}, TLocals>;
|
|
115
|
+
/**
|
|
116
|
+
* Assigning a middleware array to a variable widens it to
|
|
117
|
+
* TypedMiddleware<any, any>[], losing the per-middleware types that
|
|
118
|
+
* InferSchemaHandler needs. The usual fix is `as const` on the array;
|
|
119
|
+
* defineMiddleware does the same thing without it, since the `const` type
|
|
120
|
+
* parameter keeps each argument's specific type instead of widening.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* const middleware = defineMiddleware(auth, logging);
|
|
124
|
+
* type Handler = InferSchemaHandler<{ middleware: typeof middleware }>;
|
|
125
|
+
*/
|
|
126
|
+
declare function defineMiddleware<const M extends readonly TypedMiddleware<any, any>[]>(...mw: M): [...M];
|
|
115
127
|
type InferMiddlewareProps<T extends readonly TypedMiddleware<any, any>[]> = T extends readonly [infer First, ...infer Rest] ? First extends TypedMiddleware<infer FirstReq, any> ? Rest extends readonly TypedMiddleware<any, any>[] ? FirstReq & InferMiddlewareProps<Rest> : FirstReq : {} : {};
|
|
116
128
|
type InferMiddlewareLocals<T extends readonly TypedMiddleware<any, any>[]> = T extends readonly [infer First, ...infer Rest] ? First extends TypedMiddleware<any, infer FirstLocals> ? Rest extends readonly TypedMiddleware<any, any>[] ? FirstLocals & InferMiddlewareLocals<Rest> : FirstLocals : {} : {};
|
|
117
129
|
type SchemaRequest<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}, ParamsSchema extends SchemaLike | undefined = undefined, ParamsOverride extends Record<string, any> | undefined = undefined> = Omit<Request, "params" | "query" | "body"> & {
|
|
@@ -191,6 +203,7 @@ type InferHandlerOption<Options extends InferSchemaHandlerOptions, Key extends "
|
|
|
191
203
|
* }>;
|
|
192
204
|
*/
|
|
193
205
|
type InferSchemaHandler<Options extends InferSchemaHandlerOptions = {}> = SchemaRouteHandler<string, InferHandlerOption<Options, "bodySchema">, InferHandlerOption<Options, "querySchema">, InferMiddlewareProps<NormalizeHandlerMiddleware<InferHandlerOption<Options, "middleware">>>, InferMiddlewareLocals<NormalizeHandlerMiddleware<InferHandlerOption<Options, "middleware">>>, InferHandlerOption<Options, "paramsSchema">, Record<string, string | string[] | undefined>>;
|
|
206
|
+
type RouteHandlerFromOptions<Path extends string, BodySchema extends SchemaLike | undefined, QuerySchema extends SchemaLike | undefined, ParamsSchema extends SchemaLike | undefined, RouterReq extends Record<string, any>, RouterLocals extends Record<string, any>, M extends TypedMiddleware<any, any>[]> = SchemaRouteHandler<Path, NoInfer<BodySchema>, NoInfer<QuerySchema>, RouterReq & InferMiddlewareProps<readonly [...M]>, RouterLocals & InferMiddlewareLocals<readonly [...M]>, NoInfer<ParamsSchema>>;
|
|
194
207
|
type DocMeta = Pick<RouteOptions, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden" | "pathExample">;
|
|
195
208
|
type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "all";
|
|
196
209
|
interface DocsOptions {
|
|
@@ -391,106 +404,106 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
|
|
|
391
404
|
querySchema?: QuerySchema;
|
|
392
405
|
paramsSchema?: ParamsSchema;
|
|
393
406
|
middleware?: [...M];
|
|
394
|
-
}, handler:
|
|
407
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
395
408
|
get(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
396
409
|
get<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
397
410
|
bodySchema?: BodySchema;
|
|
398
411
|
querySchema?: QuerySchema;
|
|
399
412
|
paramsSchema?: ParamsSchema;
|
|
400
413
|
middleware?: [...M];
|
|
401
|
-
}, handler:
|
|
414
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
402
415
|
post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
403
416
|
post<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
404
417
|
bodySchema?: BodySchema;
|
|
405
418
|
querySchema?: QuerySchema;
|
|
406
419
|
paramsSchema?: ParamsSchema;
|
|
407
420
|
middleware?: [...M];
|
|
408
|
-
}, handler:
|
|
421
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
409
422
|
post(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
410
423
|
post<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
411
424
|
bodySchema?: BodySchema;
|
|
412
425
|
querySchema?: QuerySchema;
|
|
413
426
|
paramsSchema?: ParamsSchema;
|
|
414
427
|
middleware?: [...M];
|
|
415
|
-
}, handler:
|
|
428
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
416
429
|
put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
417
430
|
put<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
418
431
|
bodySchema?: BodySchema;
|
|
419
432
|
querySchema?: QuerySchema;
|
|
420
433
|
paramsSchema?: ParamsSchema;
|
|
421
434
|
middleware?: [...M];
|
|
422
|
-
}, handler:
|
|
435
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
423
436
|
put(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
424
437
|
put<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
425
438
|
bodySchema?: BodySchema;
|
|
426
439
|
querySchema?: QuerySchema;
|
|
427
440
|
paramsSchema?: ParamsSchema;
|
|
428
441
|
middleware?: [...M];
|
|
429
|
-
}, handler:
|
|
442
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
430
443
|
patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
431
444
|
patch<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
432
445
|
bodySchema?: BodySchema;
|
|
433
446
|
querySchema?: QuerySchema;
|
|
434
447
|
paramsSchema?: ParamsSchema;
|
|
435
448
|
middleware?: [...M];
|
|
436
|
-
}, handler:
|
|
449
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
437
450
|
patch(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
438
451
|
patch<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
439
452
|
bodySchema?: BodySchema;
|
|
440
453
|
querySchema?: QuerySchema;
|
|
441
454
|
paramsSchema?: ParamsSchema;
|
|
442
455
|
middleware?: [...M];
|
|
443
|
-
}, handler:
|
|
456
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
444
457
|
delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
445
458
|
delete<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
446
459
|
querySchema?: QuerySchema;
|
|
447
460
|
paramsSchema?: ParamsSchema;
|
|
448
461
|
middleware?: [...M];
|
|
449
|
-
}, handler:
|
|
462
|
+
}, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
450
463
|
delete(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
451
464
|
delete<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
452
465
|
querySchema?: QuerySchema;
|
|
453
466
|
paramsSchema?: ParamsSchema;
|
|
454
467
|
middleware?: [...M];
|
|
455
|
-
}, handler:
|
|
468
|
+
}, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
456
469
|
options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
457
470
|
options<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
458
471
|
querySchema?: QuerySchema;
|
|
459
472
|
paramsSchema?: ParamsSchema;
|
|
460
473
|
middleware?: [...M];
|
|
461
|
-
}, handler:
|
|
474
|
+
}, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
462
475
|
options(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
463
476
|
options<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
464
477
|
querySchema?: QuerySchema;
|
|
465
478
|
paramsSchema?: ParamsSchema;
|
|
466
479
|
middleware?: [...M];
|
|
467
|
-
}, handler:
|
|
480
|
+
}, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
468
481
|
head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
469
482
|
head<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
470
483
|
querySchema?: QuerySchema;
|
|
471
484
|
paramsSchema?: ParamsSchema;
|
|
472
485
|
middleware?: [...M];
|
|
473
|
-
}, handler:
|
|
486
|
+
}, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
474
487
|
head(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
475
488
|
head<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
476
489
|
querySchema?: QuerySchema;
|
|
477
490
|
paramsSchema?: ParamsSchema;
|
|
478
491
|
middleware?: [...M];
|
|
479
|
-
}, handler:
|
|
492
|
+
}, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
480
493
|
all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
481
494
|
all<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
482
495
|
bodySchema?: BodySchema;
|
|
483
496
|
querySchema?: QuerySchema;
|
|
484
497
|
paramsSchema?: ParamsSchema;
|
|
485
498
|
middleware?: [...M];
|
|
486
|
-
}, handler:
|
|
499
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
487
500
|
all(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
488
501
|
all<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
489
502
|
bodySchema?: BodySchema;
|
|
490
503
|
querySchema?: QuerySchema;
|
|
491
504
|
paramsSchema?: ParamsSchema;
|
|
492
505
|
middleware?: [...M];
|
|
493
|
-
}, handler:
|
|
506
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
494
507
|
private registerRoute;
|
|
495
508
|
private createBodyValidationMiddleware;
|
|
496
509
|
private createParamsValidationMiddleware;
|
|
@@ -575,27 +588,16 @@ type RouterDocEntry = TypedRouter<any, any> | {
|
|
|
575
588
|
router: TypedRouter<any, any>;
|
|
576
589
|
};
|
|
577
590
|
/**
|
|
578
|
-
*
|
|
579
|
-
*
|
|
591
|
+
* Build the OpenAPI spec object directly, without mounting an Express router
|
|
592
|
+
* or making an HTTP request. For generating openapi.json at build/CI time,
|
|
593
|
+
* separately from running the app. Accepts the same router(s) shape as
|
|
594
|
+
* createDocs.
|
|
580
595
|
*
|
|
581
596
|
* @example
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
* // auth.router.ts — routes like /login, /logout
|
|
586
|
-
* export const authRouter = createTypedRouter();
|
|
587
|
-
*
|
|
588
|
-
* // app.ts
|
|
589
|
-
* app.use('/api', usersRouter.getRouter());
|
|
590
|
-
* app.use('/api', authRouter.getRouter());
|
|
591
|
-
* app.use('/docs', createDocs(
|
|
592
|
-
* [
|
|
593
|
-
* { prefix: '/api', router: usersRouter },
|
|
594
|
-
* { prefix: '/api', router: authRouter },
|
|
595
|
-
* ],
|
|
596
|
-
* { title: 'My API', version: '1.0.0' }
|
|
597
|
-
* ));
|
|
597
|
+
* const spec = await generateOpenApiSpec(router, { title: 'My API' });
|
|
598
|
+
* await fs.writeFile('./openapi.json', JSON.stringify(spec, null, 2));
|
|
598
599
|
*/
|
|
600
|
+
declare function generateOpenApiSpec(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): Promise<Record<string, any>>;
|
|
599
601
|
declare function createDocs(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): express.Router & express.RequestHandler;
|
|
600
602
|
//#endregion
|
|
601
|
-
export { AdditionalLocals, AdditionalReqProps, AnyStandardSchema, DocsOptions, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaHandler, InferSchemaHandlerOptions, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteOptions, RouterConfig, RouterDocEntry, SafeParseResult, SchemaLike, SchemaRequest, SchemaRouteHandler, TypedMiddleware, TypedRouter, createDocs, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, inferJsonSchema, isSchemaError, parseSchema, safeParseSchema };
|
|
603
|
+
export { AdditionalLocals, AdditionalReqProps, AnyStandardSchema, DocsOptions, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaHandler, InferSchemaHandlerOptions, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteOptions, RouterConfig, RouterDocEntry, SafeParseResult, SchemaLike, SchemaRequest, SchemaRouteHandler, TypedMiddleware, TypedRouter, createDocs, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, defineMiddleware, generateOpenApiSpec, inferJsonSchema, isSchemaError, parseSchema, safeParseSchema };
|
package/dist/schema-router.d.mts
CHANGED
|
@@ -112,6 +112,18 @@ type RequestOnlyMiddleware<TReq extends Record<string, any>> = TypedMiddleware<T
|
|
|
112
112
|
* Simplified TypedMiddleware for response locals-only extensions
|
|
113
113
|
*/
|
|
114
114
|
type LocalsOnlyMiddleware<TLocals extends Record<string, any>> = TypedMiddleware<{}, TLocals>;
|
|
115
|
+
/**
|
|
116
|
+
* Assigning a middleware array to a variable widens it to
|
|
117
|
+
* TypedMiddleware<any, any>[], losing the per-middleware types that
|
|
118
|
+
* InferSchemaHandler needs. The usual fix is `as const` on the array;
|
|
119
|
+
* defineMiddleware does the same thing without it, since the `const` type
|
|
120
|
+
* parameter keeps each argument's specific type instead of widening.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* const middleware = defineMiddleware(auth, logging);
|
|
124
|
+
* type Handler = InferSchemaHandler<{ middleware: typeof middleware }>;
|
|
125
|
+
*/
|
|
126
|
+
declare function defineMiddleware<const M extends readonly TypedMiddleware<any, any>[]>(...mw: M): [...M];
|
|
115
127
|
type InferMiddlewareProps<T extends readonly TypedMiddleware<any, any>[]> = T extends readonly [infer First, ...infer Rest] ? First extends TypedMiddleware<infer FirstReq, any> ? Rest extends readonly TypedMiddleware<any, any>[] ? FirstReq & InferMiddlewareProps<Rest> : FirstReq : {} : {};
|
|
116
128
|
type InferMiddlewareLocals<T extends readonly TypedMiddleware<any, any>[]> = T extends readonly [infer First, ...infer Rest] ? First extends TypedMiddleware<any, infer FirstLocals> ? Rest extends readonly TypedMiddleware<any, any>[] ? FirstLocals & InferMiddlewareLocals<Rest> : FirstLocals : {} : {};
|
|
117
129
|
type SchemaRequest<Path extends string = string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, MiddlewareProps extends Record<string, any> = {}, ParamsSchema extends SchemaLike | undefined = undefined, ParamsOverride extends Record<string, any> | undefined = undefined> = Omit<Request, "params" | "query" | "body"> & {
|
|
@@ -191,6 +203,7 @@ type InferHandlerOption<Options extends InferSchemaHandlerOptions, Key extends "
|
|
|
191
203
|
* }>;
|
|
192
204
|
*/
|
|
193
205
|
type InferSchemaHandler<Options extends InferSchemaHandlerOptions = {}> = SchemaRouteHandler<string, InferHandlerOption<Options, "bodySchema">, InferHandlerOption<Options, "querySchema">, InferMiddlewareProps<NormalizeHandlerMiddleware<InferHandlerOption<Options, "middleware">>>, InferMiddlewareLocals<NormalizeHandlerMiddleware<InferHandlerOption<Options, "middleware">>>, InferHandlerOption<Options, "paramsSchema">, Record<string, string | string[] | undefined>>;
|
|
206
|
+
type RouteHandlerFromOptions<Path extends string, BodySchema extends SchemaLike | undefined, QuerySchema extends SchemaLike | undefined, ParamsSchema extends SchemaLike | undefined, RouterReq extends Record<string, any>, RouterLocals extends Record<string, any>, M extends TypedMiddleware<any, any>[]> = SchemaRouteHandler<Path, NoInfer<BodySchema>, NoInfer<QuerySchema>, RouterReq & InferMiddlewareProps<readonly [...M]>, RouterLocals & InferMiddlewareLocals<readonly [...M]>, NoInfer<ParamsSchema>>;
|
|
194
207
|
type DocMeta = Pick<RouteOptions, "tags" | "summary" | "description" | "deprecated" | "responseSchema" | "hidden" | "pathExample">;
|
|
195
208
|
type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "all";
|
|
196
209
|
interface DocsOptions {
|
|
@@ -391,106 +404,106 @@ declare class TypedRouter<Req extends Record<string, any> = AdditionalReqProps,
|
|
|
391
404
|
querySchema?: QuerySchema;
|
|
392
405
|
paramsSchema?: ParamsSchema;
|
|
393
406
|
middleware?: [...M];
|
|
394
|
-
}, handler:
|
|
407
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
395
408
|
get(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
396
409
|
get<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
397
410
|
bodySchema?: BodySchema;
|
|
398
411
|
querySchema?: QuerySchema;
|
|
399
412
|
paramsSchema?: ParamsSchema;
|
|
400
413
|
middleware?: [...M];
|
|
401
|
-
}, handler:
|
|
414
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
402
415
|
post<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
403
416
|
post<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
404
417
|
bodySchema?: BodySchema;
|
|
405
418
|
querySchema?: QuerySchema;
|
|
406
419
|
paramsSchema?: ParamsSchema;
|
|
407
420
|
middleware?: [...M];
|
|
408
|
-
}, handler:
|
|
421
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
409
422
|
post(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
410
423
|
post<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
411
424
|
bodySchema?: BodySchema;
|
|
412
425
|
querySchema?: QuerySchema;
|
|
413
426
|
paramsSchema?: ParamsSchema;
|
|
414
427
|
middleware?: [...M];
|
|
415
|
-
}, handler:
|
|
428
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
416
429
|
put<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
417
430
|
put<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
418
431
|
bodySchema?: BodySchema;
|
|
419
432
|
querySchema?: QuerySchema;
|
|
420
433
|
paramsSchema?: ParamsSchema;
|
|
421
434
|
middleware?: [...M];
|
|
422
|
-
}, handler:
|
|
435
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
423
436
|
put(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
424
437
|
put<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
425
438
|
bodySchema?: BodySchema;
|
|
426
439
|
querySchema?: QuerySchema;
|
|
427
440
|
paramsSchema?: ParamsSchema;
|
|
428
441
|
middleware?: [...M];
|
|
429
|
-
}, handler:
|
|
442
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
430
443
|
patch<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
431
444
|
patch<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
432
445
|
bodySchema?: BodySchema;
|
|
433
446
|
querySchema?: QuerySchema;
|
|
434
447
|
paramsSchema?: ParamsSchema;
|
|
435
448
|
middleware?: [...M];
|
|
436
|
-
}, handler:
|
|
449
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
437
450
|
patch(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
438
451
|
patch<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
439
452
|
bodySchema?: BodySchema;
|
|
440
453
|
querySchema?: QuerySchema;
|
|
441
454
|
paramsSchema?: ParamsSchema;
|
|
442
455
|
middleware?: [...M];
|
|
443
|
-
}, handler:
|
|
456
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
444
457
|
delete<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
445
458
|
delete<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
446
459
|
querySchema?: QuerySchema;
|
|
447
460
|
paramsSchema?: ParamsSchema;
|
|
448
461
|
middleware?: [...M];
|
|
449
|
-
}, handler:
|
|
462
|
+
}, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
450
463
|
delete(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
451
464
|
delete<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
452
465
|
querySchema?: QuerySchema;
|
|
453
466
|
paramsSchema?: ParamsSchema;
|
|
454
467
|
middleware?: [...M];
|
|
455
|
-
}, handler:
|
|
468
|
+
}, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
456
469
|
options<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
457
470
|
options<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
458
471
|
querySchema?: QuerySchema;
|
|
459
472
|
paramsSchema?: ParamsSchema;
|
|
460
473
|
middleware?: [...M];
|
|
461
|
-
}, handler:
|
|
474
|
+
}, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
462
475
|
options(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
463
476
|
options<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
464
477
|
querySchema?: QuerySchema;
|
|
465
478
|
paramsSchema?: ParamsSchema;
|
|
466
479
|
middleware?: [...M];
|
|
467
|
-
}, handler:
|
|
480
|
+
}, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
468
481
|
head<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
469
482
|
head<Path extends string, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
470
483
|
querySchema?: QuerySchema;
|
|
471
484
|
paramsSchema?: ParamsSchema;
|
|
472
485
|
middleware?: [...M];
|
|
473
|
-
}, handler:
|
|
486
|
+
}, handler: RouteHandlerFromOptions<Path, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
474
487
|
head(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
475
488
|
head<QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
476
489
|
querySchema?: QuerySchema;
|
|
477
490
|
paramsSchema?: ParamsSchema;
|
|
478
491
|
middleware?: [...M];
|
|
479
|
-
}, handler:
|
|
492
|
+
}, handler: RouteHandlerFromOptions<string, undefined, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
480
493
|
all<Path extends string>(path: Path, handler: SchemaRouteHandler<Path, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
481
494
|
all<Path extends string, BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: Path, options: DocMeta & {
|
|
482
495
|
bodySchema?: BodySchema;
|
|
483
496
|
querySchema?: QuerySchema;
|
|
484
497
|
paramsSchema?: ParamsSchema;
|
|
485
498
|
middleware?: [...M];
|
|
486
|
-
}, handler:
|
|
499
|
+
}, handler: RouteHandlerFromOptions<Path, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
487
500
|
all(path: RegExp, handler: SchemaRouteHandler<string, undefined, undefined, Req, Locals>): TypedRouter<Req, Locals>;
|
|
488
501
|
all<BodySchema extends SchemaLike | undefined = undefined, QuerySchema extends SchemaLike | undefined = undefined, ParamsSchema extends SchemaLike | undefined = undefined, M extends TypedMiddleware<any, any>[] = []>(path: RegExp, options: DocMeta & {
|
|
489
502
|
bodySchema?: BodySchema;
|
|
490
503
|
querySchema?: QuerySchema;
|
|
491
504
|
paramsSchema?: ParamsSchema;
|
|
492
505
|
middleware?: [...M];
|
|
493
|
-
}, handler:
|
|
506
|
+
}, handler: RouteHandlerFromOptions<string, BodySchema, QuerySchema, ParamsSchema, Req, Locals, M>): TypedRouter<Req, Locals>;
|
|
494
507
|
private registerRoute;
|
|
495
508
|
private createBodyValidationMiddleware;
|
|
496
509
|
private createParamsValidationMiddleware;
|
|
@@ -575,27 +588,16 @@ type RouterDocEntry = TypedRouter<any, any> | {
|
|
|
575
588
|
router: TypedRouter<any, any>;
|
|
576
589
|
};
|
|
577
590
|
/**
|
|
578
|
-
*
|
|
579
|
-
*
|
|
591
|
+
* Build the OpenAPI spec object directly, without mounting an Express router
|
|
592
|
+
* or making an HTTP request. For generating openapi.json at build/CI time,
|
|
593
|
+
* separately from running the app. Accepts the same router(s) shape as
|
|
594
|
+
* createDocs.
|
|
580
595
|
*
|
|
581
596
|
* @example
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
* // auth.router.ts — routes like /login, /logout
|
|
586
|
-
* export const authRouter = createTypedRouter();
|
|
587
|
-
*
|
|
588
|
-
* // app.ts
|
|
589
|
-
* app.use('/api', usersRouter.getRouter());
|
|
590
|
-
* app.use('/api', authRouter.getRouter());
|
|
591
|
-
* app.use('/docs', createDocs(
|
|
592
|
-
* [
|
|
593
|
-
* { prefix: '/api', router: usersRouter },
|
|
594
|
-
* { prefix: '/api', router: authRouter },
|
|
595
|
-
* ],
|
|
596
|
-
* { title: 'My API', version: '1.0.0' }
|
|
597
|
-
* ));
|
|
597
|
+
* const spec = await generateOpenApiSpec(router, { title: 'My API' });
|
|
598
|
+
* await fs.writeFile('./openapi.json', JSON.stringify(spec, null, 2));
|
|
598
599
|
*/
|
|
600
|
+
declare function generateOpenApiSpec(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): Promise<Record<string, any>>;
|
|
599
601
|
declare function createDocs(routers: RouterDocEntry | RouterDocEntry[], options?: DocsOptions): express.Router & express.RequestHandler;
|
|
600
602
|
//#endregion
|
|
601
|
-
export { AdditionalLocals, AdditionalReqProps, AnyStandardSchema, DocsOptions, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaHandler, InferSchemaHandlerOptions, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteOptions, RouterConfig, RouterDocEntry, SafeParseResult, SchemaLike, SchemaRequest, SchemaRouteHandler, TypedMiddleware, TypedRouter, createDocs, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, inferJsonSchema, isSchemaError, parseSchema, safeParseSchema };
|
|
603
|
+
export { AdditionalLocals, AdditionalReqProps, AnyStandardSchema, DocsOptions, ExtractRouteParams, HttpMethod, InferInput, InferOutput, InferSchemaHandler, InferSchemaHandlerOptions, InferSchemaOutput, LocalsOnlyMiddleware, RequestOnlyMiddleware, RouteOptions, RouterConfig, RouterDocEntry, SafeParseResult, SchemaLike, SchemaRequest, SchemaRouteHandler, TypedMiddleware, TypedRouter, createDocs, createTypedRouter, createTypedRouterWithConfig, createTypedRouterWithMiddleware, defineMiddleware, generateOpenApiSpec, inferJsonSchema, isSchemaError, parseSchema, safeParseSchema };
|
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
|
|
1
|
+
import e from"express";import{SchemaError as t}from"@standard-schema/utils";function n(e,n){let r=e;if(r&&r[`~standard`]&&typeof r[`~standard`].validate==`function`){let e=r[`~standard`].validate(n);if(e instanceof Promise)throw TypeError(`Async schema validation is not supported by parseSchema`);if(e.issues)throw new t(e.issues);return e.value}throw TypeError(`Unsupported schema shape for parseSchema`)}function r(e,t){let n=e;if(n&&n[`~standard`]&&typeof n[`~standard`].validate==`function`)return n[`~standard`].validate(t);if(n&&typeof n.safeParse==`function`)return n.safeParse(t);if(n&&typeof n.parse==`function`)try{return{value:n.parse(t)}}catch(e){return{issues:[{message:e?.message??String(e)}]}}if(n&&typeof n.validate==`function`){let e=n.validate(t);return e&&e.then&&typeof e.then==`function`?e.then(e=>e.error?{issues:[{message:e.error.message}]}:e.issues?{issues:e.issues}:{value:e.value??e}):e&&e.error?{issues:[{message:e.error.message}]}:e&&e.issues?{issues:e.issues}:{value:e.value??e}}return{issues:[{message:`Unsupported schema shape`}]}}function i(e){return typeof e==`object`&&!!e&&`issues`in e&&Array.isArray(e.issues)}function a(...e){return e}const o=Function(`m`,`return import(m)`);let s,c;async function l(e){s??=await o(`module`),c??=await o(`url`);let t=[],n=globalThis.process?.argv?.[1];n&&t.push(c.pathToFileURL(n).href);let r=globalThis.process?.cwd?.()??``;r&&t.push(c.pathToFileURL(r+`/`).href);for(let n of t)try{let t=s.createRequire(n).resolve(e);return await o(c.pathToFileURL(t).href)}catch{}return o(e)}const u=new WeakMap,d=/\(\?<[^>]+>/;function f(e){return typeof e==`string`&&d.test(e)?new RegExp(e):e}function p(e){if(typeof e.path==`string`)return e.path;if(e.pathExample)return e.pathExample;let t=0;return e.path.source.replace(/\\\//g,`/`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function m(e){let t=[],n=0,r=0,i=!1,a=!1;for(let o=0;o<e.length;o++){let s=e[o];if(a){a=!1;continue}if(s===`\\`){a=!0;continue}if(s===`[`){i=!0;continue}if(s===`]`){i=!1;continue}i||(s===`(`?r++:s===`)`?r=Math.max(0,r-1):s===`|`&&r===0&&(t.push(e.slice(n,o)),n=o+1))}return t.push(e.slice(n)),t.length>1?t:[e]}function h(e){if(!e.startsWith(`(`)||!e.endsWith(`)`))return e;let t=0,n=!1,r=!1;for(let i=0;i<e.length;i++){let a=e[i];if(r){r=!1;continue}if(a===`\\`){r=!0;continue}if(a===`[`)n=!0;else if(a===`]`)n=!1;else if(!n&&a===`(`)t++;else if(!n&&a===`)`&&(t--,t===0&&i!==e.length-1))return e}return e.slice(1,-1).replace(/^\?:/,``)}function g(e){let t=0;return h(e).replace(/^\^|\$$/g,``).replace(/\\\//g,`/`).replace(/\.\*|\.\+/g,`/:path`).replace(/\/?\?$/,``).replace(/\(\?<([A-Za-z0-9_]+)>[^()]*\)/g,`:$1`).replace(/\((?!\?)[^()]*\)/g,()=>`:${t++}`)}function _(e){if(typeof e.path==`string`||e.pathExample)return[p(e)];let t=m(e.path.source);return t.length===1?[p(e)]:t.map(g)}function v(e){return e.replace(/\{([^{}]*)\}/g,`$1`).replace(/:([A-Za-z0-9_]+)(?:\([^)]*\))?[?+*]?/g,`{$1}`).replace(/\(\?<([A-Za-z0-9_]+)>[^)]*\)/g,`{$1}`).replace(/^\^|\$$/g,``).replace(/\/{2,}/g,`/`)}function y(e){let t=e.replace(/\{([^{}]*)\}/g,`$1`);return[...t.matchAll(/:([A-Za-z0-9_]+)/g),...t.matchAll(/\(\?<([A-Za-z0-9_]+)>/g)].map(e=>e[1])}function b(e){return e.startsWith(`:`)||e.startsWith(`*`)||e.includes(`(?<`)}function x(e){return e.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(Boolean).find(e=>!b(e))??`default`}function S(e,t){let n=t.replace(/^\^|\$$/g,``).replace(/[{}]/g,``).split(`/`).filter(e=>e&&!b(e)),r=n[n.length-1]??`resource`;return`${{get:`Get`,post:`Create`,put:`Update`,patch:`Patch`,delete:`Delete`,head:`Head`,options:`Options`}[e]??e} ${r}`}async function C(e){let t=u.get(e);if(t)return t;let n=await w(e);return u.set(e,n),n}async function w(e){if(typeof e.toJsonSchema==`function`)try{return e.toJsonSchema()}catch{}let t=e[`~standard`]?.vendor;if(t===`zod`){try{let t=await l(`zod`);if(typeof t.toJSONSchema==`function`)return t.toJSONSchema(e)}catch{}try{let t=await l(`zod-to-json-schema`),n=t.zodToJsonSchema??t.default?.zodToJsonSchema;if(typeof n==`function`)return n(e)}catch{}}if(t===`valibot`)try{let t=await l(`@valibot/to-json-schema`),n=t.toJsonSchema??t.default?.toJsonSchema;if(typeof n==`function`)return n(e)}catch{}if(t===`effect`)try{let t=await l(`effect`),n=t.JSONSchema?.make??t.default?.JSONSchema?.make;if(typeof n==`function`)return n(e)}catch{}return{}}function T(e){return E(e,0,new WeakSet)}function E(e,t,n){if(e==null)return{type:`null`};if(t>=12)return{};if(e instanceof Date)return{type:`string`,format:`date-time`};if(typeof e==`bigint`)return{type:`integer`};if(typeof e==`object`&&typeof e.toJSON==`function`)return E(e.toJSON(),t,n);if(Array.isArray(e)){if(e.length===0||n.has(e))return{type:`array`,items:{}};n.add(e);let r=Math.min(e.length,20),i=E(e[0],t+1,n);for(let a=1;a<r;a++)i=k(i,E(e[a],t+1,n));return n.delete(e),{type:`array`,items:i}}switch(typeof e){case`string`:return{type:`string`};case`boolean`:return{type:`boolean`};case`number`:return Number.isFinite(e)?{type:Number.isInteger(e)?`integer`:`number`}:{type:`null`};case`object`:{if(n.has(e))return{type:`object`};n.add(e);let r={},i=[];for(let[a,o]of Object.entries(e))typeof o!=`function`&&o!==void 0&&(r[a]=E(o,t+1,n),i.push(a));n.delete(e);let a={type:`object`,properties:r};return i.length&&(a.required=i),a}default:return{}}}function D(e){return!e||e.type===void 0?new Set:new Set(Array.isArray(e.type)?e.type:[e.type])}function O(e){e.has(`integer`)&&e.has(`number`)&&e.delete(`integer`);let t=[...e];if(t.length!==0)return t.length===1?t[0]:t}function k(e,t){if(!e||Object.keys(e).length===0)return t??{};if(!t||Object.keys(t).length===0)return e??{};let n=new Set([...D(e),...D(t)]),r={},i=O(n);if(i!==void 0&&(r.type=i),n.has(`object`)&&(e.properties||t.properties)){let n=e.properties??{},i=t.properties??{},a={};for(let e of new Set([...Object.keys(n),...Object.keys(i)]))a[e]=k(n[e],i[e]);r.properties=a;let o=e.required??[],s=t.required??[],c=o.filter(e=>s.includes(e));c.length&&(r.required=c)}if(n.has(`array`)){let n=k(e.items,t.items);n&&Object.keys(n).length&&(r.items=n)}return r}function A(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`)}const j=`https://cdn.jsdelivr.net/npm/@scalar/api-reference`;function M(e,t,n){return`<!doctype html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
|
-
<title>${
|
|
4
|
+
<title>${A(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="${A(t)}"><\/script>
|
|
10
|
+
<script src="${A(n)}"><\/script>
|
|
11
11
|
</body>
|
|
12
|
-
</html>`}async function
|
|
12
|
+
</html>`}async function N(e,t){let n=Object.create(null);for(let t of e)if(!(t.method===`all`||t.hidden))for(let e of _(t)){let r=v(e);n[r]||(n[r]={});let i;if(t.paramsSchema){let e=await C(t.paramsSchema),n=e.properties??{},r=e.required??[];i=Object.entries(n).map(([e,t])=>({name:e,in:`path`,required:r.includes(e),schema:t}))}else i=y(e).map(e=>({name:e,in:`path`,required:!0,schema:{type:`string`}}));if(t.querySchema){let e=await C(t.querySchema),n=e.properties??{},r=e.required??[];for(let[e,t]of Object.entries(n))i.push({name:e,in:`query`,required:r.includes(e),schema:t})}let a={summary:t.summary??S(t.method,e),tags:t.tags??[x(e)],parameters:i};t.description&&(a.description=t.description),t.deprecated&&(a.deprecated=!0),t.bodySchema&&(a.requestBody={required:!0,content:{"application/json":{schema:await C(t.bodySchema)}}});let o={};for(let[e,n]of t.responseSamples){let t={schema:n.schema};n.example!==void 0&&(t.example=n.example),o[String(e)]={description:e<400?`Success`:`Error`,content:{"application/json":t}}}if(t.responseSchema){let e=await C(t.responseSchema);if(e&&Object.keys(e).length){let n=`200`;for(let e of t.responseSamples.keys())if(e>=200&&e<300){n=String(e);break}o[n]={description:`Success`,content:{"application/json":{schema:e}}}}}Object.keys(o).length===0&&(o[200]={description:`Success`}),a.responses=o,n[r][t.method]=a}return{openapi:`3.1.0`,info:{title:t.title??`API`,version:t.version??`1.0.0`,...t.description?{description:t.description}:{}},...t.servers?{servers:t.servers}:{},paths:n}}const P=new WeakMap;var F=class t{router;routes=[];mountedRouters=[];sampleMode=`off`;scheduleSpecWrite;constructor(){this.router=e.Router(),P.set(this.router,this)}useMiddleware(e){return this.router.use(e),this}getRouter(){return this.router}use(e,...n){let r=typeof e==`string`,i=r?e:``,a=(r?n:[e,...n]).map(e=>{if(e instanceof t)return this.trackMounted(i,e),e.getRouter();let n=P.get(e);return n&&this.trackMounted(i,n),e});return r?this.router.use(e,...a):this.router.use(...a),this}mount(e,t){if(typeof e==`string`){let n=t;this.router.use(e,n.getRouter()),this.trackMounted(e,n)}else this.router.use(e.getRouter()),this.trackMounted(``,e);return this}getRouteMetadata(){let e=this.mountedRouters.flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:e+p(t)}})));return[...this.routes,...e]}enableSampling(e=`redacted`,t,n=new Set){if(!n.has(this)){n.add(this),this.sampleMode=e,this.scheduleSpecWrite=t;for(let{router:r}of this.mountedRouters)r.enableSampling(e,t,n)}}trackMounted(e,t){this.mountedRouters.some(n=>n.router===t&&n.prefix===e)||(this.mountedRouters.push({prefix:e,router:t}),this.sampleMode!==`off`&&t.enableSampling(this.sampleMode,this.scheduleSpecWrite))}hydrateResponses(e,t=``,n=new Set){if(n.has(this))return;n.add(this);let r=e?.paths??{};for(let e of this.routes)for(let n of _(e)){let i=r[v(t+n)]?.[e.method]?.responses;if(i)for(let t of Object.keys(i)){let n=Number(t);if(Number.isNaN(n)||e.responseSamples.has(n))continue;let r=i[t]?.content?.[`application/json`];r?.schema&&e.responseSamples.set(n,r.example===void 0?{schema:r.schema}:{schema:r.schema,example:r.example})}}for(let{prefix:r,router:i}of this.mountedRouters)i.hydrateResponses(e,t+r,n)}docs(t={}){let n=e.Router(),r;if(t.specOutputPath){let e=t.specOutputPath,n=!1,i=!1,a=async()=>{if(n){i=!0;return}n=!0;try{let n=await N(this.getRouteMetadata(),t),r=await o(`fs/promises`),i=e.replace(/[/\\][^/\\]*$/,``);i&&i!==e&&await r.mkdir(i,{recursive:!0}).catch(()=>{});let a=globalThis.process?.pid??`0`,s=`${e}.${a}.tmp`;await r.writeFile(s,JSON.stringify(n,null,2),`utf8`),await r.rename(s,e)}catch{}finally{n=!1,i&&(i=!1,a())}},s;r=()=>{s&&clearTimeout(s),s=setTimeout(a,300),s.unref?.()},setImmediate(async()=>{try{let t=await(await o(`fs/promises`)).readFile(e,`utf8`).catch(()=>null);if(t)try{this.hydrateResponses(JSON.parse(t))}catch{}}catch{}await a()})}return t.sampleResponses!==!1&&this.enableSampling(t.sampleResponses===`live`?`live`:`redacted`,r),n.get(`/openapi.json`,async(e,n)=>{try{let e=await N(this.getRouteMetadata(),t);n.json(e)}catch(e){n.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),n.get(`/`,(e,n)=>{let r=`${e.baseUrl}/openapi.json`,i=t.cdnUrl??j;n.setHeader(`Content-Type`,`text/html; charset=utf-8`),n.send(M(t.title??`API`,r,i))}),n}get(e,t,n){return this.registerRoute(`get`,e,t,n)}post(e,t,n){return this.registerRoute(`post`,e,t,n)}put(e,t,n){return this.registerRoute(`put`,e,t,n)}patch(e,t,n){return this.registerRoute(`patch`,e,t,n)}delete(e,t,n){return this.registerRoute(`delete`,e,t,n)}options(e,t,n){return this.registerRoute(`options`,e,t,n)}head(e,t,n){return this.registerRoute(`head`,e,t,n)}all(e,t,n){return this.registerRoute(`all`,e,t,n)}registerRoute(e,t,n,r){let i=[],a={method:e,path:t,responseSamples:new Map};if(this.routes.push(a),typeof n==`object`){let e=n;a.bodySchema=e.bodySchema,a.querySchema=e.querySchema,a.paramsSchema=e.paramsSchema,a.tags=e.tags,a.description=e.description,a.summary=e.summary,a.deprecated=e.deprecated,a.responseSchema=e.responseSchema,a.hidden=e.hidden,a.pathExample=e.pathExample,e.middleware&&i.push(...e.middleware),e.bodySchema&&i.push(this.createBodyValidationMiddleware(e.bodySchema)),e.querySchema&&i.push(this.createQueryValidationMiddleware(e.querySchema)),e.paramsSchema&&i.push(this.createParamsValidationMiddleware(e.paramsSchema)),i.push(r)}else i.push(n);let o=0,s=(e,t)=>{let n=e.statusCode,r=T(t),i=a.responseSamples.get(n),o=i?k(i.schema,r):r,s=this.sampleMode===`live`?i?.example??t:void 0,c=!i||JSON.stringify(i.schema)!==JSON.stringify(o);a.responseSamples.set(n,{schema:o,example:s}),c&&this.scheduleSpecWrite?.()};return this.router[e](f(t),(e,t,n)=>{if(this.sampleMode===`off`||a.hidden||o>=50||a.responseSamples.size>=10){n();return}o++;let r=t.json;t.json=function(e){return s(t,e),r.call(this,e)};let i=t.send;t.send=function(e){return typeof e==`object`&&e&&!Buffer.isBuffer(e)&&s(t,e),i.call(this,e)},n()},...i),this}createBodyValidationMiddleware(e){return async(t,n,a)=>{try{let i=r(e,t.body),o=i&&typeof i.then==`function`?await i:i;if(o&&`issues`in o&&o.issues){n.status(400).json({error:`Validation failed`,details:o.errors||o.issues});return}t.body=o&&`value`in o?o.value:o,a()}catch(e){i(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):a(e)}}}createParamsValidationMiddleware(e){return async(t,n,a)=>{try{let i=r(e,t.params),o=i&&typeof i.then==`function`?await i:i;if(o&&`issues`in o&&o.issues){n.status(400).json({error:`Validation failed`,details:o.errors||o.issues});return}t.params=o&&`value`in o?o.value:o,a()}catch(e){i(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):a(e)}}}createQueryValidationMiddleware(e){return async(t,n,a)=>{try{let i=r(e,t.query),o=i&&typeof i.then==`function`?await i:i;if(o&&`issues`in o&&o.issues){n.status(400).json({error:`Validation failed`,details:o.errors||o.issues});return}let s=o&&`value`in o?o.value:o;Object.defineProperty(t,"query",{value:s,writable:!1,enumerable:!0,configurable:!0}),a()}catch(e){i(e)?n.status(400).json({error:`Validation failed`,details:e.errors||e.issues}):a(e)}}}};function I(){return new F}function L(e){let t=new F;return e?.errorHandler&&t.getRouter().use(e.errorHandler),t}function R(...e){let t=new F;for(let n of e)t=t.useMiddleware(n);return t}function z(e){return(Array.isArray(e)?e:[e]).map(e=>`prefix`in e?e:{prefix:``,router:e}).flatMap(({prefix:e,router:t})=>t.getRouteMetadata().map(t=>({...t,...typeof t.path==`string`?{path:e+t.path}:{path:t.path,pathExample:e+p(t)}})))}async function B(e,t={}){return N(z(e),t)}function V(t,n={}){if(n.sampleResponses!==!1){let e=n.sampleResponses===`live`?`live`:`redacted`,r=Array.isArray(t)?t:[t];for(let t of r)(`prefix`in t?t.router:t).enableSampling(e)}let r=e.Router();return r.get(`/openapi.json`,async(e,r)=>{try{let e=await B(t,n);r.json(e)}catch(e){r.status(500).json({error:`Failed to generate spec`,details:String(e)})}}),r.get(`/`,(e,t)=>{let r=`${e.baseUrl}/openapi.json`,i=n.cdnUrl??j;t.setHeader(`Content-Type`,`text/html; charset=utf-8`),t.send(M(n.title??`API`,r,i))}),r}export{F as TypedRouter,V as createDocs,I as createTypedRouter,L as createTypedRouterWithConfig,R as createTypedRouterWithMiddleware,a as defineMiddleware,B as generateOpenApiSpec,T as inferJsonSchema,i as isSchemaError,n as parseSchema,r as safeParseSchema};
|
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.8",
|
|
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",
|