hono-openapi 1.2.0 → 1.3.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
@@ -252,8 +252,10 @@ async function generateSpecs(hono, options = DEFAULT_OPTIONS, c) {
252
252
  }
253
253
  }
254
254
  }
255
+ const { components: resolvedDocComponents } = _documentation.components?.responses ? await resolveResponseSchemas(_documentation.components.responses) : { components: {} };
255
256
  const components = mergeComponentsObjects(
256
257
  _documentation.components,
258
+ resolvedDocComponents,
257
259
  ctx.components
258
260
  );
259
261
  return {
@@ -325,7 +327,6 @@ function getHiddenValue(options) {
325
327
  }
326
328
  async function getSpec(middlewareHandler, defaultOptions) {
327
329
  if ("spec" in middlewareHandler) {
328
- let components = {};
329
330
  const tmp = {
330
331
  ...defaultOptions,
331
332
  ...middlewareHandler.spec,
@@ -334,25 +335,10 @@ async function getSpec(middlewareHandler, defaultOptions) {
334
335
  ...middlewareHandler.spec.responses
335
336
  }
336
337
  };
338
+ let components = {};
337
339
  if (tmp.responses) {
338
- for (const key of Object.keys(tmp.responses)) {
339
- const response = tmp.responses[key];
340
- if (!response || !("content" in response)) continue;
341
- for (const contentKey of Object.keys(response.content ?? {})) {
342
- const raw = response.content?.[contentKey];
343
- if (!raw) continue;
344
- if (raw.schema && "toOpenAPISchema" in raw.schema) {
345
- const result2 = await raw.schema.toOpenAPISchema();
346
- raw.schema = result2.schema;
347
- if (result2.components) {
348
- components = mergeComponentsObjects(
349
- components,
350
- result2.components
351
- );
352
- }
353
- }
354
- }
355
- }
340
+ const resolved = await resolveResponseSchemas(tmp.responses);
341
+ components = resolved.components;
356
342
  }
357
343
  return { schema: tmp, components };
358
344
  }
@@ -362,6 +348,7 @@ async function getSpec(middlewareHandler, defaultOptions) {
362
348
  const media = middlewareHandler.options?.media ?? middlewareHandler.target === "json" ? "application/json" : "multipart/form-data";
363
349
  if (!docs.requestBody || !("content" in docs.requestBody) || !docs.requestBody.content) {
364
350
  docs.requestBody = {
351
+ required: true,
365
352
  content: {
366
353
  [media]: {
367
354
  schema: result.schema
@@ -421,6 +408,25 @@ function generateParameters(target, schema) {
421
408
  }
422
409
  return parameters;
423
410
  }
411
+ async function resolveResponseSchemas(responses) {
412
+ let components = {};
413
+ for (const key of Object.keys(responses)) {
414
+ const response = responses[key];
415
+ if (!response || !("content" in response)) continue;
416
+ for (const contentKey of Object.keys(response.content ?? {})) {
417
+ const raw = response.content?.[contentKey];
418
+ if (!raw) continue;
419
+ if (raw.schema && "toOpenAPISchema" in raw.schema) {
420
+ const result = await raw.schema.toOpenAPISchema();
421
+ raw.schema = result.schema;
422
+ if (result.components) {
423
+ components = mergeComponentsObjects(components, result.components);
424
+ }
425
+ }
426
+ }
427
+ }
428
+ return { responses, components };
429
+ }
424
430
  function mergeComponentsObjects(...components) {
425
431
  return components.reduce(
426
432
  (prev, component, index) => {
@@ -451,12 +457,55 @@ function loadVendor(vendor, fn) {
451
457
  standardOpenapi.loadVendor(vendor, fn.toOpenAPISchema);
452
458
  }
453
459
  }
460
+ const arktypeMorphFallback = (ctx) => ctx.base;
461
+ const zodV4DateOverride = (ctx) => {
462
+ if (ctx.zodSchema._zod.def.type === "date") {
463
+ ctx.jsonSchema.type = "string";
464
+ ctx.jsonSchema.format = "date-time";
465
+ }
466
+ };
454
467
  function resolver(schema, userDefinedOptions) {
468
+ const vendor = schema["~standard"].vendor;
455
469
  return {
456
- vendor: schema["~standard"].vendor,
470
+ vendor,
457
471
  validate: schema["~standard"].validate,
458
472
  toJSONSchema: (customOptions) => standardJson.toJsonSchema(schema, { ...userDefinedOptions, ...customOptions }),
459
- toOpenAPISchema: (customOptions) => standardOpenapi.toOpenAPISchema(schema, { ...userDefinedOptions, ...customOptions })
473
+ toOpenAPISchema: (customOptions) => standardOpenapi.toOpenAPISchema(schema, {
474
+ ...userDefinedOptions,
475
+ ...customOptions,
476
+ ...vendor === "arktype" ? injectArktypeFallback(userDefinedOptions, customOptions) : void 0,
477
+ ...vendor === "zod" ? injectZodV4DateOverride(schema, userDefinedOptions, customOptions) : void 0
478
+ })
479
+ };
480
+ }
481
+ function injectArktypeFallback(userDefined, custom) {
482
+ const userNested = userDefined?.options;
483
+ const customNested = custom?.options;
484
+ if (userDefined?.fallback || userNested?.fallback || custom?.fallback || customNested?.fallback) {
485
+ return void 0;
486
+ }
487
+ return {
488
+ options: {
489
+ fallback: arktypeMorphFallback,
490
+ ...userNested,
491
+ ...customNested
492
+ }
493
+ };
494
+ }
495
+ function injectZodV4DateOverride(schema, userDefined, custom) {
496
+ if (!("_zod" in schema)) return void 0;
497
+ const userNested = userDefined?.options;
498
+ const customNested = custom?.options;
499
+ if (userDefined?.override || userNested?.override || custom?.override || customNested?.override) {
500
+ return void 0;
501
+ }
502
+ return {
503
+ options: {
504
+ unrepresentable: "any",
505
+ override: zodV4DateOverride,
506
+ ...userNested,
507
+ ...customNested
508
+ }
460
509
  };
461
510
  }
462
511
  function validator(target, schema, hook, options) {
package/dist/index.d.cts CHANGED
@@ -6,6 +6,7 @@ import { loadVendor as loadVendor$2, ToOpenAPISchemaContext } from '@standard-co
6
6
  import { Hook } from '@hono/standard-validator';
7
7
  import { loadVendor as loadVendor$1 } from '@standard-community/standard-json';
8
8
  import { StatusCode } from 'hono/utils/http-status';
9
+ import { JSONParsed } from 'hono/utils/types';
9
10
  import { JSONSchema7 } from 'json-schema';
10
11
 
11
12
  /** The Standard Schema interface. */
@@ -122,7 +123,7 @@ type ResponseObject<T extends Partial<Record<StatusCode, StandardSchemaV1>>> = {
122
123
  };
123
124
  type Num<T> = T extends `${infer N extends number}` ? N : T;
124
125
  type HandlerResponse<T extends Partial<Record<StatusCode, StandardSchemaV1>> = Partial<Record<StatusCode, StandardSchemaV1>>> = PromiseOr<{
125
- [K in keyof T]: T[K] extends StandardSchemaV1 ? TypedResponse<StandardSchemaV1.InferOutput<T[K]>, Num<K> extends StatusCode ? Num<K> : never> : never;
126
+ [K in keyof T]: T[K] extends StandardSchemaV1 ? TypedResponse<JSONParsed<StandardSchemaV1.InferOutput<T[K]>>, Num<K> extends StatusCode ? Num<K> : never> : never;
126
127
  }[keyof T]>;
127
128
  type Handler<E extends Env, P extends string, I extends Input, T extends Partial<Record<StatusCode, StandardSchemaV1>> = Partial<Record<StatusCode, StandardSchemaV1>>> = (c: Context<E, P, I>, next: Next) => HandlerResponse<T>;
128
129
  declare function describeResponse<E extends Env, P extends string, I extends Input, 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>;
@@ -151,13 +152,41 @@ type HandlerUniqueProperty = (ResolverReturnType & {
151
152
  }) | {
152
153
  spec: DescribeRouteOptions;
153
154
  };
155
+ /**
156
+ * A media type object that accepts resolver() output in addition to standard schema types.
157
+ */
158
+ type MediaTypeObjectWithResolver = Omit<OpenAPIV3_1.MediaTypeObject, "schema"> & {
159
+ schema?: OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.SchemaObject | ResolverReturnType;
160
+ };
161
+ /**
162
+ * A response object that accepts resolver() output in schema positions.
163
+ */
164
+ type ResponseObjectWithResolver = (Omit<OpenAPIV3_1.ResponseObject, "content"> & {
165
+ content?: {
166
+ [media: string]: MediaTypeObjectWithResolver;
167
+ };
168
+ }) | OpenAPIV3_1.ReferenceObject;
169
+ /**
170
+ * A responses map that accepts resolver() output in schema positions.
171
+ */
172
+ type ResponsesWithResolver = {
173
+ [key: string]: ResponseObjectWithResolver;
174
+ };
175
+ /**
176
+ * Extended document type that allows resolver() in documentation.components.responses
177
+ */
178
+ type DocumentWithResolver = Omit<Partial<OpenAPIV3_1.Document>, "x-express-openapi-additional-middleware" | "x-express-openapi-validation-strict" | "components"> & {
179
+ components?: Omit<OpenAPIV3_1.ComponentsObject, "responses"> & {
180
+ responses?: ResponsesWithResolver;
181
+ };
182
+ };
154
183
  type GenerateSpecOptions = {
155
184
  /**
156
185
  * Customize OpenAPI config, refers to Swagger 2.0 config
157
186
  *
158
187
  * @see https://swagger.io/specification/v2/
159
188
  */
160
- documentation: Omit<Partial<OpenAPIV3_1.Document>, "x-express-openapi-additional-middleware" | "x-express-openapi-validation-strict">;
189
+ documentation: DocumentWithResolver;
161
190
  /**
162
191
  * Include paths which don't have the handlers.
163
192
  * This is useful when you want to document the
@@ -203,15 +232,7 @@ type DescribeRouteOptions = Omit<OpenAPIV3_1.OperationObject, "responses" | "ope
203
232
  /**
204
233
  * Responses of the request
205
234
  */
206
- responses?: {
207
- [key: string]: (OpenAPIV3_1.ResponseObject & {
208
- content?: {
209
- [key: string]: Omit<OpenAPIV3_1.MediaTypeObject, "schema"> & {
210
- schema?: OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.SchemaObject | ResolverReturnType;
211
- };
212
- };
213
- }) | OpenAPIV3_1.ReferenceObject;
214
- };
235
+ responses?: ResponsesWithResolver;
215
236
  };
216
237
  type RegisterSchemaPathOptions = {
217
238
  route: RouterRoute;
@@ -277,4 +298,4 @@ declare function generateSpecs<E extends Env = BlankEnv, P extends string = stri
277
298
  jsonSchemaDialect?: string;
278
299
  }>;
279
300
 
280
- export { ALLOWED_METHODS, type AllowedMethods, type DescribeRouteOptions, type GenerateSpecOptions, type HandlerUniqueProperty, type PromiseOr, type RegisterSchemaPathOptions, type ResolverReturnType, type SpecContext, clearSpecsContext, describeResponse, describeRoute, generateSpecs, loadVendor, openAPIRouteHandler, registerSchemaPath, removeExcludedPaths, resolver, uniqueSymbol, validator };
301
+ export { ALLOWED_METHODS, type AllowedMethods, type DescribeRouteOptions, type GenerateSpecOptions, type HandlerUniqueProperty, type PromiseOr, type RegisterSchemaPathOptions, type ResolverReturnType, type ResponsesWithResolver, type SpecContext, clearSpecsContext, describeResponse, describeRoute, generateSpecs, loadVendor, openAPIRouteHandler, registerSchemaPath, removeExcludedPaths, resolver, uniqueSymbol, validator };
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ import { loadVendor as loadVendor$2, ToOpenAPISchemaContext } from '@standard-co
6
6
  import { Hook } from '@hono/standard-validator';
7
7
  import { loadVendor as loadVendor$1 } from '@standard-community/standard-json';
8
8
  import { StatusCode } from 'hono/utils/http-status';
9
+ import { JSONParsed } from 'hono/utils/types';
9
10
  import { JSONSchema7 } from 'json-schema';
10
11
 
11
12
  /** The Standard Schema interface. */
@@ -122,7 +123,7 @@ type ResponseObject<T extends Partial<Record<StatusCode, StandardSchemaV1>>> = {
122
123
  };
123
124
  type Num<T> = T extends `${infer N extends number}` ? N : T;
124
125
  type HandlerResponse<T extends Partial<Record<StatusCode, StandardSchemaV1>> = Partial<Record<StatusCode, StandardSchemaV1>>> = PromiseOr<{
125
- [K in keyof T]: T[K] extends StandardSchemaV1 ? TypedResponse<StandardSchemaV1.InferOutput<T[K]>, Num<K> extends StatusCode ? Num<K> : never> : never;
126
+ [K in keyof T]: T[K] extends StandardSchemaV1 ? TypedResponse<JSONParsed<StandardSchemaV1.InferOutput<T[K]>>, Num<K> extends StatusCode ? Num<K> : never> : never;
126
127
  }[keyof T]>;
127
128
  type Handler<E extends Env, P extends string, I extends Input, T extends Partial<Record<StatusCode, StandardSchemaV1>> = Partial<Record<StatusCode, StandardSchemaV1>>> = (c: Context<E, P, I>, next: Next) => HandlerResponse<T>;
128
129
  declare function describeResponse<E extends Env, P extends string, I extends Input, 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>;
@@ -151,13 +152,41 @@ type HandlerUniqueProperty = (ResolverReturnType & {
151
152
  }) | {
152
153
  spec: DescribeRouteOptions;
153
154
  };
155
+ /**
156
+ * A media type object that accepts resolver() output in addition to standard schema types.
157
+ */
158
+ type MediaTypeObjectWithResolver = Omit<OpenAPIV3_1.MediaTypeObject, "schema"> & {
159
+ schema?: OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.SchemaObject | ResolverReturnType;
160
+ };
161
+ /**
162
+ * A response object that accepts resolver() output in schema positions.
163
+ */
164
+ type ResponseObjectWithResolver = (Omit<OpenAPIV3_1.ResponseObject, "content"> & {
165
+ content?: {
166
+ [media: string]: MediaTypeObjectWithResolver;
167
+ };
168
+ }) | OpenAPIV3_1.ReferenceObject;
169
+ /**
170
+ * A responses map that accepts resolver() output in schema positions.
171
+ */
172
+ type ResponsesWithResolver = {
173
+ [key: string]: ResponseObjectWithResolver;
174
+ };
175
+ /**
176
+ * Extended document type that allows resolver() in documentation.components.responses
177
+ */
178
+ type DocumentWithResolver = Omit<Partial<OpenAPIV3_1.Document>, "x-express-openapi-additional-middleware" | "x-express-openapi-validation-strict" | "components"> & {
179
+ components?: Omit<OpenAPIV3_1.ComponentsObject, "responses"> & {
180
+ responses?: ResponsesWithResolver;
181
+ };
182
+ };
154
183
  type GenerateSpecOptions = {
155
184
  /**
156
185
  * Customize OpenAPI config, refers to Swagger 2.0 config
157
186
  *
158
187
  * @see https://swagger.io/specification/v2/
159
188
  */
160
- documentation: Omit<Partial<OpenAPIV3_1.Document>, "x-express-openapi-additional-middleware" | "x-express-openapi-validation-strict">;
189
+ documentation: DocumentWithResolver;
161
190
  /**
162
191
  * Include paths which don't have the handlers.
163
192
  * This is useful when you want to document the
@@ -203,15 +232,7 @@ type DescribeRouteOptions = Omit<OpenAPIV3_1.OperationObject, "responses" | "ope
203
232
  /**
204
233
  * Responses of the request
205
234
  */
206
- responses?: {
207
- [key: string]: (OpenAPIV3_1.ResponseObject & {
208
- content?: {
209
- [key: string]: Omit<OpenAPIV3_1.MediaTypeObject, "schema"> & {
210
- schema?: OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.SchemaObject | ResolverReturnType;
211
- };
212
- };
213
- }) | OpenAPIV3_1.ReferenceObject;
214
- };
235
+ responses?: ResponsesWithResolver;
215
236
  };
216
237
  type RegisterSchemaPathOptions = {
217
238
  route: RouterRoute;
@@ -277,4 +298,4 @@ declare function generateSpecs<E extends Env = BlankEnv, P extends string = stri
277
298
  jsonSchemaDialect?: string;
278
299
  }>;
279
300
 
280
- export { ALLOWED_METHODS, type AllowedMethods, type DescribeRouteOptions, type GenerateSpecOptions, type HandlerUniqueProperty, type PromiseOr, type RegisterSchemaPathOptions, type ResolverReturnType, type SpecContext, clearSpecsContext, describeResponse, describeRoute, generateSpecs, loadVendor, openAPIRouteHandler, registerSchemaPath, removeExcludedPaths, resolver, uniqueSymbol, validator };
301
+ export { ALLOWED_METHODS, type AllowedMethods, type DescribeRouteOptions, type GenerateSpecOptions, type HandlerUniqueProperty, type PromiseOr, type RegisterSchemaPathOptions, type ResolverReturnType, type ResponsesWithResolver, type SpecContext, clearSpecsContext, describeResponse, describeRoute, generateSpecs, loadVendor, openAPIRouteHandler, registerSchemaPath, removeExcludedPaths, resolver, uniqueSymbol, validator };
package/dist/index.js CHANGED
@@ -250,8 +250,10 @@ async function generateSpecs(hono, options = DEFAULT_OPTIONS, c) {
250
250
  }
251
251
  }
252
252
  }
253
+ const { components: resolvedDocComponents } = _documentation.components?.responses ? await resolveResponseSchemas(_documentation.components.responses) : { components: {} };
253
254
  const components = mergeComponentsObjects(
254
255
  _documentation.components,
256
+ resolvedDocComponents,
255
257
  ctx.components
256
258
  );
257
259
  return {
@@ -323,7 +325,6 @@ function getHiddenValue(options) {
323
325
  }
324
326
  async function getSpec(middlewareHandler, defaultOptions) {
325
327
  if ("spec" in middlewareHandler) {
326
- let components = {};
327
328
  const tmp = {
328
329
  ...defaultOptions,
329
330
  ...middlewareHandler.spec,
@@ -332,25 +333,10 @@ async function getSpec(middlewareHandler, defaultOptions) {
332
333
  ...middlewareHandler.spec.responses
333
334
  }
334
335
  };
336
+ let components = {};
335
337
  if (tmp.responses) {
336
- for (const key of Object.keys(tmp.responses)) {
337
- const response = tmp.responses[key];
338
- if (!response || !("content" in response)) continue;
339
- for (const contentKey of Object.keys(response.content ?? {})) {
340
- const raw = response.content?.[contentKey];
341
- if (!raw) continue;
342
- if (raw.schema && "toOpenAPISchema" in raw.schema) {
343
- const result2 = await raw.schema.toOpenAPISchema();
344
- raw.schema = result2.schema;
345
- if (result2.components) {
346
- components = mergeComponentsObjects(
347
- components,
348
- result2.components
349
- );
350
- }
351
- }
352
- }
353
- }
338
+ const resolved = await resolveResponseSchemas(tmp.responses);
339
+ components = resolved.components;
354
340
  }
355
341
  return { schema: tmp, components };
356
342
  }
@@ -360,6 +346,7 @@ async function getSpec(middlewareHandler, defaultOptions) {
360
346
  const media = middlewareHandler.options?.media ?? middlewareHandler.target === "json" ? "application/json" : "multipart/form-data";
361
347
  if (!docs.requestBody || !("content" in docs.requestBody) || !docs.requestBody.content) {
362
348
  docs.requestBody = {
349
+ required: true,
363
350
  content: {
364
351
  [media]: {
365
352
  schema: result.schema
@@ -419,6 +406,25 @@ function generateParameters(target, schema) {
419
406
  }
420
407
  return parameters;
421
408
  }
409
+ async function resolveResponseSchemas(responses) {
410
+ let components = {};
411
+ for (const key of Object.keys(responses)) {
412
+ const response = responses[key];
413
+ if (!response || !("content" in response)) continue;
414
+ for (const contentKey of Object.keys(response.content ?? {})) {
415
+ const raw = response.content?.[contentKey];
416
+ if (!raw) continue;
417
+ if (raw.schema && "toOpenAPISchema" in raw.schema) {
418
+ const result = await raw.schema.toOpenAPISchema();
419
+ raw.schema = result.schema;
420
+ if (result.components) {
421
+ components = mergeComponentsObjects(components, result.components);
422
+ }
423
+ }
424
+ }
425
+ }
426
+ return { responses, components };
427
+ }
422
428
  function mergeComponentsObjects(...components) {
423
429
  return components.reduce(
424
430
  (prev, component, index) => {
@@ -449,12 +455,55 @@ function loadVendor(vendor, fn) {
449
455
  loadVendor$2(vendor, fn.toOpenAPISchema);
450
456
  }
451
457
  }
458
+ const arktypeMorphFallback = (ctx) => ctx.base;
459
+ const zodV4DateOverride = (ctx) => {
460
+ if (ctx.zodSchema._zod.def.type === "date") {
461
+ ctx.jsonSchema.type = "string";
462
+ ctx.jsonSchema.format = "date-time";
463
+ }
464
+ };
452
465
  function resolver(schema, userDefinedOptions) {
466
+ const vendor = schema["~standard"].vendor;
453
467
  return {
454
- vendor: schema["~standard"].vendor,
468
+ vendor,
455
469
  validate: schema["~standard"].validate,
456
470
  toJSONSchema: (customOptions) => toJsonSchema(schema, { ...userDefinedOptions, ...customOptions }),
457
- toOpenAPISchema: (customOptions) => toOpenAPISchema(schema, { ...userDefinedOptions, ...customOptions })
471
+ toOpenAPISchema: (customOptions) => toOpenAPISchema(schema, {
472
+ ...userDefinedOptions,
473
+ ...customOptions,
474
+ ...vendor === "arktype" ? injectArktypeFallback(userDefinedOptions, customOptions) : void 0,
475
+ ...vendor === "zod" ? injectZodV4DateOverride(schema, userDefinedOptions, customOptions) : void 0
476
+ })
477
+ };
478
+ }
479
+ function injectArktypeFallback(userDefined, custom) {
480
+ const userNested = userDefined?.options;
481
+ const customNested = custom?.options;
482
+ if (userDefined?.fallback || userNested?.fallback || custom?.fallback || customNested?.fallback) {
483
+ return void 0;
484
+ }
485
+ return {
486
+ options: {
487
+ fallback: arktypeMorphFallback,
488
+ ...userNested,
489
+ ...customNested
490
+ }
491
+ };
492
+ }
493
+ function injectZodV4DateOverride(schema, userDefined, custom) {
494
+ if (!("_zod" in schema)) return void 0;
495
+ const userNested = userDefined?.options;
496
+ const customNested = custom?.options;
497
+ if (userDefined?.override || userNested?.override || custom?.override || customNested?.override) {
498
+ return void 0;
499
+ }
500
+ return {
501
+ options: {
502
+ unrepresentable: "any",
503
+ override: zodV4DateOverride,
504
+ ...userNested,
505
+ ...customNested
506
+ }
458
507
  };
459
508
  }
460
509
  function validator(target, schema, hook, options) {
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": "1.2.0",
4
+ "version": "1.3.0",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.js",