hono-openapi 0.5.0-rc.3 → 1.0.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/dist/index.cjs +143 -107
- package/dist/index.d.cts +32 -29
- package/dist/index.d.ts +32 -29
- package/dist/index.js +143 -107
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -31,12 +31,7 @@ const toOpenAPIPath = (path) => path.split("/").map((x) => {
|
|
|
31
31
|
return tmp;
|
|
32
32
|
}).join("/");
|
|
33
33
|
const capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
|
|
34
|
-
const generateOperationIdCache = /* @__PURE__ */ new Map();
|
|
35
34
|
const generateOperationId = (route) => {
|
|
36
|
-
const operationIdKey = `${route.method}:${route.path}`;
|
|
37
|
-
if (generateOperationIdCache.has(operationIdKey)) {
|
|
38
|
-
return generateOperationIdCache.get(operationIdKey);
|
|
39
|
-
}
|
|
40
35
|
let operationId = route.method;
|
|
41
36
|
if (route.path === "/") return `${operationId}Index`;
|
|
42
37
|
for (const segment of route.path.split("/")) {
|
|
@@ -46,61 +41,61 @@ const generateOperationId = (route) => {
|
|
|
46
41
|
operationId += capitalize(segment);
|
|
47
42
|
}
|
|
48
43
|
}
|
|
49
|
-
generateOperationIdCache.set(operationIdKey, operationId);
|
|
50
44
|
return operationId;
|
|
51
45
|
};
|
|
52
46
|
const paramKey = (param) => "$ref" in param ? param.$ref : `${param.in} ${param.name}`;
|
|
53
47
|
function mergeParameters(...params) {
|
|
54
|
-
const
|
|
55
|
-
const merged = _params.reduce((acc, param) => {
|
|
48
|
+
const merged = params.flatMap((x) => x ?? []).reduce((acc, param) => {
|
|
56
49
|
acc.set(paramKey(param), param);
|
|
57
50
|
return acc;
|
|
58
51
|
}, /* @__PURE__ */ new Map());
|
|
59
52
|
return Array.from(merged.values());
|
|
60
53
|
}
|
|
61
|
-
function getProperty(obj, key, defaultValue) {
|
|
62
|
-
if (obj != null && key in obj) {
|
|
63
|
-
return obj[key];
|
|
64
|
-
}
|
|
65
|
-
return defaultValue;
|
|
66
|
-
}
|
|
67
54
|
const specsByPathContext = /* @__PURE__ */ new Map();
|
|
68
55
|
function getPathContext(path) {
|
|
69
|
-
const keys = Array.from(specsByPathContext.keys());
|
|
70
56
|
const context = [];
|
|
71
|
-
for (const key of
|
|
72
|
-
if (path.match(key)) {
|
|
73
|
-
const data = specsByPathContext.get(key);
|
|
74
|
-
if (!data) continue;
|
|
57
|
+
for (const [key, data] of specsByPathContext) {
|
|
58
|
+
if (data && path.match(key)) {
|
|
75
59
|
context.push(data);
|
|
76
60
|
}
|
|
77
61
|
}
|
|
78
62
|
return context;
|
|
79
63
|
}
|
|
80
|
-
function mergeSpecs(...specs) {
|
|
64
|
+
function mergeSpecs(route, ...specs) {
|
|
81
65
|
return specs.reduce(
|
|
82
66
|
(prev, spec) => {
|
|
83
|
-
if (!spec) return prev;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
67
|
+
if (!spec || !prev) return prev;
|
|
68
|
+
for (const [key, value] of Object.entries(spec)) {
|
|
69
|
+
if (value == null) continue;
|
|
70
|
+
if (key in prev && (typeof value === "object" || typeof value === "function" && key === "operationId")) {
|
|
71
|
+
if (Array.isArray(value)) {
|
|
72
|
+
const values = [...prev[key] ?? [], ...value];
|
|
73
|
+
if (key === "tags") {
|
|
74
|
+
prev[key] = Array.from(new Set(values));
|
|
75
|
+
} else {
|
|
76
|
+
prev[key] = values;
|
|
77
|
+
}
|
|
78
|
+
} else if (typeof value === "function") {
|
|
79
|
+
prev[key] = value(route);
|
|
80
|
+
} else {
|
|
81
|
+
if (key === "parameters") {
|
|
82
|
+
prev[key] = mergeParameters(prev[key], value);
|
|
83
|
+
} else {
|
|
84
|
+
prev[key] = {
|
|
85
|
+
...prev[key],
|
|
86
|
+
...value
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
prev[key] = value;
|
|
100
92
|
}
|
|
101
|
-
}
|
|
93
|
+
}
|
|
94
|
+
return prev;
|
|
102
95
|
},
|
|
103
|
-
{
|
|
96
|
+
{
|
|
97
|
+
operationId: generateOperationId(route)
|
|
98
|
+
}
|
|
104
99
|
);
|
|
105
100
|
}
|
|
106
101
|
function registerSchemaPath({
|
|
@@ -114,19 +109,23 @@ function registerSchemaPath({
|
|
|
114
109
|
if (!specs) return;
|
|
115
110
|
if (specsByPathContext.has(path)) {
|
|
116
111
|
const prev = specsByPathContext.get(path) ?? {};
|
|
117
|
-
specsByPathContext.set(path, mergeSpecs(prev, specs));
|
|
112
|
+
specsByPathContext.set(path, mergeSpecs(route, prev, specs));
|
|
118
113
|
} else {
|
|
119
114
|
specsByPathContext.set(path, specs);
|
|
120
115
|
}
|
|
121
116
|
} else {
|
|
122
117
|
const pathContext = getPathContext(path);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
118
|
+
if (!(path in paths)) {
|
|
119
|
+
paths[path] = {};
|
|
120
|
+
}
|
|
121
|
+
if (paths[path]) {
|
|
122
|
+
paths[path][method] = mergeSpecs(
|
|
123
|
+
route,
|
|
124
|
+
...pathContext,
|
|
125
|
+
paths[path]?.[method],
|
|
126
|
+
specs
|
|
127
|
+
);
|
|
128
|
+
}
|
|
130
129
|
}
|
|
131
130
|
}
|
|
132
131
|
function removeExcludedPaths(paths, ctx) {
|
|
@@ -152,10 +151,21 @@ function removeExcludedPaths(paths, ctx) {
|
|
|
152
151
|
for (const param of pathParameters) {
|
|
153
152
|
const paramName = param.slice(1, param.length - 1);
|
|
154
153
|
const index = schema.parameters.findIndex(
|
|
155
|
-
(x) =>
|
|
154
|
+
(x) => {
|
|
155
|
+
if ("$ref" in x) {
|
|
156
|
+
const pos = x.$ref.split("/").pop();
|
|
157
|
+
if (pos) {
|
|
158
|
+
const param2 = ctx.components.parameters?.[pos];
|
|
159
|
+
if (param2 && !("$ref" in param2)) {
|
|
160
|
+
return param2.in === "path" && param2.name === paramName;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
return x.in === "path" && x.name === paramName;
|
|
166
|
+
}
|
|
156
167
|
);
|
|
157
|
-
if (index
|
|
158
|
-
else {
|
|
168
|
+
if (index === -1) {
|
|
159
169
|
schema.parameters.push({
|
|
160
170
|
schema: { type: "string" },
|
|
161
171
|
in: "path",
|
|
@@ -202,20 +212,24 @@ async function generateSpecs(hono, options = DEFAULT_OPTIONS, c) {
|
|
|
202
212
|
}
|
|
203
213
|
};
|
|
204
214
|
const _documentation = ctx.options.documentation ?? {};
|
|
205
|
-
const
|
|
206
|
-
for (const path in
|
|
207
|
-
for (const method in
|
|
215
|
+
const paths = await generatePaths(hono, ctx);
|
|
216
|
+
for (const path in paths) {
|
|
217
|
+
for (const method in paths[path]) {
|
|
208
218
|
const isHidden = getHiddenValue({
|
|
209
|
-
valueOrFunc:
|
|
219
|
+
valueOrFunc: paths[path][method]?.hide,
|
|
210
220
|
method,
|
|
211
221
|
path,
|
|
212
222
|
c
|
|
213
223
|
});
|
|
214
224
|
if (isHidden) {
|
|
215
|
-
|
|
225
|
+
paths[path][method] = void 0;
|
|
216
226
|
}
|
|
217
227
|
}
|
|
218
228
|
}
|
|
229
|
+
const components = mergeComponentsObjects(
|
|
230
|
+
_documentation.components,
|
|
231
|
+
ctx.components
|
|
232
|
+
);
|
|
219
233
|
return {
|
|
220
234
|
openapi: "3.1.0",
|
|
221
235
|
..._documentation,
|
|
@@ -229,22 +243,17 @@ async function generateSpecs(hono, options = DEFAULT_OPTIONS, c) {
|
|
|
229
243
|
..._documentation.info
|
|
230
244
|
},
|
|
231
245
|
paths: {
|
|
232
|
-
...removeExcludedPaths(
|
|
246
|
+
...removeExcludedPaths(paths, ctx),
|
|
233
247
|
..._documentation.paths
|
|
234
248
|
},
|
|
235
|
-
components
|
|
236
|
-
..._documentation.components,
|
|
237
|
-
schemas: {
|
|
238
|
-
...ctx.components.schemas,
|
|
239
|
-
..._documentation.components?.schemas
|
|
240
|
-
}
|
|
241
|
-
}
|
|
249
|
+
components
|
|
242
250
|
};
|
|
243
251
|
}
|
|
244
252
|
async function generatePaths(hono, ctx) {
|
|
245
253
|
const paths = {};
|
|
246
254
|
for (const route of hono.routes) {
|
|
247
|
-
|
|
255
|
+
const middlewareHandler = route.handler[uniqueSymbol];
|
|
256
|
+
if (!middlewareHandler) {
|
|
248
257
|
if (ctx.options.includeEmptyPaths) {
|
|
249
258
|
registerSchemaPath({
|
|
250
259
|
route,
|
|
@@ -262,20 +271,12 @@ async function generatePaths(hono, ctx) {
|
|
|
262
271
|
continue;
|
|
263
272
|
}
|
|
264
273
|
}
|
|
265
|
-
const middlewareHandler = route.handler[uniqueSymbol];
|
|
266
274
|
const defaultOptionsForThisMethod = ctx.options.defaultOptions?.[routeMethod];
|
|
267
275
|
const { schema: routeSpecs, components = {} } = await getSpec(
|
|
268
276
|
middlewareHandler,
|
|
269
277
|
defaultOptionsForThisMethod
|
|
270
278
|
);
|
|
271
|
-
ctx.components =
|
|
272
|
-
...ctx.components,
|
|
273
|
-
...components,
|
|
274
|
-
schemas: {
|
|
275
|
-
...ctx.components.schemas,
|
|
276
|
-
...components.schemas
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
+
ctx.components = mergeComponentsObjects(ctx.components, components);
|
|
279
280
|
registerSchemaPath({
|
|
280
281
|
route,
|
|
281
282
|
specs: routeSpecs,
|
|
@@ -289,14 +290,9 @@ function getHiddenValue(options) {
|
|
|
289
290
|
if (valueOrFunc != null) {
|
|
290
291
|
if (typeof valueOrFunc === "boolean") {
|
|
291
292
|
return valueOrFunc;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
} else {
|
|
296
|
-
console.warn(
|
|
297
|
-
`'c' is not defined, cannot evaluate hide function for ${method} ${path}`
|
|
298
|
-
);
|
|
299
|
-
}
|
|
293
|
+
}
|
|
294
|
+
if (typeof valueOrFunc === "function") {
|
|
295
|
+
return valueOrFunc({ c, method, path });
|
|
300
296
|
}
|
|
301
297
|
}
|
|
302
298
|
return false;
|
|
@@ -320,7 +316,7 @@ async function getSpec(middlewareHandler, defaultOptions) {
|
|
|
320
316
|
const raw = response.content?.[contentKey];
|
|
321
317
|
if (!raw) continue;
|
|
322
318
|
if (raw.schema && "toOpenAPISchema" in raw.schema) {
|
|
323
|
-
const result2 = await raw.schema.toOpenAPISchema(
|
|
319
|
+
const result2 = await raw.schema.toOpenAPISchema();
|
|
324
320
|
raw.schema = result2.schema;
|
|
325
321
|
if (result2.components) {
|
|
326
322
|
components = {
|
|
@@ -337,7 +333,7 @@ async function getSpec(middlewareHandler, defaultOptions) {
|
|
|
337
333
|
const result = await middlewareHandler.toOpenAPISchema();
|
|
338
334
|
const docs = {};
|
|
339
335
|
if (middlewareHandler.target === "form" || middlewareHandler.target === "json") {
|
|
340
|
-
const media = middlewareHandler.target === "json" ? "application/json" : "multipart/form-data";
|
|
336
|
+
const media = middlewareHandler.options?.media ?? middlewareHandler.target === "json" ? "application/json" : "multipart/form-data";
|
|
341
337
|
if (!docs.requestBody || !("content" in docs.requestBody) || !docs.requestBody.content) {
|
|
342
338
|
docs.requestBody = {
|
|
343
339
|
content: {
|
|
@@ -352,39 +348,78 @@ async function getSpec(middlewareHandler, defaultOptions) {
|
|
|
352
348
|
};
|
|
353
349
|
}
|
|
354
350
|
} else {
|
|
355
|
-
|
|
351
|
+
let parameters = [];
|
|
356
352
|
if ("$ref" in result.schema) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
353
|
+
const ref = result.schema.$ref;
|
|
354
|
+
const pos = ref.split("/").pop();
|
|
355
|
+
if (pos && result.components?.schemas?.[pos]) {
|
|
356
|
+
const schema = result.components.schemas[pos];
|
|
357
|
+
const newParameters = generateParameters(
|
|
358
|
+
middlewareHandler.target,
|
|
359
|
+
schema
|
|
360
|
+
)[0];
|
|
361
|
+
if (!result.components.parameters) {
|
|
362
|
+
result.components.parameters = {};
|
|
363
|
+
}
|
|
364
|
+
result.components.parameters[pos] = newParameters;
|
|
365
|
+
delete result.components.schemas[pos];
|
|
368
366
|
parameters.push({
|
|
369
|
-
|
|
370
|
-
name: key,
|
|
371
|
-
// @ts-expect-error
|
|
372
|
-
schema: value,
|
|
373
|
-
required: result.schema.required?.includes(key)
|
|
367
|
+
$ref: `#/components/parameters/${pos}`
|
|
374
368
|
});
|
|
375
369
|
}
|
|
370
|
+
} else {
|
|
371
|
+
parameters = generateParameters(middlewareHandler.target, result.schema);
|
|
376
372
|
}
|
|
377
373
|
docs.parameters = parameters;
|
|
378
374
|
}
|
|
379
375
|
return { schema: docs, components: result.components };
|
|
380
376
|
}
|
|
377
|
+
function generateParameters(target, schema) {
|
|
378
|
+
const parameters = [];
|
|
379
|
+
for (const [key, value] of Object.entries(schema.properties ?? {})) {
|
|
380
|
+
const def = {
|
|
381
|
+
in: target === "param" ? "path" : target,
|
|
382
|
+
name: key,
|
|
383
|
+
// @ts-expect-error
|
|
384
|
+
schema: value,
|
|
385
|
+
required: schema.required?.includes(key)
|
|
386
|
+
};
|
|
387
|
+
if (def.schema && "description" in def.schema && def.schema.description) {
|
|
388
|
+
def.description = def.schema.description;
|
|
389
|
+
def.schema.description = void 0;
|
|
390
|
+
}
|
|
391
|
+
parameters.push(def);
|
|
392
|
+
}
|
|
393
|
+
return parameters;
|
|
394
|
+
}
|
|
395
|
+
function mergeComponentsObjects(...components) {
|
|
396
|
+
return components.reduce(
|
|
397
|
+
(prev, component, index) => {
|
|
398
|
+
if (!component || index === 0) return prev;
|
|
399
|
+
if (prev.schemas && Object.keys(prev.schemas).length > 0 || component.schemas && Object.keys(component.schemas).length > 0) {
|
|
400
|
+
prev.schemas = {
|
|
401
|
+
...prev.schemas,
|
|
402
|
+
...component.schemas
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
if (prev.parameters && Object.keys(prev.parameters).length > 0 || component.parameters && Object.keys(component.parameters).length > 0) {
|
|
406
|
+
prev.parameters = {
|
|
407
|
+
...prev.parameters,
|
|
408
|
+
...component.parameters
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
return prev;
|
|
412
|
+
},
|
|
413
|
+
components[0] ?? {}
|
|
414
|
+
);
|
|
415
|
+
}
|
|
381
416
|
|
|
382
|
-
function resolver(schema) {
|
|
417
|
+
function resolver(schema, options) {
|
|
383
418
|
return {
|
|
384
419
|
vendor: schema["~standard"].vendor,
|
|
385
420
|
validate: schema["~standard"].validate,
|
|
386
|
-
toJSONSchema: (
|
|
387
|
-
toOpenAPISchema: (
|
|
421
|
+
toJSONSchema: () => standardJson.toJsonSchema(schema, options),
|
|
422
|
+
toOpenAPISchema: () => standardOpenapi.toOpenAPISchema(schema, options)
|
|
388
423
|
};
|
|
389
424
|
}
|
|
390
425
|
function validator(target, schema, hook, options) {
|
|
@@ -392,7 +427,7 @@ function validator(target, schema, hook, options) {
|
|
|
392
427
|
return Object.assign(middleware, {
|
|
393
428
|
[uniqueSymbol]: {
|
|
394
429
|
target,
|
|
395
|
-
...resolver(schema),
|
|
430
|
+
...resolver(schema, options),
|
|
396
431
|
options
|
|
397
432
|
}
|
|
398
433
|
});
|
|
@@ -407,16 +442,17 @@ function describeRoute(spec) {
|
|
|
407
442
|
}
|
|
408
443
|
});
|
|
409
444
|
}
|
|
410
|
-
function describeResponse(handler, responses) {
|
|
445
|
+
function describeResponse(handler, responses, options) {
|
|
411
446
|
const _responses = Object.entries(responses).reduce(
|
|
412
447
|
(acc, [statusCode, response]) => {
|
|
413
448
|
if (response.content) {
|
|
414
449
|
const content = Object.entries(response.content).reduce(
|
|
415
450
|
(contentAcc, [mediaType, media]) => {
|
|
416
451
|
if (media.vSchema) {
|
|
452
|
+
const { vSchema, ...rest } = media;
|
|
417
453
|
contentAcc[mediaType] = {
|
|
418
|
-
...
|
|
419
|
-
schema: resolver(
|
|
454
|
+
...rest,
|
|
455
|
+
schema: resolver(vSchema, options)
|
|
420
456
|
};
|
|
421
457
|
} else {
|
|
422
458
|
contentAcc[mediaType] = media;
|
package/dist/index.d.cts
CHANGED
|
@@ -68,11 +68,11 @@ declare namespace StandardSchemaV1 {
|
|
|
68
68
|
* @param schema Validation schema
|
|
69
69
|
* @returns Resolver result
|
|
70
70
|
*/
|
|
71
|
-
declare function resolver<Schema extends StandardSchemaV1>(schema: Schema): {
|
|
71
|
+
declare function resolver<Schema extends StandardSchemaV1>(schema: Schema, options?: Record<string, unknown>): {
|
|
72
72
|
vendor: string;
|
|
73
73
|
validate: (value: unknown) => StandardSchemaV1.Result<unknown> | Promise<StandardSchemaV1.Result<unknown>>;
|
|
74
|
-
toJSONSchema: (
|
|
75
|
-
toOpenAPISchema: (
|
|
74
|
+
toJSONSchema: () => Promise<json_schema.JSONSchema7>;
|
|
75
|
+
toOpenAPISchema: () => Promise<{
|
|
76
76
|
schema: OpenAPIV3_1.SchemaObject;
|
|
77
77
|
components: OpenAPIV3_1.ComponentsObject | undefined;
|
|
78
78
|
}>;
|
|
@@ -98,7 +98,7 @@ declare function validator<Schema extends StandardSchemaV1, Target extends keyof
|
|
|
98
98
|
out: {
|
|
99
99
|
[K in Target]: Out;
|
|
100
100
|
};
|
|
101
|
-
}, V extends I = I>(target: Target, schema: Schema, hook?: Hook<StandardSchemaV1.InferOutput<Schema>, E, P, Target>, options?:
|
|
101
|
+
}, V extends I = I>(target: Target, schema: Schema, hook?: Hook<StandardSchemaV1.InferOutput<Schema>, E, P, Target>, options?: ResolverReturnType["options"]): MiddlewareHandler<E, P, V>;
|
|
102
102
|
/**
|
|
103
103
|
* Describe a route with OpenAPI specs.
|
|
104
104
|
* @param spec Options for describing a route
|
|
@@ -119,7 +119,7 @@ type HandlerResponse<T extends Partial<Record<StatusCode, StandardSchemaV1>> = P
|
|
|
119
119
|
[K in keyof T]: T[K] extends StandardSchemaV1 ? PromiseOr<TypedResponse<StandardSchemaV1.InferOutput<T[K]>, Num<K> extends StatusCode ? Num<K> : never>> : never;
|
|
120
120
|
}[keyof T];
|
|
121
121
|
type Handler<E extends Env = any, P extends string = any, I extends Input = BlankInput, T extends Partial<Record<StatusCode, StandardSchemaV1>> = Partial<Record<StatusCode, StandardSchemaV1>>> = (c: Context<E, P, I>, next: Next) => HandlerResponse<T>;
|
|
122
|
-
declare function describeResponse<E extends Env = any, P extends string = any, I extends Input = BlankInput, T extends Partial<Record<StatusCode, StandardSchemaV1>> = Partial<Record<StatusCode, StandardSchemaV1>>>(handler: Handler<E, P, I, T>, responses: ResponseObject<T>): Handler<E, P, I, T>;
|
|
122
|
+
declare function describeResponse<E extends Env = any, P extends string = any, I extends Input = BlankInput, T extends Partial<Record<StatusCode, StandardSchemaV1>> = Partial<Record<StatusCode, StandardSchemaV1>>>(handler: Handler<E, P, I, T>, responses: ResponseObject<T>, options?: Record<string, unknown>): Handler<E, P, I, T>;
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
125
|
* The unique symbol for the middlewares, which makes it easier to identify them. Not meant to be used directly, unless you're creating a custom middleware.
|
|
@@ -128,15 +128,21 @@ declare const uniqueSymbol: unique symbol;
|
|
|
128
128
|
declare const ALLOWED_METHODS: readonly ["GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"];
|
|
129
129
|
type AllowedMethods = (typeof ALLOWED_METHODS)[number];
|
|
130
130
|
declare function registerSchemaPath({ route, specs, paths, }: RegisterSchemaPathOptions): void;
|
|
131
|
-
declare function removeExcludedPaths(paths: OpenAPIV3_1.PathsObject, ctx: {
|
|
132
|
-
options: SanitizedGenerateSpecOptions;
|
|
133
|
-
}): OpenAPIV3_1.PathsObject<{}, {}>;
|
|
131
|
+
declare function removeExcludedPaths(paths: OpenAPIV3_1.PathsObject, ctx: SpecContext): OpenAPIV3_1.PathsObject<{}, {}>;
|
|
134
132
|
|
|
135
133
|
type PromiseOr<T> = T | Promise<T>;
|
|
136
|
-
type ResolverReturnType = ReturnType<typeof resolver
|
|
134
|
+
type ResolverReturnType = ReturnType<typeof resolver> & {
|
|
135
|
+
options?: {
|
|
136
|
+
/**
|
|
137
|
+
* Override the media type of the request body, if not specified, it will be `application/json` for `json` target and `multipart/form-data` for `form` target.
|
|
138
|
+
*/
|
|
139
|
+
media?: string;
|
|
140
|
+
} & {
|
|
141
|
+
[key: string]: unknown;
|
|
142
|
+
};
|
|
143
|
+
};
|
|
137
144
|
type HandlerUniqueProperty = (ResolverReturnType & {
|
|
138
145
|
target: keyof ValidationTargets$1;
|
|
139
|
-
options?: Record<string, unknown>;
|
|
140
146
|
}) | {
|
|
141
147
|
spec: DescribeRouteOptions;
|
|
142
148
|
};
|
|
@@ -178,13 +184,15 @@ type GenerateSpecOptions = {
|
|
|
178
184
|
*/
|
|
179
185
|
defaultOptions: Partial<Record<AllowedMethods | "ALL", DescribeRouteOptions>>;
|
|
180
186
|
};
|
|
181
|
-
type HaveDefaultValues = "documentation" | "excludeStaticFile" | "exclude" | "excludeMethods" | "excludeTags";
|
|
182
|
-
type SanitizedGenerateSpecOptions = Pick<GenerateSpecOptions, HaveDefaultValues> & Omit<Partial<GenerateSpecOptions>, HaveDefaultValues>;
|
|
183
187
|
type DescribeRouteOptions = Omit<OpenAPIV3_1.OperationObject, "responses" | "parameters"> & {
|
|
184
188
|
/**
|
|
185
189
|
* Pass `true` to hide route from OpenAPI/swagger document
|
|
186
190
|
*/
|
|
187
|
-
hide?: boolean | ((
|
|
191
|
+
hide?: boolean | ((props: {
|
|
192
|
+
c?: Context;
|
|
193
|
+
method: string;
|
|
194
|
+
path: string;
|
|
195
|
+
}) => boolean);
|
|
188
196
|
/**
|
|
189
197
|
* Responses of the request
|
|
190
198
|
*/
|
|
@@ -200,9 +208,17 @@ type DescribeRouteOptions = Omit<OpenAPIV3_1.OperationObject, "responses" | "par
|
|
|
200
208
|
};
|
|
201
209
|
type RegisterSchemaPathOptions = {
|
|
202
210
|
route: RouterRoute;
|
|
203
|
-
specs?: DescribeRouteOptions
|
|
211
|
+
specs?: DescribeRouteOptions & {
|
|
212
|
+
operationId?: string | ((route: RouterRoute) => string);
|
|
213
|
+
};
|
|
204
214
|
paths: Partial<OpenAPIV3_1.PathsObject>;
|
|
205
215
|
};
|
|
216
|
+
type HaveDefaultValues = "documentation" | "excludeStaticFile" | "exclude" | "excludeMethods" | "excludeTags";
|
|
217
|
+
type SanitizedGenerateSpecOptions = Pick<GenerateSpecOptions, HaveDefaultValues> & Omit<Partial<GenerateSpecOptions>, HaveDefaultValues>;
|
|
218
|
+
type SpecContext = {
|
|
219
|
+
components: OpenAPIV3_1.ComponentsObject;
|
|
220
|
+
options: SanitizedGenerateSpecOptions;
|
|
221
|
+
};
|
|
206
222
|
|
|
207
223
|
/**
|
|
208
224
|
* Route handler for OpenAPI specs
|
|
@@ -245,20 +261,7 @@ declare function generateSpecs<E extends Env = BlankEnv, P extends string = stri
|
|
|
245
261
|
trace?: OpenAPIV3_1.OperationObject<{}>;
|
|
246
262
|
};
|
|
247
263
|
};
|
|
248
|
-
components:
|
|
249
|
-
schemas: {
|
|
250
|
-
[x: string]: OpenAPIV3_1.SchemaObject;
|
|
251
|
-
};
|
|
252
|
-
responses?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.ResponseObject>;
|
|
253
|
-
parameters?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.ParameterObject>;
|
|
254
|
-
examples?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.ExampleObject>;
|
|
255
|
-
requestBodies?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.RequestBodyObject>;
|
|
256
|
-
headers?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.HeaderObject>;
|
|
257
|
-
securitySchemes?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.SecuritySchemeObject>;
|
|
258
|
-
links?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.LinkObject>;
|
|
259
|
-
callbacks?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.CallbackObject>;
|
|
260
|
-
pathItems?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.PathItemObject>;
|
|
261
|
-
};
|
|
264
|
+
components: OpenAPIV3_1.ComponentsObject;
|
|
262
265
|
openapi: string;
|
|
263
266
|
externalDocs?: openapi_types.OpenAPIV3.ExternalDocumentationObject;
|
|
264
267
|
security?: openapi_types.OpenAPIV3.SecurityRequirementObject[];
|
|
@@ -267,4 +270,4 @@ declare function generateSpecs<E extends Env = BlankEnv, P extends string = stri
|
|
|
267
270
|
jsonSchemaDialect?: string;
|
|
268
271
|
}>;
|
|
269
272
|
|
|
270
|
-
export { ALLOWED_METHODS, type AllowedMethods, type DescribeRouteOptions, type GenerateSpecOptions, type HandlerUniqueProperty, type PromiseOr, type RegisterSchemaPathOptions, type ResolverReturnType, type
|
|
273
|
+
export { ALLOWED_METHODS, type AllowedMethods, type DescribeRouteOptions, type GenerateSpecOptions, type HandlerUniqueProperty, type PromiseOr, type RegisterSchemaPathOptions, type ResolverReturnType, type SpecContext, describeResponse, describeRoute, generateSpecs, openAPIRouteHandler, registerSchemaPath, removeExcludedPaths, resolver, uniqueSymbol, validator };
|
package/dist/index.d.ts
CHANGED
|
@@ -68,11 +68,11 @@ declare namespace StandardSchemaV1 {
|
|
|
68
68
|
* @param schema Validation schema
|
|
69
69
|
* @returns Resolver result
|
|
70
70
|
*/
|
|
71
|
-
declare function resolver<Schema extends StandardSchemaV1>(schema: Schema): {
|
|
71
|
+
declare function resolver<Schema extends StandardSchemaV1>(schema: Schema, options?: Record<string, unknown>): {
|
|
72
72
|
vendor: string;
|
|
73
73
|
validate: (value: unknown) => StandardSchemaV1.Result<unknown> | Promise<StandardSchemaV1.Result<unknown>>;
|
|
74
|
-
toJSONSchema: (
|
|
75
|
-
toOpenAPISchema: (
|
|
74
|
+
toJSONSchema: () => Promise<json_schema.JSONSchema7>;
|
|
75
|
+
toOpenAPISchema: () => Promise<{
|
|
76
76
|
schema: OpenAPIV3_1.SchemaObject;
|
|
77
77
|
components: OpenAPIV3_1.ComponentsObject | undefined;
|
|
78
78
|
}>;
|
|
@@ -98,7 +98,7 @@ declare function validator<Schema extends StandardSchemaV1, Target extends keyof
|
|
|
98
98
|
out: {
|
|
99
99
|
[K in Target]: Out;
|
|
100
100
|
};
|
|
101
|
-
}, V extends I = I>(target: Target, schema: Schema, hook?: Hook<StandardSchemaV1.InferOutput<Schema>, E, P, Target>, options?:
|
|
101
|
+
}, V extends I = I>(target: Target, schema: Schema, hook?: Hook<StandardSchemaV1.InferOutput<Schema>, E, P, Target>, options?: ResolverReturnType["options"]): MiddlewareHandler<E, P, V>;
|
|
102
102
|
/**
|
|
103
103
|
* Describe a route with OpenAPI specs.
|
|
104
104
|
* @param spec Options for describing a route
|
|
@@ -119,7 +119,7 @@ type HandlerResponse<T extends Partial<Record<StatusCode, StandardSchemaV1>> = P
|
|
|
119
119
|
[K in keyof T]: T[K] extends StandardSchemaV1 ? PromiseOr<TypedResponse<StandardSchemaV1.InferOutput<T[K]>, Num<K> extends StatusCode ? Num<K> : never>> : never;
|
|
120
120
|
}[keyof T];
|
|
121
121
|
type Handler<E extends Env = any, P extends string = any, I extends Input = BlankInput, T extends Partial<Record<StatusCode, StandardSchemaV1>> = Partial<Record<StatusCode, StandardSchemaV1>>> = (c: Context<E, P, I>, next: Next) => HandlerResponse<T>;
|
|
122
|
-
declare function describeResponse<E extends Env = any, P extends string = any, I extends Input = BlankInput, T extends Partial<Record<StatusCode, StandardSchemaV1>> = Partial<Record<StatusCode, StandardSchemaV1>>>(handler: Handler<E, P, I, T>, responses: ResponseObject<T>): Handler<E, P, I, T>;
|
|
122
|
+
declare function describeResponse<E extends Env = any, P extends string = any, I extends Input = BlankInput, T extends Partial<Record<StatusCode, StandardSchemaV1>> = Partial<Record<StatusCode, StandardSchemaV1>>>(handler: Handler<E, P, I, T>, responses: ResponseObject<T>, options?: Record<string, unknown>): Handler<E, P, I, T>;
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
125
|
* The unique symbol for the middlewares, which makes it easier to identify them. Not meant to be used directly, unless you're creating a custom middleware.
|
|
@@ -128,15 +128,21 @@ declare const uniqueSymbol: unique symbol;
|
|
|
128
128
|
declare const ALLOWED_METHODS: readonly ["GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"];
|
|
129
129
|
type AllowedMethods = (typeof ALLOWED_METHODS)[number];
|
|
130
130
|
declare function registerSchemaPath({ route, specs, paths, }: RegisterSchemaPathOptions): void;
|
|
131
|
-
declare function removeExcludedPaths(paths: OpenAPIV3_1.PathsObject, ctx: {
|
|
132
|
-
options: SanitizedGenerateSpecOptions;
|
|
133
|
-
}): OpenAPIV3_1.PathsObject<{}, {}>;
|
|
131
|
+
declare function removeExcludedPaths(paths: OpenAPIV3_1.PathsObject, ctx: SpecContext): OpenAPIV3_1.PathsObject<{}, {}>;
|
|
134
132
|
|
|
135
133
|
type PromiseOr<T> = T | Promise<T>;
|
|
136
|
-
type ResolverReturnType = ReturnType<typeof resolver
|
|
134
|
+
type ResolverReturnType = ReturnType<typeof resolver> & {
|
|
135
|
+
options?: {
|
|
136
|
+
/**
|
|
137
|
+
* Override the media type of the request body, if not specified, it will be `application/json` for `json` target and `multipart/form-data` for `form` target.
|
|
138
|
+
*/
|
|
139
|
+
media?: string;
|
|
140
|
+
} & {
|
|
141
|
+
[key: string]: unknown;
|
|
142
|
+
};
|
|
143
|
+
};
|
|
137
144
|
type HandlerUniqueProperty = (ResolverReturnType & {
|
|
138
145
|
target: keyof ValidationTargets$1;
|
|
139
|
-
options?: Record<string, unknown>;
|
|
140
146
|
}) | {
|
|
141
147
|
spec: DescribeRouteOptions;
|
|
142
148
|
};
|
|
@@ -178,13 +184,15 @@ type GenerateSpecOptions = {
|
|
|
178
184
|
*/
|
|
179
185
|
defaultOptions: Partial<Record<AllowedMethods | "ALL", DescribeRouteOptions>>;
|
|
180
186
|
};
|
|
181
|
-
type HaveDefaultValues = "documentation" | "excludeStaticFile" | "exclude" | "excludeMethods" | "excludeTags";
|
|
182
|
-
type SanitizedGenerateSpecOptions = Pick<GenerateSpecOptions, HaveDefaultValues> & Omit<Partial<GenerateSpecOptions>, HaveDefaultValues>;
|
|
183
187
|
type DescribeRouteOptions = Omit<OpenAPIV3_1.OperationObject, "responses" | "parameters"> & {
|
|
184
188
|
/**
|
|
185
189
|
* Pass `true` to hide route from OpenAPI/swagger document
|
|
186
190
|
*/
|
|
187
|
-
hide?: boolean | ((
|
|
191
|
+
hide?: boolean | ((props: {
|
|
192
|
+
c?: Context;
|
|
193
|
+
method: string;
|
|
194
|
+
path: string;
|
|
195
|
+
}) => boolean);
|
|
188
196
|
/**
|
|
189
197
|
* Responses of the request
|
|
190
198
|
*/
|
|
@@ -200,9 +208,17 @@ type DescribeRouteOptions = Omit<OpenAPIV3_1.OperationObject, "responses" | "par
|
|
|
200
208
|
};
|
|
201
209
|
type RegisterSchemaPathOptions = {
|
|
202
210
|
route: RouterRoute;
|
|
203
|
-
specs?: DescribeRouteOptions
|
|
211
|
+
specs?: DescribeRouteOptions & {
|
|
212
|
+
operationId?: string | ((route: RouterRoute) => string);
|
|
213
|
+
};
|
|
204
214
|
paths: Partial<OpenAPIV3_1.PathsObject>;
|
|
205
215
|
};
|
|
216
|
+
type HaveDefaultValues = "documentation" | "excludeStaticFile" | "exclude" | "excludeMethods" | "excludeTags";
|
|
217
|
+
type SanitizedGenerateSpecOptions = Pick<GenerateSpecOptions, HaveDefaultValues> & Omit<Partial<GenerateSpecOptions>, HaveDefaultValues>;
|
|
218
|
+
type SpecContext = {
|
|
219
|
+
components: OpenAPIV3_1.ComponentsObject;
|
|
220
|
+
options: SanitizedGenerateSpecOptions;
|
|
221
|
+
};
|
|
206
222
|
|
|
207
223
|
/**
|
|
208
224
|
* Route handler for OpenAPI specs
|
|
@@ -245,20 +261,7 @@ declare function generateSpecs<E extends Env = BlankEnv, P extends string = stri
|
|
|
245
261
|
trace?: OpenAPIV3_1.OperationObject<{}>;
|
|
246
262
|
};
|
|
247
263
|
};
|
|
248
|
-
components:
|
|
249
|
-
schemas: {
|
|
250
|
-
[x: string]: OpenAPIV3_1.SchemaObject;
|
|
251
|
-
};
|
|
252
|
-
responses?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.ResponseObject>;
|
|
253
|
-
parameters?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.ParameterObject>;
|
|
254
|
-
examples?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.ExampleObject>;
|
|
255
|
-
requestBodies?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.RequestBodyObject>;
|
|
256
|
-
headers?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.HeaderObject>;
|
|
257
|
-
securitySchemes?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.SecuritySchemeObject>;
|
|
258
|
-
links?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.LinkObject>;
|
|
259
|
-
callbacks?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.CallbackObject>;
|
|
260
|
-
pathItems?: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.PathItemObject>;
|
|
261
|
-
};
|
|
264
|
+
components: OpenAPIV3_1.ComponentsObject;
|
|
262
265
|
openapi: string;
|
|
263
266
|
externalDocs?: openapi_types.OpenAPIV3.ExternalDocumentationObject;
|
|
264
267
|
security?: openapi_types.OpenAPIV3.SecurityRequirementObject[];
|
|
@@ -267,4 +270,4 @@ declare function generateSpecs<E extends Env = BlankEnv, P extends string = stri
|
|
|
267
270
|
jsonSchemaDialect?: string;
|
|
268
271
|
}>;
|
|
269
272
|
|
|
270
|
-
export { ALLOWED_METHODS, type AllowedMethods, type DescribeRouteOptions, type GenerateSpecOptions, type HandlerUniqueProperty, type PromiseOr, type RegisterSchemaPathOptions, type ResolverReturnType, type
|
|
273
|
+
export { ALLOWED_METHODS, type AllowedMethods, type DescribeRouteOptions, type GenerateSpecOptions, type HandlerUniqueProperty, type PromiseOr, type RegisterSchemaPathOptions, type ResolverReturnType, type SpecContext, describeResponse, describeRoute, generateSpecs, openAPIRouteHandler, registerSchemaPath, removeExcludedPaths, resolver, uniqueSymbol, validator };
|
package/dist/index.js
CHANGED
|
@@ -29,12 +29,7 @@ const toOpenAPIPath = (path) => path.split("/").map((x) => {
|
|
|
29
29
|
return tmp;
|
|
30
30
|
}).join("/");
|
|
31
31
|
const capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
|
|
32
|
-
const generateOperationIdCache = /* @__PURE__ */ new Map();
|
|
33
32
|
const generateOperationId = (route) => {
|
|
34
|
-
const operationIdKey = `${route.method}:${route.path}`;
|
|
35
|
-
if (generateOperationIdCache.has(operationIdKey)) {
|
|
36
|
-
return generateOperationIdCache.get(operationIdKey);
|
|
37
|
-
}
|
|
38
33
|
let operationId = route.method;
|
|
39
34
|
if (route.path === "/") return `${operationId}Index`;
|
|
40
35
|
for (const segment of route.path.split("/")) {
|
|
@@ -44,61 +39,61 @@ const generateOperationId = (route) => {
|
|
|
44
39
|
operationId += capitalize(segment);
|
|
45
40
|
}
|
|
46
41
|
}
|
|
47
|
-
generateOperationIdCache.set(operationIdKey, operationId);
|
|
48
42
|
return operationId;
|
|
49
43
|
};
|
|
50
44
|
const paramKey = (param) => "$ref" in param ? param.$ref : `${param.in} ${param.name}`;
|
|
51
45
|
function mergeParameters(...params) {
|
|
52
|
-
const
|
|
53
|
-
const merged = _params.reduce((acc, param) => {
|
|
46
|
+
const merged = params.flatMap((x) => x ?? []).reduce((acc, param) => {
|
|
54
47
|
acc.set(paramKey(param), param);
|
|
55
48
|
return acc;
|
|
56
49
|
}, /* @__PURE__ */ new Map());
|
|
57
50
|
return Array.from(merged.values());
|
|
58
51
|
}
|
|
59
|
-
function getProperty(obj, key, defaultValue) {
|
|
60
|
-
if (obj != null && key in obj) {
|
|
61
|
-
return obj[key];
|
|
62
|
-
}
|
|
63
|
-
return defaultValue;
|
|
64
|
-
}
|
|
65
52
|
const specsByPathContext = /* @__PURE__ */ new Map();
|
|
66
53
|
function getPathContext(path) {
|
|
67
|
-
const keys = Array.from(specsByPathContext.keys());
|
|
68
54
|
const context = [];
|
|
69
|
-
for (const key of
|
|
70
|
-
if (path.match(key)) {
|
|
71
|
-
const data = specsByPathContext.get(key);
|
|
72
|
-
if (!data) continue;
|
|
55
|
+
for (const [key, data] of specsByPathContext) {
|
|
56
|
+
if (data && path.match(key)) {
|
|
73
57
|
context.push(data);
|
|
74
58
|
}
|
|
75
59
|
}
|
|
76
60
|
return context;
|
|
77
61
|
}
|
|
78
|
-
function mergeSpecs(...specs) {
|
|
62
|
+
function mergeSpecs(route, ...specs) {
|
|
79
63
|
return specs.reduce(
|
|
80
64
|
(prev, spec) => {
|
|
81
|
-
if (!spec) return prev;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
65
|
+
if (!spec || !prev) return prev;
|
|
66
|
+
for (const [key, value] of Object.entries(spec)) {
|
|
67
|
+
if (value == null) continue;
|
|
68
|
+
if (key in prev && (typeof value === "object" || typeof value === "function" && key === "operationId")) {
|
|
69
|
+
if (Array.isArray(value)) {
|
|
70
|
+
const values = [...prev[key] ?? [], ...value];
|
|
71
|
+
if (key === "tags") {
|
|
72
|
+
prev[key] = Array.from(new Set(values));
|
|
73
|
+
} else {
|
|
74
|
+
prev[key] = values;
|
|
75
|
+
}
|
|
76
|
+
} else if (typeof value === "function") {
|
|
77
|
+
prev[key] = value(route);
|
|
78
|
+
} else {
|
|
79
|
+
if (key === "parameters") {
|
|
80
|
+
prev[key] = mergeParameters(prev[key], value);
|
|
81
|
+
} else {
|
|
82
|
+
prev[key] = {
|
|
83
|
+
...prev[key],
|
|
84
|
+
...value
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
prev[key] = value;
|
|
98
90
|
}
|
|
99
|
-
}
|
|
91
|
+
}
|
|
92
|
+
return prev;
|
|
100
93
|
},
|
|
101
|
-
{
|
|
94
|
+
{
|
|
95
|
+
operationId: generateOperationId(route)
|
|
96
|
+
}
|
|
102
97
|
);
|
|
103
98
|
}
|
|
104
99
|
function registerSchemaPath({
|
|
@@ -112,19 +107,23 @@ function registerSchemaPath({
|
|
|
112
107
|
if (!specs) return;
|
|
113
108
|
if (specsByPathContext.has(path)) {
|
|
114
109
|
const prev = specsByPathContext.get(path) ?? {};
|
|
115
|
-
specsByPathContext.set(path, mergeSpecs(prev, specs));
|
|
110
|
+
specsByPathContext.set(path, mergeSpecs(route, prev, specs));
|
|
116
111
|
} else {
|
|
117
112
|
specsByPathContext.set(path, specs);
|
|
118
113
|
}
|
|
119
114
|
} else {
|
|
120
115
|
const pathContext = getPathContext(path);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
116
|
+
if (!(path in paths)) {
|
|
117
|
+
paths[path] = {};
|
|
118
|
+
}
|
|
119
|
+
if (paths[path]) {
|
|
120
|
+
paths[path][method] = mergeSpecs(
|
|
121
|
+
route,
|
|
122
|
+
...pathContext,
|
|
123
|
+
paths[path]?.[method],
|
|
124
|
+
specs
|
|
125
|
+
);
|
|
126
|
+
}
|
|
128
127
|
}
|
|
129
128
|
}
|
|
130
129
|
function removeExcludedPaths(paths, ctx) {
|
|
@@ -150,10 +149,21 @@ function removeExcludedPaths(paths, ctx) {
|
|
|
150
149
|
for (const param of pathParameters) {
|
|
151
150
|
const paramName = param.slice(1, param.length - 1);
|
|
152
151
|
const index = schema.parameters.findIndex(
|
|
153
|
-
(x) =>
|
|
152
|
+
(x) => {
|
|
153
|
+
if ("$ref" in x) {
|
|
154
|
+
const pos = x.$ref.split("/").pop();
|
|
155
|
+
if (pos) {
|
|
156
|
+
const param2 = ctx.components.parameters?.[pos];
|
|
157
|
+
if (param2 && !("$ref" in param2)) {
|
|
158
|
+
return param2.in === "path" && param2.name === paramName;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
return x.in === "path" && x.name === paramName;
|
|
164
|
+
}
|
|
154
165
|
);
|
|
155
|
-
if (index
|
|
156
|
-
else {
|
|
166
|
+
if (index === -1) {
|
|
157
167
|
schema.parameters.push({
|
|
158
168
|
schema: { type: "string" },
|
|
159
169
|
in: "path",
|
|
@@ -200,20 +210,24 @@ async function generateSpecs(hono, options = DEFAULT_OPTIONS, c) {
|
|
|
200
210
|
}
|
|
201
211
|
};
|
|
202
212
|
const _documentation = ctx.options.documentation ?? {};
|
|
203
|
-
const
|
|
204
|
-
for (const path in
|
|
205
|
-
for (const method in
|
|
213
|
+
const paths = await generatePaths(hono, ctx);
|
|
214
|
+
for (const path in paths) {
|
|
215
|
+
for (const method in paths[path]) {
|
|
206
216
|
const isHidden = getHiddenValue({
|
|
207
|
-
valueOrFunc:
|
|
217
|
+
valueOrFunc: paths[path][method]?.hide,
|
|
208
218
|
method,
|
|
209
219
|
path,
|
|
210
220
|
c
|
|
211
221
|
});
|
|
212
222
|
if (isHidden) {
|
|
213
|
-
|
|
223
|
+
paths[path][method] = void 0;
|
|
214
224
|
}
|
|
215
225
|
}
|
|
216
226
|
}
|
|
227
|
+
const components = mergeComponentsObjects(
|
|
228
|
+
_documentation.components,
|
|
229
|
+
ctx.components
|
|
230
|
+
);
|
|
217
231
|
return {
|
|
218
232
|
openapi: "3.1.0",
|
|
219
233
|
..._documentation,
|
|
@@ -227,22 +241,17 @@ async function generateSpecs(hono, options = DEFAULT_OPTIONS, c) {
|
|
|
227
241
|
..._documentation.info
|
|
228
242
|
},
|
|
229
243
|
paths: {
|
|
230
|
-
...removeExcludedPaths(
|
|
244
|
+
...removeExcludedPaths(paths, ctx),
|
|
231
245
|
..._documentation.paths
|
|
232
246
|
},
|
|
233
|
-
components
|
|
234
|
-
..._documentation.components,
|
|
235
|
-
schemas: {
|
|
236
|
-
...ctx.components.schemas,
|
|
237
|
-
..._documentation.components?.schemas
|
|
238
|
-
}
|
|
239
|
-
}
|
|
247
|
+
components
|
|
240
248
|
};
|
|
241
249
|
}
|
|
242
250
|
async function generatePaths(hono, ctx) {
|
|
243
251
|
const paths = {};
|
|
244
252
|
for (const route of hono.routes) {
|
|
245
|
-
|
|
253
|
+
const middlewareHandler = route.handler[uniqueSymbol];
|
|
254
|
+
if (!middlewareHandler) {
|
|
246
255
|
if (ctx.options.includeEmptyPaths) {
|
|
247
256
|
registerSchemaPath({
|
|
248
257
|
route,
|
|
@@ -260,20 +269,12 @@ async function generatePaths(hono, ctx) {
|
|
|
260
269
|
continue;
|
|
261
270
|
}
|
|
262
271
|
}
|
|
263
|
-
const middlewareHandler = route.handler[uniqueSymbol];
|
|
264
272
|
const defaultOptionsForThisMethod = ctx.options.defaultOptions?.[routeMethod];
|
|
265
273
|
const { schema: routeSpecs, components = {} } = await getSpec(
|
|
266
274
|
middlewareHandler,
|
|
267
275
|
defaultOptionsForThisMethod
|
|
268
276
|
);
|
|
269
|
-
ctx.components =
|
|
270
|
-
...ctx.components,
|
|
271
|
-
...components,
|
|
272
|
-
schemas: {
|
|
273
|
-
...ctx.components.schemas,
|
|
274
|
-
...components.schemas
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
+
ctx.components = mergeComponentsObjects(ctx.components, components);
|
|
277
278
|
registerSchemaPath({
|
|
278
279
|
route,
|
|
279
280
|
specs: routeSpecs,
|
|
@@ -287,14 +288,9 @@ function getHiddenValue(options) {
|
|
|
287
288
|
if (valueOrFunc != null) {
|
|
288
289
|
if (typeof valueOrFunc === "boolean") {
|
|
289
290
|
return valueOrFunc;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
} else {
|
|
294
|
-
console.warn(
|
|
295
|
-
`'c' is not defined, cannot evaluate hide function for ${method} ${path}`
|
|
296
|
-
);
|
|
297
|
-
}
|
|
291
|
+
}
|
|
292
|
+
if (typeof valueOrFunc === "function") {
|
|
293
|
+
return valueOrFunc({ c, method, path });
|
|
298
294
|
}
|
|
299
295
|
}
|
|
300
296
|
return false;
|
|
@@ -318,7 +314,7 @@ async function getSpec(middlewareHandler, defaultOptions) {
|
|
|
318
314
|
const raw = response.content?.[contentKey];
|
|
319
315
|
if (!raw) continue;
|
|
320
316
|
if (raw.schema && "toOpenAPISchema" in raw.schema) {
|
|
321
|
-
const result2 = await raw.schema.toOpenAPISchema(
|
|
317
|
+
const result2 = await raw.schema.toOpenAPISchema();
|
|
322
318
|
raw.schema = result2.schema;
|
|
323
319
|
if (result2.components) {
|
|
324
320
|
components = {
|
|
@@ -335,7 +331,7 @@ async function getSpec(middlewareHandler, defaultOptions) {
|
|
|
335
331
|
const result = await middlewareHandler.toOpenAPISchema();
|
|
336
332
|
const docs = {};
|
|
337
333
|
if (middlewareHandler.target === "form" || middlewareHandler.target === "json") {
|
|
338
|
-
const media = middlewareHandler.target === "json" ? "application/json" : "multipart/form-data";
|
|
334
|
+
const media = middlewareHandler.options?.media ?? middlewareHandler.target === "json" ? "application/json" : "multipart/form-data";
|
|
339
335
|
if (!docs.requestBody || !("content" in docs.requestBody) || !docs.requestBody.content) {
|
|
340
336
|
docs.requestBody = {
|
|
341
337
|
content: {
|
|
@@ -350,39 +346,78 @@ async function getSpec(middlewareHandler, defaultOptions) {
|
|
|
350
346
|
};
|
|
351
347
|
}
|
|
352
348
|
} else {
|
|
353
|
-
|
|
349
|
+
let parameters = [];
|
|
354
350
|
if ("$ref" in result.schema) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
351
|
+
const ref = result.schema.$ref;
|
|
352
|
+
const pos = ref.split("/").pop();
|
|
353
|
+
if (pos && result.components?.schemas?.[pos]) {
|
|
354
|
+
const schema = result.components.schemas[pos];
|
|
355
|
+
const newParameters = generateParameters(
|
|
356
|
+
middlewareHandler.target,
|
|
357
|
+
schema
|
|
358
|
+
)[0];
|
|
359
|
+
if (!result.components.parameters) {
|
|
360
|
+
result.components.parameters = {};
|
|
361
|
+
}
|
|
362
|
+
result.components.parameters[pos] = newParameters;
|
|
363
|
+
delete result.components.schemas[pos];
|
|
366
364
|
parameters.push({
|
|
367
|
-
|
|
368
|
-
name: key,
|
|
369
|
-
// @ts-expect-error
|
|
370
|
-
schema: value,
|
|
371
|
-
required: result.schema.required?.includes(key)
|
|
365
|
+
$ref: `#/components/parameters/${pos}`
|
|
372
366
|
});
|
|
373
367
|
}
|
|
368
|
+
} else {
|
|
369
|
+
parameters = generateParameters(middlewareHandler.target, result.schema);
|
|
374
370
|
}
|
|
375
371
|
docs.parameters = parameters;
|
|
376
372
|
}
|
|
377
373
|
return { schema: docs, components: result.components };
|
|
378
374
|
}
|
|
375
|
+
function generateParameters(target, schema) {
|
|
376
|
+
const parameters = [];
|
|
377
|
+
for (const [key, value] of Object.entries(schema.properties ?? {})) {
|
|
378
|
+
const def = {
|
|
379
|
+
in: target === "param" ? "path" : target,
|
|
380
|
+
name: key,
|
|
381
|
+
// @ts-expect-error
|
|
382
|
+
schema: value,
|
|
383
|
+
required: schema.required?.includes(key)
|
|
384
|
+
};
|
|
385
|
+
if (def.schema && "description" in def.schema && def.schema.description) {
|
|
386
|
+
def.description = def.schema.description;
|
|
387
|
+
def.schema.description = void 0;
|
|
388
|
+
}
|
|
389
|
+
parameters.push(def);
|
|
390
|
+
}
|
|
391
|
+
return parameters;
|
|
392
|
+
}
|
|
393
|
+
function mergeComponentsObjects(...components) {
|
|
394
|
+
return components.reduce(
|
|
395
|
+
(prev, component, index) => {
|
|
396
|
+
if (!component || index === 0) return prev;
|
|
397
|
+
if (prev.schemas && Object.keys(prev.schemas).length > 0 || component.schemas && Object.keys(component.schemas).length > 0) {
|
|
398
|
+
prev.schemas = {
|
|
399
|
+
...prev.schemas,
|
|
400
|
+
...component.schemas
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
if (prev.parameters && Object.keys(prev.parameters).length > 0 || component.parameters && Object.keys(component.parameters).length > 0) {
|
|
404
|
+
prev.parameters = {
|
|
405
|
+
...prev.parameters,
|
|
406
|
+
...component.parameters
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
return prev;
|
|
410
|
+
},
|
|
411
|
+
components[0] ?? {}
|
|
412
|
+
);
|
|
413
|
+
}
|
|
379
414
|
|
|
380
|
-
function resolver(schema) {
|
|
415
|
+
function resolver(schema, options) {
|
|
381
416
|
return {
|
|
382
417
|
vendor: schema["~standard"].vendor,
|
|
383
418
|
validate: schema["~standard"].validate,
|
|
384
|
-
toJSONSchema: (
|
|
385
|
-
toOpenAPISchema: (
|
|
419
|
+
toJSONSchema: () => toJsonSchema(schema, options),
|
|
420
|
+
toOpenAPISchema: () => toOpenAPISchema(schema, options)
|
|
386
421
|
};
|
|
387
422
|
}
|
|
388
423
|
function validator(target, schema, hook, options) {
|
|
@@ -390,7 +425,7 @@ function validator(target, schema, hook, options) {
|
|
|
390
425
|
return Object.assign(middleware, {
|
|
391
426
|
[uniqueSymbol]: {
|
|
392
427
|
target,
|
|
393
|
-
...resolver(schema),
|
|
428
|
+
...resolver(schema, options),
|
|
394
429
|
options
|
|
395
430
|
}
|
|
396
431
|
});
|
|
@@ -405,16 +440,17 @@ function describeRoute(spec) {
|
|
|
405
440
|
}
|
|
406
441
|
});
|
|
407
442
|
}
|
|
408
|
-
function describeResponse(handler, responses) {
|
|
443
|
+
function describeResponse(handler, responses, options) {
|
|
409
444
|
const _responses = Object.entries(responses).reduce(
|
|
410
445
|
(acc, [statusCode, response]) => {
|
|
411
446
|
if (response.content) {
|
|
412
447
|
const content = Object.entries(response.content).reduce(
|
|
413
448
|
(contentAcc, [mediaType, media]) => {
|
|
414
449
|
if (media.vSchema) {
|
|
450
|
+
const { vSchema, ...rest } = media;
|
|
415
451
|
contentAcc[mediaType] = {
|
|
416
|
-
...
|
|
417
|
-
schema: resolver(
|
|
452
|
+
...rest,
|
|
453
|
+
schema: resolver(vSchema, options)
|
|
418
454
|
};
|
|
419
455
|
} else {
|
|
420
456
|
contentAcc[mediaType] = media;
|
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
|
+
"version": "1.0.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.js",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"@hono/standard-validator": "^0.1.2",
|
|
46
46
|
"@sinclair/typebox": "^0.34.9",
|
|
47
|
-
"@standard-community/standard-json": "^0.3.0-rc.
|
|
47
|
+
"@standard-community/standard-json": "^0.3.0-rc.4",
|
|
48
48
|
"@standard-community/standard-openapi": "^0.2.0-rc.1",
|
|
49
49
|
"@types/json-schema": "^7.0.15",
|
|
50
50
|
"arktype": "^2.0.0",
|
|
@@ -83,7 +83,8 @@
|
|
|
83
83
|
"@valibot/to-json-schema": "^1.3.0",
|
|
84
84
|
"pkgroll": "^2.13.1",
|
|
85
85
|
"typescript": "^5.8.3",
|
|
86
|
-
"vitest": "^3.2.4"
|
|
86
|
+
"vitest": "^3.2.4",
|
|
87
|
+
"zod-openapi": "4"
|
|
87
88
|
},
|
|
88
89
|
"scripts": {
|
|
89
90
|
"build": "pkgroll --clean-dist",
|