@honestjs/api-docs-plugin 1.0.2 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # API Docs Plugin
2
2
 
3
- Serves OpenAPI JSON and Swagger UI for your HonestJS application. Always generates the spec from an artifactpass `{ routes, schemas }` directly or a context key (default `'rpc.artifact'` when using with `@honestjs/rpc-plugin`).
3
+ Serves OpenAPI JSON and Swagger UI for your HonestJS application. Always generates the spec from an artifact-pass
4
+ `{ routes, schemas }` directly or a context key (default `'rpc.artifact'` when using with `@honestjs/rpc-plugin`).
4
5
 
5
6
  ## Installation
6
7
 
@@ -14,22 +15,24 @@ pnpm add @honestjs/api-docs-plugin
14
15
 
15
16
  ## Usage with RPC Plugin
16
17
 
17
- The RPC plugin writes its artifact to the application context. ApiDocs defaults to the context key `'rpc.artifact'`, so you can omit `artifact` when using both plugins. Ensure RPC runs before ApiDocs in the plugins array:
18
+ The RPC plugin writes its artifact to the application context. ApiDocs defaults to the context key `'rpc.artifact'`, so
19
+ you can omit `artifact` when using both plugins. Ensure RPC runs before ApiDocs in the plugins array:
18
20
 
19
21
  ```typescript
20
- import { Application } from "honestjs"
21
- import { RPCPlugin } from "@honestjs/rpc-plugin"
22
- import { ApiDocsPlugin } from "@honestjs/api-docs-plugin"
23
- import AppModule from "./app.module"
22
+ import { Application } from 'honestjs'
23
+ import { RPCPlugin } from '@honestjs/rpc-plugin'
24
+ import { ApiDocsPlugin } from '@honestjs/api-docs-plugin'
25
+ import AppModule from './app.module'
24
26
 
25
27
  const { hono } = await Application.create(AppModule, {
26
- plugins: [new RPCPlugin(), new ApiDocsPlugin()],
28
+ plugins: [new RPCPlugin(), new ApiDocsPlugin()]
27
29
  })
28
30
 
29
31
  export default hono
30
32
  ```
31
33
 
32
- If RPC uses custom `context.namespace` / `context.keys.artifact`, pass the resulting full key to `artifact` (e.g. `new ApiDocsPlugin({ artifact: "custom.artifact" })`).
34
+ If RPC uses custom `context.namespace` / `context.keys.artifact`, pass the resulting full key to `artifact` (e.g.
35
+ `new ApiDocsPlugin({ artifact: "custom.artifact" })`).
33
36
 
34
37
  By default:
35
38
 
@@ -41,19 +44,19 @@ By default:
41
44
  Pass the artifact object directly:
42
45
 
43
46
  ```typescript
44
- import { ApiDocsPlugin } from "@honestjs/api-docs-plugin"
47
+ import { ApiDocsPlugin } from '@honestjs/api-docs-plugin'
45
48
 
46
49
  const artifact = {
47
- routes: [
48
- {
49
- method: "GET",
50
- handler: "list",
51
- controller: "UsersController",
52
- fullPath: "/users",
53
- parameters: [],
54
- },
55
- ],
56
- schemas: [],
50
+ routes: [
51
+ {
52
+ method: 'GET',
53
+ handler: 'list',
54
+ controller: 'UsersController',
55
+ fullPath: '/users',
56
+ parameters: []
57
+ }
58
+ ],
59
+ schemas: []
57
60
  }
58
61
 
59
62
  plugins: [new ApiDocsPlugin({ artifact })]
@@ -63,23 +66,27 @@ plugins: [new ApiDocsPlugin({ artifact })]
63
66
 
64
67
  ```typescript
65
68
  interface ApiDocsPluginOptions {
66
- // Optional: artifact - direct object or context key. Default: 'rpc.artifact'
67
- artifact?: OpenApiArtifactInput | string
68
-
69
- // OpenAPI generation (when converting artifact to spec)
70
- title?: string
71
- version?: string
72
- description?: string
73
- servers?: readonly { url: string; description?: string }[]
74
-
75
- // Serving
76
- openApiRoute?: string // default: '/openapi.json'
77
- uiRoute?: string // default: '/docs'
78
- uiTitle?: string // default: 'API Docs'
79
- reloadOnRequest?: boolean // default: false
69
+ // Optional: artifact - direct object or context key. Default: 'rpc.artifact'
70
+ artifact?: OpenApiArtifactInput | string
71
+
72
+ // OpenAPI generation (when converting artifact to spec)
73
+ title?: string
74
+ version?: string
75
+ description?: string
76
+ servers?: readonly { url: string; description?: string }[]
77
+
78
+ // Serving
79
+ openApiRoute?: string // default: '/openapi.json'
80
+ uiRoute?: string // default: '/docs'
81
+ uiTitle?: string // default: 'API Docs'
82
+ reloadOnRequest?: boolean // default: false
83
+ onOpenApiRequest?: (c, next) => void | Response | Promise<void | Response> // optional auth hook
84
+ onUiRequest?: (c, next) => void | Response | Promise<void | Response> // optional auth hook
80
85
  }
81
86
  ```
82
87
 
88
+ If artifact contains `artifactVersion`, supported value is currently `"1"`.
89
+
83
90
  ## Programmatic API
84
91
 
85
92
  For custom workflows, use the exported utilities:
@@ -100,6 +107,33 @@ const spec: OpenApiDocument = fromArtifactSync(artifact, {
100
107
  await write(spec, "./generated/openapi.json")
101
108
  ```
102
109
 
110
+ ## Route Auth Hooks
111
+
112
+ Use optional hooks to protect docs routes:
113
+
114
+ ```typescript
115
+ const plugin = new ApiDocsPlugin({
116
+ artifact: 'rpc.artifact',
117
+ onOpenApiRequest: async (c, next) => {
118
+ if (c.req.header('x-api-key') !== 'secret') {
119
+ return new Response('Unauthorized', { status: 401 })
120
+ }
121
+ await next()
122
+ },
123
+ onUiRequest: async (_c, next) => {
124
+ await next()
125
+ }
126
+ })
127
+ ```
128
+
129
+ ## Known Limitations
130
+
131
+ - When `artifact` is a context key, the producer plugin must run before `ApiDocsPlugin` and write a valid artifact
132
+ object to that key.
133
+ - OpenAPI generation currently reflects artifact shape and inferred primitive parameter types; advanced custom schema
134
+ mappings should be done upstream in the artifact producer.
135
+ - Swagger UI is served from CDN assets by default; environments with restricted outbound access should account for this.
136
+
103
137
  ## License
104
138
 
105
139
  MIT
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { IPlugin, Application } from 'honestjs';
2
- import { Hono } from 'hono';
2
+ import { Context, Next, Hono } from 'hono';
3
3
  import { OpenAPIV3 } from 'openapi-types';
4
4
  export { OpenAPIV3 } from 'openapi-types';
5
5
 
@@ -13,6 +13,7 @@ interface OpenApiGenerationOptions {
13
13
  }[];
14
14
  }
15
15
  interface OpenApiArtifactInput {
16
+ readonly artifactVersion?: string;
16
17
  readonly routes: readonly OpenApiRouteInput[];
17
18
  readonly schemas: readonly OpenApiSchemaInput[];
18
19
  }
@@ -55,6 +56,8 @@ interface ApiDocsPluginOptions extends OpenApiGenerationOptions {
55
56
  readonly uiRoute?: string;
56
57
  readonly uiTitle?: string;
57
58
  readonly reloadOnRequest?: boolean;
59
+ readonly onOpenApiRequest?: (c: Context, next: Next) => void | Response | Promise<void | Response>;
60
+ readonly onUiRequest?: (c: Context, next: Next) => void | Response | Promise<void | Response>;
58
61
  }
