hono-openapi 1.3.3 → 1.3.5

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
@@ -5,7 +5,7 @@ var standardValidator = require('@hono/standard-validator');
5
5
  var standardJson = require('@standard-community/standard-json');
6
6
  var standardOpenapi = require('@standard-community/standard-openapi');
7
7
 
8
- const uniqueSymbol = Symbol("openapi");
8
+ const uniqueSymbol = Symbol.for("hono-openapi");
9
9
  const VALIDATION_MARKER = "__HonoOpenAPIValidator__";
10
10
  const ALLOWED_METHODS = [
11
11
  "GET",
@@ -55,6 +55,9 @@ function mergeParameters(...params) {
55
55
  }, /* @__PURE__ */ new Map());
56
56
  return Array.from(merged.values());
57
57
  }
58
+ const specComponents = /* @__PURE__ */ new WeakMap();
59
+ const setSpecComponents = (spec, components) => specComponents.set(spec, components);
60
+ const getSpecComponents = (spec) => spec != null && typeof spec === "object" && specComponents.get(spec) || [];
58
61
  const specsByPathContext = /* @__PURE__ */ new Map();
59
62
  function getPathContext(path, pathContext) {
60
63
  const context = [];
@@ -84,7 +87,7 @@ function mergeRequestBodies(previous, current) {
84
87
  };
85
88
  }
