hono-openapi 0.4.8 → 0.5.0-rc.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 +4 -253
- package/dist/index.cjs +410 -0
- package/dist/index.d.cts +413 -0
- package/dist/index.d.ts +413 -0
- package/dist/index.js +401 -0
- package/package.json +34 -98
- package/arktype.cjs +0 -1
- package/arktype.d.cts +0 -1
- package/arktype.d.ts +0 -1
- package/arktype.js +0 -1
- package/effect.cjs +0 -1
- package/effect.d.cts +0 -1
- package/effect.d.ts +0 -1
- package/effect.js +0 -1
- package/index.cjs +0 -1
- package/index.d.cts +0 -1
- package/index.d.ts +0 -1
- package/index.js +0 -1
- package/src/arktype.d.ts +0 -36
- package/src/effect.d.ts +0 -33
- package/src/helper.d.ts +0 -10
- package/src/index.d.ts +0 -4
- package/src/openapi.d.ts +0 -201
- package/src/route.d.ts +0 -49
- package/src/toOpenAPISchema.d.ts +0 -21
- package/src/typebox.d.ts +0 -25
- package/src/types.d.ts +0 -101
- package/src/utils.d.ts +0 -31
- package/src/valibot.d.ts +0 -36
- package/src/zod.d.ts +0 -31
- package/toOpenAPISchema.cjs +0 -1
- package/toOpenAPISchema.js +0 -1
- package/typebox.cjs +0 -1
- package/typebox.d.cts +0 -1
- package/typebox.d.ts +0 -1
- package/typebox.js +0 -1
- package/utils.cjs +0 -1
- package/utils.js +0 -1
- package/valibot.cjs +0 -1
- package/valibot.d.cts +0 -1
- package/valibot.d.ts +0 -1
- package/valibot.js +0 -1
- package/zod.cjs +0 -1
- package/zod.d.cts +0 -1
- package/zod.d.ts +0 -1
- package/zod.js +0 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { sValidator } from '@hono/standard-validator';
|
|
2
|
+
import { toJsonSchema } from '@standard-community/standard-json';
|
|
3
|
+
import { toOpenAPISchema } from '@standard-community/standard-openapi';
|
|
4
|
+
|
|
5
|
+
const uniqueSymbol = Symbol("openapi");
|
|
6
|
+
const ALLOWED_METHODS = [
|
|
7
|
+
"GET",
|
|
8
|
+
"PUT",
|
|
9
|
+
"POST",
|
|
10
|
+
"DELETE",
|
|
11
|
+
"OPTIONS",
|
|
12
|
+
"HEAD",
|
|
13
|
+
"PATCH",
|
|
14
|
+
"TRACE"
|
|
15
|
+
];
|
|
16
|
+
const toOpenAPIPath = (path) => path.split("/").map((x) => {
|
|
17
|
+
let tmp = x;
|
|
18
|
+
if (tmp.startsWith(":")) {
|
|
19
|
+
const match = tmp.match(/^:([^{?]+)(?:{(.+)})?(\?)?$/);
|
|
20
|
+
if (match) {
|
|
21
|
+
const paramName = match[1];
|
|
22
|
+
tmp = `{${paramName}}`;
|
|
23
|
+
} else {
|
|
24
|
+
tmp = tmp.slice(1, tmp.length);
|
|
25
|
+
if (tmp.endsWith("?")) tmp = tmp.slice(0, -1);
|
|
26
|
+
tmp = `{${tmp}}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return tmp;
|
|
30
|
+
}).join("/");
|
|
31
|
+
const capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
|
|
32
|
+
const generateOperationIdCache = /* @__PURE__ */ new Map();
|
|
33
|
+
const generateOperationId = (route) => {
|
|
34
|
+
const operationIdKey = `${route.method}:${route.path}`;
|
|
35
|
+
if (generateOperationIdCache.has(operationIdKey)) {
|
|
36
|
+
return generateOperationIdCache.get(operationIdKey);
|
|
37
|
+
}
|
|
38
|
+
let operationId = route.method;
|
|
39
|
+
if (route.path === "/") return `${operationId}Index`;
|
|
40
|
+
for (const segment of route.path.split("/")) {
|
|
41
|
+
if (segment.charCodeAt(0) === 123) {
|
|
42
|
+
operationId += `By${capitalize(segment.slice(1, -1))}`;
|
|
43
|
+
} else {
|
|
44
|
+
operationId += capitalize(segment);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
generateOperationIdCache.set(operationIdKey, operationId);
|
|
48
|
+
return operationId;
|
|
49
|
+
};
|
|
50
|
+
const paramKey = (param) => "$ref" in param ? param.$ref : `${param.in} ${param.name}`;
|
|
51
|
+
function mergeParameters(...params) {
|
|
52
|
+
const _params = params.flatMap((x) => x ?? []);
|
|
53
|
+
const merged = _params.reduce((acc, param) => {
|
|
54
|
+
acc.set(paramKey(param), param);
|
|
55
|
+
return acc;
|
|
56
|
+
}, /* @__PURE__ */ new Map());
|
|
57
|
+
return Array.from(merged.values());
|
|
58
|
+
}
|
|
59
|
+
function getProperty(obj, key, defaultValue) {
|
|
60
|
+
if (obj != null && key in obj) {
|
|
61
|
+
return obj[key];
|
|
62
|
+
}
|
|
63
|
+
return defaultValue;
|
|
64
|
+
}
|
|
65
|
+
const specsByPathContext = /* @__PURE__ */ new Map();
|
|
66
|
+
function getPathContext(path) {
|
|
67
|
+
const keys = Array.from(specsByPathContext.keys());
|
|
68
|
+
const context = [];
|
|
69
|
+
for (const key of keys) {
|
|
70
|
+
if (path.match(key)) {
|
|
71
|
+
const data = specsByPathContext.get(key);
|
|
72
|
+
if (!data) continue;
|
|
73
|
+
context.push(data);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return context;
|
|
77
|
+
}
|
|
78
|
+
function mergeSpecs(...specs) {
|
|
79
|
+
return specs.reduce(
|
|
80
|
+
(prev, spec) => {
|
|
81
|
+
if (!spec) return prev;
|
|
82
|
+
return {
|
|
83
|
+
...prev,
|
|
84
|
+
...spec,
|
|
85
|
+
tags: Array.from(
|
|
86
|
+
/* @__PURE__ */ new Set([
|
|
87
|
+
...getProperty(prev, "tags") ?? [],
|
|
88
|
+
...getProperty(spec, "tags") ?? []
|
|
89
|
+
])
|
|
90
|
+
),
|
|
91
|
+
parameters: mergeParameters(
|
|
92
|
+
getProperty(prev, "parameters"),
|
|
93
|
+
getProperty(spec, "parameters")
|
|
94
|
+
),
|
|
95
|
+
responses: {
|
|
96
|
+
...getProperty(prev, "responses", {}),
|
|
97
|
+
...getProperty(spec, "responses", {})
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
{}
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
function registerSchemaPath({
|
|
105
|
+
route,
|
|
106
|
+
specs,
|
|
107
|
+
paths
|
|
108
|
+
}) {
|
|
109
|
+
const path = toOpenAPIPath(route.path);
|
|
110
|
+
const method = route.method.toLowerCase();
|
|
111
|
+
if (method === "all") {
|
|
112
|
+
if (!specs) return;
|
|
113
|
+
if (specsByPathContext.has(path)) {
|
|
114
|
+
const prev = specsByPathContext.get(path) ?? {};
|
|
115
|
+
specsByPathContext.set(path, mergeSpecs(prev, specs));
|
|
116
|
+
} else {
|
|
117
|
+
specsByPathContext.set(path, specs);
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
const pathContext = getPathContext(path);
|
|
121
|
+
paths[path] = {
|
|
122
|
+
...paths[path] ? paths[path] : {},
|
|
123
|
+
[method]: {
|
|
124
|
+
operationId: generateOperationId(route),
|
|
125
|
+
...mergeSpecs(...pathContext, paths[path]?.[method], specs)
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function removeExcludedPaths(paths, ctx) {
|
|
131
|
+
const { exclude, excludeStaticFile } = ctx.options;
|
|
132
|
+
const newPaths = {};
|
|
133
|
+
const _exclude = Array.isArray(exclude) ? exclude : [exclude];
|
|
134
|
+
for (const [key, value] of Object.entries(paths)) {
|
|
135
|
+
const isPathExcluded = !_exclude.some((x) => {
|
|
136
|
+
if (typeof x === "string") return key === x;
|
|
137
|
+
return x.test(key);
|
|
138
|
+
});
|
|
139
|
+
const isStaticFileExcluded = excludeStaticFile ? !key.includes(".") || key.includes("{") : true;
|
|
140
|
+
if (isPathExcluded && !(key.includes("*") && !key.includes("{")) && isStaticFileExcluded && value != null) {
|
|
141
|
+
for (const method of Object.keys(value)) {
|
|
142
|
+
const schema = value[method];
|
|
143
|
+
if (key.includes("{")) {
|
|
144
|
+
if (!schema.parameters) schema.parameters = [];
|
|
145
|
+
const pathParameters = key.split("/").filter(
|
|
146
|
+
(x) => x.startsWith("{") && !schema.parameters.find(
|
|
147
|
+
(params) => params.in === "path" && params.name === x.slice(1, x.length - 1)
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
for (const param of pathParameters) {
|
|
151
|
+
const paramName = param.slice(1, param.length - 1);
|
|
152
|
+
const index = schema.parameters.findIndex(
|
|
153
|
+
(x) => x.in === "param" && x.name === paramName
|
|
154
|
+
);
|
|
155
|
+
if (index !== -1) schema.parameters[index].in = "path";
|
|
156
|
+
else {
|
|
157
|
+
schema.parameters.push({
|
|
158
|
+
schema: { type: "string" },
|
|
159
|
+
in: "path",
|
|
160
|
+
name: paramName,
|
|
161
|
+
required: true
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (!schema.responses) {
|
|
167
|
+
schema.responses = {
|
|
168
|
+
200: {}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
newPaths[key] = value;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return newPaths;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const DEFAULT_OPTIONS = {
|
|
179
|
+
documentation: {},
|
|
180
|
+
excludeStaticFile: true,
|
|
181
|
+
exclude: [],
|
|
182
|
+
excludeMethods: ["OPTIONS"],
|
|
183
|
+
excludeTags: []
|
|
184
|
+
};
|
|
185
|
+
async function generateSpecs(hono, options = DEFAULT_OPTIONS, c) {
|
|
186
|
+
const ctx = {
|
|
187
|
+
components: {},
|
|
188
|
+
// @ts-expect-error
|
|
189
|
+
options: {
|
|
190
|
+
...DEFAULT_OPTIONS,
|
|
191
|
+
...options
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
const _documentation = ctx.options.documentation ?? {};
|
|
195
|
+
const schema = await generatePaths(hono, ctx);
|
|
196
|
+
for (const path in schema) {
|
|
197
|
+
for (const method in schema[path]) {
|
|
198
|
+
const isHidden = getHiddenValue({
|
|
199
|
+
valueOrFunc: schema[path][method]?.hide,
|
|
200
|
+
method,
|
|
201
|
+
path,
|
|
202
|
+
c
|
|
203
|
+
});
|
|
204
|
+
if (isHidden) {
|
|
205
|
+
delete schema[path][method];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
openapi: "3.1.0",
|
|
211
|
+
..._documentation,
|
|
212
|
+
tags: _documentation.tags?.filter(
|
|
213
|
+
(tag) => !ctx.options.excludeTags?.includes(tag?.name)
|
|
214
|
+
),
|
|
215
|
+
info: {
|
|
216
|
+
title: "Hono Documentation",
|
|
217
|
+
description: "Development documentation",
|
|
218
|
+
version: "0.0.0",
|
|
219
|
+
..._documentation.info
|
|
220
|
+
},
|
|
221
|
+
paths: {
|
|
222
|
+
...removeExcludedPaths(schema, ctx),
|
|
223
|
+
..._documentation.paths
|
|
224
|
+
},
|
|
225
|
+
components: {
|
|
226
|
+
..._documentation.components,
|
|
227
|
+
schemas: {
|
|
228
|
+
...ctx.components.schemas,
|
|
229
|
+
..._documentation.components?.schemas
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
async function generatePaths(hono, ctx) {
|
|
235
|
+
const paths = {};
|
|
236
|
+
for (const route of hono.routes) {
|
|
237
|
+
if (!(uniqueSymbol in route.handler)) {
|
|
238
|
+
if (ctx.options.includeEmptyPaths) {
|
|
239
|
+
registerSchemaPath({
|
|
240
|
+
route,
|
|
241
|
+
paths
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const routeMethod = route.method;
|
|
247
|
+
if (routeMethod !== "ALL") {
|
|
248
|
+
if (ctx.options.excludeMethods?.includes(routeMethod)) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (!ALLOWED_METHODS.includes(routeMethod)) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const middlewareHandler = route.handler[uniqueSymbol];
|
|
256
|
+
const defaultOptionsForThisMethod = ctx.options.defaultOptions?.[routeMethod];
|
|
257
|
+
const { schema: routeSpecs, components = {} } = await getSpec(
|
|
258
|
+
middlewareHandler,
|
|
259
|
+
defaultOptionsForThisMethod
|
|
260
|
+
);
|
|
261
|
+
ctx.components = {
|
|
262
|
+
...ctx.components,
|
|
263
|
+
...components,
|
|
264
|
+
schemas: {
|
|
265
|
+
...ctx.components.schemas,
|
|
266
|
+
...components.schemas
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
registerSchemaPath({
|
|
270
|
+
route,
|
|
271
|
+
specs: routeSpecs,
|
|
272
|
+
paths
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return paths;
|
|
276
|
+
}
|
|
277
|
+
function getHiddenValue(options) {
|
|
278
|
+
const { valueOrFunc, c, method, path } = options;
|
|
279
|
+
if (valueOrFunc != null) {
|
|
280
|
+
if (typeof valueOrFunc === "boolean") {
|
|
281
|
+
return valueOrFunc;
|
|
282
|
+
} else if (typeof valueOrFunc === "function") {
|
|
283
|
+
if (c) {
|
|
284
|
+
return valueOrFunc(c);
|
|
285
|
+
} else {
|
|
286
|
+
console.warn(
|
|
287
|
+
`'c' is not defined, cannot evaluate hide function for ${method} ${path}`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
async function getSpec(middlewareHandler, defaultOptions) {
|
|
295
|
+
if ("spec" in middlewareHandler) {
|
|
296
|
+
let components = {};
|
|
297
|
+
const tmp = {
|
|
298
|
+
...defaultOptions,
|
|
299
|
+
...middlewareHandler.spec,
|
|
300
|
+
responses: {
|
|
301
|
+
...defaultOptions?.responses,
|
|
302
|
+
...middlewareHandler.spec.responses
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
if (tmp.responses) {
|
|
306
|
+
for (const key of Object.keys(tmp.responses)) {
|
|
307
|
+
const response = tmp.responses[key];
|
|
308
|
+
if (!response || !("content" in response)) continue;
|
|
309
|
+
for (const contentKey of Object.keys(response.content ?? {})) {
|
|
310
|
+
const raw = response.content?.[contentKey];
|
|
311
|
+
if (!raw) continue;
|
|
312
|
+
if (raw.schema && "toOpenAPISchema" in raw.schema) {
|
|
313
|
+
const result2 = await raw.schema.toOpenAPISchema(defaultOptions);
|
|
314
|
+
raw.schema = result2.schema;
|
|
315
|
+
if (result2.components) {
|
|
316
|
+
components = {
|
|
317
|
+
...components,
|
|
318
|
+
...result2.components
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return { schema: tmp, components };
|
|
326
|
+
}
|
|
327
|
+
const result = await middlewareHandler.toOpenAPISchema();
|
|
328
|
+
const docs = {};
|
|
329
|
+
if (middlewareHandler.target === "form" || middlewareHandler.target === "json") {
|
|
330
|
+
const media = middlewareHandler.target === "json" ? "application/json" : "multipart/form-data";
|
|
331
|
+
if (!docs.requestBody || !("content" in docs.requestBody) || !docs.requestBody.content) {
|
|
332
|
+
docs.requestBody = {
|
|
333
|
+
content: {
|
|
334
|
+
[media]: {
|
|
335
|
+
schema: result.schema
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
} else {
|
|
340
|
+
docs.requestBody.content[media] = {
|
|
341
|
+
schema: result.schema
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
const parameters = [];
|
|
346
|
+
if ("$ref" in result.schema) {
|
|
347
|
+
parameters.push({
|
|
348
|
+
in: middlewareHandler.target,
|
|
349
|
+
// @ts-expect-error
|
|
350
|
+
name: result.schema.$ref,
|
|
351
|
+
// @ts-expect-error
|
|
352
|
+
schema: result.schema
|
|
353
|
+
});
|
|
354
|
+
} else {
|
|
355
|
+
for (const [key, value] of Object.entries(
|
|
356
|
+
result.schema.properties ?? {}
|
|
357
|
+
)) {
|
|
358
|
+
parameters.push({
|
|
359
|
+
in: middlewareHandler.target,
|
|
360
|
+
name: key,
|
|
361
|
+
// @ts-expect-error
|
|
362
|
+
schema: value,
|
|
363
|
+
required: result.schema.required?.includes(key)
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
docs.parameters = parameters;
|
|
368
|
+
}
|
|
369
|
+
return { schema: docs, components: result.components };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function resolver(schema) {
|
|
373
|
+
return {
|
|
374
|
+
vendor: schema["~standard"].vendor,
|
|
375
|
+
validate: schema["~standard"].validate,
|
|
376
|
+
toJSONSchema: (options) => toJsonSchema(schema, options),
|
|
377
|
+
toOpenAPISchema: (options) => toOpenAPISchema(schema, options)
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
function validator(target, schema, hook, options) {
|
|
381
|
+
const middleware = sValidator(target, schema, hook);
|
|
382
|
+
return Object.assign(middleware, {
|
|
383
|
+
[uniqueSymbol]: {
|
|
384
|
+
target,
|
|
385
|
+
...resolver(schema),
|
|
386
|
+
options
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
function describeRoute(spec) {
|
|
391
|
+
const middleware = async (_c, next) => {
|
|
392
|
+
await next();
|
|
393
|
+
};
|
|
394
|
+
return Object.assign(middleware, {
|
|
395
|
+
[uniqueSymbol]: {
|
|
396
|
+
spec
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export { ALLOWED_METHODS, describeRoute, generateSpecs, registerSchemaPath, removeExcludedPaths, resolver, uniqueSymbol, validator };
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hono-openapi",
|
|
3
3
|
"description": "OpenAPI schema generator for Hono",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"types": "./index.d.ts",
|
|
6
|
-
"main": "./index.js",
|
|
4
|
+
"version": "0.5.0-rc.0",
|
|
7
5
|
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
8
12
|
"license": "MIT",
|
|
9
13
|
"keywords": [
|
|
10
14
|
"hono",
|
|
@@ -27,47 +31,35 @@
|
|
|
27
31
|
"bugs": {
|
|
28
32
|
"url": "https://github.com/rhinobase/hono-openapi/issues"
|
|
29
33
|
},
|
|
30
|
-
"
|
|
31
|
-
"
|
|
34
|
+
"exports": {
|
|
35
|
+
"import": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"default": "./dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
"require": {
|
|
40
|
+
"types": "./dist/index.d.cts",
|
|
41
|
+
"default": "./dist/index.cjs"
|
|
42
|
+
}
|
|
32
43
|
},
|
|
33
44
|
"peerDependencies": {
|
|
34
|
-
"@hono/
|
|
35
|
-
"@hono/effect-validator": "^1.2.0",
|
|
36
|
-
"@hono/typebox-validator": "^0.2.0 || ^0.3.0",
|
|
37
|
-
"@hono/valibot-validator": "^0.5.1",
|
|
38
|
-
"@hono/zod-validator": "^0.4.1",
|
|
45
|
+
"@hono/standard-validator": "^0.1.2",
|
|
39
46
|
"@sinclair/typebox": "^0.34.9",
|
|
40
|
-
"@
|
|
47
|
+
"@standard-community/standard-json": "^0.3.0-rc.1",
|
|
48
|
+
"@standard-community/standard-openapi": "^0.2.0-rc.1",
|
|
41
49
|
"arktype": "^2.0.0",
|
|
42
|
-
"effect": "^3.
|
|
43
|
-
"hono": "^4.
|
|
50
|
+
"effect": "^3.16.12",
|
|
51
|
+
"hono": "^4.8.3",
|
|
44
52
|
"openapi-types": "^12.1.3",
|
|
45
53
|
"valibot": "^1.0.0-beta.9",
|
|
46
|
-
"zod": "^3.23.8"
|
|
47
|
-
"zod-openapi": "^4.0.0"
|
|
54
|
+
"zod": "^3.23.8"
|
|
48
55
|
},
|
|
49
56
|
"peerDependenciesMeta": {
|
|
50
|
-
"@hono/
|
|
51
|
-
"optional": true
|
|
52
|
-
},
|
|
53
|
-
"@hono/effect-validator": {
|
|
54
|
-
"optional": true
|
|
55
|
-
},
|
|
56
|
-
"@hono/typebox-validator": {
|
|
57
|
-
"optional": true
|
|
58
|
-
},
|
|
59
|
-
"@hono/valibot-validator": {
|
|
60
|
-
"optional": true
|
|
61
|
-
},
|
|
62
|
-
"@hono/zod-validator": {
|
|
57
|
+
"@hono/standard-validator": {
|
|
63
58
|
"optional": true
|
|
64
59
|
},
|
|
65
60
|
"@sinclair/typebox": {
|
|
66
61
|
"optional": true
|
|
67
62
|
},
|
|
68
|
-
"@valibot/to-json-schema": {
|
|
69
|
-
"optional": true
|
|
70
|
-
},
|
|
71
63
|
"arktype": {
|
|
72
64
|
"optional": true
|
|
73
65
|
},
|
|
@@ -82,76 +74,20 @@
|
|
|
82
74
|
},
|
|
83
75
|
"zod": {
|
|
84
76
|
"optional": true
|
|
85
|
-
},
|
|
86
|
-
"zod-openapi": {
|
|
87
|
-
"optional": true
|
|
88
77
|
}
|
|
89
78
|
},
|
|
90
79
|
"devDependencies": {
|
|
91
|
-
"@
|
|
92
|
-
"@
|
|
93
|
-
"@types/json-schema": "^7.0.15"
|
|
80
|
+
"@biomejs/biome": "^2.0.6",
|
|
81
|
+
"@standard-schema/spec": "^1.0.0",
|
|
82
|
+
"@types/json-schema": "^7.0.15",
|
|
83
|
+
"@valibot/to-json-schema": "^1.3.0",
|
|
84
|
+
"pkgroll": "^2.13.1",
|
|
85
|
+
"typescript": "^5.8.3",
|
|
86
|
+
"vitest": "^3.2.4"
|
|
94
87
|
},
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
"default": "./index.js"
|
|
100
|
-
},
|
|
101
|
-
"require": {
|
|
102
|
-
"types": "./index.d.cts",
|
|
103
|
-
"default": "./index.cjs"
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
"./zod": {
|
|
107
|
-
"import": {
|
|
108
|
-
"types": "./zod.d.ts",
|
|
109
|
-
"default": "./zod.js"
|
|
110
|
-
},
|
|
111
|
-
"require": {
|
|
112
|
-
"types": "./zod.d.cts",
|
|
113
|
-
"default": "./zod.cjs"
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
"./valibot": {
|
|
117
|
-
"import": {
|
|
118
|
-
"types": "./valibot.d.ts",
|
|
119
|
-
"default": "./valibot.js"
|
|
120
|
-
},
|
|
121
|
-
"require": {
|
|
122
|
-
"types": "./valibot.d.cts",
|
|
123
|
-
"default": "./valibot.cjs"
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
"./typebox": {
|
|
127
|
-
"import": {
|
|
128
|
-
"types": "./typebox.d.ts",
|
|
129
|
-
"default": "./typebox.js"
|
|
130
|
-
},
|
|
131
|
-
"require": {
|
|
132
|
-
"types": "./typebox.d.cts",
|
|
133
|
-
"default": "./typebox.cjs"
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
|
-
"./arktype": {
|
|
137
|
-
"import": {
|
|
138
|
-
"types": "./arktype.d.ts",
|
|
139
|
-
"default": "./arktype.js"
|
|
140
|
-
},
|
|
141
|
-
"require": {
|
|
142
|
-
"types": "./arktype.d.cts",
|
|
143
|
-
"default": "./arktype.cjs"
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
"./effect": {
|
|
147
|
-
"import": {
|
|
148
|
-
"types": "./effect.d.ts",
|
|
149
|
-
"default": "./effect.js"
|
|
150
|
-
},
|
|
151
|
-
"require": {
|
|
152
|
-
"types": "./effect.d.cts",
|
|
153
|
-
"default": "./effect.cjs"
|
|
154
|
-
}
|
|
155
|
-
}
|
|
88
|
+
"scripts": {
|
|
89
|
+
"build": "pkgroll --clean-dist",
|
|
90
|
+
"format": "biome check --write .",
|
|
91
|
+
"test": "vitest"
|
|
156
92
|
}
|
|
157
93
|
}
|
package/arktype.cjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
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.d.cts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./src/arktype";
|
package/arktype.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./src/arktype.js";
|
package/arktype.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
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/effect.cjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";var e=require("@hono/effect-validator"),r=require("effect"),a=require("./toOpenAPISchema.cjs"),t=require("./utils.cjs");function i(e){return{builder:async()=>({schema:await a.convert(r.JSONSchema.make(e))}),validator:async a=>{await r.Schema.decodeUnknownPromise(e)(a)}}}require("json-schema-walker"),exports.resolver=i,exports.validator=function(r,a){const o=e.effectValidator(r,a);return Object.assign(o,{[t.uniqueSymbol]:{resolver:async e=>t.generateValidatorDocs(r,await i(a).builder(e))}})};
|
package/effect.d.cts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./src/effect";
|
package/effect.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./src/effect.js";
|
package/effect.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{effectValidator as o}from"@hono/effect-validator";import{JSONSchema as a,Schema as r}from"effect";import{c as e}from"./toOpenAPISchema.js";import{u as t,g as i}from"./utils.js";import"json-schema-walker";function n(o){return{builder:async()=>({schema:await e(a.make(o))}),validator:async a=>{await r.decodeUnknownPromise(o)(a)}}}function s(a,r){const e=o(a,r);return Object.assign(e,{[t]:{resolver:async o=>i(a,await n(r).builder(o))}})}export{n as resolver,s as validator};
|
package/index.cjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
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.d.cts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./src/index";
|
package/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./src/index.js";
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
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/src/arktype.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { type Hook } from "@hono/arktype-validator";
|
|
2
|
-
import type { Type } from "arktype";
|
|
3
|
-
import type { Env, MiddlewareHandler, ValidationTargets } from "hono";
|
|
4
|
-
import type { HasUndefined, ResolverResult } from "./types.js";
|
|
5
|
-
/**
|
|
6
|
-
* Generate a resolver for an Arktype schema
|
|
7
|
-
* @param schema Arktype schema
|
|
8
|
-
* @returns Resolver result
|
|
9
|
-
*/
|
|
10
|
-
export declare function resolver<T extends Type>(schema: T): ResolverResult;
|
|
11
|
-
/**
|
|
12
|
-
* Create a validator middleware
|
|
13
|
-
* @param target Target for validation
|
|
14
|
-
* @param schema Arktype schema
|
|
15
|
-
* @param hook Hook for validation
|
|
16
|
-
* @returns Middleware handler
|
|
17
|
-
*/
|
|
18
|
-
export declare function validator<T extends Type, Target extends keyof ValidationTargets, E extends Env, P extends string, I = T["inferIn"], O = T["infer"], V extends {
|
|
19
|
-
in: HasUndefined<I> extends true ? {
|
|
20
|
-
[K in Target]?: I;
|
|
21
|
-
} : {
|
|
22
|
-
[K in Target]: I;
|
|
23
|
-
};
|
|
24
|
-
out: {
|
|
25
|
-
[K in Target]: O;
|
|
26
|
-
};
|
|
27
|
-
} = {
|
|
28
|
-
in: HasUndefined<I> extends true ? {
|
|
29
|
-
[K in Target]?: I;
|
|
30
|
-
} : {
|
|
31
|
-
[K in Target]: I;
|
|
32
|
-
};
|
|
33
|
-
out: {
|
|
34
|
-
[K in Target]: O;
|
|
35
|
-
};
|
|
36
|
-
}>(target: Target, schema: T, hook?: Hook<T["infer"], E, P>): MiddlewareHandler<E, P, V>;
|