hono-openapi 0.4.6 → 0.4.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 CHANGED
@@ -151,11 +151,9 @@ Now, you can access the OpenAPI specification by visiting `http://localhost:3000
151
151
  ```ts
152
152
  app.get(
153
153
  "/docs",
154
- apiReference({
154
+ Scalar({
155
155
  theme: "saturn",
156
- spec: {
157
- url: "/openapi",
158
- },
156
+ url: "/openapi",
159
157
  })
160
158
  );
161
159
  ```
@@ -240,6 +238,28 @@ app.get(
240
238
  );
241
239
  ```
242
240
 
241
+ #### Persisting OpenAPI Spec to a file
242
+
243
+ You can save the spec to a file for cache or any other external use.
244
+
245
+ ```ts
246
+ import fs from 'node:fs';
247
+ import { openAPISpecs, generateSpecs } from 'hono-openapi';
248
+
249
+ const options = {/* ... */};
250
+ const app = new Hono()
251
+ .get(
252
+ "/openapi",
253
+ openAPISpecs(app, options),
254
+ );
255
+
256
+ generateSpecs(app, options)
257
+ .then(spec => {
258
+ const pathToSpec = "openapi.json"
259
+ fs.writeFileSync(pathToSpec, JSON.stringify(spec, null, 2));
260
+ })
261
+ ```
262
+
243
263
  ## Contributing
244
264
 
245
265
  We would love to have more contributors involved!
package/arktype.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var e=require("@hono/arktype-validator"),r=require("./toOpenAPISchema.cjs"),a=require("./utils.cjs");function t(e){return{builder:async()=>({schema:await r.convert(e.toJsonSchema())}),validator:r=>{e.assert(r)}}}require("json-schema-walker"),exports.resolver=t,exports.validator=function(r,o,s){const i=e.arktypeValidator(r,o,s);return Object.assign(i,{[a.uniqueSymbol]:{resolver:async e=>a.generateValidatorDocs(r,await t(o).builder(e))}})};
1
+ "use strict";var e=require("@hono/arktype-validator"),r=require("./toOpenAPISchema.cjs"),a=require("./utils.cjs");function t(e){return{builder:async()=>({schema:await r.convert(e.in.toJsonSchema())}),validator:r=>{e.assert(r)}}}require("json-schema-walker"),exports.resolver=t,exports.validator=function(r,o,s){const i=e.arktypeValidator(r,o,s);return Object.assign(i,{[a.uniqueSymbol]:{resolver:async e=>a.generateValidatorDocs(r,await t(o).builder(e))}})};
package/arktype.js CHANGED
@@ -1 +1 @@
1
- import{arktypeValidator as a}from"@hono/arktype-validator";import{c as r}from"./toOpenAPISchema.js";import{u as o,g as t}from"./utils.js";import"json-schema-walker";function s(a){return{builder:async()=>({schema:await r(a.toJsonSchema())}),validator:r=>{a.assert(r)}}}function e(r,e,i){const n=a(r,e,i);return Object.assign(n,{[o]:{resolver:async a=>t(r,await s(e).builder(a))}})}export{s as resolver,e as validator};
1
+ import{arktypeValidator as a}from"@hono/arktype-validator";import{c as r}from"./toOpenAPISchema.js";import{u as o,g as t}from"./utils.js";import"json-schema-walker";function s(a){return{builder:async()=>({schema:await r(a.in.toJsonSchema())}),validator:r=>{a.assert(r)}}}function e(r,e,i){const n=a(r,e,i);return Object.assign(n,{[o]:{resolver:async a=>t(r,await s(e).builder(a))}})}export{s as resolver,e as validator};
package/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var e=require("./utils.cjs"),t=require("hono/http-exception");const s=["GET","PUT","POST","DELETE","OPTIONS","HEAD","PATCH","TRACE"],n=e=>e.charAt(0).toUpperCase()+e.slice(1),o=new Map,a=(e,t)=>{const s=`${e}:${t}`;if(o.has(s))return o.get(s);let a=e;if("/"===t)return`${a}Index`;for(const e of t.split("/"))123===e.charCodeAt(0)?a+=`By${n(e.slice(1,-1))}`:a+=n(e);return o.set(s,a),a},r=new Map;function c(e,t,s){return e&&t in e?e[t]??s:s}function i(...e){return e.reduce(((e,t)=>{if(!t)return e;let s;return("tags"in e&&e.tags||"tags"in t&&t.tags)&&(s=[...c(e,"tags",[]),...c(t,"tags",[])]),{...e,...t,tags:s,responses:{...c(e,"responses",{}),...c(t,"responses",{})},parameters:u(e.parameters,t.parameters)}}),{})}function p({path:e,method:t,data:s,schema:n}){e=(e=>e.split("/").map((e=>{let t=e;return t.startsWith(":")&&(t=t.slice(1,t.length),t.endsWith("?")&&(t=t.slice(0,-1)),t=`{${t}}`),t})).join("/"))(e);const o=t.toLowerCase();if("all"===o)if(r.has(e)){const t=r.get(e)??{};r.set(e,{...t,...s,parameters:u(t.parameters,s.parameters)})}else r.set(e,s);else{const t=function(e){const t=Array.from(r.keys());let s={};for(const n of t)e.match(n)&&(s=i(s,r.get(n)??{}));return s}(e);n[e]={...n[e]?n[e]:{},[o]:{responses:{},operationId:a(o,e),...i(t,n[e]?.[o],s)}}}}const l=e=>"$ref"in e?e.$ref:`${e.in} ${e.name}`;function u(...e){const t=e.flatMap((e=>e??[])).reduce(((e,t)=>(e.set(l(t),t),e)),new Map);return Array.from(t.values())}function m(e,{excludeStaticFile:t=!0,exclude:s=[]}){const n={},o=Array.isArray(s)?s:[s];for(const[s,a]of Object.entries(e))if(!(o.some((e=>"string"==typeof e?s===e:e.test(s)))||s.includes("*")||t&&s.includes("."))){for(const e of Object.keys(a)){const t=a[e];if(s.includes("{")){t.parameters||(t.parameters=[]);const e=s.split("/").filter((e=>e.startsWith("{")&&!t.parameters.find((t=>"path"===t.in&&t.name===e.slice(1,e.length-1)))));for(const s of e){const e=s.slice(1,s.length-1),n=t.parameters.findIndex((t=>"param"===t.in&&t.name===e));-1!==n?t.parameters[n].in="path":t.parameters.push({schema:{type:"string"},in:"path",name:e,required:!0})}}t.responses||(t.responses={200:{}})}n[s]=a}return n}async function d(t,{documentation:n={},excludeStaticFile:o=!0,exclude:a=[],excludeMethods:r=["OPTIONS"],excludeTags:c=[],defaultOptions:i}={documentation:{},excludeStaticFile:!0,exclude:[],excludeMethods:["OPTIONS"],excludeTags:[]},{version:l="3.1.0",components:u={}}={version:"3.1.0",components:{}},d){const f={version:l,components:u},h={};for(const n of t.routes){if(!(e.uniqueSymbol in n.handler))continue;if(r.includes(n.method))continue;if(!1===s.includes(n.method)&&"ALL"!==n.method)continue;const{resolver:t,metadata:o={}}=n.handler[e.uniqueSymbol],a=i?.[n.method],{docs:c,components:l}=await t({...f,...o},a);f.components={...f.components,...l??{}},p({method:n.method,path:n.path,data:c,schema:h})}for(const e in h)for(const t in h[e]){const s=h[e][t]?.hide;s&&("boolean"==typeof s?s:d&&s(d))&&delete h[e][t]}return{openapi:f.version,...{...n,tags:n.tags?.filter((e=>!c?.includes(e?.name))),info:{title:"Hono Documentation",description:"Development documentation",version:"0.0.0",...n.info},paths:{...m(h,{excludeStaticFile:o,exclude:a}),...n.paths},components:{...n.components,schemas:{...f.components,...n.components?.schemas}}}}}async function f(e,t,s={}){let n={};const o={...s,...t,responses:{...s?.responses,...t.responses}};if(o.responses)for(const t of Object.keys(o.responses)){const s=o.responses[t];if(s&&"content"in s)for(const t of Object.keys(s.content??{})){const o=s.content?.[t];if(o&&(o.schema&&"builder"in o.schema)){const t=await o.schema.builder(e);o.schema=t.schema,t.components&&(n={...n,...t.components})}}}return{docs:o,components:n}}exports.generateValidatorDocs=e.generateValidatorDocs,exports.uniqueSymbol=e.uniqueSymbol,exports.describeRoute=function(s){const{validateResponse:n,...o}=s;return Object.assign((async(e,o)=>{if(await o(),n&&s.responses){const o=e.res.status,a=e.res.headers.get("content-type");if(o&&a){const r=s.responses[o];if(r&&"content"in r&&r.content){const s=a.split(";")[0],o=r.content[s];if(o?.schema&&"validator"in o.schema)try{let t;const n=e.res.clone();if("application/json"===s?t=await n.json():"text/plain"===s&&(t=await n.text()),!t)throw new Error("No data to validate!");await o.schema.validator(t)}catch(e){let s={status:500,message:"Response validation failed!"};throw"object"==typeof n&&(s={...s,...n}),new t.HTTPException(s.status,{message:s.message,cause:e})}}}}}),{[e.uniqueSymbol]:{resolver:(e,t)=>f(e,o,t)}})},exports.generateRouteSpecs=f,exports.generateSpecs=d,exports.openAPISpecs=function(e,t){const s={version:"3.1.0",components:{}};let n=null;return async o=>(n||(n=await d(e,t,s,o)),o.json(n))};
1
+ "use strict";var e=require("./utils.cjs"),t=require("hono/http-exception");const n=["GET","PUT","POST","DELETE","OPTIONS","HEAD","PATCH","TRACE"],s=e=>e.charAt(0).toUpperCase()+e.slice(1),o=new Map,a=(e,t)=>{const n=`${e}:${t}`;if(o.has(n))return o.get(n);let a=e;if("/"===t)return`${a}Index`;for(const e of t.split("/"))123===e.charCodeAt(0)?a+=`By${s(e.slice(1,-1))}`:a+=s(e);return o.set(n,a),a},r=new Map;function c(e,t,n){return e&&t in e?e[t]??n:n}function i(...e){return e.reduce(((e,t)=>{if(!t)return e;let n;return("tags"in e&&e.tags||"tags"in t&&t.tags)&&(n=Array.from(new Set([...c(e,"tags",[]),...c(t,"tags",[])]))),{...e,...t,tags:n,responses:{...c(e,"responses",{}),...c(t,"responses",{})},parameters:l(e.parameters,t.parameters)}}),{})}function p({path:e,method:t,data:n,schema:s}){e=(e=>e.split("/").map((e=>{let t=e;if(t.startsWith(":")){const e=t.match(/^:([^{?]+)(?:{(.+)})?(\?)?$/);e?t=`{${e[1]}}`:(t=t.slice(1,t.length),t.endsWith("?")&&(t=t.slice(0,-1)),t=`{${t}}`)}return t})).join("/"))(e);const o=t.toLowerCase();if("all"===o){if(!n)return;if(r.has(e)){const t=r.get(e)??{};r.set(e,{...t,...n,parameters:l(t.parameters,n.parameters)})}else r.set(e,n)}else{const t=function(e){const t=Array.from(r.keys());let n={};for(const s of t)e.match(s)&&(n=i(n,r.get(s)??{}));return n}(e);s[e]={...s[e]?s[e]:{},[o]:{responses:{},operationId:a(o,e),...i(t,s[e]?.[o],n)}}}}const u=e=>"$ref"in e?e.$ref:`${e.in} ${e.name}`;function l(...e){const t=e.flatMap((e=>e??[])).reduce(((e,t)=>(e.set(u(t),t),e)),new Map);return Array.from(t.values())}function m(e,{excludeStaticFile:t=!0,exclude:n=[]}){const s={},o=Array.isArray(n)?n:[n];for(const[n,a]of Object.entries(e))if(!o.some((e=>"string"==typeof e?n===e:e.test(n)))&&(!n.includes("*")||n.includes("{"))&&(!t||(!n.includes(".")||n.includes("{")))){for(const e of Object.keys(a)){const t=a[e];if(n.includes("{")){t.parameters||(t.parameters=[]);const e=n.split("/").filter((e=>e.startsWith("{")&&!t.parameters.find((t=>"path"===t.in&&t.name===e.slice(1,e.length-1)))));for(const n of e){const e=n.slice(1,n.length-1),s=t.parameters.findIndex((t=>"param"===t.in&&t.name===e));-1!==s?t.parameters[s].in="path":t.parameters.push({schema:{type:"string"},in:"path",name:e,required:!0})}}t.responses||(t.responses={200:{}})}s[n]=a}return s}const f={documentation:{},excludeStaticFile:!0,exclude:[],excludeMethods:["OPTIONS"],excludeTags:[]},d={version:"3.1.0",components:{}};async function h(t,s=f,o=d,a){const r={...f,...s},c={...d,...o},i=r.documentation??{},u=await async function(t,s,o){const a={};for(const r of t.routes){if(!(e.uniqueSymbol in r.handler)){s.includeEmptyPaths&&p({method:r.method,path:r.path,schema:a});continue}if(s.excludeMethods.includes(r.method))continue;if(!1===n.includes(r.method)&&"ALL"!==r.method)continue;const{resolver:t,metadata:c={}}=r.handler[e.uniqueSymbol],i=s.defaultOptions?.[r.method],{docs:u,components:l}=await t({...o,...c},i);o.components={...o.components,...l??{}},p({method:r.method,path:r.path,data:u,schema:a})}return a}(t,r,c);for(const e in u)for(const t in u[e]){const n=u[e][t]?.hide;if(n){let s=!1;"boolean"==typeof n?s=n:"function"==typeof n&&(a?s=n(a):console.warn(`'c' is not defined, cannot evaluate hide function for ${t} ${e}`)),s&&delete u[e][t]}}return{openapi:c.version,...{...i,tags:i.tags?.filter((e=>!r.excludeTags?.includes(e?.name))),info:{title:"Hono Documentation",description:"Development documentation",version:"0.0.0",...i.info},paths:{...m(u,r),...i.paths},components:{...i.components,schemas:{...c.components,...i.components?.schemas}}}}}async function g(e,t,n={}){let s={};const o={...n,...t,responses:{...n?.responses,...t.responses}};if(o.responses)for(const t of Object.keys(o.responses)){const n=o.responses[t];if(n&&"content"in n)for(const t of Object.keys(n.content??{})){const o=n.content?.[t];if(o&&(o.schema&&"builder"in o.schema)){const t=await o.schema.builder(e);o.schema=t.schema,t.components&&(s={...s,...t.components})}}}return{docs:o,components:s}}exports.generateValidatorDocs=e.generateValidatorDocs,exports.uniqueSymbol=e.uniqueSymbol,exports.describeRoute=function(n){const{validateResponse:s,...o}=n;return Object.assign((async(e,o)=>{if(await o(),s&&n.responses){const o=e.res.status,a=e.res.headers.get("content-type");if(o&&a){const r=n.responses[o];if(r&&"content"in r&&r.content){const n=a.split(";")[0],o=r.content[n];if(o?.schema&&"validator"in o.schema)try{let t;const s=e.res.clone();if("application/json"===n?t=await s.json():"text/plain"===n&&(t=await s.text()),!t)throw new Error("No data to validate!");await o.schema.validator(t)}catch(e){let n={status:500,message:"Response validation failed!"};throw"object"==typeof s&&(n={...n,...s}),new t.HTTPException(n.status,{message:n.message,cause:e})}}}}}),{[e.uniqueSymbol]:{resolver:(e,t)=>g(e,o,t)}})},exports.generateRouteSpecs=g,exports.generateSpecs=h,exports.openAPISpecs=function(e,t){const n={version:"3.1.0",components:{}};let s;return async o=>(s||(s=await h(e,t,n,o)),o.json(s))};
package/index.js CHANGED
@@ -1 +1 @@
1
- import{u as e}from"./utils.js";export{g as generateValidatorDocs}from"./utils.js";import{HTTPException as t}from"hono/http-exception";const s=["GET","PUT","POST","DELETE","OPTIONS","HEAD","PATCH","TRACE"],n=e=>e.charAt(0).toUpperCase()+e.slice(1),o=new Map,a=(e,t)=>{const s=`${e}:${t}`;if(o.has(s))return o.get(s);let a=e;if("/"===t)return`${a}Index`;for(const e of t.split("/"))123===e.charCodeAt(0)?a+=`By${n(e.slice(1,-1))}`:a+=n(e);return o.set(s,a),a},r=new Map;function c(e,t,s){return e&&t in e?e[t]??s:s}function i(...e){return e.reduce(((e,t)=>{if(!t)return e;let s;return("tags"in e&&e.tags||"tags"in t&&t.tags)&&(s=[...c(e,"tags",[]),...c(t,"tags",[])]),{...e,...t,tags:s,responses:{...c(e,"responses",{}),...c(t,"responses",{})},parameters:m(e.parameters,t.parameters)}}),{})}function p({path:e,method:t,data:s,schema:n}){e=(e=>e.split("/").map((e=>{let t=e;return t.startsWith(":")&&(t=t.slice(1,t.length),t.endsWith("?")&&(t=t.slice(0,-1)),t=`{${t}}`),t})).join("/"))(e);const o=t.toLowerCase();if("all"===o)if(r.has(e)){const t=r.get(e)??{};r.set(e,{...t,...s,parameters:m(t.parameters,s.parameters)})}else r.set(e,s);else{const t=function(e){const t=Array.from(r.keys());let s={};for(const n of t)e.match(n)&&(s=i(s,r.get(n)??{}));return s}(e);n[e]={...n[e]?n[e]:{},[o]:{responses:{},operationId:a(o,e),...i(t,n[e]?.[o],s)}}}}const l=e=>"$ref"in e?e.$ref:`${e.in} ${e.name}`;function m(...e){const t=e.flatMap((e=>e??[])).reduce(((e,t)=>(e.set(l(t),t),e)),new Map);return Array.from(t.values())}function u(e,{excludeStaticFile:t=!0,exclude:s=[]}){const n={},o=Array.isArray(s)?s:[s];for(const[s,a]of Object.entries(e))if(!(o.some((e=>"string"==typeof e?s===e:e.test(s)))||s.includes("*")||t&&s.includes("."))){for(const e of Object.keys(a)){const t=a[e];if(s.includes("{")){t.parameters||(t.parameters=[]);const e=s.split("/").filter((e=>e.startsWith("{")&&!t.parameters.find((t=>"path"===t.in&&t.name===e.slice(1,e.length-1)))));for(const s of e){const e=s.slice(1,s.length-1),n=t.parameters.findIndex((t=>"param"===t.in&&t.name===e));-1!==n?t.parameters[n].in="path":t.parameters.push({schema:{type:"string"},in:"path",name:e,required:!0})}}t.responses||(t.responses={200:{}})}n[s]=a}return n}function f(e,t){const s={version:"3.1.0",components:{}};let n=null;return async o=>(n||(n=await d(e,t,s,o)),o.json(n))}async function d(t,{documentation:n={},excludeStaticFile:o=!0,exclude:a=[],excludeMethods:r=["OPTIONS"],excludeTags:c=[],defaultOptions:i}={documentation:{},excludeStaticFile:!0,exclude:[],excludeMethods:["OPTIONS"],excludeTags:[]},{version:l="3.1.0",components:m={}}={version:"3.1.0",components:{}},f){const d={version:l,components:m},h={};for(const n of t.routes){if(!(e in n.handler))continue;if(r.includes(n.method))continue;if(!1===s.includes(n.method)&&"ALL"!==n.method)continue;const{resolver:t,metadata:o={}}=n.handler[e],a=i?.[n.method],{docs:c,components:l}=await t({...d,...o},a);d.components={...d.components,...l??{}},p({method:n.method,path:n.path,data:c,schema:h})}for(const e in h)for(const t in h[e]){const s=h[e][t]?.hide;s&&("boolean"==typeof s?s:f&&s(f))&&delete h[e][t]}return{openapi:d.version,...{...n,tags:n.tags?.filter((e=>!c?.includes(e?.name))),info:{title:"Hono Documentation",description:"Development documentation",version:"0.0.0",...n.info},paths:{...u(h,{excludeStaticFile:o,exclude:a}),...n.paths},components:{...n.components,schemas:{...d.components,...n.components?.schemas}}}}}function h(s){const{validateResponse:n,...o}=s;return Object.assign((async(e,o)=>{if(await o(),n&&s.responses){const o=e.res.status,a=e.res.headers.get("content-type");if(o&&a){const r=s.responses[o];if(r&&"content"in r&&r.content){const s=a.split(";")[0],o=r.content[s];if(o?.schema&&"validator"in o.schema)try{let t;const n=e.res.clone();if("application/json"===s?t=await n.json():"text/plain"===s&&(t=await n.text()),!t)throw new Error("No data to validate!");await o.schema.validator(t)}catch(e){let s={status:500,message:"Response validation failed!"};throw"object"==typeof n&&(s={...s,...n}),new t(s.status,{message:s.message,cause:e})}}}}}),{[e]:{resolver:(e,t)=>x(e,o,t)}})}async function x(e,t,s={}){let n={};const o={...s,...t,responses:{...s?.responses,...t.responses}};if(o.responses)for(const t of Object.keys(o.responses)){const s=o.responses[t];if(s&&"content"in s)for(const t of Object.keys(s.content??{})){const o=s.content?.[t];if(o&&(o.schema&&"builder"in o.schema)){const t=await o.schema.builder(e);o.schema=t.schema,t.components&&(n={...n,...t.components})}}}return{docs:o,components:n}}export{h as describeRoute,x as generateRouteSpecs,d as generateSpecs,f as openAPISpecs,e as uniqueSymbol};
1
+ import{u as e}from"./utils.js";export{g as generateValidatorDocs}from"./utils.js";import{HTTPException as t}from"hono/http-exception";const n=["GET","PUT","POST","DELETE","OPTIONS","HEAD","PATCH","TRACE"],s=e=>e.charAt(0).toUpperCase()+e.slice(1),o=new Map,a=(e,t)=>{const n=`${e}:${t}`;if(o.has(n))return o.get(n);let a=e;if("/"===t)return`${a}Index`;for(const e of t.split("/"))123===e.charCodeAt(0)?a+=`By${s(e.slice(1,-1))}`:a+=s(e);return o.set(n,a),a},r=new Map;function c(e,t,n){return e&&t in e?e[t]??n:n}function i(...e){return e.reduce(((e,t)=>{if(!t)return e;let n;return("tags"in e&&e.tags||"tags"in t&&t.tags)&&(n=Array.from(new Set([...c(e,"tags",[]),...c(t,"tags",[])]))),{...e,...t,tags:n,responses:{...c(e,"responses",{}),...c(t,"responses",{})},parameters:m(e.parameters,t.parameters)}}),{})}function p({path:e,method:t,data:n,schema:s}){e=(e=>e.split("/").map((e=>{let t=e;if(t.startsWith(":")){const e=t.match(/^:([^{?]+)(?:{(.+)})?(\?)?$/);e?t=`{${e[1]}}`:(t=t.slice(1,t.length),t.endsWith("?")&&(t=t.slice(0,-1)),t=`{${t}}`)}return t})).join("/"))(e);const o=t.toLowerCase();if("all"===o){if(!n)return;if(r.has(e)){const t=r.get(e)??{};r.set(e,{...t,...n,parameters:m(t.parameters,n.parameters)})}else r.set(e,n)}else{const t=function(e){const t=Array.from(r.keys());let n={};for(const s of t)e.match(s)&&(n=i(n,r.get(s)??{}));return n}(e);s[e]={...s[e]?s[e]:{},[o]:{responses:{},operationId:a(o,e),...i(t,s[e]?.[o],n)}}}}const f=e=>"$ref"in e?e.$ref:`${e.in} ${e.name}`;function m(...e){const t=e.flatMap((e=>e??[])).reduce(((e,t)=>(e.set(f(t),t),e)),new Map);return Array.from(t.values())}function l(e,{excludeStaticFile:t=!0,exclude:n=[]}){const s={},o=Array.isArray(n)?n:[n];for(const[n,a]of Object.entries(e))if(!o.some((e=>"string"==typeof e?n===e:e.test(n)))&&(!n.includes("*")||n.includes("{"))&&(!t||(!n.includes(".")||n.includes("{")))){for(const e of Object.keys(a)){const t=a[e];if(n.includes("{")){t.parameters||(t.parameters=[]);const e=n.split("/").filter((e=>e.startsWith("{")&&!t.parameters.find((t=>"path"===t.in&&t.name===e.slice(1,e.length-1)))));for(const n of e){const e=n.slice(1,n.length-1),s=t.parameters.findIndex((t=>"param"===t.in&&t.name===e));-1!==s?t.parameters[s].in="path":t.parameters.push({schema:{type:"string"},in:"path",name:e,required:!0})}}t.responses||(t.responses={200:{}})}s[n]=a}return s}const u={documentation:{},excludeStaticFile:!0,exclude:[],excludeMethods:["OPTIONS"],excludeTags:[]},d={version:"3.1.0",components:{}};function h(e,t){const n={version:"3.1.0",components:{}};let s;return async o=>(s||(s=await y(e,t,n,o)),o.json(s))}async function y(t,s=u,o=d,a){const r={...u,...s},c={...d,...o},i=r.documentation??{},f=await async function(t,s,o){const a={};for(const r of t.routes){if(!(e in r.handler)){s.includeEmptyPaths&&p({method:r.method,path:r.path,schema:a});continue}if(s.excludeMethods.includes(r.method))continue;if(!1===n.includes(r.method)&&"ALL"!==r.method)continue;const{resolver:t,metadata:c={}}=r.handler[e],i=s.defaultOptions?.[r.method],{docs:f,components:m}=await t({...o,...c},i);o.components={...o.components,...m??{}},p({method:r.method,path:r.path,data:f,schema:a})}return a}(t,r,c);for(const e in f)for(const t in f[e]){const n=f[e][t]?.hide;if(n){let s=!1;"boolean"==typeof n?s=n:"function"==typeof n&&(a?s=n(a):console.warn(`'c' is not defined, cannot evaluate hide function for ${t} ${e}`)),s&&delete f[e][t]}}return{openapi:c.version,...{...i,tags:i.tags?.filter((e=>!r.excludeTags?.includes(e?.name))),info:{title:"Hono Documentation",description:"Development documentation",version:"0.0.0",...i.info},paths:{...l(f,r),...i.paths},components:{...i.components,schemas:{...c.components,...i.components?.schemas}}}}}function w(n){const{validateResponse:s,...o}=n;return Object.assign((async(e,o)=>{if(await o(),s&&n.responses){const o=e.res.status,a=e.res.headers.get("content-type");if(o&&a){const r=n.responses[o];if(r&&"content"in r&&r.content){const n=a.split(";")[0],o=r.content[n];if(o?.schema&&"validator"in o.schema)try{let t;const s=e.res.clone();if("application/json"===n?t=await s.json():"text/plain"===n&&(t=await s.text()),!t)throw new Error("No data to validate!");await o.schema.validator(t)}catch(e){let n={status:500,message:"Response validation failed!"};throw"object"==typeof s&&(n={...n,...s}),new t(n.status,{message:n.message,cause:e})}}}}}),{[e]:{resolver:(e,t)=>x(e,o,t)}})}async function x(e,t,n={}){let s={};const o={...n,...t,responses:{...n?.responses,...t.responses}};if(o.responses)for(const t of Object.keys(o.responses)){const n=o.responses[t];if(n&&"content"in n)for(const t of Object.keys(n.content??{})){const o=n.content?.[t];if(o&&(o.schema&&"builder"in o.schema)){const t=await o.schema.builder(e);o.schema=t.schema,t.components&&(s={...s,...t.components})}}}return{docs:o,components:s}}export{w as describeRoute,x as generateRouteSpecs,y as generateSpecs,h as openAPISpecs,e as uniqueSymbol};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hono-openapi",
3
3
  "description": "OpenAPI schema generator for Hono",
4
- "version": "0.4.6",
4
+ "version": "0.4.8",
5
5
  "types": "./index.d.ts",
6
6
  "main": "./index.js",
7
7
  "type": "module",
@@ -38,7 +38,7 @@
38
38
  "@hono/zod-validator": "^0.4.1",
39
39
  "@sinclair/typebox": "^0.34.9",
40
40
  "@valibot/to-json-schema": "^1.0.0-beta.3",
41
- "arktype": "^2.0.0-rc.25",
41
+ "arktype": "^2.0.0",
42
42
  "effect": "^3.11.3",
43
43
  "hono": "^4.6.13",
44
44
  "openapi-types": "^12.1.3",
@@ -77,9 +77,6 @@
77
77
  "hono": {
78
78
  "optional": true
79
79
  },
80
- "openapi-types": {
81
- "optional": true
82
- },
83
80
  "valibot": {
84
81
  "optional": true
85
82
  },
@@ -91,7 +88,7 @@
91
88
  }
92
89
  },
93
90
  "devDependencies": {
94
- "@apidevtools/json-schema-ref-parser": "^11.7.3",
91
+ "@apidevtools/json-schema-ref-parser": "^12.0.1",
95
92
  "@rollup/plugin-terser": "^0.4.4",
96
93
  "@types/json-schema": "^7.0.15"
97
94
  },
package/src/openapi.d.ts CHANGED
@@ -17,7 +17,7 @@ export declare function openAPISpecs<E extends Env = BlankEnv, P extends string
17
17
  * @param Context Route context for hiding routes
18
18
  * @returns OpenAPI specs
19
19
  */
20
- export declare function generateSpecs<E extends Env = BlankEnv, P extends string = string, I extends Input = BlankInput, S extends Schema = BlankSchema>(hono: Hono<E, S, P>, { documentation, excludeStaticFile, exclude, excludeMethods, excludeTags, defaultOptions, }?: OpenApiSpecsOptions, { version, components }?: OpenAPIRouteHandlerConfig, c?: Context<E, P, I>): Promise<{
20
+ export declare function generateSpecs<E extends Env = BlankEnv, P extends string = string, I extends Input = BlankInput, S extends Schema = BlankSchema>(hono: Hono<E, S, P>, options?: OpenApiSpecsOptions, config?: OpenAPIRouteHandlerConfig, c?: Context<E, P, I>): Promise<{
21
21
  tags: OpenAPIV3.TagObject[] | undefined;
22
22
  info: {
23
23
  title: string;
package/src/route.d.ts CHANGED
@@ -29,7 +29,6 @@ export declare function generateRouteSpecs(config: OpenAPIRouteHandlerConfig, do
29
29
  description?: string;
30
30
  externalDocs?: import("openapi-types").OpenAPIV3.ExternalDocumentationObject;
31
31
  operationId?: string;
32
- parameters?: (import("openapi-types").OpenAPIV3.ReferenceObject | import("openapi-types").OpenAPIV3.ParameterObject)[];
33
32
  requestBody?: import("openapi-types").OpenAPIV3.ReferenceObject | import("openapi-types").OpenAPIV3.RequestBodyObject;
34
33
  callbacks?: {
35
34
  [callback: string]: import("openapi-types").OpenAPIV3.ReferenceObject | import("openapi-types").OpenAPIV3.CallbackObject;
@@ -42,6 +41,9 @@ export declare function generateRouteSpecs(config: OpenAPIRouteHandlerConfig, do
42
41
  status: ClientErrorStatusCode | ServerErrorStatusCode;
43
42
  message?: string;
44
43
  };
44
+ parameters?: (import("openapi-types").OpenAPIV3.ParameterObject | (import("openapi-types").OpenAPIV3.ParameterObject & {
45
+ schema: import("./types.js").ResolverResult;
46
+ }))[];
45
47
  };
46
48
  components: {};
47
49
  }>;
package/src/types.d.ts CHANGED
@@ -59,7 +59,7 @@ export type DescribeRouteOptions = Omit<OpenAPIV3.OperationObject, "responses" |
59
59
  export interface OpenAPIRoute {
60
60
  path: string;
61
61
  method: (typeof ALLOWED_METHODS)[number] | "ALL";
62
- data: DescribeRouteOptions | Pick<OpenAPIV3.OperationObject, "parameters" | "requestBody">;
62
+ data?: DescribeRouteOptions | Pick<OpenAPIV3.OperationObject, "parameters" | "requestBody">;
63
63
  }
64
64
  export type OpenApiSpecsOptions = {
65
65
  /**
@@ -68,6 +68,12 @@ export type OpenApiSpecsOptions = {
68
68
  * @see https://swagger.io/specification/v2/
69
69
  */
70
70
  documentation?: Omit<Partial<OpenAPIV3.Document>, "x-express-openapi-additional-middleware" | "x-express-openapi-validation-strict">;
71
+ /**
72
+ * Include paths which don't have the handlers.
73
+ * This is useful when you want to document the
74
+ * API without implementing it or index all the paths.
75
+ */
76
+ includeEmptyPaths?: boolean;
71
77
  /**
72
78
  * Determine if Swagger should exclude static files.
73
79
  *
package/utils.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";const e=Symbol("openapi");exports.generateValidatorDocs=async function(e,s){const n=await s,o={};if("form"===e||"json"===e)o.requestBody={content:{["json"===e?"application/json":"multipart/form-data"]:{schema:n.schema}}};else{const s=[];if("$ref"in n.schema)s.push({in:e,name:n.schema.$ref,schema:n.schema});else for(const[o,t]of Object.entries(n.schema.properties??{}))s.push({in:e,name:o,schema:t,required:n.schema.required?.includes(o)});o.parameters=s}return{docs:o,components:n.components}},exports.uniqueSymbol=e;
1
+ "use strict";const e=Symbol("openapi");exports.generateValidatorDocs=async function(e,s){const t=await s,n={};if("form"===e||"json"===e){const s="json"===e?"application/json":"multipart/form-data";n.requestBody&&"content"in n.requestBody&&n.requestBody.content?n.requestBody.content[s]={schema:t.schema}:n.requestBody={content:{[s]:{schema:t.schema}}}}else{const s=[];if("$ref"in t.schema)s.push({in:e,name:t.schema.$ref,schema:t.schema});else for(const[n,o]of Object.entries(t.schema.properties??{}))s.push({in:e,name:n,schema:o,required:t.schema.required?.includes(n)});n.parameters=s}return{docs:n,components:t.components}},exports.uniqueSymbol=e;
package/utils.js CHANGED
@@ -1 +1 @@
1
- const e=Symbol("openapi");async function s(e,s){const n=await s,a={};if("form"===e||"json"===e)a.requestBody={content:{["json"===e?"application/json":"multipart/form-data"]:{schema:n.schema}}};else{const s=[];if("$ref"in n.schema)s.push({in:e,name:n.schema.$ref,schema:n.schema});else for(const[a,o]of Object.entries(n.schema.properties??{}))s.push({in:e,name:a,schema:o,required:n.schema.required?.includes(a)});a.parameters=s}return{docs:a,components:n.components}}export{s as g,e as u};
1
+ const e=Symbol("openapi");async function s(e,s){const n=await s,o={};if("form"===e||"json"===e){const s="json"===e?"application/json":"multipart/form-data";o.requestBody&&"content"in o.requestBody&&o.requestBody.content?o.requestBody.content[s]={schema:n.schema}:o.requestBody={content:{[s]:{schema:n.schema}}}}else{const s=[];if("$ref"in n.schema)s.push({in:e,name:n.schema.$ref,schema:n.schema});else for(const[o,t]of Object.entries(n.schema.properties??{}))s.push({in:e,name:o,schema:t,required:n.schema.required?.includes(o)});o.parameters=s}return{docs:o,components:n.components}}export{s as g,e as u};
package/valibot.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var e=require("@hono/valibot-validator"),r=require("@valibot/to-json-schema"),a=require("valibot"),t=require("./toOpenAPISchema.cjs"),o=require("./utils.cjs");function i(e,o){return{builder:async()=>({schema:await t.convert(r.toJsonSchema(e,o))}),validator:async r=>{await a.parseAsync(e,r)}}}require("json-schema-walker"),exports.resolver=i,exports.validator=function(r,a,t){const s=e.vValidator(r,a,t);return Object.assign(s,{[o.uniqueSymbol]:{resolver:async e=>o.generateValidatorDocs(r,await i(a).builder(e))}})};
1
+ "use strict";var e=require("@hono/valibot-validator"),r=require("@valibot/to-json-schema"),a=require("valibot"),o=require("./toOpenAPISchema.cjs"),t=require("./utils.cjs");function i(e,t){return{builder:async()=>({schema:await o.convert(r.toJsonSchema(e,t))}),validator:async r=>{await a.parseAsync(e,r)}}}require("json-schema-walker"),exports.resolver=i,exports.validator=function(r,a,o){const s=e.vValidator(r,a,o);return Object.assign(s,{[t.uniqueSymbol]:{resolver:async e=>t.generateValidatorDocs(r,await i(a,{errorMode:"ignore"}).builder(e))}})};
package/valibot.js CHANGED
@@ -1 +1 @@
1
- import{vValidator as o}from"@hono/valibot-validator";import{toJsonSchema as a}from"@valibot/to-json-schema";import{parseAsync as t}from"valibot";import{c as r}from"./toOpenAPISchema.js";import{u as i,g as s}from"./utils.js";import"json-schema-walker";function m(o,i){return{builder:async()=>({schema:await r(a(o,i))}),validator:async a=>{await t(o,a)}}}function n(a,t,r){const n=o(a,t,r);return Object.assign(n,{[i]:{resolver:async o=>s(a,await m(t).builder(o))}})}export{m as resolver,n as validator};
1
+ import{vValidator as o}from"@hono/valibot-validator";import{toJsonSchema as r}from"@valibot/to-json-schema";import{parseAsync as a}from"valibot";import{c as t}from"./toOpenAPISchema.js";import{u as i,g as s}from"./utils.js";import"json-schema-walker";function e(o,i){return{builder:async()=>({schema:await t(r(o,i))}),validator:async r=>{await a(o,r)}}}function n(r,a,t){const n=o(r,a,t);return Object.assign(n,{[i]:{resolver:async o=>s(r,await e(a,{errorMode:"ignore"}).builder(o))}})}export{e as resolver,n as validator};