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 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 _params = params.flatMap((x) => x ?? []);
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 keys) {
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
- return {
85
- ...prev,
86
- ...spec,
87
- tags: Array.from(
88
- /* @__PURE__ */ new Set([
89
- ...getProperty(prev, "tags") ?? [],
90
- ...getProperty(spec, "tags") ?? []
91
- ])
92
- ),
93
- parameters: mergeParameters(
94
- getProperty(prev, "parameters"),
95
- getProperty(spec, "parameters")
96
- ),
97
- responses: {
98
- ...getProperty(prev, "responses", {}),
99
- ...getProperty(spec, "responses", {})
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
- paths[path] = {
124
- ...paths[path] ? paths[path] : {},
125
- [method]: {
126
- operationId: generateOperationId(route),
127
- ...mergeSpecs(...pathContext, paths[path]?.[method], specs)
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) => x.in === "param" && x.name === paramName
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 !== -1) schema.parameters[index].in = "path";
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 schema = await generatePaths(hono, ctx);
206
- for (const path in schema) {
207
- for (const method in schema[path]) {
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: schema[path][method]?.hide,
219
+ valueOrFunc: paths[path][method]?.hide,
210
220
  method,
211
221
  path,
212
222
  c
213
223
  });
214
224
  if (isHidden) {
215
- delete schema[path][method];
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(schema, ctx),
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
- if (!(uniqueSymbol in route.handler)) {
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
- } else if (typeof valueOrFunc === "function") {
293
- if (c) {
294
- return valueOrFunc(c);
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(defaultOptions);
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
- const parameters = [];
351
+ let parameters = [];
356
352
  if ("$ref" in result.schema) {
357
- parameters.push({
358
- in: middlewareHandler.target,
359
- // @ts-expect-error
360
- name: result.schema.$ref,
361
- // @ts-expect-error
362
- schema: result.schema
363
- });
364
- } else {
365
- for (const [key, value] of Object.entries(
366
- result.schema.properties ?? {}
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
- in: middlewareHandler.target,
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: (options) => standardJson.toJsonSchema(schema, options),
387
- toOpenAPISchema: (options) => standardOpenapi.toOpenAPISchema(schema, options)
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
- ...media,
419
- schema: resolver(media.vSchema)
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: (options?: Record<string, unknown>) => Promise<json_schema.JSONSchema7>;
75
- toOpenAPISchema: (options?: Record<string, unknown>) => Promise<{
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?: Record<string, unknown>): MiddlewareHandler<E, P, V>;
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 | ((c: Context) => 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 | Pick<OpenAPIV3_1.OperationObject, "parameters" | "requestBody">;
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 SanitizedGenerateSpecOptions, describeResponse, describeRoute, generateSpecs, openAPIRouteHandler, registerSchemaPath, removeExcludedPaths, resolver, uniqueSymbol, validator };
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: (options?: Record<string, unknown>) => Promise<json_schema.JSONSchema7>;
75
- toOpenAPISchema: (options?: Record<string, unknown>) => Promise<{
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?: Record<string, unknown>): MiddlewareHandler<E, P, V>;
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 | ((c: Context) => 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 | Pick<OpenAPIV3_1.OperationObject, "parameters" | "requestBody">;
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 SanitizedGenerateSpecOptions, describeResponse, describeRoute, generateSpecs, openAPIRouteHandler, registerSchemaPath, removeExcludedPaths, resolver, uniqueSymbol, validator };
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 _params = params.flatMap((x) => x ?? []);
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 keys) {
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
- 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", {})
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
- paths[path] = {
122
- ...paths[path] ? paths[path] : {},
123
- [method]: {
124
- operationId: generateOperationId(route),
125
- ...mergeSpecs(...pathContext, paths[path]?.[method], specs)
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) => x.in === "param" && x.name === paramName
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 !== -1) schema.parameters[index].in = "path";
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 schema = await generatePaths(hono, ctx);
204
- for (const path in schema) {
205
- for (const method in schema[path]) {
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: schema[path][method]?.hide,
217
+ valueOrFunc: paths[path][method]?.hide,
208
218
  method,
209
219
  path,
210
220
  c
211
221
  });
212
222
  if (isHidden) {
213
- delete schema[path][method];
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(schema, ctx),
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
- if (!(uniqueSymbol in route.handler)) {
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
- } else if (typeof valueOrFunc === "function") {
291
- if (c) {
292
- return valueOrFunc(c);
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(defaultOptions);
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
- const parameters = [];
349
+ let parameters = [];
354
350
  if ("$ref" in result.schema) {
355
- parameters.push({
356
- in: middlewareHandler.target,
357
- // @ts-expect-error
358
- name: result.schema.$ref,
359
- // @ts-expect-error
360
- schema: result.schema
361
- });
362
- } else {
363
- for (const [key, value] of Object.entries(
364
- result.schema.properties ?? {}
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
- in: middlewareHandler.target,
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: (options) => toJsonSchema(schema, options),
385
- toOpenAPISchema: (options) => toOpenAPISchema(schema, options)
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
- ...media,
417
- schema: resolver(media.vSchema)
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.5.0-rc.3",
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.3",
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",