59
62
  declare class ApiDocsPlugin implements IPlugin {
60
63
  private readonly artifact;
@@ -63,12 +66,15 @@ declare class ApiDocsPlugin implements IPlugin {
63
66
  private readonly uiTitle;
64
67
  private readonly reloadOnRequest;
65
68
  private readonly genOptions;
69
+ private readonly onOpenApiRequest?;
70
+ private readonly onUiRequest?;
66
71
  private app;
67
72
  private cachedSpec;
68
73
  constructor(options?: ApiDocsPluginOptions);
69
74
  afterModulesRegistered: (app: Application, hono: Hono) => Promise<void>;
70
75
  private normalizeRoute;
71
76
  private resolveSpec;
77
+ private runHook;
72
78
  private renderSwaggerUiHtml;
73
79
  private escapeHtml;
74
80
  private escapeJsString;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { IPlugin, Application } from 'honestjs';
2
- import { Hono } from 'hono';
2
+ import { Context, Next, Hono } from 'hono';
3
3
  import { OpenAPIV3 } from 'openapi-types';
4
4
  export { OpenAPIV3 } from 'openapi-types';
5
5
 
@@ -13,6 +13,7 @@ interface OpenApiGenerationOptions {
13
13
  }[];
14
14
  }
15
15
  interface OpenApiArtifactInput {
16
+ readonly artifactVersion?: string;
16
17
  readonly routes: readonly OpenApiRouteInput[];
17
18
  readonly schemas: readonly OpenApiSchemaInput[];
18
19
  }
@@ -55,6 +56,8 @@ interface ApiDocsPluginOptions extends OpenApiGenerationOptions {
55
56
  readonly uiRoute?: string;
56
57
  readonly uiTitle?: string;
57
58
  readonly reloadOnRequest?: boolean;
59
+ readonly onOpenApiRequest?: (c: Context, next: Next) => void | Response | Promise<void | Response>;
60
+ readonly onUiRequest?: (c: Context, next: Next) => void | Response | Promise<void | Response>;
58
61
  }
59
62
  declare class ApiDocsPlugin implements IPlugin {
60
63
  private readonly artifact;
@@ -63,12 +66,15 @@ declare class ApiDocsPlugin implements IPlugin {
63
66
  private readonly uiTitle;
64
67
  private readonly reloadOnRequest;
65
68
  private readonly genOptions;
69
+ private readonly onOpenApiRequest?;
70
+ private readonly onUiRequest?;
66
71
  private app;
67
72
  private cachedSpec;
68
73
  constructor(options?: ApiDocsPluginOptions);
69
74
  afterModulesRegistered: (app: Application, hono: Hono) => Promise<void>;
70
75
  private normalizeRoute;
71
76
  private resolveSpec;
77
+ private runHook;
72
78
  private renderSwaggerUiHtml;
73
79
  private escapeHtml;
74
80
  private escapeJsString;
package/dist/index.js CHANGED
@@ -239,6 +239,8 @@ var ApiDocsPlugin = class {
239
239
  uiTitle;
240
240
  reloadOnRequest;
241
241
  genOptions;
242
+ onOpenApiRequest;
243
+ onUiRequest;
242
244
  app = null;
243
245
  cachedSpec = null;
244
246
  constructor(options = {}) {
@@ -247,6 +249,8 @@ var ApiDocsPlugin = class {
247
249
  this.uiRoute = this.normalizeRoute(options.uiRoute ?? DEFAULT_UI_ROUTE);
248
250
  this.uiTitle = options.uiTitle ?? DEFAULT_UI_TITLE;
249
251
  this.reloadOnRequest = options.reloadOnRequest ?? false;
252
+ this.onOpenApiRequest = options.onOpenApiRequest;
253
+ this.onUiRequest = options.onUiRequest;
250
254
  this.genOptions = {
251
255
  title: options.title,
252
256
  version: options.version,
@@ -258,6 +262,8 @@ var ApiDocsPlugin = class {
258
262
  this.app = app;
259
263
  hono.get(this.openApiRoute, async (c) => {
260
264
  try {
265
+ const earlyResponse = await this.runHook(this.onOpenApiRequest, c);
266
+ if (earlyResponse) return earlyResponse;
261
267
  const spec = await this.resolveSpec();
262
268
  return c.json(spec);
263
269
  } catch (error) {
@@ -270,7 +276,9 @@ var ApiDocsPlugin = class {
270
276
  );
271
277
  }
272
278
  });
273
- hono.get(this.uiRoute, (c) => {
279
+ hono.get(this.uiRoute, async (c) => {
280
+ const earlyResponse = await this.runHook(this.onUiRequest, c);
281
+ if (earlyResponse) return earlyResponse;
274
282
  return c.html(this.renderSwaggerUiHtml());
275
283
  });
276
284
  };
@@ -307,10 +315,30 @@ var ApiDocsPlugin = class {
307
315
  } else {
308
316
  artifact = this.artifact;
309
317
  }
318
+ const artifactVersion = artifact.artifactVersion;
319
+ if (artifactVersion !== void 0 && artifactVersion !== "1") {
320
+ throw new Error(
321
+ `ApiDocsPlugin: unsupported artifactVersion '${String(artifactVersion)}'. Supported versions: 1.`
322
+ );
323
+ }
310
324
  const spec = fromArtifactSync(artifact, this.genOptions);
311
325
  if (!this.reloadOnRequest) this.cachedSpec = spec;
312
326
  return spec;
313
327
  }
328
+ async runHook(hook, c) {
329
+ if (!hook) return void 0;
330
+ let nextCalled = false;
331
+ const maybeResponse = await hook(c, async () => {
332
+ nextCalled = true;
333
+ });
334
+ if (maybeResponse instanceof Response) {
335
+ return maybeResponse;
336
+ }
337
+ if (!nextCalled) {
338
+ return new Response("Forbidden", { status: 403 });
339
+ }
340
+ return void 0;
341
+ }
314
342
  renderSwaggerUiHtml() {
315
343
  const title = this.escapeHtml(this.uiTitle);
316
344
  const openApiRoute = this.escapeJsString(this.openApiRoute);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/openapi.generator.ts","../src/api-docs.plugin.ts"],"sourcesContent":["export { ApiDocsPlugin, DEFAULT_ARTIFACT_KEY } from './api-docs.plugin'\nexport type { ApiDocsPluginOptions, ArtifactInput } from './api-docs.plugin'\nexport { fromArtifact, fromArtifactSync, write } from './openapi.generator'\nexport type {\n\tOpenApiArtifactInput,\n\tOpenApiDocument,\n\tOpenApiGenerationOptions,\n\tOpenApiParameterInput,\n\tOpenApiRouteInput,\n\tOpenApiSchemaInput\n} from './openapi.generator'\nexport type { OpenAPIV3 } from 'openapi-types'\n","import fs from 'fs/promises'\nimport path from 'path'\nimport type { OpenAPIV3 } from 'openapi-types'\n\nexport interface OpenApiGenerationOptions {\n\treadonly title?: string\n\treadonly version?: string\n\treadonly description?: string\n\treadonly servers?: readonly { url: string; description?: string }[]\n}\n\nexport interface OpenApiArtifactInput {\n\treadonly routes: readonly OpenApiRouteInput[]\n\treadonly schemas: readonly OpenApiSchemaInput[]\n}\n\nexport interface OpenApiRouteInput {\n\treadonly method: string\n\treadonly handler: string\n\treadonly controller: string\n\treadonly fullPath: string\n\treadonly path?: string\n\treadonly prefix?: string\n\treadonly version?: string\n\treadonly route?: string\n\treadonly returns?: string\n\treadonly parameters?: readonly OpenApiParameterInput[]\n}\n\nexport interface OpenApiParameterInput {\n\treadonly name: string\n\treadonly data?: string\n\treadonly type: string\n\treadonly required?: boolean\n\treadonly decoratorType: string\n}\n\nexport interface OpenApiSchemaInput {\n\treadonly type: string\n\treadonly schema: Record<string, any>\n}\n\n/** OpenAPI 3.x document type (openapi-types). */\nexport type OpenApiDocument = OpenAPIV3.Document\n\ninterface ResolvedOpenApiOptions {\n\treadonly title: string\n\treadonly version: string\n\treadonly description: string\n\treadonly servers: readonly { url: string; description?: string }[]\n}\n\nfunction resolveOptions(options: OpenApiGenerationOptions = {}): ResolvedOpenApiOptions {\n\treturn {\n\t\ttitle: options.title ?? 'API',\n\t\tversion: options.version ?? '1.0.0',\n\t\tdescription: options.description ?? '',\n\t\tservers: options.servers ?? []\n\t}\n}\n\nexport function fromArtifactSync(\n\tartifact: OpenApiArtifactInput,\n\toptions: OpenApiGenerationOptions = {}\n): OpenApiDocument {\n\tconst resolved = resolveOptions(options)\n\tconst schemaMap = buildSchemaMap(artifact.schemas)\n\tconst spec: OpenAPIV3.Document = {\n\t\topenapi: '3.0.3',\n\t\tinfo: {\n\t\t\ttitle: resolved.title,\n\t\t\tversion: resolved.version,\n\t\t\tdescription: resolved.description\n\t\t},\n\t\tpaths: {},\n\t\tcomponents: {\n\t\t\tschemas: schemaMap as OpenAPIV3.ComponentsObject['schemas']\n\t\t}\n\t}\n\n\tif (resolved.servers.length > 0) {\n\t\tspec.servers = resolved.servers.map((server) => ({ ...server }))\n\t}\n\n\tfor (const route of artifact.routes) {\n\t\tconst routePath =\n\t\t\troute.prefix != null || route.version != null || route.route != null\n\t\t\t\t? buildFallbackPath(route)\n\t\t\t\t: (route.fullPath || buildFallbackPath(route))\n\t\tconst openApiPath = toOpenApiPath(routePath)\n\t\tconst method = route.method.toLowerCase() as keyof OpenAPIV3.PathItemObject\n\t\tif (!spec.paths[openApiPath]) {\n\t\t\tspec.paths[openApiPath] = {}\n\t\t}\n\t\t;(spec.paths[openApiPath] as Record<string, OpenAPIV3.OperationObject>)[method] = buildOperation(\n\t\t\troute,\n\t\t\tschemaMap\n\t\t)\n\t}\n\n\treturn spec\n}\n\nexport async function fromArtifact(\n\tartifact: OpenApiArtifactInput,\n\toptions: OpenApiGenerationOptions = {}\n): Promise<OpenApiDocument> {\n\treturn fromArtifactSync(artifact, options)\n}\n\nexport async function write(openapi: OpenApiDocument, outputPath: string): Promise<string> {\n\tconst absolute = path.isAbsolute(outputPath) ? outputPath : path.resolve(process.cwd(), outputPath)\n\tawait fs.mkdir(path.dirname(absolute), { recursive: true })\n\tawait fs.writeFile(absolute, JSON.stringify(openapi, null, 2), 'utf-8')\n\treturn absolute\n}\n\nfunction buildOperation(\n\troute: OpenApiRouteInput,\n\tschemaMap: Record<string, Record<string, any>>\n): OpenAPIV3.OperationObject {\n\tconst controllerName = route.controller.replace(/Controller$/, '')\n\tconst parameters = route.parameters ?? []\n\tconst operation: OpenAPIV3.OperationObject = {\n\t\toperationId: route.handler,\n\t\ttags: [controllerName],\n\t\tresponses: buildResponses(route.returns, schemaMap)\n\t}\n\n\tconst openApiParameters = buildParameters(parameters)\n\tif (openApiParameters.length > 0) {\n\t\toperation.parameters = openApiParameters\n\t}\n\n\tconst requestBody = buildRequestBody(parameters, schemaMap)\n\tif (requestBody) {\n\t\toperation.requestBody = requestBody\n\t}\n\n\treturn operation\n}\n\nfunction buildParameters(parameters: readonly OpenApiParameterInput[]): OpenAPIV3.ParameterObject[] {\n\tconst result: OpenAPIV3.ParameterObject[] = []\n\n\tfor (const param of parameters) {\n\t\tif (param.decoratorType === 'param') {\n\t\t\tresult.push({\n\t\t\t\tname: param.data ?? param.name,\n\t\t\t\tin: 'path',\n\t\t\t\trequired: true,\n\t\t\t\tschema: tsTypeToJsonSchema(param.type)\n\t\t\t})\n\t\t} else if (param.decoratorType === 'query') {\n\t\t\tresult.push({\n\t\t\t\tname: param.data ?? param.name,\n\t\t\t\tin: 'query',\n\t\t\t\trequired: param.required === true,\n\t\t\t\tschema: tsTypeToJsonSchema(param.type)\n\t\t\t})\n\t\t}\n\t}\n\n\treturn result\n}\n\nfunction buildRequestBody(\n\tparameters: readonly OpenApiParameterInput[],\n\tschemaMap: Record<string, Record<string, any>>\n): OpenAPIV3.RequestBodyObject | null {\n\tconst bodyParam = parameters.find((param) => param.decoratorType === 'body')\n\tif (!bodyParam) return null\n\n\tconst typeName = extractBaseTypeName(bodyParam.type)\n\tconst schema: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject =\n\t\ttypeName && schemaMap[typeName]\n\t\t\t? { $ref: `#/components/schemas/${typeName}` }\n\t\t\t: { type: 'object' as const }\n\n\treturn {\n\t\trequired: true,\n\t\tcontent: {\n\t\t\t'application/json': { schema }\n\t\t}\n\t}\n}\n\nfunction buildResponses(\n\treturns: string | undefined,\n\tschemaMap: Record<string, Record<string, any>>\n): OpenAPIV3.ResponsesObject {\n\tconst responseSchema = resolveResponseSchema(returns, schemaMap)\n\n\tif (!responseSchema) {\n\t\treturn { '200': { description: 'Successful response' } }\n\t}\n\n\treturn {\n\t\t'200': {\n\t\t\tdescription: 'Successful response',\n\t\t\tcontent: {\n\t\t\t\t'application/json': { schema: responseSchema }\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction resolveResponseSchema(\n\treturns: string | undefined,\n\tschemaMap: Record<string, Record<string, any>>\n): Record<string, any> | null {\n\tif (!returns) return null\n\n\tlet innerType = returns\n\tconst promiseMatch = returns.match(/^Promise<(.+)>$/)\n\tif (promiseMatch) {\n\t\tinnerType = promiseMatch[1]\n\t}\n\n\tconst isArray = innerType.endsWith('[]')\n\tconst baseType = isArray ? innerType.slice(0, -2) : innerType\n\tif (['string', 'number', 'boolean'].includes(baseType)) {\n\t\tconst primitiveSchema = tsTypeToJsonSchema(baseType)\n\t\treturn isArray ? { type: 'array', items: primitiveSchema } : primitiveSchema\n\t}\n\tif (['void', 'any', 'unknown'].includes(baseType)) return null\n\tif (schemaMap[baseType]) {\n\t\tconst ref = { $ref: `#/components/schemas/${baseType}` }\n\t\treturn isArray ? { type: 'array', items: ref } : ref\n\t}\n\treturn null\n}\n\nfunction buildSchemaMap(schemas: readonly OpenApiSchemaInput[]): Record<string, Record<string, any>> {\n\tconst result: Record<string, Record<string, any>> = {}\n\tfor (const schemaInfo of schemas) {\n\t\tconst definition = schemaInfo.schema?.definitions?.[schemaInfo.type]\n\t\tif (definition) {\n\t\t\tresult[schemaInfo.type] = definition\n\t\t}\n\t}\n\treturn result\n}\n\nfunction tsTypeToJsonSchema(tsType: string): Record<string, unknown> {\n\tswitch (tsType) {\n\t\tcase 'number':\n\t\t\treturn { type: 'number' as const }\n\t\tcase 'boolean':\n\t\t\treturn { type: 'boolean' as const }\n\t\tcase 'string':\n\t\tdefault:\n\t\t\treturn { type: 'string' as const }\n\t}\n}\n\nfunction extractBaseTypeName(tsType: string): string | null {\n\tif (!tsType) return null\n\n\tlet type = tsType\n\tconst promiseMatch = type.match(/^Promise<(.+)>$/)\n\tif (promiseMatch) type = promiseMatch[1]\n\ttype = type.replace(/\\[\\]$/, '')\n\tconst genericMatch = type.match(/^\\w+<(\\w+)>$/)\n\tif (genericMatch) type = genericMatch[1]\n\tif (['string', 'number', 'boolean', 'any', 'void', 'unknown', 'object'].includes(type)) {\n\t\treturn null\n\t}\n\treturn type\n}\n\nfunction toOpenApiPath(expressPath: string): string {\n\treturn expressPath.replace(/:(\\w+)/g, '{$1}')\n}\n\nfunction buildFallbackPath(route: OpenApiRouteInput): string {\n\tconst parts = [route.prefix, route.version, route.route, route.path]\n\t\t.filter((part) => part !== undefined && part !== null && part !== '')\n\t\t.map((part) => String(part).replace(/^\\/+|\\/+$/g, ''))\n\t\t.filter((part) => part.length > 0)\n\tconst joined = parts.join('/')\n\treturn `/${joined}`\n}\n","import type { Application, IPlugin } from 'honestjs'\nimport type { Hono } from 'hono'\n\nimport { fromArtifactSync } from './openapi.generator'\nimport type { OpenApiArtifactInput, OpenApiGenerationOptions } from './openapi.generator'\n\nconst DEFAULT_OPENAPI_ROUTE = '/openapi.json'\nconst DEFAULT_UI_ROUTE = '/docs'\nconst DEFAULT_UI_TITLE = 'API Docs'\n/** Default context key when using with RPC plugin (writes to this key). */\nexport const DEFAULT_ARTIFACT_KEY = 'rpc.artifact'\n\nexport type ArtifactInput = OpenApiArtifactInput | string\n\nfunction isContextKey(artifact: ArtifactInput): artifact is string {\n\treturn typeof artifact === 'string'\n}\n\nfunction isArtifact(value: unknown): value is OpenApiArtifactInput {\n\tif (!value || typeof value !== 'object' || Array.isArray(value)) return false\n\tconst obj = value as Record<string, unknown>\n\treturn Array.isArray(obj.routes) && Array.isArray(obj.schemas)\n}\n\nexport interface ApiDocsPluginOptions extends OpenApiGenerationOptions {\n\t/** Artifact: direct object `{ routes, schemas }` or context key string (e.g. `'rpc.artifact'`). Defaults to `'rpc.artifact'` when omitted. */\n\treadonly artifact?: ArtifactInput\n\treadonly openApiRoute?: string\n\treadonly uiRoute?: string\n\treadonly uiTitle?: string\n\treadonly reloadOnRequest?: boolean\n}\n\nexport class ApiDocsPlugin implements IPlugin {\n\tprivate readonly artifact: ArtifactInput\n\tprivate readonly openApiRoute: string\n\tprivate readonly uiRoute: string\n\tprivate readonly uiTitle: string\n\tprivate readonly reloadOnRequest: boolean\n\tprivate readonly genOptions: OpenApiGenerationOptions\n\n\tprivate app: Application | null = null\n\tprivate cachedSpec: Record<string, unknown> | null = null\n\n\tconstructor(options: ApiDocsPluginOptions = {}) {\n\t\tthis.artifact = options.artifact ?? DEFAULT_ARTIFACT_KEY\n\t\tthis.openApiRoute = this.normalizeRoute(options.openApiRoute ?? DEFAULT_OPENAPI_ROUTE)\n\t\tthis.uiRoute = this.normalizeRoute(options.uiRoute ?? DEFAULT_UI_ROUTE)\n\t\tthis.uiTitle = options.uiTitle ?? DEFAULT_UI_TITLE\n\t\tthis.reloadOnRequest = options.reloadOnRequest ?? false\n\t\tthis.genOptions = {\n\t\t\ttitle: options.title,\n\t\t\tversion: options.version,\n\t\t\tdescription: options.description,\n\t\t\tservers: options.servers\n\t\t}\n\t}\n\n\tafterModulesRegistered = async (app: Application, hono: Hono): Promise<void> => {\n\t\tthis.app = app\n\n\t\thono.get(this.openApiRoute, async (c) => {\n\t\t\ttry {\n\t\t\t\tconst spec = await this.resolveSpec()\n\t\t\t\treturn c.json(spec)\n\t\t\t} catch (error) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: 'Failed to load OpenAPI spec',\n\t\t\t\t\t\tmessage: this.toErrorMessage(error)\n\t\t\t\t\t},\n\t\t\t\t\t500\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\n\t\thono.get(this.uiRoute, (c) => {\n\t\t\treturn c.html(this.renderSwaggerUiHtml())\n\t\t})\n\t}\n\n\tprivate normalizeRoute(input: string): string {\n\t\tconst trimmed = input.trim()\n\t\tif (!trimmed) return '/'\n\t\tlet normalized = trimmed.startsWith('/') ? trimmed : `/${trimmed}`\n\t\tif (normalized.length > 1) {\n\t\t\tnormalized = normalized.replace(/\\/+$/g, '')\n\t\t}\n\t\treturn normalized || '/'\n\t}\n\n\tprivate async resolveSpec(): Promise<Record<string, unknown>> {\n\t\tif (!this.reloadOnRequest && this.cachedSpec) {\n\t\t\treturn this.cachedSpec\n\t\t}\n\n\t\tlet artifact: OpenApiArtifactInput\n\n\t\tif (isContextKey(this.artifact)) {\n\t\t\tif (!this.app) {\n\t\t\t\tthrow new Error('ApiDocsPlugin: app not available when resolving artifact from context')\n\t\t\t}\n\t\t\tconst value = this.app.getContext().get<unknown>(this.artifact)\n\t\t\tif (value === undefined) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`ApiDocsPlugin: no artifact at context key '${this.artifact}'. Ensure RPC plugin (or another producer) runs before ApiDocs and writes to this key.`\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (!isArtifact(value)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`ApiDocsPlugin: value at '${this.artifact}' is not a valid artifact (expected object with routes and schemas)`\n\t\t\t\t)\n\t\t\t}\n\t\t\tartifact = value\n\t\t} else {\n\t\t\tartifact = this.artifact\n\t\t}\n\n\t\tconst spec = fromArtifactSync(artifact, this.genOptions) as unknown as Record<string, unknown>\n\t\tif (!this.reloadOnRequest) this.cachedSpec = spec\n\t\treturn spec\n\t}\n\n\tprivate renderSwaggerUiHtml(): string {\n\t\tconst title = this.escapeHtml(this.uiTitle)\n\t\tconst openApiRoute = this.escapeJsString(this.openApiRoute)\n\n\t\treturn `<!doctype html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"utf-8\" />\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t<title>${title}</title>\n\t<link rel=\"stylesheet\" href=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui.css\" />\n</head>\n<body>\n\t<div id=\"swagger-ui\"></div>\n\t<script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js\"></script>\n\t<script>\n\t\twindow.onload = function () {\n\t\t\twindow.ui = SwaggerUIBundle({\n\t\t\t\turl: '${openApiRoute}',\n\t\t\t\tdom_id: '#swagger-ui'\n\t\t\t});\n\t\t};\n\t</script>\n</body>\n</html>`\n\t}\n\n\tprivate escapeHtml(value: string): string {\n\t\treturn value\n\t\t\t.replace(/&/g, '&amp;')\n\t\t\t.replace(/</g, '&lt;')\n\t\t\t.replace(/>/g, '&gt;')\n\t\t\t.replace(/\"/g, '&quot;')\n\t\t\t.replace(/'/g, '&#39;')\n\t}\n\n\tprivate escapeJsString(value: string): string {\n\t\treturn value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\")\n\t}\n\n\tprivate toErrorMessage(error: unknown): string {\n\t\treturn error instanceof Error ? error.message : String(error)\n\t}\n}\n"],"mappings":"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","names":["path","fs"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/openapi.generator.ts","../src/api-docs.plugin.ts"],"sourcesContent":["export { ApiDocsPlugin, DEFAULT_ARTIFACT_KEY } from './api-docs.plugin'\nexport type { ApiDocsPluginOptions, ArtifactInput } from './api-docs.plugin'\nexport { fromArtifact, fromArtifactSync, write } from './openapi.generator'\nexport type {\n\tOpenApiArtifactInput,\n\tOpenApiDocument,\n\tOpenApiGenerationOptions,\n\tOpenApiParameterInput,\n\tOpenApiRouteInput,\n\tOpenApiSchemaInput\n} from './openapi.generator'\nexport type { OpenAPIV3 } from 'openapi-types'\n","import fs from 'fs/promises'\nimport path from 'path'\nimport type { OpenAPIV3 } from 'openapi-types'\n\nexport interface OpenApiGenerationOptions {\n\treadonly title?: string\n\treadonly version?: string\n\treadonly description?: string\n\treadonly servers?: readonly { url: string; description?: string }[]\n}\n\nexport interface OpenApiArtifactInput {\n\treadonly artifactVersion?: string\n\treadonly routes: readonly OpenApiRouteInput[]\n\treadonly schemas: readonly OpenApiSchemaInput[]\n}\n\nexport interface OpenApiRouteInput {\n\treadonly method: string\n\treadonly handler: string\n\treadonly controller: string\n\treadonly fullPath: string\n\treadonly path?: string\n\treadonly prefix?: string\n\treadonly version?: string\n\treadonly route?: string\n\treadonly returns?: string\n\treadonly parameters?: readonly OpenApiParameterInput[]\n}\n\nexport interface OpenApiParameterInput {\n\treadonly name: string\n\treadonly data?: string\n\treadonly type: string\n\treadonly required?: boolean\n\treadonly decoratorType: string\n}\n\nexport interface OpenApiSchemaInput {\n\treadonly type: string\n\treadonly schema: Record<string, any>\n}\n\n/** OpenAPI 3.x document type (openapi-types). */\nexport type OpenApiDocument = OpenAPIV3.Document\n\ninterface ResolvedOpenApiOptions {\n\treadonly title: string\n\treadonly version: string\n\treadonly description: string\n\treadonly servers: readonly { url: string; description?: string }[]\n}\n\nfunction resolveOptions(options: OpenApiGenerationOptions = {}): ResolvedOpenApiOptions {\n\treturn {\n\t\ttitle: options.title ?? 'API',\n\t\tversion: options.version ?? '1.0.0',\n\t\tdescription: options.description ?? '',\n\t\tservers: options.servers ?? []\n\t}\n}\n\nexport function fromArtifactSync(\n\tartifact: OpenApiArtifactInput,\n\toptions: OpenApiGenerationOptions = {}\n): OpenApiDocument {\n\tconst resolved = resolveOptions(options)\n\tconst schemaMap = buildSchemaMap(artifact.schemas)\n\tconst spec: OpenAPIV3.Document = {\n\t\topenapi: '3.0.3',\n\t\tinfo: {\n\t\t\ttitle: resolved.title,\n\t\t\tversion: resolved.version,\n\t\t\tdescription: resolved.description\n\t\t},\n\t\tpaths: {},\n\t\tcomponents: {\n\t\t\tschemas: schemaMap as OpenAPIV3.ComponentsObject['schemas']\n\t\t}\n\t}\n\n\tif (resolved.servers.length > 0) {\n\t\tspec.servers = resolved.servers.map((server) => ({ ...server }))\n\t}\n\n\tfor (const route of artifact.routes) {\n\t\tconst routePath =\n\t\t\troute.prefix != null || route.version != null || route.route != null\n\t\t\t\t? buildFallbackPath(route)\n\t\t\t\t: route.fullPath || buildFallbackPath(route)\n\t\tconst openApiPath = toOpenApiPath(routePath)\n\t\tconst method = route.method.toLowerCase() as keyof OpenAPIV3.PathItemObject\n\t\tif (!spec.paths[openApiPath]) {\n\t\t\tspec.paths[openApiPath] = {}\n\t\t}\n\t\t;(spec.paths[openApiPath] as Record<string, OpenAPIV3.OperationObject>)[method] = buildOperation(\n\t\t\troute,\n\t\t\tschemaMap\n\t\t)\n\t}\n\n\treturn spec\n}\n\nexport async function fromArtifact(\n\tartifact: OpenApiArtifactInput,\n\toptions: OpenApiGenerationOptions = {}\n): Promise<OpenApiDocument> {\n\treturn fromArtifactSync(artifact, options)\n}\n\nexport async function write(openapi: OpenApiDocument, outputPath: string): Promise<string> {\n\tconst absolute = path.isAbsolute(outputPath) ? outputPath : path.resolve(process.cwd(), outputPath)\n\tawait fs.mkdir(path.dirname(absolute), { recursive: true })\n\tawait fs.writeFile(absolute, JSON.stringify(openapi, null, 2), 'utf-8')\n\treturn absolute\n}\n\nfunction buildOperation(\n\troute: OpenApiRouteInput,\n\tschemaMap: Record<string, Record<string, any>>\n): OpenAPIV3.OperationObject {\n\tconst controllerName = route.controller.replace(/Controller$/, '')\n\tconst parameters = route.parameters ?? []\n\tconst operation: OpenAPIV3.OperationObject = {\n\t\toperationId: route.handler,\n\t\ttags: [controllerName],\n\t\tresponses: buildResponses(route.returns, schemaMap)\n\t}\n\n\tconst openApiParameters = buildParameters(parameters)\n\tif (openApiParameters.length > 0) {\n\t\toperation.parameters = openApiParameters\n\t}\n\n\tconst requestBody = buildRequestBody(parameters, schemaMap)\n\tif (requestBody) {\n\t\toperation.requestBody = requestBody\n\t}\n\n\treturn operation\n}\n\nfunction buildParameters(parameters: readonly OpenApiParameterInput[]): OpenAPIV3.ParameterObject[] {\n\tconst result: OpenAPIV3.ParameterObject[] = []\n\n\tfor (const param of parameters) {\n\t\tif (param.decoratorType === 'param') {\n\t\t\tresult.push({\n\t\t\t\tname: param.data ?? param.name,\n\t\t\t\tin: 'path',\n\t\t\t\trequired: true,\n\t\t\t\tschema: tsTypeToJsonSchema(param.type)\n\t\t\t})\n\t\t} else if (param.decoratorType === 'query') {\n\t\t\tresult.push({\n\t\t\t\tname: param.data ?? param.name,\n\t\t\t\tin: 'query',\n\t\t\t\trequired: param.required === true,\n\t\t\t\tschema: tsTypeToJsonSchema(param.type)\n\t\t\t})\n\t\t}\n\t}\n\n\treturn result\n}\n\nfunction buildRequestBody(\n\tparameters: readonly OpenApiParameterInput[],\n\tschemaMap: Record<string, Record<string, any>>\n): OpenAPIV3.RequestBodyObject | null {\n\tconst bodyParam = parameters.find((param) => param.decoratorType === 'body')\n\tif (!bodyParam) return null\n\n\tconst typeName = extractBaseTypeName(bodyParam.type)\n\tconst schema: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject =\n\t\ttypeName && schemaMap[typeName] ? { $ref: `#/components/schemas/${typeName}` } : { type: 'object' as const }\n\n\treturn {\n\t\trequired: true,\n\t\tcontent: {\n\t\t\t'application/json': { schema }\n\t\t}\n\t}\n}\n\nfunction buildResponses(\n\treturns: string | undefined,\n\tschemaMap: Record<string, Record<string, any>>\n): OpenAPIV3.ResponsesObject {\n\tconst responseSchema = resolveResponseSchema(returns, schemaMap)\n\n\tif (!responseSchema) {\n\t\treturn { '200': { description: 'Successful response' } }\n\t}\n\n\treturn {\n\t\t'200': {\n\t\t\tdescription: 'Successful response',\n\t\t\tcontent: {\n\t\t\t\t'application/json': { schema: responseSchema }\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction resolveResponseSchema(\n\treturns: string | undefined,\n\tschemaMap: Record<string, Record<string, any>>\n): Record<string, any> | null {\n\tif (!returns) return null\n\n\tlet innerType = returns\n\tconst promiseMatch = returns.match(/^Promise<(.+)>$/)\n\tif (promiseMatch) {\n\t\tinnerType = promiseMatch[1]\n\t}\n\n\tconst isArray = innerType.endsWith('[]')\n\tconst baseType = isArray ? innerType.slice(0, -2) : innerType\n\tif (['string', 'number', 'boolean'].includes(baseType)) {\n\t\tconst primitiveSchema = tsTypeToJsonSchema(baseType)\n\t\treturn isArray ? { type: 'array', items: primitiveSchema } : primitiveSchema\n\t}\n\tif (['void', 'any', 'unknown'].includes(baseType)) return null\n\tif (schemaMap[baseType]) {\n\t\tconst ref = { $ref: `#/components/schemas/${baseType}` }\n\t\treturn isArray ? { type: 'array', items: ref } : ref\n\t}\n\treturn null\n}\n\nfunction buildSchemaMap(schemas: readonly OpenApiSchemaInput[]): Record<string, Record<string, any>> {\n\tconst result: Record<string, Record<string, any>> = {}\n\tfor (const schemaInfo of schemas) {\n\t\tconst definition = schemaInfo.schema?.definitions?.[schemaInfo.type]\n\t\tif (definition) {\n\t\t\tresult[schemaInfo.type] = definition\n\t\t}\n\t}\n\treturn result\n}\n\nfunction tsTypeToJsonSchema(tsType: string): Record<string, unknown> {\n\tswitch (tsType) {\n\t\tcase 'number':\n\t\t\treturn { type: 'number' as const }\n\t\tcase 'boolean':\n\t\t\treturn { type: 'boolean' as const }\n\t\tcase 'string':\n\t\tdefault:\n\t\t\treturn { type: 'string' as const }\n\t}\n}\n\nfunction extractBaseTypeName(tsType: string): string | null {\n\tif (!tsType) return null\n\n\tlet type = tsType\n\tconst promiseMatch = type.match(/^Promise<(.+)>$/)\n\tif (promiseMatch) type = promiseMatch[1]\n\ttype = type.replace(/\\[\\]$/, '')\n\tconst genericMatch = type.match(/^\\w+<(\\w+)>$/)\n\tif (genericMatch) type = genericMatch[1]\n\tif (['string', 'number', 'boolean', 'any', 'void', 'unknown', 'object'].includes(type)) {\n\t\treturn null\n\t}\n\treturn type\n}\n\nfunction toOpenApiPath(expressPath: string): string {\n\treturn expressPath.replace(/:(\\w+)/g, '{$1}')\n}\n\nfunction buildFallbackPath(route: OpenApiRouteInput): string {\n\tconst parts = [route.prefix, route.version, route.route, route.path]\n\t\t.filter((part) => part !== undefined && part !== null && part !== '')\n\t\t.map((part) => String(part).replace(/^\\/+|\\/+$/g, ''))\n\t\t.filter((part) => part.length > 0)\n\tconst joined = parts.join('/')\n\treturn `/${joined}`\n}\n","import type { Application, IPlugin } from 'honestjs'\nimport type { Context, Hono, Next } from 'hono'\n\nimport { fromArtifactSync } from './openapi.generator'\nimport type { OpenApiArtifactInput, OpenApiGenerationOptions } from './openapi.generator'\n\nconst DEFAULT_OPENAPI_ROUTE = '/openapi.json'\nconst DEFAULT_UI_ROUTE = '/docs'\nconst DEFAULT_UI_TITLE = 'API Docs'\n/** Default context key when using with RPC plugin (writes to this key). */\nexport const DEFAULT_ARTIFACT_KEY = 'rpc.artifact'\n\nexport type ArtifactInput = OpenApiArtifactInput | string\n\nfunction isContextKey(artifact: ArtifactInput): artifact is string {\n\treturn typeof artifact === 'string'\n}\n\nfunction isArtifact(value: unknown): value is OpenApiArtifactInput {\n\tif (!value || typeof value !== 'object' || Array.isArray(value)) return false\n\tconst obj = value as Record<string, unknown>\n\treturn Array.isArray(obj.routes) && Array.isArray(obj.schemas)\n}\n\nexport interface ApiDocsPluginOptions extends OpenApiGenerationOptions {\n\t/** Artifact: direct object `{ routes, schemas }` or context key string (e.g. `'rpc.artifact'`). Defaults to `'rpc.artifact'` when omitted. */\n\treadonly artifact?: ArtifactInput\n\treadonly openApiRoute?: string\n\treadonly uiRoute?: string\n\treadonly uiTitle?: string\n\treadonly reloadOnRequest?: boolean\n\treadonly onOpenApiRequest?: (c: Context, next: Next) => void | Response | Promise<void | Response>\n\treadonly onUiRequest?: (c: Context, next: Next) => void | Response | Promise<void | Response>\n}\n\nexport class ApiDocsPlugin implements IPlugin {\n\tprivate readonly artifact: ArtifactInput\n\tprivate readonly openApiRoute: string\n\tprivate readonly uiRoute: string\n\tprivate readonly uiTitle: string\n\tprivate readonly reloadOnRequest: boolean\n\tprivate readonly genOptions: OpenApiGenerationOptions\n\tprivate readonly onOpenApiRequest?: (c: Context, next: Next) => void | Response | Promise<void | Response>\n\tprivate readonly onUiRequest?: (c: Context, next: Next) => void | Response | Promise<void | Response>\n\n\tprivate app: Application | null = null\n\tprivate cachedSpec: Record<string, unknown> | null = null\n\n\tconstructor(options: ApiDocsPluginOptions = {}) {\n\t\tthis.artifact = options.artifact ?? DEFAULT_ARTIFACT_KEY\n\t\tthis.openApiRoute = this.normalizeRoute(options.openApiRoute ?? DEFAULT_OPENAPI_ROUTE)\n\t\tthis.uiRoute = this.normalizeRoute(options.uiRoute ?? DEFAULT_UI_ROUTE)\n\t\tthis.uiTitle = options.uiTitle ?? DEFAULT_UI_TITLE\n\t\tthis.reloadOnRequest = options.reloadOnRequest ?? false\n\t\tthis.onOpenApiRequest = options.onOpenApiRequest\n\t\tthis.onUiRequest = options.onUiRequest\n\t\tthis.genOptions = {\n\t\t\ttitle: options.title,\n\t\t\tversion: options.version,\n\t\t\tdescription: options.description,\n\t\t\tservers: options.servers\n\t\t}\n\t}\n\n\tafterModulesRegistered = async (app: Application, hono: Hono): Promise<void> => {\n\t\tthis.app = app\n\n\t\thono.get(this.openApiRoute, async (c) => {\n\t\t\ttry {\n\t\t\t\tconst earlyResponse = await this.runHook(this.onOpenApiRequest, c)\n\t\t\t\tif (earlyResponse) return earlyResponse\n\t\t\t\tconst spec = await this.resolveSpec()\n\t\t\t\treturn c.json(spec)\n\t\t\t} catch (error) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: 'Failed to load OpenAPI spec',\n\t\t\t\t\t\tmessage: this.toErrorMessage(error)\n\t\t\t\t\t},\n\t\t\t\t\t500\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\n\t\thono.get(this.uiRoute, async (c) => {\n\t\t\tconst earlyResponse = await this.runHook(this.onUiRequest, c)\n\t\t\tif (earlyResponse) return earlyResponse\n\t\t\treturn c.html(this.renderSwaggerUiHtml())\n\t\t})\n\t}\n\n\tprivate normalizeRoute(input: string): string {\n\t\tconst trimmed = input.trim()\n\t\tif (!trimmed) return '/'\n\t\tlet normalized = trimmed.startsWith('/') ? trimmed : `/${trimmed}`\n\t\tif (normalized.length > 1) {\n\t\t\tnormalized = normalized.replace(/\\/+$/g, '')\n\t\t}\n\t\treturn normalized || '/'\n\t}\n\n\tprivate async resolveSpec(): Promise<Record<string, unknown>> {\n\t\tif (!this.reloadOnRequest && this.cachedSpec) {\n\t\t\treturn this.cachedSpec\n\t\t}\n\n\t\tlet artifact: OpenApiArtifactInput\n\n\t\tif (isContextKey(this.artifact)) {\n\t\t\tif (!this.app) {\n\t\t\t\tthrow new Error('ApiDocsPlugin: app not available when resolving artifact from context')\n\t\t\t}\n\t\t\tconst value = this.app.getContext().get<unknown>(this.artifact)\n\t\t\tif (value === undefined) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`ApiDocsPlugin: no artifact at context key '${this.artifact}'. Ensure RPC plugin (or another producer) runs before ApiDocs and writes to this key.`\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (!isArtifact(value)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`ApiDocsPlugin: value at '${this.artifact}' is not a valid artifact (expected object with routes and schemas)`\n\t\t\t\t)\n\t\t\t}\n\t\t\tartifact = value\n\t\t} else {\n\t\t\tartifact = this.artifact\n\t\t}\n\n\t\tconst artifactVersion = (artifact as { artifactVersion?: unknown }).artifactVersion\n\t\tif (artifactVersion !== undefined && artifactVersion !== '1') {\n\t\t\tthrow new Error(\n\t\t\t\t`ApiDocsPlugin: unsupported artifactVersion '${String(artifactVersion)}'. Supported versions: 1.`\n\t\t\t)\n\t\t}\n\n\t\tconst spec = fromArtifactSync(artifact, this.genOptions) as unknown as Record<string, unknown>\n\t\tif (!this.reloadOnRequest) this.cachedSpec = spec\n\t\treturn spec\n\t}\n\n\tprivate async runHook(\n\t\thook: ((c: Context, next: Next) => void | Response | Promise<void | Response>) | undefined,\n\t\tc: Context\n\t): Promise<Response | undefined> {\n\t\tif (!hook) return undefined\n\t\tlet nextCalled = false\n\t\tconst maybeResponse = await hook(c, async () => {\n\t\t\tnextCalled = true\n\t\t})\n\t\tif (maybeResponse instanceof Response) {\n\t\t\treturn maybeResponse\n\t\t}\n\t\tif (!nextCalled) {\n\t\t\treturn new Response('Forbidden', { status: 403 })\n\t\t}\n\t\treturn undefined\n\t}\n\n\tprivate renderSwaggerUiHtml(): string {\n\t\tconst title = this.escapeHtml(this.uiTitle)\n\t\tconst openApiRoute = this.escapeJsString(this.openApiRoute)\n\n\t\treturn `<!doctype html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"utf-8\" />\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t<title>${title}</title>\n\t<link rel=\"stylesheet\" href=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui.css\" />\n</head>\n<body>\n\t<div id=\"swagger-ui\"></div>\n\t<script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js\"></script>\n\t<script>\n\t\twindow.onload = function () {\n\t\t\twindow.ui = SwaggerUIBundle({\n\t\t\t\turl: '${openApiRoute}',\n\t\t\t\tdom_id: '#swagger-ui'\n\t\t\t});\n\t\t};\n\t</script>\n</body>\n</html>`\n\t}\n\n\tprivate escapeHtml(value: string): string {\n\t\treturn value\n\t\t\t.replace(/&/g, '&amp;')\n\t\t\t.replace(/</g, '&lt;')\n\t\t\t.replace(/>/g, '&gt;')\n\t\t\t.replace(/\"/g, '&quot;')\n\t\t\t.replace(/'/g, '&#39;')\n\t}\n\n\tprivate escapeJsString(value: string): string {\n\t\treturn value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\")\n\t}\n\n\tprivate toErrorMessage(error: unknown): string {\n\t\treturn error instanceof Error ? error.message : String(error)\n\t}\n}\n"],"mappings":"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","names":["path","fs"]}
package/dist/index.mjs CHANGED
@@ -199,6 +199,8 @@ var ApiDocsPlugin = class {
199
199
  uiTitle;
200
200
  reloadOnRequest;
201
201
  genOptions;
202
+ onOpenApiRequest;
203
+ onUiRequest;
202
204
  app = null;
203
205
  cachedSpec = null;
204
206
  constructor(options = {}) {
@@ -207,6 +209,8 @@ var ApiDocsPlugin = class {
207
209
  this.uiRoute = this.normalizeRoute(options.uiRoute ?? DEFAULT_UI_ROUTE);
208
210
  this.uiTitle = options.uiTitle ?? DEFAULT_UI_TITLE;
209
211
  this.reloadOnRequest = options.reloadOnRequest ?? false;
212
+ this.onOpenApiRequest = options.onOpenApiRequest;
213
+ this.onUiRequest = options.onUiRequest;
210
214
  this.genOptions = {
211
215
  title: options.title,
212
216
  version: options.version,
@@ -218,6 +222,8 @@ var ApiDocsPlugin = class {
218
222
  this.app = app;
219
223
  hono.get(this.openApiRoute, async (c) => {
220
224
  try {
225
+ const earlyResponse = await this.runHook(this.onOpenApiRequest, c);
226
+ if (earlyResponse) return earlyResponse;
221
227
  const spec = await this.resolveSpec();
222
228
  return c.json(spec);
223
229
  } catch (error) {
@@ -230,7 +236,9 @@ var ApiDocsPlugin = class {
230
236
  );
231
237
  }
232
238
  });
233
- hono.get(this.uiRoute, (c) => {
239
+ hono.get(this.uiRoute, async (c) => {
240
+ const earlyResponse = await this.runHook(this.onUiRequest, c);
241
+ if (earlyResponse) return earlyResponse;
234
242
  return c.html(this.renderSwaggerUiHtml());
235
243
  });
236
244
  };
@@ -267,10 +275,30 @@ var ApiDocsPlugin = class {
267
275
  } else {
268
276
  artifact = this.artifact;
269
277
  }
278
+ const artifactVersion = artifact.artifactVersion;
279
+ if (artifactVersion !== void 0 && artifactVersion !== "1") {
280
+ throw new Error(
281
+ `ApiDocsPlugin: unsupported artifactVersion '${String(artifactVersion)}'. Supported versions: 1.`
282
+ );
283
+ }
270
284
  const spec = fromArtifactSync(artifact, this.genOptions);
271
285
  if (!this.reloadOnRequest) this.cachedSpec = spec;
272
286
  return spec;
273
287
  }
288
+ async runHook(hook, c) {
289
+ if (!hook) return void 0;
290
+ let nextCalled = false;
291
+ const maybeResponse = await hook(c, async () => {
292
+ nextCalled = true;
293
+ });
294
+ if (maybeResponse instanceof Response) {
295
+ return maybeResponse;
296
+ }
297
+ if (!nextCalled) {
298
+ return new Response("Forbidden", { status: 403 });
299
+ }
300
+ return void 0;
301
+ }
274
302
  renderSwaggerUiHtml() {
275
303
  const title = this.escapeHtml(this.uiTitle);
276
304
  const openApiRoute = this.escapeJsString(this.openApiRoute);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/openapi.generator.ts","../src/api-docs.plugin.ts"],"sourcesContent":["import fs from 'fs/promises'\nimport path from 'path'\nimport type { OpenAPIV3 } from 'openapi-types'\n\nexport interface OpenApiGenerationOptions {\n\treadonly title?: string\n\treadonly version?: string\n\treadonly description?: string\n\treadonly servers?: readonly { url: string; description?: string }[]\n}\n\nexport interface OpenApiArtifactInput {\n\treadonly routes: readonly OpenApiRouteInput[]\n\treadonly schemas: readonly OpenApiSchemaInput[]\n}\n\nexport interface OpenApiRouteInput {\n\treadonly method: string\n\treadonly handler: string\n\treadonly controller: string\n\treadonly fullPath: string\n\treadonly path?: string\n\treadonly prefix?: string\n\treadonly version?: string\n\treadonly route?: string\n\treadonly returns?: string\n\treadonly parameters?: readonly OpenApiParameterInput[]\n}\n\nexport interface OpenApiParameterInput {\n\treadonly name: string\n\treadonly data?: string\n\treadonly type: string\n\treadonly required?: boolean\n\treadonly decoratorType: string\n}\n\nexport interface OpenApiSchemaInput {\n\treadonly type: string\n\treadonly schema: Record<string, any>\n}\n\n/** OpenAPI 3.x document type (openapi-types). */\nexport type OpenApiDocument = OpenAPIV3.Document\n\ninterface ResolvedOpenApiOptions {\n\treadonly title: string\n\treadonly version: string\n\treadonly description: string\n\treadonly servers: readonly { url: string; description?: string }[]\n}\n\nfunction resolveOptions(options: OpenApiGenerationOptions = {}): ResolvedOpenApiOptions {\n\treturn {\n\t\ttitle: options.title ?? 'API',\n\t\tversion: options.version ?? '1.0.0',\n\t\tdescription: options.description ?? '',\n\t\tservers: options.servers ?? []\n\t}\n}\n\nexport function fromArtifactSync(\n\tartifact: OpenApiArtifactInput,\n\toptions: OpenApiGenerationOptions = {}\n): OpenApiDocument {\n\tconst resolved = resolveOptions(options)\n\tconst schemaMap = buildSchemaMap(artifact.schemas)\n\tconst spec: OpenAPIV3.Document = {\n\t\topenapi: '3.0.3',\n\t\tinfo: {\n\t\t\ttitle: resolved.title,\n\t\t\tversion: resolved.version,\n\t\t\tdescription: resolved.description\n\t\t},\n\t\tpaths: {},\n\t\tcomponents: {\n\t\t\tschemas: schemaMap as OpenAPIV3.ComponentsObject['schemas']\n\t\t}\n\t}\n\n\tif (resolved.servers.length > 0) {\n\t\tspec.servers = resolved.servers.map((server) => ({ ...server }))\n\t}\n\n\tfor (const route of artifact.routes) {\n\t\tconst routePath =\n\t\t\troute.prefix != null || route.version != null || route.route != null\n\t\t\t\t? buildFallbackPath(route)\n\t\t\t\t: (route.fullPath || buildFallbackPath(route))\n\t\tconst openApiPath = toOpenApiPath(routePath)\n\t\tconst method = route.method.toLowerCase() as keyof OpenAPIV3.PathItemObject\n\t\tif (!spec.paths[openApiPath]) {\n\t\t\tspec.paths[openApiPath] = {}\n\t\t}\n\t\t;(spec.paths[openApiPath] as Record<string, OpenAPIV3.OperationObject>)[method] = buildOperation(\n\t\t\troute,\n\t\t\tschemaMap\n\t\t)\n\t}\n\n\treturn spec\n}\n\nexport async function fromArtifact(\n\tartifact: OpenApiArtifactInput,\n\toptions: OpenApiGenerationOptions = {}\n): Promise<OpenApiDocument> {\n\treturn fromArtifactSync(artifact, options)\n}\n\nexport async function write(openapi: OpenApiDocument, outputPath: string): Promise<string> {\n\tconst absolute = path.isAbsolute(outputPath) ? outputPath : path.resolve(process.cwd(), outputPath)\n\tawait fs.mkdir(path.dirname(absolute), { recursive: true })\n\tawait fs.writeFile(absolute, JSON.stringify(openapi, null, 2), 'utf-8')\n\treturn absolute\n}\n\nfunction buildOperation(\n\troute: OpenApiRouteInput,\n\tschemaMap: Record<string, Record<string, any>>\n): OpenAPIV3.OperationObject {\n\tconst controllerName = route.controller.replace(/Controller$/, '')\n\tconst parameters = route.parameters ?? []\n\tconst operation: OpenAPIV3.OperationObject = {\n\t\toperationId: route.handler,\n\t\ttags: [controllerName],\n\t\tresponses: buildResponses(route.returns, schemaMap)\n\t}\n\n\tconst openApiParameters = buildParameters(parameters)\n\tif (openApiParameters.length > 0) {\n\t\toperation.parameters = openApiParameters\n\t}\n\n\tconst requestBody = buildRequestBody(parameters, schemaMap)\n\tif (requestBody) {\n\t\toperation.requestBody = requestBody\n\t}\n\n\treturn operation\n}\n\nfunction buildParameters(parameters: readonly OpenApiParameterInput[]): OpenAPIV3.ParameterObject[] {\n\tconst result: OpenAPIV3.ParameterObject[] = []\n\n\tfor (const param of parameters) {\n\t\tif (param.decoratorType === 'param') {\n\t\t\tresult.push({\n\t\t\t\tname: param.data ?? param.name,\n\t\t\t\tin: 'path',\n\t\t\t\trequired: true,\n\t\t\t\tschema: tsTypeToJsonSchema(param.type)\n\t\t\t})\n\t\t} else if (param.decoratorType === 'query') {\n\t\t\tresult.push({\n\t\t\t\tname: param.data ?? param.name,\n\t\t\t\tin: 'query',\n\t\t\t\trequired: param.required === true,\n\t\t\t\tschema: tsTypeToJsonSchema(param.type)\n\t\t\t})\n\t\t}\n\t}\n\n\treturn result\n}\n\nfunction buildRequestBody(\n\tparameters: readonly OpenApiParameterInput[],\n\tschemaMap: Record<string, Record<string, any>>\n): OpenAPIV3.RequestBodyObject | null {\n\tconst bodyParam = parameters.find((param) => param.decoratorType === 'body')\n\tif (!bodyParam) return null\n\n\tconst typeName = extractBaseTypeName(bodyParam.type)\n\tconst schema: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject =\n\t\ttypeName && schemaMap[typeName]\n\t\t\t? { $ref: `#/components/schemas/${typeName}` }\n\t\t\t: { type: 'object' as const }\n\n\treturn {\n\t\trequired: true,\n\t\tcontent: {\n\t\t\t'application/json': { schema }\n\t\t}\n\t}\n}\n\nfunction buildResponses(\n\treturns: string | undefined,\n\tschemaMap: Record<string, Record<string, any>>\n): OpenAPIV3.ResponsesObject {\n\tconst responseSchema = resolveResponseSchema(returns, schemaMap)\n\n\tif (!responseSchema) {\n\t\treturn { '200': { description: 'Successful response' } }\n\t}\n\n\treturn {\n\t\t'200': {\n\t\t\tdescription: 'Successful response',\n\t\t\tcontent: {\n\t\t\t\t'application/json': { schema: responseSchema }\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction resolveResponseSchema(\n\treturns: string | undefined,\n\tschemaMap: Record<string, Record<string, any>>\n): Record<string, any> | null {\n\tif (!returns) return null\n\n\tlet innerType = returns\n\tconst promiseMatch = returns.match(/^Promise<(.+)>$/)\n\tif (promiseMatch) {\n\t\tinnerType = promiseMatch[1]\n\t}\n\n\tconst isArray = innerType.endsWith('[]')\n\tconst baseType = isArray ? innerType.slice(0, -2) : innerType\n\tif (['string', 'number', 'boolean'].includes(baseType)) {\n\t\tconst primitiveSchema = tsTypeToJsonSchema(baseType)\n\t\treturn isArray ? { type: 'array', items: primitiveSchema } : primitiveSchema\n\t}\n\tif (['void', 'any', 'unknown'].includes(baseType)) return null\n\tif (schemaMap[baseType]) {\n\t\tconst ref = { $ref: `#/components/schemas/${baseType}` }\n\t\treturn isArray ? { type: 'array', items: ref } : ref\n\t}\n\treturn null\n}\n\nfunction buildSchemaMap(schemas: readonly OpenApiSchemaInput[]): Record<string, Record<string, any>> {\n\tconst result: Record<string, Record<string, any>> = {}\n\tfor (const schemaInfo of schemas) {\n\t\tconst definition = schemaInfo.schema?.definitions?.[schemaInfo.type]\n\t\tif (definition) {\n\t\t\tresult[schemaInfo.type] = definition\n\t\t}\n\t}\n\treturn result\n}\n\nfunction tsTypeToJsonSchema(tsType: string): Record<string, unknown> {\n\tswitch (tsType) {\n\t\tcase 'number':\n\t\t\treturn { type: 'number' as const }\n\t\tcase 'boolean':\n\t\t\treturn { type: 'boolean' as const }\n\t\tcase 'string':\n\t\tdefault:\n\t\t\treturn { type: 'string' as const }\n\t}\n}\n\nfunction extractBaseTypeName(tsType: string): string | null {\n\tif (!tsType) return null\n\n\tlet type = tsType\n\tconst promiseMatch = type.match(/^Promise<(.+)>$/)\n\tif (promiseMatch) type = promiseMatch[1]\n\ttype = type.replace(/\\[\\]$/, '')\n\tconst genericMatch = type.match(/^\\w+<(\\w+)>$/)\n\tif (genericMatch) type = genericMatch[1]\n\tif (['string', 'number', 'boolean', 'any', 'void', 'unknown', 'object'].includes(type)) {\n\t\treturn null\n\t}\n\treturn type\n}\n\nfunction toOpenApiPath(expressPath: string): string {\n\treturn expressPath.replace(/:(\\w+)/g, '{$1}')\n}\n\nfunction buildFallbackPath(route: OpenApiRouteInput): string {\n\tconst parts = [route.prefix, route.version, route.route, route.path]\n\t\t.filter((part) => part !== undefined && part !== null && part !== '')\n\t\t.map((part) => String(part).replace(/^\\/+|\\/+$/g, ''))\n\t\t.filter((part) => part.length > 0)\n\tconst joined = parts.join('/')\n\treturn `/${joined}`\n}\n","import type { Application, IPlugin } from 'honestjs'\nimport type { Hono } from 'hono'\n\nimport { fromArtifactSync } from './openapi.generator'\nimport type { OpenApiArtifactInput, OpenApiGenerationOptions } from './openapi.generator'\n\nconst DEFAULT_OPENAPI_ROUTE = '/openapi.json'\nconst DEFAULT_UI_ROUTE = '/docs'\nconst DEFAULT_UI_TITLE = 'API Docs'\n/** Default context key when using with RPC plugin (writes to this key). */\nexport const DEFAULT_ARTIFACT_KEY = 'rpc.artifact'\n\nexport type ArtifactInput = OpenApiArtifactInput | string\n\nfunction isContextKey(artifact: ArtifactInput): artifact is string {\n\treturn typeof artifact === 'string'\n}\n\nfunction isArtifact(value: unknown): value is OpenApiArtifactInput {\n\tif (!value || typeof value !== 'object' || Array.isArray(value)) return false\n\tconst obj = value as Record<string, unknown>\n\treturn Array.isArray(obj.routes) && Array.isArray(obj.schemas)\n}\n\nexport interface ApiDocsPluginOptions extends OpenApiGenerationOptions {\n\t/** Artifact: direct object `{ routes, schemas }` or context key string (e.g. `'rpc.artifact'`). Defaults to `'rpc.artifact'` when omitted. */\n\treadonly artifact?: ArtifactInput\n\treadonly openApiRoute?: string\n\treadonly uiRoute?: string\n\treadonly uiTitle?: string\n\treadonly reloadOnRequest?: boolean\n}\n\nexport class ApiDocsPlugin implements IPlugin {\n\tprivate readonly artifact: ArtifactInput\n\tprivate readonly openApiRoute: string\n\tprivate readonly uiRoute: string\n\tprivate readonly uiTitle: string\n\tprivate readonly reloadOnRequest: boolean\n\tprivate readonly genOptions: OpenApiGenerationOptions\n\n\tprivate app: Application | null = null\n\tprivate cachedSpec: Record<string, unknown> | null = null\n\n\tconstructor(options: ApiDocsPluginOptions = {}) {\n\t\tthis.artifact = options.artifact ?? DEFAULT_ARTIFACT_KEY\n\t\tthis.openApiRoute = this.normalizeRoute(options.openApiRoute ?? DEFAULT_OPENAPI_ROUTE)\n\t\tthis.uiRoute = this.normalizeRoute(options.uiRoute ?? DEFAULT_UI_ROUTE)\n\t\tthis.uiTitle = options.uiTitle ?? DEFAULT_UI_TITLE\n\t\tthis.reloadOnRequest = options.reloadOnRequest ?? false\n\t\tthis.genOptions = {\n\t\t\ttitle: options.title,\n\t\t\tversion: options.version,\n\t\t\tdescription: options.description,\n\t\t\tservers: options.servers\n\t\t}\n\t}\n\n\tafterModulesRegistered = async (app: Application, hono: Hono): Promise<void> => {\n\t\tthis.app = app\n\n\t\thono.get(this.openApiRoute, async (c) => {\n\t\t\ttry {\n\t\t\t\tconst spec = await this.resolveSpec()\n\t\t\t\treturn c.json(spec)\n\t\t\t} catch (error) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: 'Failed to load OpenAPI spec',\n\t\t\t\t\t\tmessage: this.toErrorMessage(error)\n\t\t\t\t\t},\n\t\t\t\t\t500\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\n\t\thono.get(this.uiRoute, (c) => {\n\t\t\treturn c.html(this.renderSwaggerUiHtml())\n\t\t})\n\t}\n\n\tprivate normalizeRoute(input: string): string {\n\t\tconst trimmed = input.trim()\n\t\tif (!trimmed) return '/'\n\t\tlet normalized = trimmed.startsWith('/') ? trimmed : `/${trimmed}`\n\t\tif (normalized.length > 1) {\n\t\t\tnormalized = normalized.replace(/\\/+$/g, '')\n\t\t}\n\t\treturn normalized || '/'\n\t}\n\n\tprivate async resolveSpec(): Promise<Record<string, unknown>> {\n\t\tif (!this.reloadOnRequest && this.cachedSpec) {\n\t\t\treturn this.cachedSpec\n\t\t}\n\n\t\tlet artifact: OpenApiArtifactInput\n\n\t\tif (isContextKey(this.artifact)) {\n\t\t\tif (!this.app) {\n\t\t\t\tthrow new Error('ApiDocsPlugin: app not available when resolving artifact from context')\n\t\t\t}\n\t\t\tconst value = this.app.getContext().get<unknown>(this.artifact)\n\t\t\tif (value === undefined) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`ApiDocsPlugin: no artifact at context key '${this.artifact}'. Ensure RPC plugin (or another producer) runs before ApiDocs and writes to this key.`\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (!isArtifact(value)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`ApiDocsPlugin: value at '${this.artifact}' is not a valid artifact (expected object with routes and schemas)`\n\t\t\t\t)\n\t\t\t}\n\t\t\tartifact = value\n\t\t} else {\n\t\t\tartifact = this.artifact\n\t\t}\n\n\t\tconst spec = fromArtifactSync(artifact, this.genOptions) as unknown as Record<string, unknown>\n\t\tif (!this.reloadOnRequest) this.cachedSpec = spec\n\t\treturn spec\n\t}\n\n\tprivate renderSwaggerUiHtml(): string {\n\t\tconst title = this.escapeHtml(this.uiTitle)\n\t\tconst openApiRoute = this.escapeJsString(this.openApiRoute)\n\n\t\treturn `<!doctype html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"utf-8\" />\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t<title>${title}</title>\n\t<link rel=\"stylesheet\" href=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui.css\" />\n</head>\n<body>\n\t<div id=\"swagger-ui\"></div>\n\t<script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js\"></script>\n\t<script>\n\t\twindow.onload = function () {\n\t\t\twindow.ui = SwaggerUIBundle({\n\t\t\t\turl: '${openApiRoute}',\n\t\t\t\tdom_id: '#swagger-ui'\n\t\t\t});\n\t\t};\n\t</script>\n</body>\n</html>`\n\t}\n\n\tprivate escapeHtml(value: string): string {\n\t\treturn value\n\t\t\t.replace(/&/g, '&amp;')\n\t\t\t.replace(/</g, '&lt;')\n\t\t\t.replace(/>/g, '&gt;')\n\t\t\t.replace(/\"/g, '&quot;')\n\t\t\t.replace(/'/g, '&#39;')\n\t}\n\n\tprivate escapeJsString(value: string): string {\n\t\treturn value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\")\n\t}\n\n\tprivate toErrorMessage(error: unknown): string {\n\t\treturn error instanceof Error ? error.message : String(error)\n\t}\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/openapi.generator.ts","../src/api-docs.plugin.ts"],"sourcesContent":["import fs from 'fs/promises'\nimport path from 'path'\nimport type { OpenAPIV3 } from 'openapi-types'\n\nexport interface OpenApiGenerationOptions {\n\treadonly title?: string\n\treadonly version?: string\n\treadonly description?: string\n\treadonly servers?: readonly { url: string; description?: string }[]\n}\n\nexport interface OpenApiArtifactInput {\n\treadonly artifactVersion?: string\n\treadonly routes: readonly OpenApiRouteInput[]\n\treadonly schemas: readonly OpenApiSchemaInput[]\n}\n\nexport interface OpenApiRouteInput {\n\treadonly method: string\n\treadonly handler: string\n\treadonly controller: string\n\treadonly fullPath: string\n\treadonly path?: string\n\treadonly prefix?: string\n\treadonly version?: string\n\treadonly route?: string\n\treadonly returns?: string\n\treadonly parameters?: readonly OpenApiParameterInput[]\n}\n\nexport interface OpenApiParameterInput {\n\treadonly name: string\n\treadonly data?: string\n\treadonly type: string\n\treadonly required?: boolean\n\treadonly decoratorType: string\n}\n\nexport interface OpenApiSchemaInput {\n\treadonly type: string\n\treadonly schema: Record<string, any>\n}\n\n/** OpenAPI 3.x document type (openapi-types). */\nexport type OpenApiDocument = OpenAPIV3.Document\n\ninterface ResolvedOpenApiOptions {\n\treadonly title: string\n\treadonly version: string\n\treadonly description: string\n\treadonly servers: readonly { url: string; description?: string }[]\n}\n\nfunction resolveOptions(options: OpenApiGenerationOptions = {}): ResolvedOpenApiOptions {\n\treturn {\n\t\ttitle: options.title ?? 'API',\n\t\tversion: options.version ?? '1.0.0',\n\t\tdescription: options.description ?? '',\n\t\tservers: options.servers ?? []\n\t}\n}\n\nexport function fromArtifactSync(\n\tartifact: OpenApiArtifactInput,\n\toptions: OpenApiGenerationOptions = {}\n): OpenApiDocument {\n\tconst resolved = resolveOptions(options)\n\tconst schemaMap = buildSchemaMap(artifact.schemas)\n\tconst spec: OpenAPIV3.Document = {\n\t\topenapi: '3.0.3',\n\t\tinfo: {\n\t\t\ttitle: resolved.title,\n\t\t\tversion: resolved.version,\n\t\t\tdescription: resolved.description\n\t\t},\n\t\tpaths: {},\n\t\tcomponents: {\n\t\t\tschemas: schemaMap as OpenAPIV3.ComponentsObject['schemas']\n\t\t}\n\t}\n\n\tif (resolved.servers.length > 0) {\n\t\tspec.servers = resolved.servers.map((server) => ({ ...server }))\n\t}\n\n\tfor (const route of artifact.routes) {\n\t\tconst routePath =\n\t\t\troute.prefix != null || route.version != null || route.route != null\n\t\t\t\t? buildFallbackPath(route)\n\t\t\t\t: route.fullPath || buildFallbackPath(route)\n\t\tconst openApiPath = toOpenApiPath(routePath)\n\t\tconst method = route.method.toLowerCase() as keyof OpenAPIV3.PathItemObject\n\t\tif (!spec.paths[openApiPath]) {\n\t\t\tspec.paths[openApiPath] = {}\n\t\t}\n\t\t;(spec.paths[openApiPath] as Record<string, OpenAPIV3.OperationObject>)[method] = buildOperation(\n\t\t\troute,\n\t\t\tschemaMap\n\t\t)\n\t}\n\n\treturn spec\n}\n\nexport async function fromArtifact(\n\tartifact: OpenApiArtifactInput,\n\toptions: OpenApiGenerationOptions = {}\n): Promise<OpenApiDocument> {\n\treturn fromArtifactSync(artifact, options)\n}\n\nexport async function write(openapi: OpenApiDocument, outputPath: string): Promise<string> {\n\tconst absolute = path.isAbsolute(outputPath) ? outputPath : path.resolve(process.cwd(), outputPath)\n\tawait fs.mkdir(path.dirname(absolute), { recursive: true })\n\tawait fs.writeFile(absolute, JSON.stringify(openapi, null, 2), 'utf-8')\n\treturn absolute\n}\n\nfunction buildOperation(\n\troute: OpenApiRouteInput,\n\tschemaMap: Record<string, Record<string, any>>\n): OpenAPIV3.OperationObject {\n\tconst controllerName = route.controller.replace(/Controller$/, '')\n\tconst parameters = route.parameters ?? []\n\tconst operation: OpenAPIV3.OperationObject = {\n\t\toperationId: route.handler,\n\t\ttags: [controllerName],\n\t\tresponses: buildResponses(route.returns, schemaMap)\n\t}\n\n\tconst openApiParameters = buildParameters(parameters)\n\tif (openApiParameters.length > 0) {\n\t\toperation.parameters = openApiParameters\n\t}\n\n\tconst requestBody = buildRequestBody(parameters, schemaMap)\n\tif (requestBody) {\n\t\toperation.requestBody = requestBody\n\t}\n\n\treturn operation\n}\n\nfunction buildParameters(parameters: readonly OpenApiParameterInput[]): OpenAPIV3.ParameterObject[] {\n\tconst result: OpenAPIV3.ParameterObject[] = []\n\n\tfor (const param of parameters) {\n\t\tif (param.decoratorType === 'param') {\n\t\t\tresult.push({\n\t\t\t\tname: param.data ?? param.name,\n\t\t\t\tin: 'path',\n\t\t\t\trequired: true,\n\t\t\t\tschema: tsTypeToJsonSchema(param.type)\n\t\t\t})\n\t\t} else if (param.decoratorType === 'query') {\n\t\t\tresult.push({\n\t\t\t\tname: param.data ?? param.name,\n\t\t\t\tin: 'query',\n\t\t\t\trequired: param.required === true,\n\t\t\t\tschema: tsTypeToJsonSchema(param.type)\n\t\t\t})\n\t\t}\n\t}\n\n\treturn result\n}\n\nfunction buildRequestBody(\n\tparameters: readonly OpenApiParameterInput[],\n\tschemaMap: Record<string, Record<string, any>>\n): OpenAPIV3.RequestBodyObject | null {\n\tconst bodyParam = parameters.find((param) => param.decoratorType === 'body')\n\tif (!bodyParam) return null\n\n\tconst typeName = extractBaseTypeName(bodyParam.type)\n\tconst schema: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject =\n\t\ttypeName && schemaMap[typeName] ? { $ref: `#/components/schemas/${typeName}` } : { type: 'object' as const }\n\n\treturn {\n\t\trequired: true,\n\t\tcontent: {\n\t\t\t'application/json': { schema }\n\t\t}\n\t}\n}\n\nfunction buildResponses(\n\treturns: string | undefined,\n\tschemaMap: Record<string, Record<string, any>>\n): OpenAPIV3.ResponsesObject {\n\tconst responseSchema = resolveResponseSchema(returns, schemaMap)\n\n\tif (!responseSchema) {\n\t\treturn { '200': { description: 'Successful response' } }\n\t}\n\n\treturn {\n\t\t'200': {\n\t\t\tdescription: 'Successful response',\n\t\t\tcontent: {\n\t\t\t\t'application/json': { schema: responseSchema }\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction resolveResponseSchema(\n\treturns: string | undefined,\n\tschemaMap: Record<string, Record<string, any>>\n): Record<string, any> | null {\n\tif (!returns) return null\n\n\tlet innerType = returns\n\tconst promiseMatch = returns.match(/^Promise<(.+)>$/)\n\tif (promiseMatch) {\n\t\tinnerType = promiseMatch[1]\n\t}\n\n\tconst isArray = innerType.endsWith('[]')\n\tconst baseType = isArray ? innerType.slice(0, -2) : innerType\n\tif (['string', 'number', 'boolean'].includes(baseType)) {\n\t\tconst primitiveSchema = tsTypeToJsonSchema(baseType)\n\t\treturn isArray ? { type: 'array', items: primitiveSchema } : primitiveSchema\n\t}\n\tif (['void', 'any', 'unknown'].includes(baseType)) return null\n\tif (schemaMap[baseType]) {\n\t\tconst ref = { $ref: `#/components/schemas/${baseType}` }\n\t\treturn isArray ? { type: 'array', items: ref } : ref\n\t}\n\treturn null\n}\n\nfunction buildSchemaMap(schemas: readonly OpenApiSchemaInput[]): Record<string, Record<string, any>> {\n\tconst result: Record<string, Record<string, any>> = {}\n\tfor (const schemaInfo of schemas) {\n\t\tconst definition = schemaInfo.schema?.definitions?.[schemaInfo.type]\n\t\tif (definition) {\n\t\t\tresult[schemaInfo.type] = definition\n\t\t}\n\t}\n\treturn result\n}\n\nfunction tsTypeToJsonSchema(tsType: string): Record<string, unknown> {\n\tswitch (tsType) {\n\t\tcase 'number':\n\t\t\treturn { type: 'number' as const }\n\t\tcase 'boolean':\n\t\t\treturn { type: 'boolean' as const }\n\t\tcase 'string':\n\t\tdefault:\n\t\t\treturn { type: 'string' as const }\n\t}\n}\n\nfunction extractBaseTypeName(tsType: string): string | null {\n\tif (!tsType) return null\n\n\tlet type = tsType\n\tconst promiseMatch = type.match(/^Promise<(.+)>$/)\n\tif (promiseMatch) type = promiseMatch[1]\n\ttype = type.replace(/\\[\\]$/, '')\n\tconst genericMatch = type.match(/^\\w+<(\\w+)>$/)\n\tif (genericMatch) type = genericMatch[1]\n\tif (['string', 'number', 'boolean', 'any', 'void', 'unknown', 'object'].includes(type)) {\n\t\treturn null\n\t}\n\treturn type\n}\n\nfunction toOpenApiPath(expressPath: string): string {\n\treturn expressPath.replace(/:(\\w+)/g, '{$1}')\n}\n\nfunction buildFallbackPath(route: OpenApiRouteInput): string {\n\tconst parts = [route.prefix, route.version, route.route, route.path]\n\t\t.filter((part) => part !== undefined && part !== null && part !== '')\n\t\t.map((part) => String(part).replace(/^\\/+|\\/+$/g, ''))\n\t\t.filter((part) => part.length > 0)\n\tconst joined = parts.join('/')\n\treturn `/${joined}`\n}\n","import type { Application, IPlugin } from 'honestjs'\nimport type { Context, Hono, Next } from 'hono'\n\nimport { fromArtifactSync } from './openapi.generator'\nimport type { OpenApiArtifactInput, OpenApiGenerationOptions } from './openapi.generator'\n\nconst DEFAULT_OPENAPI_ROUTE = '/openapi.json'\nconst DEFAULT_UI_ROUTE = '/docs'\nconst DEFAULT_UI_TITLE = 'API Docs'\n/** Default context key when using with RPC plugin (writes to this key). */\nexport const DEFAULT_ARTIFACT_KEY = 'rpc.artifact'\n\nexport type ArtifactInput = OpenApiArtifactInput | string\n\nfunction isContextKey(artifact: ArtifactInput): artifact is string {\n\treturn typeof artifact === 'string'\n}\n\nfunction isArtifact(value: unknown): value is OpenApiArtifactInput {\n\tif (!value || typeof value !== 'object' || Array.isArray(value)) return false\n\tconst obj = value as Record<string, unknown>\n\treturn Array.isArray(obj.routes) && Array.isArray(obj.schemas)\n}\n\nexport interface ApiDocsPluginOptions extends OpenApiGenerationOptions {\n\t/** Artifact: direct object `{ routes, schemas }` or context key string (e.g. `'rpc.artifact'`). Defaults to `'rpc.artifact'` when omitted. */\n\treadonly artifact?: ArtifactInput\n\treadonly openApiRoute?: string\n\treadonly uiRoute?: string\n\treadonly uiTitle?: string\n\treadonly reloadOnRequest?: boolean\n\treadonly onOpenApiRequest?: (c: Context, next: Next) => void | Response | Promise<void | Response>\n\treadonly onUiRequest?: (c: Context, next: Next) => void | Response | Promise<void | Response>\n}\n\nexport class ApiDocsPlugin implements IPlugin {\n\tprivate readonly artifact: ArtifactInput\n\tprivate readonly openApiRoute: string\n\tprivate readonly uiRoute: string\n\tprivate readonly uiTitle: string\n\tprivate readonly reloadOnRequest: boolean\n\tprivate readonly genOptions: OpenApiGenerationOptions\n\tprivate readonly onOpenApiRequest?: (c: Context, next: Next) => void | Response | Promise<void | Response>\n\tprivate readonly onUiRequest?: (c: Context, next: Next) => void | Response | Promise<void | Response>\n\n\tprivate app: Application | null = null\n\tprivate cachedSpec: Record<string, unknown> | null = null\n\n\tconstructor(options: ApiDocsPluginOptions = {}) {\n\t\tthis.artifact = options.artifact ?? DEFAULT_ARTIFACT_KEY\n\t\tthis.openApiRoute = this.normalizeRoute(options.openApiRoute ?? DEFAULT_OPENAPI_ROUTE)\n\t\tthis.uiRoute = this.normalizeRoute(options.uiRoute ?? DEFAULT_UI_ROUTE)\n\t\tthis.uiTitle = options.uiTitle ?? DEFAULT_UI_TITLE\n\t\tthis.reloadOnRequest = options.reloadOnRequest ?? false\n\t\tthis.onOpenApiRequest = options.onOpenApiRequest\n\t\tthis.onUiRequest = options.onUiRequest\n\t\tthis.genOptions = {\n\t\t\ttitle: options.title,\n\t\t\tversion: options.version,\n\t\t\tdescription: options.description,\n\t\t\tservers: options.servers\n\t\t}\n\t}\n\n\tafterModulesRegistered = async (app: Application, hono: Hono): Promise<void> => {\n\t\tthis.app = app\n\n\t\thono.get(this.openApiRoute, async (c) => {\n\t\t\ttry {\n\t\t\t\tconst earlyResponse = await this.runHook(this.onOpenApiRequest, c)\n\t\t\t\tif (earlyResponse) return earlyResponse\n\t\t\t\tconst spec = await this.resolveSpec()\n\t\t\t\treturn c.json(spec)\n\t\t\t} catch (error) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: 'Failed to load OpenAPI spec',\n\t\t\t\t\t\tmessage: this.toErrorMessage(error)\n\t\t\t\t\t},\n\t\t\t\t\t500\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\n\t\thono.get(this.uiRoute, async (c) => {\n\t\t\tconst earlyResponse = await this.runHook(this.onUiRequest, c)\n\t\t\tif (earlyResponse) return earlyResponse\n\t\t\treturn c.html(this.renderSwaggerUiHtml())\n\t\t})\n\t}\n\n\tprivate normalizeRoute(input: string): string {\n\t\tconst trimmed = input.trim()\n\t\tif (!trimmed) return '/'\n\t\tlet normalized = trimmed.startsWith('/') ? trimmed : `/${trimmed}`\n\t\tif (normalized.length > 1) {\n\t\t\tnormalized = normalized.replace(/\\/+$/g, '')\n\t\t}\n\t\treturn normalized || '/'\n\t}\n\n\tprivate async resolveSpec(): Promise<Record<string, unknown>> {\n\t\tif (!this.reloadOnRequest && this.cachedSpec) {\n\t\t\treturn this.cachedSpec\n\t\t}\n\n\t\tlet artifact: OpenApiArtifactInput\n\n\t\tif (isContextKey(this.artifact)) {\n\t\t\tif (!this.app) {\n\t\t\t\tthrow new Error('ApiDocsPlugin: app not available when resolving artifact from context')\n\t\t\t}\n\t\t\tconst value = this.app.getContext().get<unknown>(this.artifact)\n\t\t\tif (value === undefined) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`ApiDocsPlugin: no artifact at context key '${this.artifact}'. Ensure RPC plugin (or another producer) runs before ApiDocs and writes to this key.`\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (!isArtifact(value)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`ApiDocsPlugin: value at '${this.artifact}' is not a valid artifact (expected object with routes and schemas)`\n\t\t\t\t)\n\t\t\t}\n\t\t\tartifact = value\n\t\t} else {\n\t\t\tartifact = this.artifact\n\t\t}\n\n\t\tconst artifactVersion = (artifact as { artifactVersion?: unknown }).artifactVersion\n\t\tif (artifactVersion !== undefined && artifactVersion !== '1') {\n\t\t\tthrow new Error(\n\t\t\t\t`ApiDocsPlugin: unsupported artifactVersion '${String(artifactVersion)}'. Supported versions: 1.`\n\t\t\t)\n\t\t}\n\n\t\tconst spec = fromArtifactSync(artifact, this.genOptions) as unknown as Record<string, unknown>\n\t\tif (!this.reloadOnRequest) this.cachedSpec = spec\n\t\treturn spec\n\t}\n\n\tprivate async runHook(\n\t\thook: ((c: Context, next: Next) => void | Response | Promise<void | Response>) | undefined,\n\t\tc: Context\n\t): Promise<Response | undefined> {\n\t\tif (!hook) return undefined\n\t\tlet nextCalled = false\n\t\tconst maybeResponse = await hook(c, async () => {\n\t\t\tnextCalled = true\n\t\t})\n\t\tif (maybeResponse instanceof Response) {\n\t\t\treturn maybeResponse\n\t\t}\n\t\tif (!nextCalled) {\n\t\t\treturn new Response('Forbidden', { status: 403 })\n\t\t}\n\t\treturn undefined\n\t}\n\n\tprivate renderSwaggerUiHtml(): string {\n\t\tconst title = this.escapeHtml(this.uiTitle)\n\t\tconst openApiRoute = this.escapeJsString(this.openApiRoute)\n\n\t\treturn `<!doctype html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"utf-8\" />\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t<title>${title}</title>\n\t<link rel=\"stylesheet\" href=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui.css\" />\n</head>\n<body>\n\t<div id=\"swagger-ui\"></div>\n\t<script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js\"></script>\n\t<script>\n\t\twindow.onload = function () {\n\t\t\twindow.ui = SwaggerUIBundle({\n\t\t\t\turl: '${openApiRoute}',\n\t\t\t\tdom_id: '#swagger-ui'\n\t\t\t});\n\t\t};\n\t</script>\n</body>\n</html>`\n\t}\n\n\tprivate escapeHtml(value: string): string {\n\t\treturn value\n\t\t\t.replace(/&/g, '&amp;')\n\t\t\t.replace(/</g, '&lt;')\n\t\t\t.replace(/>/g, '&gt;')\n\t\t\t.replace(/\"/g, '&quot;')\n\t\t\t.replace(/'/g, '&#39;')\n\t}\n\n\tprivate escapeJsString(value: string): string {\n\t\treturn value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\")\n\t}\n\n\tprivate toErrorMessage(error: unknown): string {\n\t\treturn error instanceof Error ? error.message : String(error)\n\t}\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@honestjs/api-docs-plugin",
3
- "version": "1.0.2",
3
+ "version": "1.1.1",
4
4
  "description": "API Docs plugin for HonestJS - generates OpenAPI from artifact object or context key and serves Swagger UI",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -30,8 +30,8 @@
30
30
  "directory": "packages/api-docs-plugin"
31
31
  },
32
32
  "devDependencies": {
33
- "honestjs": "^0.1.9",
34
- "hono": "^4.12.6",
33
+ "honestjs": "^0.1.11",
34
+ "hono": "^4.12.8",
35
35
  "reflect-metadata": "^0.2.2",
36
36
  "vitest": "^3.2.4"
37
37
  },
@@ -49,6 +49,7 @@
49
49
  "clean": "rm -rf dist",
50
50
  "type-check": "tsc --noEmit",
51
51
  "test": "vitest run",
52
- "test:watch": "vitest"
52
+ "test:watch": "vitest",
53
+ "lint": "prettier --check ."
53
54
  }
54
55
  }