86
89
  function mergeSpecs(route, ...specs) {
87
- return specs.reduce(
90
+ const merged = specs.reduce(
88
91
  (prev, spec) => {
89
92
  if (!spec || !prev) return prev;
90
93
  for (const [key, value] of Object.entries(spec)) {
@@ -126,6 +129,8 @@ function mergeSpecs(route, ...specs) {
126
129
  operationId: generateOperationId(route)
127
130
  }
128
131
  );
132
+ setSpecComponents(merged, specs.flatMap(getSpecComponents));
133
+ return merged;
129
134
  }
130
135
  function registerSchemaPath({ route, specs, paths }, pathContext = specsByPathContext) {
131
136
  const path = toOpenAPIPath(route.path);
@@ -240,6 +245,52 @@ function removeExcludedPaths(paths, ctx) {
240
245
  }
241
246
  return newPaths;
242
247
  }
248
+ const COMPONENTS_PREFIX = "#/components/";
249
+ const componentKey = (type, name) => `${type}/${name.replaceAll("~", "~0").replaceAll("/", "~1")}`;
250
+ const componentRefs = (value) => {
251
+ if (Array.isArray(value)) {
252
+ return value.flatMap(componentRefs);
253
+ }
254
+ if (value == null || typeof value !== "object") {
255
+ return [];
256
+ }
257
+ return Object.entries(value).flatMap(([key, item]) => {
258
+ if (key !== "$ref" || typeof item !== "string") {
259
+ return componentRefs(item);
260
+ }
261
+ return item.startsWith(COMPONENTS_PREFIX) ? [item.slice(COMPONENTS_PREFIX.length)] : [];
262
+ });
263
+ };
264
+ const reachableRefs = (refs, components, reached) => refs.reduce((seen, ref) => {
265
+ if (seen.has(ref)) {
266
+ return seen;
267
+ }
268
+ const [type, ...name] = ref.split("/");
269
+ const component = components[type]?.[name.join("/").replaceAll("~1", "/").replaceAll("~0", "~")];
270
+ return reachableRefs(componentRefs(component), components, seen.add(ref));
271
+ }, reached);
272
+ function documentedComponents(components, paths, documentation) {
273
+ const contributed = new Set(
274
+ Object.values(paths).flatMap((item) => item == null ? [] : Object.values(item)).flatMap(getSpecComponents).flatMap(
275
+ (operationComponents) => Object.entries(operationComponents).flatMap(
276
+ ([type, entries]) => Object.keys(entries ?? {}).map((name) => componentKey(type, name))
277
+ )
278
+ )
279
+ );
280
+ const kept = reachableRefs(
281
+ componentRefs(documentation),
282
+ components,
283
+ contributed
284
+ );
285
+ return Object.fromEntries(
286
+ Object.entries(components).flatMap(([type, entries]) => {
287
+ const remaining = Object.entries(entries ?? {}).filter(
288
+ ([name]) => kept.has(componentKey(type, name))
289
+ );
290
+ return remaining.length > 0 ? [[type, Object.fromEntries(remaining)]] : [];
291
+ })
292
+ );
293
+ }
243
294
 
244
295
  const DEFAULT_OPTIONS = {
245
296
  documentation: {},
@@ -309,10 +360,14 @@ async function generateSpecs(hono, options = DEFAULT_OPTIONS, c) {
309
360
  if (resolvedDocumentation && documentation.components) {
310
361
  documentation.components.responses = resolvedDocumentation.responses;
311
362
  }
363
+ const documentedPaths = removeExcludedPaths(paths, ctx);
312
364
  const components = mergeComponentsObjects(
313
365
  documentation.components,
314
366
  resolvedDocumentation?.components,
315
- ctx.components
367
+ documentedComponents(ctx.components, documentedPaths, [
368
+ documentation,
369
+ resolvedDocumentation?.components
370
+ ])
316
371
  );
317
372
  return {
318
373
  openapi: "3.1.0",
@@ -327,7 +382,7 @@ async function generateSpecs(hono, options = DEFAULT_OPTIONS, c) {
327
382
  ...documentation.info
328
383
  },
329
384
  paths: {
330
- ...removeExcludedPaths(paths, ctx),
385
+ ...documentedPaths,
331
386
  ...documentation.paths
332
387
  },
333
388
  components
@@ -366,6 +421,7 @@ async function generatePaths(hono, ctx) {
366
421
  ctx.components.parameters
367
422
  );
368
423
  ctx.components = mergeComponentsObjects(ctx.components, components);
424
+ setSpecComponents(routeSpecs, [components]);
369
425
  registerSchemaPath(
370
426
  {
371
427
  route,
@@ -719,11 +775,14 @@ exports.VALIDATION_MARKER = VALIDATION_MARKER;
719
775
  exports.clearSpecsContext = clearSpecsContext;
720
776
  exports.describeResponse = describeResponse;
721
777
  exports.describeRoute = describeRoute;
778
+ exports.documentedComponents = documentedComponents;
722
779
  exports.generateSpecs = generateSpecs;
780
+ exports.getSpecComponents = getSpecComponents;
723
781
  exports.loadVendor = loadVendor;
724
782
  exports.openAPIRouteHandler = openAPIRouteHandler;
725
783
  exports.registerSchemaPath = registerSchemaPath;
726
784
  exports.removeExcludedPaths = removeExcludedPaths;
727
785
  exports.resolver = resolver;
786
+ exports.setSpecComponents = setSpecComponents;
728
787
  exports.uniqueSymbol = uniqueSymbol;
729
788
  exports.validator = validator;
package/dist/index.d.cts CHANGED
@@ -84,8 +84,10 @@ declare const uniqueSymbol: unique symbol;
84
84
  declare const VALIDATION_MARKER = "__HonoOpenAPIValidator__";
85
85
  declare const ALLOWED_METHODS: readonly ["GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"];
86
86
  type AllowedMethods = (typeof ALLOWED_METHODS)[number];
87
+ declare const setSpecComponents: (spec: object, components: OpenAPIV3_1.ComponentsObject[]) => WeakMap<object, OpenAPIV3_1.ComponentsObject[]>;
88
+ declare const getSpecComponents: (spec: unknown) => OpenAPIV3_1.ComponentsObject[];
87
89
  declare function clearSpecsContext(): void;
88
- declare function registerSchemaPath({ route, specs, paths }: RegisterSchemaPathOptions, pathContext?: Map<string, Omit<OpenAPIV3_1.OperationObject<{}>, "operationId" | "requestBody" | "responses"> & {
90
+ declare function registerSchemaPath({ route, specs, paths }: RegisterSchemaPathOptions, pathContext?: Map<string, Omit<OpenAPIV3_1.OperationObject<{}>, "responses" | "operationId" | "requestBody"> & {
89
91
  operationId?: string | ((route: RouterRoute) => string);
90
92
  requestBody?: OpenAPIV3_1.ReferenceObject | (Omit<OpenAPIV3_1.RequestBodyObject, "content"> & {
91
93
  content: ContentWithResolver;
@@ -100,6 +102,16 @@ declare function registerSchemaPath({ route, specs, paths }: RegisterSchemaPathO
100
102
  operationId?: string | ((route: RouterRoute) => string);
101
103
  }>): void;
102
104
  declare function removeExcludedPaths(paths: OpenAPIV3_1.PathsObject, ctx: SpecContext): OpenAPIV3_1.PathsObject<{}, {}>;
105
+ /**
106
+ * Keeps the generated components that the documented operations brought in,
107
+ * and the ones `documentation` refers to, directly or through other
108
+ * components.
109
+ *
110
+ * Components are collected from every route while the paths are generated,
111
+ * including routes that are hidden or excluded afterwards. Without this, those
112
+ * routes would still publish the schemas only they use.
113
+ */
114
+ declare function documentedComponents(components: OpenAPIV3_1.ComponentsObject, paths: OpenAPIV3_1.PathsObject, documentation: unknown): OpenAPIV3_1.ComponentsObject;
103
115
 
104
116
  type PromiseOr<T> = T | Promise<T>;
105
117
  type ResolverReturnType = ReturnType<typeof resolver> & {
@@ -271,12 +283,12 @@ declare function generateSpecs<E extends Env = BlankEnv, P extends string = stri
271
283
  };
272
284
  };
273
285
  components: OpenAPIV3_1.ComponentsObject;
274
- openapi: string;
275
286
  externalDocs?: openapi_types.OpenAPIV3.ExternalDocumentationObject;
276
287
  security?: openapi_types.OpenAPIV3.SecurityRequirementObject[];
277
288
  servers?: OpenAPIV3_1.ServerObject[];
289
+ openapi: string;
278
290
  webhooks?: Record<string, OpenAPIV3_1.PathItemObject | OpenAPIV3_1.ReferenceObject>;
279
291
  jsonSchemaDialect?: string;
280
292
  }>;
281
293
 
282
- export { ALLOWED_METHODS, type AllowedMethods, type ContentWithResolver, type DescribeRouteOptions, type GenerateSpecOptions, type HandlerUniqueProperty, type PromiseOr, type RegisterSchemaPathOptions, type ResolverReturnType, type ResponsesWithResolver, type SpecContext, VALIDATION_MARKER, clearSpecsContext, describeResponse, describeRoute, generateSpecs, loadVendor, openAPIRouteHandler, registerSchemaPath, removeExcludedPaths, resolver, uniqueSymbol, validator };
294
+ export { ALLOWED_METHODS, type AllowedMethods, type ContentWithResolver, type DescribeRouteOptions, type GenerateSpecOptions, type HandlerUniqueProperty, type PromiseOr, type RegisterSchemaPathOptions, type ResolverReturnType, type ResponsesWithResolver, type SpecContext, VALIDATION_MARKER, clearSpecsContext, describeResponse, describeRoute, documentedComponents, generateSpecs, getSpecComponents, loadVendor, openAPIRouteHandler, registerSchemaPath, removeExcludedPaths, resolver, setSpecComponents, uniqueSymbol, validator };
package/dist/index.d.ts CHANGED
@@ -84,8 +84,10 @@ declare const uniqueSymbol: unique symbol;
84
84
  declare const VALIDATION_MARKER = "__HonoOpenAPIValidator__";
85
85
  declare const ALLOWED_METHODS: readonly ["GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"];
86
86
  type AllowedMethods = (typeof ALLOWED_METHODS)[number];
87
+ declare const setSpecComponents: (spec: object, components: OpenAPIV3_1.ComponentsObject[]) => WeakMap<object, OpenAPIV3_1.ComponentsObject[]>;
88
+ declare const getSpecComponents: (spec: unknown) => OpenAPIV3_1.ComponentsObject[];
87
89
  declare function clearSpecsContext(): void;
88
- declare function registerSchemaPath({ route, specs, paths }: RegisterSchemaPathOptions, pathContext?: Map<string, Omit<OpenAPIV3_1.OperationObject<{}>, "operationId" | "requestBody" | "responses"> & {
90
+ declare function registerSchemaPath({ route, specs, paths }: RegisterSchemaPathOptions, pathContext?: Map<string, Omit<OpenAPIV3_1.OperationObject<{}>, "responses" | "operationId" | "requestBody"> & {
89
91
  operationId?: string | ((route: RouterRoute) => string);
90
92
  requestBody?: OpenAPIV3_1.ReferenceObject | (Omit<OpenAPIV3_1.RequestBodyObject, "content"> & {
91
93
  content: ContentWithResolver;
@@ -100,6 +102,16 @@ declare function registerSchemaPath({ route, specs, paths }: RegisterSchemaPathO
100
102
  operationId?: string | ((route: RouterRoute) => string);
101
103
  }>): void;
102
104
  declare function removeExcludedPaths(paths: OpenAPIV3_1.PathsObject, ctx: SpecContext): OpenAPIV3_1.PathsObject<{}, {}>;
105
+ /**
106
+ * Keeps the generated components that the documented operations brought in,
107
+ * and the ones `documentation` refers to, directly or through other
108
+ * components.
109
+ *
110
+ * Components are collected from every route while the paths are generated,
111
+ * including routes that are hidden or excluded afterwards. Without this, those
112
+ * routes would still publish the schemas only they use.
113
+ */
114
+ declare function documentedComponents(components: OpenAPIV3_1.ComponentsObject, paths: OpenAPIV3_1.PathsObject, documentation: unknown): OpenAPIV3_1.ComponentsObject;
103
115
 
104
116
  type PromiseOr<T> = T | Promise<T>;
105
117
  type ResolverReturnType = ReturnType<typeof resolver> & {
@@ -271,12 +283,12 @@ declare function generateSpecs<E extends Env = BlankEnv, P extends string = stri
271
283
  };
272
284
  };
273
285
  components: OpenAPIV3_1.ComponentsObject;
274
- openapi: string;
275
286
  externalDocs?: openapi_types.OpenAPIV3.ExternalDocumentationObject;
276
287
  security?: openapi_types.OpenAPIV3.SecurityRequirementObject[];
277
288
  servers?: OpenAPIV3_1.ServerObject[];
289
+ openapi: string;
278
290
  webhooks?: Record<string, OpenAPIV3_1.PathItemObject | OpenAPIV3_1.ReferenceObject>;
279
291
  jsonSchemaDialect?: string;
280
292
  }>;
281
293
 
282
- export { ALLOWED_METHODS, type AllowedMethods, type ContentWithResolver, type DescribeRouteOptions, type GenerateSpecOptions, type HandlerUniqueProperty, type PromiseOr, type RegisterSchemaPathOptions, type ResolverReturnType, type ResponsesWithResolver, type SpecContext, VALIDATION_MARKER, clearSpecsContext, describeResponse, describeRoute, generateSpecs, loadVendor, openAPIRouteHandler, registerSchemaPath, removeExcludedPaths, resolver, uniqueSymbol, validator };
294
+ export { ALLOWED_METHODS, type AllowedMethods, type ContentWithResolver, type DescribeRouteOptions, type GenerateSpecOptions, type HandlerUniqueProperty, type PromiseOr, type RegisterSchemaPathOptions, type ResolverReturnType, type ResponsesWithResolver, type SpecContext, VALIDATION_MARKER, clearSpecsContext, describeResponse, describeRoute, documentedComponents, generateSpecs, getSpecComponents, loadVendor, openAPIRouteHandler, registerSchemaPath, removeExcludedPaths, resolver, setSpecComponents, uniqueSymbol, validator };
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { sValidator } from '@hono/standard-validator';
3
3
  import { loadVendor as loadVendor$1, toJsonSchema } from '@standard-community/standard-json';
4
4
  import { loadVendor as loadVendor$2, toOpenAPISchema } from '@standard-community/standard-openapi';
5
5
 
6
- const uniqueSymbol = Symbol("openapi");
6
+ const uniqueSymbol = Symbol.for("hono-openapi");
7
7
  const VALIDATION_MARKER = "__HonoOpenAPIValidator__";
8
8
  const ALLOWED_METHODS = [
9
9
  "GET",
@@ -53,6 +53,9 @@ function mergeParameters(...params) {
53
53
  }, /* @__PURE__ */ new Map());
54
54
  return Array.from(merged.values());
55
55
  }
56
+ const specComponents = /* @__PURE__ */ new WeakMap();
57
+ const setSpecComponents = (spec, components) => specComponents.set(spec, components);
58
+ const getSpecComponents = (spec) => spec != null && typeof spec === "object" && specComponents.get(spec) || [];
56
59
  const specsByPathContext = /* @__PURE__ */ new Map();
57
60
  function getPathContext(path, pathContext) {
58
61
  const context = [];
@@ -82,7 +85,7 @@ function mergeRequestBodies(previous, current) {
82
85
  };
83
86
  }
84
87
  function mergeSpecs(route, ...specs) {
85
- return specs.reduce(
88
+ const merged = specs.reduce(
86
89
  (prev, spec) => {
87
90
  if (!spec || !prev) return prev;
88
91
  for (const [key, value] of Object.entries(spec)) {
@@ -124,6 +127,8 @@ function mergeSpecs(route, ...specs) {
124
127
  operationId: generateOperationId(route)
125
128
  }
126
129
  );
130
+ setSpecComponents(merged, specs.flatMap(getSpecComponents));
131
+ return merged;
127
132
  }
128
133
  function registerSchemaPath({ route, specs, paths }, pathContext = specsByPathContext) {
129
134
  const path = toOpenAPIPath(route.path);
@@ -238,6 +243,52 @@ function removeExcludedPaths(paths, ctx) {
238
243
  }
239
244
  return newPaths;
240
245
  }
246
+ const COMPONENTS_PREFIX = "#/components/";
247
+ const componentKey = (type, name) => `${type}/${name.replaceAll("~", "~0").replaceAll("/", "~1")}`;
248
+ const componentRefs = (value) => {
249
+ if (Array.isArray(value)) {
250
+ return value.flatMap(componentRefs);
251
+ }
252
+ if (value == null || typeof value !== "object") {
253
+ return [];
254
+ }
255
+ return Object.entries(value).flatMap(([key, item]) => {
256
+ if (key !== "$ref" || typeof item !== "string") {
257
+ return componentRefs(item);
258
+ }
259
+ return item.startsWith(COMPONENTS_PREFIX) ? [item.slice(COMPONENTS_PREFIX.length)] : [];
260
+ });
261
+ };
262
+ const reachableRefs = (refs, components, reached) => refs.reduce((seen, ref) => {
263
+ if (seen.has(ref)) {
264
+ return seen;
265
+ }
266
+ const [type, ...name] = ref.split("/");
267
+ const component = components[type]?.[name.join("/").replaceAll("~1", "/").replaceAll("~0", "~")];
268
+ return reachableRefs(componentRefs(component), components, seen.add(ref));
269
+ }, reached);
270
+ function documentedComponents(components, paths, documentation) {
271
+ const contributed = new Set(
272
+ Object.values(paths).flatMap((item) => item == null ? [] : Object.values(item)).flatMap(getSpecComponents).flatMap(
273
+ (operationComponents) => Object.entries(operationComponents).flatMap(
274
+ ([type, entries]) => Object.keys(entries ?? {}).map((name) => componentKey(type, name))
275
+ )
276
+ )
277
+ );
278
+ const kept = reachableRefs(
279
+ componentRefs(documentation),
280
+ components,
281
+ contributed
282
+ );
283
+ return Object.fromEntries(
284
+ Object.entries(components).flatMap(([type, entries]) => {
285
+ const remaining = Object.entries(entries ?? {}).filter(
286
+ ([name]) => kept.has(componentKey(type, name))
287
+ );
288
+ return remaining.length > 0 ? [[type, Object.fromEntries(remaining)]] : [];
289
+ })
290
+ );
291
+ }
241
292
 
242
293
  const DEFAULT_OPTIONS = {
243
294
  documentation: {},
@@ -307,10 +358,14 @@ async function generateSpecs(hono, options = DEFAULT_OPTIONS, c) {
307
358
  if (resolvedDocumentation && documentation.components) {
308
359
  documentation.components.responses = resolvedDocumentation.responses;
309
360
  }
361
+ const documentedPaths = removeExcludedPaths(paths, ctx);
310
362
  const components = mergeComponentsObjects(
311
363
  documentation.components,
312
364
  resolvedDocumentation?.components,
313
- ctx.components
365
+ documentedComponents(ctx.components, documentedPaths, [
366
+ documentation,
367
+ resolvedDocumentation?.components
368
+ ])
314
369
  );
315
370
  return {
316
371
  openapi: "3.1.0",
@@ -325,7 +380,7 @@ async function generateSpecs(hono, options = DEFAULT_OPTIONS, c) {
325
380
  ...documentation.info
326
381
  },
327
382
  paths: {
328
- ...removeExcludedPaths(paths, ctx),
383
+ ...documentedPaths,
329
384
  ...documentation.paths
330
385
  },
331
386
  components
@@ -364,6 +419,7 @@ async function generatePaths(hono, ctx) {
364
419
  ctx.components.parameters
365
420
  );
366
421
  ctx.components = mergeComponentsObjects(ctx.components, components);
422
+ setSpecComponents(routeSpecs, [components]);
367
423
  registerSchemaPath(
368
424
  {
369
425
  route,
@@ -712,4 +768,4 @@ function describeResponse(handler, responses, options) {
712
768
  });
713
769
  }
714
770
 
715
- export { ALLOWED_METHODS, VALIDATION_MARKER, clearSpecsContext, describeResponse, describeRoute, generateSpecs, loadVendor, openAPIRouteHandler, registerSchemaPath, removeExcludedPaths, resolver, uniqueSymbol, validator };
771
+ export { ALLOWED_METHODS, VALIDATION_MARKER, clearSpecsContext, describeResponse, describeRoute, documentedComponents, generateSpecs, getSpecComponents, loadVendor, openAPIRouteHandler, registerSchemaPath, removeExcludedPaths, resolver, setSpecComponents, uniqueSymbol, validator };
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.3.3",
4
+ "version": "1.3.5",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.js",