@hono/zod-openapi 0.3.1 → 0.5.0
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 +23 -37
- package/dist/index.cjs +63 -4
- package/dist/index.d.cts +14 -3
- package/dist/index.d.ts +14 -3
- package/dist/index.js +64 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -174,6 +174,15 @@ app.openapi(
|
|
|
174
174
|
)
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
+
### OpenAPI v3.1
|
|
178
|
+
|
|
179
|
+
You can generate OpenAPI v3.1 spec using the following methods:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
app.doc31('/docs', {openapi: '3.1.0'}) // new endpoint
|
|
183
|
+
app.getOpenAPI31Document(, {openapi: '3.1.0'}) // raw json
|
|
184
|
+
```
|
|
185
|
+
|
|
177
186
|
### The Registry
|
|
178
187
|
|
|
179
188
|
You can access the [`OpenAPIRegistry`](https://github.com/asteasolutions/zod-to-openapi#the-registry) object via `app.openAPIRegistry`:
|
|
@@ -194,6 +203,18 @@ import { prettyJSON } from 'hono/pretty-json'
|
|
|
194
203
|
app.use('/doc/*', prettyJSON())
|
|
195
204
|
```
|
|
196
205
|
|
|
206
|
+
### Configure middleware for each endpoint
|
|
207
|
+
|
|
208
|
+
You can configure middleware for each endpoint from a route created by `createRoute` as follows.
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
import { prettyJSON } from 'hono/pretty-json'
|
|
212
|
+
import { cache } from 'honoc/cache'
|
|
213
|
+
|
|
214
|
+
app.use(route.getRoutingPath(), prettyJSON(), cache({ cacheName: "my-cache" }))
|
|
215
|
+
app.openapi(route, handler)
|
|
216
|
+
```
|
|
217
|
+
|
|
197
218
|
### RPC Mode
|
|
198
219
|
|
|
199
220
|
Zod OpenAPI Hono supports Hono's RPC mode. You can define types for the Hono Client as follows:
|
|
@@ -214,44 +235,9 @@ const client = hc<typeof appRoutes>('http://localhost:8787/')
|
|
|
214
235
|
|
|
215
236
|
## Limitations
|
|
216
237
|
|
|
217
|
-
|
|
218
|
-
Use `app.mount('/api', subApp.fetch)` instead.
|
|
238
|
+
Be careful when combining `OpenAPIHono` instances with plain `Hono` instances. `OpenAPIHono` will merge the definitions of direct subapps, but plain `Hono` knows nothing about the OpenAPI spec additions. Similarly `OpenAPIHono` will not "dig" for instances deep inside a branch of plain `Hono` instances.
|
|
219
239
|
|
|
220
|
-
|
|
221
|
-
const api = OpenAPIHono()
|
|
222
|
-
|
|
223
|
-
// ...
|
|
224
|
-
|
|
225
|
-
// Set the `/api` as a base path in the document.
|
|
226
|
-
api.get('/doc', (c) => {
|
|
227
|
-
const url = new URL(c.req.url)
|
|
228
|
-
url.pathname = '/api'
|
|
229
|
-
url.search = ''
|
|
230
|
-
|
|
231
|
-
return c.json(
|
|
232
|
-
// `api.getOpenAPIDocument()` will return a JSON object of the docs.
|
|
233
|
-
api.getOpenAPIDocument({
|
|
234
|
-
openapi: '3.0.0',
|
|
235
|
-
info: {
|
|
236
|
-
version: '1.0.0',
|
|
237
|
-
title: 'My API',
|
|
238
|
-
},
|
|
239
|
-
servers: [
|
|
240
|
-
{
|
|
241
|
-
url: `${url.toString()}`,
|
|
242
|
-
},
|
|
243
|
-
],
|
|
244
|
-
})
|
|
245
|
-
)
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
const app = new Hono()
|
|
249
|
-
|
|
250
|
-
// Mount the Open API app to `/api` in the main app.
|
|
251
|
-
app.mount('/api', api.fetch)
|
|
252
|
-
|
|
253
|
-
export default app
|
|
254
|
-
```
|
|
240
|
+
If you're migrating from plain `Hono` to `OpenAPIHono`, we recommend porting your top-level app, then working your way down the router tree.
|
|
255
241
|
|
|
256
242
|
## References
|
|
257
243
|
|
package/dist/index.cjs
CHANGED
|
@@ -30,10 +30,10 @@ var import_zod_to_openapi2 = require("@asteasolutions/zod-to-openapi");
|
|
|
30
30
|
var import_zod_validator = require("@hono/zod-validator");
|
|
31
31
|
var import_hono = require("hono");
|
|
32
32
|
var import_zod = require("zod");
|
|
33
|
-
var OpenAPIHono = class extends import_hono.Hono {
|
|
33
|
+
var OpenAPIHono = class _OpenAPIHono extends import_hono.Hono {
|
|
34
34
|
openAPIRegistry;
|
|
35
|
-
constructor() {
|
|
36
|
-
super();
|
|
35
|
+
constructor(init) {
|
|
36
|
+
super(init);
|
|
37
37
|
this.openAPIRegistry = new import_zod_to_openapi.OpenAPIRegistry();
|
|
38
38
|
}
|
|
39
39
|
openapi = (route, handler, hook) => {
|
|
@@ -82,14 +82,73 @@ var OpenAPIHono = class extends import_hono.Hono {
|
|
|
82
82
|
const document = generator.generateDocument(config);
|
|
83
83
|
return document;
|
|
84
84
|
};
|
|
85
|
+
getOpenAPI31Document = (config) => {
|
|
86
|
+
const generator = new import_zod_to_openapi.OpenApiGeneratorV31(this.openAPIRegistry.definitions);
|
|
87
|
+
const document = generator.generateDocument(config);
|
|
88
|
+
return document;
|
|
89
|
+
};
|
|
85
90
|
doc = (path, config) => {
|
|
86
91
|
this.get(path, (c) => {
|
|
87
92
|
const document = this.getOpenAPIDocument(config);
|
|
88
93
|
return c.json(document);
|
|
89
94
|
});
|
|
90
95
|
};
|
|
96
|
+
doc31 = (path, config) => {
|
|
97
|
+
this.get(path, (c) => {
|
|
98
|
+
const document = this.getOpenAPI31Document(config);
|
|
99
|
+
return c.json(document);
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
route(path, app) {
|
|
103
|
+
super.route(path, app);
|
|
104
|
+
if (!(app instanceof _OpenAPIHono)) {
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
app.openAPIRegistry.definitions.forEach((def) => {
|
|
108
|
+
switch (def.type) {
|
|
109
|
+
case "component":
|
|
110
|
+
return this.openAPIRegistry.registerComponent(
|
|
111
|
+
def.componentType,
|
|
112
|
+
def.name,
|
|
113
|
+
def.component
|
|
114
|
+
);
|
|
115
|
+
case "route":
|
|
116
|
+
return this.openAPIRegistry.registerPath({
|
|
117
|
+
...def.route,
|
|
118
|
+
path: `${path}${def.route.path}`
|
|
119
|
+
});
|
|
120
|
+
case "webhook":
|
|
121
|
+
return this.openAPIRegistry.registerWebhook({
|
|
122
|
+
...def.webhook,
|
|
123
|
+
path: `${path}${def.webhook.path}`
|
|
124
|
+
});
|
|
125
|
+
case "schema":
|
|
126
|
+
return this.openAPIRegistry.register(
|
|
127
|
+
def.schema._def.openapi._internal.refId,
|
|
128
|
+
def.schema
|
|
129
|
+
);
|
|
130
|
+
case "parameter":
|
|
131
|
+
return this.openAPIRegistry.registerParameter(
|
|
132
|
+
def.schema._def.openapi._internal.refId,
|
|
133
|
+
def.schema
|
|
134
|
+
);
|
|
135
|
+
default: {
|
|
136
|
+
const errorIfNotExhaustive = def;
|
|
137
|
+
throw new Error(`Unknown registry type: ${errorIfNotExhaustive}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
var createRoute = (routeConfig) => {
|
|
145
|
+
return {
|
|
146
|
+
...routeConfig,
|
|
147
|
+
getRoutingPath() {
|
|
148
|
+
return routeConfig.path.replaceAll(/\/{(.+?)}/g, "/:$1");
|
|
149
|
+
}
|
|
150
|
+
};
|
|
91
151
|
};
|
|
92
|
-
var createRoute = (routeConfig) => routeConfig;
|
|
93
152
|
(0, import_zod_to_openapi2.extendZodWithOpenApi)(import_zod.z);
|
|
94
153
|
// Annotate the CommonJS export names for ESM import in node:
|
|
95
154
|
0 && (module.exports = {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import * as openapi3_ts_oas31 from 'openapi3-ts/oas31';
|
|
1
2
|
import * as openapi3_ts_oas30 from 'openapi3-ts/oas30';
|
|
2
3
|
import { OpenAPIRegistry, RouteConfig, ZodRequestBody, ZodContentObject, ResponseConfig } from '@asteasolutions/zod-to-openapi';
|
|
3
4
|
import { OpenAPIObjectConfig } from '@asteasolutions/zod-to-openapi/dist/v3.0/openapi-generator';
|
|
4
5
|
import { Env, Schema, Hono, Input, Handler, ToSchema, Context, TypedResponse } from 'hono';
|
|
6
|
+
import { MergeSchemaPath, MergePath } from 'hono/dist/types/types';
|
|
7
|
+
import { RemoveBlankRecord } from 'hono/utils/types';
|
|
5
8
|
import { AnyZodObject, z, ZodSchema, ZodError, ZodType } from 'zod';
|
|
6
9
|
export { z } from 'zod';
|
|
7
10
|
|
|
@@ -49,15 +52,23 @@ type Hook<T, E extends Env, P extends string, O> = (result: {
|
|
|
49
52
|
}, c: Context<E, P>) => TypedResponse<O> | Promise<TypedResponse<T>> | void;
|
|
50
53
|
type ConvertPathType<T extends string> = T extends `${infer _}/{${infer Param}}${infer _}` ? `/:${Param}` : T;
|
|
51
54
|
type HandlerResponse<O> = TypedResponse<O> | Promise<TypedResponse<O>>;
|
|
55
|
+
type HonoInit = ConstructorParameters<typeof Hono>[0];
|
|
52
56
|
declare class OpenAPIHono<E extends Env = Env, S extends Schema = {}, BasePath extends string = '/'> extends Hono<E, S, BasePath> {
|
|
53
57
|
openAPIRegistry: OpenAPIRegistry;
|
|
54
|
-
constructor();
|
|
55
|
-
openapi: <R extends RouteConfig, I extends Input = InputTypeBase<R, "params", "param"> & InputTypeBase<R, "query", "query"> & InputTypeBase<R, "headers", "header"> & InputTypeBase<R, "cookies", "cookie"> & InputTypeForm<R> & InputTypeJson<R>, P extends string = ConvertPathType<R["path"]>>(route: R, handler: Handler<E, P, I, HandlerResponse<OutputType<R>>>, hook?: Hook<I, E, P, OutputType<R>> | undefined) =>
|
|
58
|
+
constructor(init?: HonoInit);
|
|
59
|
+
openapi: <R extends RouteConfig, I extends Input = InputTypeBase<R, "params", "param"> & InputTypeBase<R, "query", "query"> & InputTypeBase<R, "headers", "header"> & InputTypeBase<R, "cookies", "cookie"> & InputTypeForm<R> & InputTypeJson<R>, P extends string = ConvertPathType<R["path"]>>(route: R, handler: Handler<E, P, I, HandlerResponse<OutputType<R>>>, hook?: Hook<I, E, P, OutputType<R>> | undefined) => OpenAPIHono<E, ToSchema<R["method"], P, I["in"], OutputType<R>>, BasePath>;
|
|
56
60
|
getOpenAPIDocument: (config: OpenAPIObjectConfig) => openapi3_ts_oas30.OpenAPIObject;
|
|
61
|
+
getOpenAPI31Document: (config: OpenAPIObjectConfig) => openapi3_ts_oas31.OpenAPIObject;
|
|
57
62
|
doc: (path: string, config: OpenAPIObjectConfig) => void;
|
|
63
|
+
doc31: (path: string, config: OpenAPIObjectConfig) => void;
|
|
64
|
+
route<SubPath extends string, SubEnv extends Env, SubSchema extends Schema, SubBasePath extends string>(path: SubPath, app: Hono<SubEnv, SubSchema, SubBasePath>): OpenAPIHono<E, MergeSchemaPath<SubSchema, MergePath<BasePath, SubPath>> & S, BasePath>;
|
|
65
|
+
route<SubPath extends string>(path: SubPath): Hono<E, RemoveBlankRecord<S>, BasePath>;
|
|
58
66
|
}
|
|
67
|
+
type RoutingPath<P extends string> = P extends `${infer Head}/{${infer Param}}${infer Tail}` ? `${Head}/:${Param}${RoutingPath<Tail>}` : P;
|
|
59
68
|
declare const createRoute: <P extends string, R extends Omit<RouteConfig, "path"> & {
|
|
60
69
|
path: P;
|
|
61
|
-
}>(routeConfig: R) => R
|
|
70
|
+
}>(routeConfig: R) => R & {
|
|
71
|
+
getRoutingPath(): RoutingPath<R["path"]>;
|
|
72
|
+
};
|
|
62
73
|
|
|
63
74
|
export { OpenAPIHono, createRoute };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import * as openapi3_ts_oas31 from 'openapi3-ts/oas31';
|
|
1
2
|
import * as openapi3_ts_oas30 from 'openapi3-ts/oas30';
|
|
2
3
|
import { OpenAPIRegistry, RouteConfig, ZodRequestBody, ZodContentObject, ResponseConfig } from '@asteasolutions/zod-to-openapi';
|
|
3
4
|
import { OpenAPIObjectConfig } from '@asteasolutions/zod-to-openapi/dist/v3.0/openapi-generator';
|
|
4
5
|
import { Env, Schema, Hono, Input, Handler, ToSchema, Context, TypedResponse } from 'hono';
|
|
6
|
+
import { MergeSchemaPath, MergePath } from 'hono/dist/types/types';
|
|
7
|
+
import { RemoveBlankRecord } from 'hono/utils/types';
|
|
5
8
|
import { AnyZodObject, z, ZodSchema, ZodError, ZodType } from 'zod';
|
|
6
9
|
export { z } from 'zod';
|
|
7
10
|
|
|
@@ -49,15 +52,23 @@ type Hook<T, E extends Env, P extends string, O> = (result: {
|
|
|
49
52
|
}, c: Context<E, P>) => TypedResponse<O> | Promise<TypedResponse<T>> | void;
|
|
50
53
|
type ConvertPathType<T extends string> = T extends `${infer _}/{${infer Param}}${infer _}` ? `/:${Param}` : T;
|
|
51
54
|
type HandlerResponse<O> = TypedResponse<O> | Promise<TypedResponse<O>>;
|
|
55
|
+
type HonoInit = ConstructorParameters<typeof Hono>[0];
|
|
52
56
|
declare class OpenAPIHono<E extends Env = Env, S extends Schema = {}, BasePath extends string = '/'> extends Hono<E, S, BasePath> {
|
|
53
57
|
openAPIRegistry: OpenAPIRegistry;
|
|
54
|
-
constructor();
|
|
55
|
-
openapi: <R extends RouteConfig, I extends Input = InputTypeBase<R, "params", "param"> & InputTypeBase<R, "query", "query"> & InputTypeBase<R, "headers", "header"> & InputTypeBase<R, "cookies", "cookie"> & InputTypeForm<R> & InputTypeJson<R>, P extends string = ConvertPathType<R["path"]>>(route: R, handler: Handler<E, P, I, HandlerResponse<OutputType<R>>>, hook?: Hook<I, E, P, OutputType<R>> | undefined) =>
|
|
58
|
+
constructor(init?: HonoInit);
|
|
59
|
+
openapi: <R extends RouteConfig, I extends Input = InputTypeBase<R, "params", "param"> & InputTypeBase<R, "query", "query"> & InputTypeBase<R, "headers", "header"> & InputTypeBase<R, "cookies", "cookie"> & InputTypeForm<R> & InputTypeJson<R>, P extends string = ConvertPathType<R["path"]>>(route: R, handler: Handler<E, P, I, HandlerResponse<OutputType<R>>>, hook?: Hook<I, E, P, OutputType<R>> | undefined) => OpenAPIHono<E, ToSchema<R["method"], P, I["in"], OutputType<R>>, BasePath>;
|
|
56
60
|
getOpenAPIDocument: (config: OpenAPIObjectConfig) => openapi3_ts_oas30.OpenAPIObject;
|
|
61
|
+
getOpenAPI31Document: (config: OpenAPIObjectConfig) => openapi3_ts_oas31.OpenAPIObject;
|
|
57
62
|
doc: (path: string, config: OpenAPIObjectConfig) => void;
|
|
63
|
+
doc31: (path: string, config: OpenAPIObjectConfig) => void;
|
|
64
|
+
route<SubPath extends string, SubEnv extends Env, SubSchema extends Schema, SubBasePath extends string>(path: SubPath, app: Hono<SubEnv, SubSchema, SubBasePath>): OpenAPIHono<E, MergeSchemaPath<SubSchema, MergePath<BasePath, SubPath>> & S, BasePath>;
|
|
65
|
+
route<SubPath extends string>(path: SubPath): Hono<E, RemoveBlankRecord<S>, BasePath>;
|
|
58
66
|
}
|
|
67
|
+
type RoutingPath<P extends string> = P extends `${infer Head}/{${infer Param}}${infer Tail}` ? `${Head}/:${Param}${RoutingPath<Tail>}` : P;
|
|
59
68
|
declare const createRoute: <P extends string, R extends Omit<RouteConfig, "path"> & {
|
|
60
69
|
path: P;
|
|
61
|
-
}>(routeConfig: R) => R
|
|
70
|
+
}>(routeConfig: R) => R & {
|
|
71
|
+
getRoutingPath(): RoutingPath<R["path"]>;
|
|
72
|
+
};
|
|
62
73
|
|
|
63
74
|
export { OpenAPIHono, createRoute };
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { OpenApiGeneratorV3, OpenAPIRegistry } from "@asteasolutions/zod-to-openapi";
|
|
2
|
+
import { OpenApiGeneratorV3, OpenApiGeneratorV31, OpenAPIRegistry } from "@asteasolutions/zod-to-openapi";
|
|
3
3
|
import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi";
|
|
4
4
|
import { zValidator } from "@hono/zod-validator";
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import { z, ZodType } from "zod";
|
|
7
|
-
var OpenAPIHono = class extends Hono {
|
|
7
|
+
var OpenAPIHono = class _OpenAPIHono extends Hono {
|
|
8
8
|
openAPIRegistry;
|
|
9
|
-
constructor() {
|
|
10
|
-
super();
|
|
9
|
+
constructor(init) {
|
|
10
|
+
super(init);
|
|
11
11
|
this.openAPIRegistry = new OpenAPIRegistry();
|
|
12
12
|
}
|
|
13
13
|
openapi = (route, handler, hook) => {
|
|
@@ -56,14 +56,73 @@ var OpenAPIHono = class extends Hono {
|
|
|
56
56
|
const document = generator.generateDocument(config);
|
|
57
57
|
return document;
|
|
58
58
|
};
|
|
59
|
+
getOpenAPI31Document = (config) => {
|
|
60
|
+
const generator = new OpenApiGeneratorV31(this.openAPIRegistry.definitions);
|
|
61
|
+
const document = generator.generateDocument(config);
|
|
62
|
+
return document;
|
|
63
|
+
};
|
|
59
64
|
doc = (path, config) => {
|
|
60
65
|
this.get(path, (c) => {
|
|
61
66
|
const document = this.getOpenAPIDocument(config);
|
|
62
67
|
return c.json(document);
|
|
63
68
|
});
|
|
64
69
|
};
|
|
70
|
+
doc31 = (path, config) => {
|
|
71
|
+
this.get(path, (c) => {
|
|
72
|
+
const document = this.getOpenAPI31Document(config);
|
|
73
|
+
return c.json(document);
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
route(path, app) {
|
|
77
|
+
super.route(path, app);
|
|
78
|
+
if (!(app instanceof _OpenAPIHono)) {
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
app.openAPIRegistry.definitions.forEach((def) => {
|
|
82
|
+
switch (def.type) {
|
|
83
|
+
case "component":
|
|
84
|
+
return this.openAPIRegistry.registerComponent(
|
|
85
|
+
def.componentType,
|
|
86
|
+
def.name,
|
|
87
|
+
def.component
|
|
88
|
+
);
|
|
89
|
+
case "route":
|
|
90
|
+
return this.openAPIRegistry.registerPath({
|
|
91
|
+
...def.route,
|
|
92
|
+
path: `${path}${def.route.path}`
|
|
93
|
+
});
|
|
94
|
+
case "webhook":
|
|
95
|
+
return this.openAPIRegistry.registerWebhook({
|
|
96
|
+
...def.webhook,
|
|
97
|
+
path: `${path}${def.webhook.path}`
|
|
98
|
+
});
|
|
99
|
+
case "schema":
|
|
100
|
+
return this.openAPIRegistry.register(
|
|
101
|
+
def.schema._def.openapi._internal.refId,
|
|
102
|
+
def.schema
|
|
103
|
+
);
|
|
104
|
+
case "parameter":
|
|
105
|
+
return this.openAPIRegistry.registerParameter(
|
|
106
|
+
def.schema._def.openapi._internal.refId,
|
|
107
|
+
def.schema
|
|
108
|
+
);
|
|
109
|
+
default: {
|
|
110
|
+
const errorIfNotExhaustive = def;
|
|
111
|
+
throw new Error(`Unknown registry type: ${errorIfNotExhaustive}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var createRoute = (routeConfig) => {
|
|
119
|
+
return {
|
|
120
|
+
...routeConfig,
|
|
121
|
+
getRoutingPath() {
|
|
122
|
+
return routeConfig.path.replaceAll(/\/{(.+?)}/g, "/:$1");
|
|
123
|
+
}
|
|
124
|
+
};
|
|
65
125
|
};
|
|
66
|
-
var createRoute = (routeConfig) => routeConfig;
|
|
67
126
|
extendZodWithOpenApi(z);
|
|
68
127
|
export {
|
|
69
128
|
OpenAPIHono,
|