@hono/zod-openapi 0.0.1

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 ADDED
@@ -0,0 +1,129 @@
1
+ # Zod OpenAPI Hono
2
+
3
+ A wrapper class for Hono that supports OpenAPI. With it, you can validate values and types using [Zod](https://zod.dev/) and generate OpenAPI Swagger documentation.
4
+ This is based on [Zod to OpenAPI](https://github.com/asteasolutions/zod-to-openapi).
5
+ For details on creating schemas and defining routes, please refer to this resource.
6
+
7
+ _This is not a middleware but hosted on this monorepo_
8
+
9
+ ## Usage
10
+
11
+ ### Install
12
+
13
+ ```
14
+ npm i hono zod @hono/zod-openapi
15
+ ```
16
+
17
+ ### Write your application
18
+
19
+ Define schemas:
20
+
21
+ ```ts
22
+ import { z } from '@hono/zod-openapi'
23
+
24
+ const ParamsSchema = z.object({
25
+ id: z
26
+ .string()
27
+ .min(3)
28
+ .openapi({
29
+ param: {
30
+ name: 'id',
31
+ in: 'path',
32
+ },
33
+ example: '1212121',
34
+ }),
35
+ })
36
+
37
+ const UserSchema = z
38
+ .object({
39
+ id: z.number().openapi({
40
+ example: 123,
41
+ }),
42
+ name: z.string().openapi({
43
+ example: 'John Doe',
44
+ }),
45
+ age: z.number().openapi({
46
+ example: 42,
47
+ }),
48
+ })
49
+ .openapi('User')
50
+ ```
51
+
52
+ Create routes:
53
+
54
+ ```ts
55
+ import { createRoute } from '@hono/zod-openapi'
56
+
57
+ const route = createRoute({
58
+ method: 'get',
59
+ path: '/users/:id',
60
+ request: {
61
+ params: ParamsSchema,
62
+ },
63
+ responses: {
64
+ 200: {
65
+ content: {
66
+ 'application/json': {
67
+ schema: UserSchema,
68
+ },
69
+ },
70
+ description: 'Get the user',
71
+ },
72
+ 400: {
73
+ content: {
74
+ 'application/json': {
75
+ schema: ErrorSchema,
76
+ },
77
+ },
78
+ description: 'Error!',
79
+ },
80
+ },
81
+ })
82
+ ```
83
+
84
+ Create the App:
85
+
86
+ ```ts
87
+ import { OpenAPIHono } from '@hono/zod-openapi'
88
+
89
+ const app = new OpenAPIHono()
90
+
91
+ app.openapi(
92
+ route,
93
+ (c) => {
94
+ const { id } = c.req.valid('param')
95
+ return c.jsonT({
96
+ id: Number(id),
97
+ age: 20,
98
+ name: 'Ultra-man',
99
+ })
100
+ },
101
+ (result, c) => {
102
+ if (!result.success) {
103
+ const res = c.jsonT(
104
+ {
105
+ ok: false,
106
+ },
107
+ 400
108
+ )
109
+ return res
110
+ }
111
+ }
112
+ )
113
+
114
+ app.doc('/doc', {
115
+ openapi: '3.0.0',
116
+ info: {
117
+ version: '1.0.0',
118
+ title: 'My API',
119
+ },
120
+ })
121
+ ```
122
+
123
+ ## Author
124
+
125
+ Yusuke Wada <https://github.com/yusukebe>
126
+
127
+ ## License
128
+
129
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var __accessCheck = (obj, member, msg) => {
20
+ if (!member.has(obj))
21
+ throw TypeError("Cannot " + msg);
22
+ };
23
+ var __privateGet = (obj, member, getter) => {
24
+ __accessCheck(obj, member, "read from private field");
25
+ return getter ? getter.call(obj) : member.get(obj);
26
+ };
27
+ var __privateAdd = (obj, member, value) => {
28
+ if (member.has(obj))
29
+ throw TypeError("Cannot add the same private member more than once");
30
+ member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
31
+ };
32
+ var __privateSet = (obj, member, value, setter) => {
33
+ __accessCheck(obj, member, "write to private field");
34
+ setter ? setter.call(obj, value) : member.set(obj, value);
35
+ return value;
36
+ };
37
+
38
+ // src/index.ts
39
+ var src_exports = {};
40
+ __export(src_exports, {
41
+ OpenAPIHono: () => OpenAPIHono,
42
+ createRoute: () => createRoute,
43
+ z: () => import_zod.z
44
+ });
45
+ module.exports = __toCommonJS(src_exports);
46
+ var import_zod_to_openapi = require("@asteasolutions/zod-to-openapi");
47
+ var import_zod_to_openapi2 = require("@asteasolutions/zod-to-openapi");
48
+ var import_zod_validator = require("@hono/zod-validator");
49
+ var import_hono = require("hono");
50
+ var import_zod = require("zod");
51
+ var _registry;
52
+ var OpenAPIHono = class extends import_hono.Hono {
53
+ constructor() {
54
+ super();
55
+ __privateAdd(this, _registry, void 0);
56
+ this.openapi = (route, handler, hook) => {
57
+ __privateGet(this, _registry).registerPath(route);
58
+ const validators = [];
59
+ if (route.request?.query) {
60
+ const validator = (0, import_zod_validator.zValidator)("query", route.request.query, hook);
61
+ validators.push(validator);
62
+ }
63
+ if (route.request?.params) {
64
+ const validator = (0, import_zod_validator.zValidator)("param", route.request.params, hook);
65
+ validators.push(validator);
66
+ }
67
+ const bodyContent = route.request?.body?.content;
68
+ if (bodyContent) {
69
+ for (const mediaType of Object.keys(bodyContent)) {
70
+ if (mediaType.startsWith("application/json")) {
71
+ const schema = bodyContent[mediaType]["schema"];
72
+ if (schema instanceof import_zod.ZodType) {
73
+ const validator = (0, import_zod_validator.zValidator)("json", schema, hook);
74
+ validators.push(validator);
75
+ }
76
+ }
77
+ if (mediaType.startsWith("multipart/form-data") || mediaType.startsWith("application/x-www-form-urlencoded")) {
78
+ const schema = bodyContent[mediaType]["schema"];
79
+ if (schema instanceof import_zod.ZodType) {
80
+ const validator = (0, import_zod_validator.zValidator)("form", schema, hook);
81
+ validators.push(validator);
82
+ }
83
+ }
84
+ }
85
+ }
86
+ this.on([route.method], route.path, ...validators, handler);
87
+ return this;
88
+ };
89
+ this.getOpenAPIDocument = (config) => {
90
+ const generator = new import_zod_to_openapi.OpenApiGeneratorV3(__privateGet(this, _registry).definitions);
91
+ const document = generator.generateDocument(config);
92
+ return document;
93
+ };
94
+ this.doc = (path, config) => {
95
+ this.get(path, (c) => {
96
+ const document = this.getOpenAPIDocument(config);
97
+ return c.json(document);
98
+ });
99
+ };
100
+ __privateSet(this, _registry, new import_zod_to_openapi.OpenAPIRegistry());
101
+ }
102
+ };
103
+ _registry = new WeakMap();
104
+ var createRoute = (routeConfig) => routeConfig;
105
+ (0, import_zod_to_openapi2.extendZodWithOpenApi)(import_zod.z);
106
+ // Annotate the CommonJS export names for ESM import in node:
107
+ 0 && (module.exports = {
108
+ OpenAPIHono,
109
+ createRoute,
110
+ z
111
+ });
@@ -0,0 +1,52 @@
1
+ import * as openapi3_ts_oas30 from 'openapi3-ts/oas30';
2
+ import { RouteConfig, ZodRequestBody, ZodContentObject, ResponseConfig } from '@asteasolutions/zod-to-openapi';
3
+ import { OpenAPIObjectConfig } from '@asteasolutions/zod-to-openapi/dist/v3.0/openapi-generator';
4
+ import { Env, Hono, Input, Handler, Context, TypedResponse } from 'hono';
5
+ import { AnyZodObject, z, ZodSchema, ZodError, ZodType } from 'zod';
6
+ export { z } from 'zod';
7
+
8
+ type RequestTypes = {
9
+ body?: ZodRequestBody;
10
+ params?: AnyZodObject;
11
+ query?: AnyZodObject;
12
+ cookies?: AnyZodObject;
13
+ headers?: AnyZodObject | ZodType<unknown>[];
14
+ };
15
+ type IsJson<T> = T extends string ? T extends `application/json${infer _Rest}` ? 'json' : never : never;
16
+ type IsForm<T> = T extends string ? T extends `multipart/form-data${infer _Rest}` | `application/x-www-form-urlencoded${infer _Rest}` ? 'form' : never : never;
17
+ type RequestPart<R extends RouteConfig, Part extends string> = Part extends keyof R['request'] ? R['request'][Part] : never;
18
+ type InputTypeBase<R extends RouteConfig, Part extends string, Type extends string> = R['request'] extends RequestTypes ? RequestPart<R, Part> extends AnyZodObject ? {
19
+ out: {
20
+ [K in Type]: z.input<RequestPart<R, Part>>;
21
+ };
22
+ } : {} : {};
23
+ type InputTypeJson<R extends RouteConfig> = R['request'] extends RequestTypes ? R['request']['body'] extends ZodRequestBody ? R['request']['body']['content'] extends ZodContentObject ? IsJson<keyof R['request']['body']['content']> extends never ? {} : R['request']['body']['content'][keyof R['request']['body']['content']]['schema'] extends ZodSchema<any> ? {
24
+ out: {
25
+ json: z.input<R['request']['body']['content'][keyof R['request']['body']['content']]['schema']>;
26
+ };
27
+ } : {} : {} : {} : {};
28
+ type InputTypeForm<R extends RouteConfig> = R['request'] extends RequestTypes ? R['request']['body'] extends ZodRequestBody ? R['request']['body']['content'] extends ZodContentObject ? IsForm<keyof R['request']['body']['content']> extends never ? {} : R['request']['body']['content'][keyof R['request']['body']['content']]['schema'] extends ZodSchema<any> ? {
29
+ out: {
30
+ form: z.input<R['request']['body']['content'][keyof R['request']['body']['content']]['schema']>;
31
+ };
32
+ } : {} : {} : {} : {};
33
+ type OutputType<R extends RouteConfig> = R['responses'] extends Record<infer _, infer C> ? C extends ResponseConfig ? C['content'] extends ZodContentObject ? IsJson<keyof C['content']> extends never ? {} : C['content'][keyof C['content']]['schema'] extends ZodSchema ? z.infer<C['content'][keyof C['content']]['schema']> : {} : {} : {} : {};
34
+ type Hook<T, E extends Env, P extends string, O> = (result: {
35
+ success: true;
36
+ data: T;
37
+ } | {
38
+ success: false;
39
+ error: ZodError;
40
+ }, c: Context<E, P>) => TypedResponse<O> | Promise<TypedResponse<T>> | void;
41
+ declare class OpenAPIHono<E extends Env = Env, S = {}, BasePath extends string = '/'> extends Hono<E, S, BasePath> {
42
+ #private;
43
+ constructor();
44
+ openapi: <R extends RouteConfig, I extends Input = InputTypeBase<R, "params", "param"> & InputTypeBase<R, "query", "query"> & InputTypeForm<R> & InputTypeJson<R>>(route: R, handler: Handler<E, R["path"], I, OutputType<R>>, hook?: Hook<I, E, R["path"], OutputType<R>> | undefined) => this;
45
+ getOpenAPIDocument: (config: OpenAPIObjectConfig) => openapi3_ts_oas30.OpenAPIObject;
46
+ doc: (path: string, config: OpenAPIObjectConfig) => void;
47
+ }
48
+ declare const createRoute: <P extends string, R extends Omit<RouteConfig, "path"> & {
49
+ path: P;
50
+ }>(routeConfig: R) => R;
51
+
52
+ export { OpenAPIHono, createRoute };
@@ -0,0 +1,52 @@
1
+ import * as openapi3_ts_oas30 from 'openapi3-ts/oas30';
2
+ import { RouteConfig, ZodRequestBody, ZodContentObject, ResponseConfig } from '@asteasolutions/zod-to-openapi';
3
+ import { OpenAPIObjectConfig } from '@asteasolutions/zod-to-openapi/dist/v3.0/openapi-generator';
4
+ import { Env, Hono, Input, Handler, Context, TypedResponse } from 'hono';
5
+ import { AnyZodObject, z, ZodSchema, ZodError, ZodType } from 'zod';
6
+ export { z } from 'zod';
7
+
8
+ type RequestTypes = {
9
+ body?: ZodRequestBody;
10
+ params?: AnyZodObject;
11
+ query?: AnyZodObject;
12
+ cookies?: AnyZodObject;
13
+ headers?: AnyZodObject | ZodType<unknown>[];
14
+ };
15
+ type IsJson<T> = T extends string ? T extends `application/json${infer _Rest}` ? 'json' : never : never;
16
+ type IsForm<T> = T extends string ? T extends `multipart/form-data${infer _Rest}` | `application/x-www-form-urlencoded${infer _Rest}` ? 'form' : never : never;
17
+ type RequestPart<R extends RouteConfig, Part extends string> = Part extends keyof R['request'] ? R['request'][Part] : never;
18
+ type InputTypeBase<R extends RouteConfig, Part extends string, Type extends string> = R['request'] extends RequestTypes ? RequestPart<R, Part> extends AnyZodObject ? {
19
+ out: {
20
+ [K in Type]: z.input<RequestPart<R, Part>>;
21
+ };
22
+ } : {} : {};
23
+ type InputTypeJson<R extends RouteConfig> = R['request'] extends RequestTypes ? R['request']['body'] extends ZodRequestBody ? R['request']['body']['content'] extends ZodContentObject ? IsJson<keyof R['request']['body']['content']> extends never ? {} : R['request']['body']['content'][keyof R['request']['body']['content']]['schema'] extends ZodSchema<any> ? {
24
+ out: {
25
+ json: z.input<R['request']['body']['content'][keyof R['request']['body']['content']]['schema']>;
26
+ };
27
+ } : {} : {} : {} : {};
28
+ type InputTypeForm<R extends RouteConfig> = R['request'] extends RequestTypes ? R['request']['body'] extends ZodRequestBody ? R['request']['body']['content'] extends ZodContentObject ? IsForm<keyof R['request']['body']['content']> extends never ? {} : R['request']['body']['content'][keyof R['request']['body']['content']]['schema'] extends ZodSchema<any> ? {
29
+ out: {
30
+ form: z.input<R['request']['body']['content'][keyof R['request']['body']['content']]['schema']>;
31
+ };
32
+ } : {} : {} : {} : {};
33
+ type OutputType<R extends RouteConfig> = R['responses'] extends Record<infer _, infer C> ? C extends ResponseConfig ? C['content'] extends ZodContentObject ? IsJson<keyof C['content']> extends never ? {} : C['content'][keyof C['content']]['schema'] extends ZodSchema ? z.infer<C['content'][keyof C['content']]['schema']> : {} : {} : {} : {};
34
+ type Hook<T, E extends Env, P extends string, O> = (result: {
35
+ success: true;
36
+ data: T;
37
+ } | {
38
+ success: false;
39
+ error: ZodError;
40
+ }, c: Context<E, P>) => TypedResponse<O> | Promise<TypedResponse<T>> | void;
41
+ declare class OpenAPIHono<E extends Env = Env, S = {}, BasePath extends string = '/'> extends Hono<E, S, BasePath> {
42
+ #private;
43
+ constructor();
44
+ openapi: <R extends RouteConfig, I extends Input = InputTypeBase<R, "params", "param"> & InputTypeBase<R, "query", "query"> & InputTypeForm<R> & InputTypeJson<R>>(route: R, handler: Handler<E, R["path"], I, OutputType<R>>, hook?: Hook<I, E, R["path"], OutputType<R>> | undefined) => this;
45
+ getOpenAPIDocument: (config: OpenAPIObjectConfig) => openapi3_ts_oas30.OpenAPIObject;
46
+ doc: (path: string, config: OpenAPIObjectConfig) => void;
47
+ }
48
+ declare const createRoute: <P extends string, R extends Omit<RouteConfig, "path"> & {
49
+ path: P;
50
+ }>(routeConfig: R) => R;
51
+
52
+ export { OpenAPIHono, createRoute };
package/dist/index.js ADDED
@@ -0,0 +1,85 @@
1
+ var __accessCheck = (obj, member, msg) => {
2
+ if (!member.has(obj))
3
+ throw TypeError("Cannot " + msg);
4
+ };
5
+ var __privateGet = (obj, member, getter) => {
6
+ __accessCheck(obj, member, "read from private field");
7
+ return getter ? getter.call(obj) : member.get(obj);
8
+ };
9
+ var __privateAdd = (obj, member, value) => {
10
+ if (member.has(obj))
11
+ throw TypeError("Cannot add the same private member more than once");
12
+ member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
13
+ };
14
+ var __privateSet = (obj, member, value, setter) => {
15
+ __accessCheck(obj, member, "write to private field");
16
+ setter ? setter.call(obj, value) : member.set(obj, value);
17
+ return value;
18
+ };
19
+
20
+ // src/index.ts
21
+ import { OpenApiGeneratorV3, OpenAPIRegistry } from "@asteasolutions/zod-to-openapi";
22
+ import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi";
23
+ import { zValidator } from "@hono/zod-validator";
24
+ import { Hono } from "hono";
25
+ import { z, ZodType } from "zod";
26
+ var _registry;
27
+ var OpenAPIHono = class extends Hono {
28
+ constructor() {
29
+ super();
30
+ __privateAdd(this, _registry, void 0);
31
+ this.openapi = (route, handler, hook) => {
32
+ __privateGet(this, _registry).registerPath(route);
33
+ const validators = [];
34
+ if (route.request?.query) {
35
+ const validator = zValidator("query", route.request.query, hook);
36
+ validators.push(validator);
37
+ }
38
+ if (route.request?.params) {
39
+ const validator = zValidator("param", route.request.params, hook);
40
+ validators.push(validator);
41
+ }
42
+ const bodyContent = route.request?.body?.content;
43
+ if (bodyContent) {
44
+ for (const mediaType of Object.keys(bodyContent)) {
45
+ if (mediaType.startsWith("application/json")) {
46
+ const schema = bodyContent[mediaType]["schema"];
47
+ if (schema instanceof ZodType) {
48
+ const validator = zValidator("json", schema, hook);
49
+ validators.push(validator);
50
+ }
51
+ }
52
+ if (mediaType.startsWith("multipart/form-data") || mediaType.startsWith("application/x-www-form-urlencoded")) {
53
+ const schema = bodyContent[mediaType]["schema"];
54
+ if (schema instanceof ZodType) {
55
+ const validator = zValidator("form", schema, hook);
56
+ validators.push(validator);
57
+ }
58
+ }
59
+ }
60
+ }
61
+ this.on([route.method], route.path, ...validators, handler);
62
+ return this;
63
+ };
64
+ this.getOpenAPIDocument = (config) => {
65
+ const generator = new OpenApiGeneratorV3(__privateGet(this, _registry).definitions);
66
+ const document = generator.generateDocument(config);
67
+ return document;
68
+ };
69
+ this.doc = (path, config) => {
70
+ this.get(path, (c) => {
71
+ const document = this.getOpenAPIDocument(config);
72
+ return c.json(document);
73
+ });
74
+ };
75
+ __privateSet(this, _registry, new OpenAPIRegistry());
76
+ }
77
+ };
78
+ _registry = new WeakMap();
79
+ var createRoute = (routeConfig) => routeConfig;
80
+ extendZodWithOpenApi(z);
81
+ export {
82
+ OpenAPIHono,
83
+ createRoute,
84
+ z
85
+ };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@hono/zod-openapi",
3
+ "version": "0.0.1",
4
+ "description": "A wrapper class of Hono which supports OpenAPI.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "test": "vitest run",
14
+ "build": "tsup ./src/index.ts --format esm,cjs --dts",
15
+ "publint": "publint",
16
+ "release": "yarn build && yarn test && yarn publint && yarn publish"
17
+ },
18
+ "license": "MIT",
19
+ "private": false,
20
+ "publishConfig": {
21
+ "registry": "https://registry.npmjs.org",
22
+ "access": "public"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/honojs/middleware.git"
27
+ },
28
+ "homepage": "https://github.com/honojs/middleware",
29
+ "peerDependencies": {
30
+ "hono": "*",
31
+ "zod": "3.*"
32
+ },
33
+ "devDependencies": {
34
+ "hono": "^3.4.3",
35
+ "zod": "^3.22.1"
36
+ },
37
+ "dependencies": {
38
+ "@asteasolutions/zod-to-openapi": "^5.5.0",
39
+ "@hono/zod-validator": "^0.1.7"
40
+ },
41
+ "engines": {
42
+ "node": ">=16.0.0"
43
+ }
44
+ }