jsonv-ts
Version:
JSON Schema builder and validator for TypeScript with static type inference, Hono middleware for OpenAPI generation and validation, and MCP server/client implementation. Lightweight, dependency-free, and built on Web Standards.
2 lines (1 loc) • 3.56 kB
JavaScript
import{validator as x}from"hono/validator";var c=Symbol.for("jsonv"),m=(e,n)=>Object.assign(e,{[c]:n});var g=(e,n,t,r)=>{let o=x(e,async(a,i)=>{let s=t?.coerce!==!1?n.coerce(a,{dropUnknown:t?.dropUnknown}):a,p=n.validate(s);if(!p.valid)return i.json({...p,schema:n},400);if(r){let d=r({result:p,data:s},i);if(d)return d}return s});return m(o,{type:"parameters",skip:t?.skipOpenAPI,value:{target:e,schema:n}})};function R(e){return Object.prototype.toString.call(e)==="[object Object]"}function P(e){return e!==null&&typeof e=="object"}function u(e,...n){for(let t of n)for(let[r,o]of Object.entries(t))o!==void 0&&(!R(o)&&!Array.isArray(o)||Array.isArray(o)&&!Array.isArray(e[r])?e[r]=o:P(e[r])?u(e[r],o):e[r]=o);return e}var S={query:"query",param:"path",header:"header",cookie:"cookie"},b={json:{type:"application/json",example:!0},form:{type:"multipart/form-data"},binary:{type:"application/octet-stream"}};function l(e,n,{type:t,value:r}){let o=k(n.path),a=n.method.toLowerCase();e.paths||(e.paths={}),e.paths?.[o]||(e.paths[o]={}),e.paths?.[o]?.[a]||(e.paths[o][a]={responses:{200:{description:"Success"}},operationId:I(a,o)});let i=e.paths[o][a];switch(t){case"parameters":let{parameters:s,requestBody:p}=O(r.schema,r.target);s&&(i.parameters||(i.parameters=[]),i.parameters.push(...s.filter(d=>{try{return!i.parameters?.some(f=>f.name===d.name&&f.in===d.in)}catch{return!0}}))),p&&(i.requestBody||(i.requestBody={}),u(i.requestBody,p));break;case"route-doc":u(i,r);break}i.parameters&&Array.isArray(i.parameters)&&(i.parameters=v(i.parameters))}function O(e,n){let t=S[n],r=b[n];if(t)return{parameters:Object.entries(e.properties).map(([o,a])=>({name:o,in:t,required:e.required?.includes(o)||void 0,description:a?.description||void 0,schema:structuredClone(a.toJSON())}))};if(r){let o=!!r.example;return{requestBody:{content:{[r.type]:{schema:structuredClone(e.toJSON()),example:o?e.examples?.[0]??e.template({},{withOptional:!0,withExtendedOptional:!0}):void 0}}}}}return{}}function v(e){return e.sort((n,t)=>{let r=o=>{let a=o.in==="path",i=o.required===!0;return a&&i?1:a?2:i?3:4};return r(n)-r(t)})}var k=e=>e.split("/").map(n=>{let t=n;if(t.startsWith(":")){let r=t.match(/^:([^{?]+)(?:{(.+)})?(\?)?$/);r?t=`{${r[1]}}`:(t=t.slice(1,t.length),t.endsWith("?")&&(t=t.slice(0,-1)),t=`{${t}}`)}return t}).join("/"),h=e=>e.charAt(0).toUpperCase()+e.slice(1),y=new Map,I=(e,n)=>{let t=`${e}:${n}`;if(y.has(t))return y.get(t);let r=e;if(n==="/")return`${r}Index`;for(let o of n.split("/"))o.charCodeAt(0)===123?r+=`By${h(o.slice(1,-1))}`:r+=h(o);return y.set(t,r),r};var j=(e,n={})=>{let t=!1;return async r=>{if(!t){t=!0,n.paths=n.paths??{};for(let o of e.routes)if(c in o.handler){let a=o.handler[c];(!("skip"in a)||a.skip!==!0)&&l(n,o,a)}}return r.json({openapi:"3.1.0",info:{title:"API",version:"1.0.0",...n.info},...n})}},w=e=>m(async(t,r)=>{await r()},{type:"route-doc",value:e??{}});function A(e,n){let t={};for(let r of e.routes){let o=r.path,a=r.method.toUpperCase();if(t[o]||(t[o]={}),t[o][a]||(t[o][a]={method:a}),r.handler&&(t[o][a].handler=r.handler),c in r.handler){let i=r.handler[c];if(i){if(!n?.skipOpenAPI){t[o][a].openAPI||(t[o][a].openAPI={});let s={};l(s,r,i);let p=s.paths[Object.keys(s.paths)[0]];u(t[o][a].openAPI,p[Object.keys(p)[0]])}i.type==="parameters"&&(t[o][a].validation||(t[o][a].validation={}),t[o][a].validation[i.value.target]||(t[o][a].validation[i.value.target]=n?.useSchemas?i.value.schema:i.value.schema.toJSON()))}}}return t}export{w as describeRoute,A as info,j as openAPISpecs,O as schemaToSpec,g as validator};