@honestjs/api-docs-plugin 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Orkhan Karimov <karimovok1@gmail.com> (https://github.com/kerimovok)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # API Docs Plugin
2
+
3
+ Serves OpenAPI JSON and Swagger UI for your HonestJS application. Always generates the spec from an artifact—pass `{ routes, schemas }` directly or a context key like `'rpc.artifact'` (e.g. from `@honestjs/rpc-plugin`).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @honestjs/api-docs-plugin
9
+ # or
10
+ yarn add @honestjs/api-docs-plugin
11
+ # or
12
+ pnpm add @honestjs/api-docs-plugin
13
+ ```
14
+
15
+ ## Usage with RPC Plugin
16
+
17
+ The RPC plugin writes its artifact to the application context. Pass the context key to read it. Ensure RPC runs before ApiDocs in the plugins array:
18
+
19
+ ```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"
24
+
25
+ const { hono } = await Application.create(AppModule, {
26
+ plugins: [
27
+ new RPCPlugin(),
28
+ new ApiDocsPlugin({ artifact: "rpc.artifact" }),
29
+ ],
30
+ })
31
+
32
+ export default hono
33
+ ```
34
+
35
+ If RPC uses custom `context.namespace` / `context.keys.artifact`, pass the resulting full key (for example, `custom.artifact`) to `artifact`.
36
+
37
+ By default:
38
+
39
+ - OpenAPI JSON: `/openapi.json`
40
+ - Swagger UI: `/docs`
41
+
42
+ ## Manual Artifact
43
+
44
+ Pass the artifact object directly:
45
+
46
+ ```typescript
47
+ import { ApiDocsPlugin } from "@honestjs/api-docs-plugin"
48
+
49
+ const artifact = {
50
+ routes: [
51
+ {
52
+ method: "GET",
53
+ handler: "list",
54
+ controller: "UsersController",
55
+ fullPath: "/users",
56
+ parameters: [],
57
+ },
58
+ ],
59
+ schemas: [],
60
+ }
61
+
62
+ plugins: [new ApiDocsPlugin({ artifact })]
63
+ ```
64
+
65
+ ## Configuration Options
66
+
67
+ ```typescript
68
+ interface ApiDocsPluginOptions {
69
+ // Required: artifact - direct object or context key (resolved by plugin via app.getContext().get)
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
+ }
84
+ ```
85
+
86
+ ## Programmatic API
87
+
88
+ For custom workflows, use the exported utilities:
89
+
90
+ ```typescript
91
+ import {
92
+ fromArtifactSync,
93
+ write,
94
+ type OpenApiArtifactInput,
95
+ type OpenApiDocument,
96
+ } from "@honestjs/api-docs-plugin"
97
+
98
+ const artifact: OpenApiArtifactInput = { routes: [...], schemas: [...] }
99
+ const spec: OpenApiDocument = fromArtifactSync(artifact, {
100
+ title: "My API",
101
+ version: "1.0.0",
102
+ })
103
+ await write(spec, "./generated/openapi.json")
104
+ ```
105
+
106
+ ## License
107
+
108
+ MIT
@@ -0,0 +1,76 @@
1
+ import { IPlugin, Application } from 'honestjs';
2
+ import { Hono } from 'hono';
3
+ import { OpenAPIV3 } from 'openapi-types';
4
+ export { OpenAPIV3 } from 'openapi-types';
5
+
6
+ interface OpenApiGenerationOptions {
7
+ readonly title?: string;
8
+ readonly version?: string;
9
+ readonly description?: string;
10
+ readonly servers?: readonly {
11
+ url: string;
12
+ description?: string;
13
+ }[];
14
+ }
15
+ interface OpenApiArtifactInput {
16
+ readonly routes: readonly OpenApiRouteInput[];
17
+ readonly schemas: readonly OpenApiSchemaInput[];
18
+ }
19
+ interface OpenApiRouteInput {
20
+ readonly method: string;
21
+ readonly handler: string;
22
+ readonly controller: string;
23
+ readonly fullPath: string;
24
+ readonly path?: string;
25
+ readonly prefix?: string;
26
+ readonly version?: string;
27
+ readonly route?: string;
28
+ readonly returns?: string;
29
+ readonly parameters?: readonly OpenApiParameterInput[];
30
+ }
31
+ interface OpenApiParameterInput {
32
+ readonly name: string;
33
+ readonly data?: string;
34
+ readonly type: string;
35
+ readonly required?: boolean;
36
+ readonly decoratorType: string;
37
+ }
38
+ interface OpenApiSchemaInput {
39
+ readonly type: string;
40
+ readonly schema: Record<string, any>;
41
+ }
42
+ /** OpenAPI 3.x document type (openapi-types). */
43
+ type OpenApiDocument = OpenAPIV3.Document;
44
+ declare function fromArtifactSync(artifact: OpenApiArtifactInput, options?: OpenApiGenerationOptions): OpenApiDocument;
45
+ declare function fromArtifact(artifact: OpenApiArtifactInput, options?: OpenApiGenerationOptions): Promise<OpenApiDocument>;
46
+ declare function write(openapi: OpenApiDocument, outputPath: string): Promise<string>;
47
+
48
+ type ArtifactInput = OpenApiArtifactInput | string;
49
+ interface ApiDocsPluginOptions extends OpenApiGenerationOptions {
50
+ /** Artifact: direct object `{ routes, schemas }` or context key string (e.g. `'rpc.artifact'`) resolved via app.getContext().get(key). OpenAPI is always generated from the artifact. */
51
+ readonly artifact: ArtifactInput;
52
+ readonly openApiRoute?: string;
53
+ readonly uiRoute?: string;
54
+ readonly uiTitle?: string;
55
+ readonly reloadOnRequest?: boolean;
56
+ }
57
+ declare class ApiDocsPlugin implements IPlugin {
58
+ private readonly artifact;
59
+ private readonly openApiRoute;
60
+ private readonly uiRoute;
61
+ private readonly uiTitle;
62
+ private readonly reloadOnRequest;
63
+ private readonly genOptions;
64
+ private app;
65
+ private cachedSpec;
66
+ constructor(options: ApiDocsPluginOptions);
67
+ afterModulesRegistered: (app: Application, hono: Hono) => Promise<void>;
68
+ private normalizeRoute;
69
+ private resolveSpec;
70
+ private renderSwaggerUiHtml;
71
+ private escapeHtml;
72
+ private escapeJsString;
73
+ private toErrorMessage;
74
+ }
75
+
76
+ export { ApiDocsPlugin, type ApiDocsPluginOptions, type ArtifactInput, type OpenApiArtifactInput, type OpenApiDocument, type OpenApiGenerationOptions, type OpenApiParameterInput, type OpenApiRouteInput, type OpenApiSchemaInput, fromArtifact, fromArtifactSync, write };
@@ -0,0 +1,76 @@
1
+ import { IPlugin, Application } from 'honestjs';
2
+ import { Hono } from 'hono';
3
+ import { OpenAPIV3 } from 'openapi-types';
4
+ export { OpenAPIV3 } from 'openapi-types';
5
+
6
+ interface OpenApiGenerationOptions {
7
+ readonly title?: string;
8
+ readonly version?: string;
9
+ readonly description?: string;
10
+ readonly servers?: readonly {
11
+ url: string;
12
+ description?: string;
13
+ }[];
14
+ }
15
+ interface OpenApiArtifactInput {
16
+ readonly routes: readonly OpenApiRouteInput[];
17
+ readonly schemas: readonly OpenApiSchemaInput[];
18
+ }
19
+ interface OpenApiRouteInput {
20
+ readonly method: string;
21
+ readonly handler: string;
22
+ readonly controller: string;
23
+ readonly fullPath: string;
24
+ readonly path?: string;
25
+ readonly prefix?: string;
26
+ readonly version?: string;
27
+ readonly route?: string;
28
+ readonly returns?: string;
29
+ readonly parameters?: readonly OpenApiParameterInput[];
30
+ }
31
+ interface OpenApiParameterInput {
32
+ readonly name: string;
33
+ readonly data?: string;
34
+ readonly type: string;
35
+ readonly required?: boolean;
36
+ readonly decoratorType: string;
37
+ }
38
+ interface OpenApiSchemaInput {
39
+ readonly type: string;
40
+ readonly schema: Record<string, any>;
41
+ }
42
+ /** OpenAPI 3.x document type (openapi-types). */
43
+ type OpenApiDocument = OpenAPIV3.Document;
44
+ declare function fromArtifactSync(artifact: OpenApiArtifactInput, options?: OpenApiGenerationOptions): OpenApiDocument;
45
+ declare function fromArtifact(artifact: OpenApiArtifactInput, options?: OpenApiGenerationOptions): Promise<OpenApiDocument>;
46
+ declare function write(openapi: OpenApiDocument, outputPath: string): Promise<string>;
47
+
48
+ type ArtifactInput = OpenApiArtifactInput | string;
49
+ interface ApiDocsPluginOptions extends OpenApiGenerationOptions {
50
+ /** Artifact: direct object `{ routes, schemas }` or context key string (e.g. `'rpc.artifact'`) resolved via app.getContext().get(key). OpenAPI is always generated from the artifact. */
51
+ readonly artifact: ArtifactInput;
52
+ readonly openApiRoute?: string;
53
+ readonly uiRoute?: string;
54
+ readonly uiTitle?: string;
55
+ readonly reloadOnRequest?: boolean;
56
+ }
57
+ declare class ApiDocsPlugin implements IPlugin {
58
+ private readonly artifact;
59
+ private readonly openApiRoute;
60
+ private readonly uiRoute;
61
+ private readonly uiTitle;
62
+ private readonly reloadOnRequest;
63
+ private readonly genOptions;
64
+ private app;
65
+ private cachedSpec;
66
+ constructor(options: ApiDocsPluginOptions);
67
+ afterModulesRegistered: (app: Application, hono: Hono) => Promise<void>;
68
+ private normalizeRoute;
69
+ private resolveSpec;
70
+ private renderSwaggerUiHtml;
71
+ private escapeHtml;
72
+ private escapeJsString;
73
+ private toErrorMessage;
74
+ }
75
+
76
+ export { ApiDocsPlugin, type ApiDocsPluginOptions, type ArtifactInput, type OpenApiArtifactInput, type OpenApiDocument, type OpenApiGenerationOptions, type OpenApiParameterInput, type OpenApiRouteInput, type OpenApiSchemaInput, fromArtifact, fromArtifactSync, write };
package/dist/index.js ADDED
@@ -0,0 +1,354 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ApiDocsPlugin: () => ApiDocsPlugin,
34
+ fromArtifact: () => fromArtifact,
35
+ fromArtifactSync: () => fromArtifactSync,
36
+ write: () => write
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+
40
+ // src/openapi.generator.ts
41
+ var import_promises = __toESM(require("fs/promises"));
42
+ var import_path = __toESM(require("path"));
43
+ function resolveOptions(options = {}) {
44
+ return {
45
+ title: options.title ?? "API",
46
+ version: options.version ?? "1.0.0",
47
+ description: options.description ?? "",
48
+ servers: options.servers ?? []
49
+ };
50
+ }
51
+ function fromArtifactSync(artifact, options = {}) {
52
+ const resolved = resolveOptions(options);
53
+ const schemaMap = buildSchemaMap(artifact.schemas);
54
+ const spec = {
55
+ openapi: "3.0.3",
56
+ info: {
57
+ title: resolved.title,
58
+ version: resolved.version,
59
+ description: resolved.description
60
+ },
61
+ paths: {},
62
+ components: {
63
+ schemas: schemaMap
64
+ }
65
+ };
66
+ if (resolved.servers.length > 0) {
67
+ spec.servers = resolved.servers.map((server) => ({ ...server }));
68
+ }
69
+ for (const route of artifact.routes) {
70
+ const routePath = route.fullPath || buildFallbackPath(route);
71
+ const openApiPath = toOpenApiPath(routePath);
72
+ const method = route.method.toLowerCase();
73
+ if (!spec.paths[openApiPath]) {
74
+ spec.paths[openApiPath] = {};
75
+ }
76
+ ;
77
+ spec.paths[openApiPath][method] = buildOperation(
78
+ route,
79
+ schemaMap
80
+ );
81
+ }
82
+ return spec;
83
+ }
84
+ async function fromArtifact(artifact, options = {}) {
85
+ return fromArtifactSync(artifact, options);
86
+ }
87
+ async function write(openapi, outputPath) {
88
+ const absolute = import_path.default.isAbsolute(outputPath) ? outputPath : import_path.default.resolve(process.cwd(), outputPath);
89
+ await import_promises.default.mkdir(import_path.default.dirname(absolute), { recursive: true });
90
+ await import_promises.default.writeFile(absolute, JSON.stringify(openapi, null, 2), "utf-8");
91
+ return absolute;
92
+ }
93
+ function buildOperation(route, schemaMap) {
94
+ const controllerName = route.controller.replace(/Controller$/, "");
95
+ const parameters = route.parameters ?? [];
96
+ const operation = {
97
+ operationId: route.handler,
98
+ tags: [controllerName],
99
+ responses: buildResponses(route.returns, schemaMap)
100
+ };
101
+ const openApiParameters = buildParameters(parameters);
102
+ if (openApiParameters.length > 0) {
103
+ operation.parameters = openApiParameters;
104
+ }
105
+ const requestBody = buildRequestBody(parameters, schemaMap);
106
+ if (requestBody) {
107
+ operation.requestBody = requestBody;
108
+ }
109
+ return operation;
110
+ }
111
+ function buildParameters(parameters) {
112
+ const result = [];
113
+ for (const param of parameters) {
114
+ if (param.decoratorType === "param") {
115
+ result.push({
116
+ name: param.data ?? param.name,
117
+ in: "path",
118
+ required: true,
119
+ schema: tsTypeToJsonSchema(param.type)
120
+ });
121
+ } else if (param.decoratorType === "query") {
122
+ result.push({
123
+ name: param.data ?? param.name,
124
+ in: "query",
125
+ required: param.required === true,
126
+ schema: tsTypeToJsonSchema(param.type)
127
+ });
128
+ }
129
+ }
130
+ return result;
131
+ }
132
+ function buildRequestBody(parameters, schemaMap) {
133
+ const bodyParam = parameters.find((param) => param.decoratorType === "body");
134
+ if (!bodyParam) return null;
135
+ const typeName = extractBaseTypeName(bodyParam.type);
136
+ const schema = typeName && schemaMap[typeName] ? { $ref: `#/components/schemas/${typeName}` } : { type: "object" };
137
+ return {
138
+ required: true,
139
+ content: {
140
+ "application/json": { schema }
141
+ }
142
+ };
143
+ }
144
+ function buildResponses(returns, schemaMap) {
145
+ const responseSchema = resolveResponseSchema(returns, schemaMap);
146
+ if (!responseSchema) {
147
+ return { "200": { description: "Successful response" } };
148
+ }
149
+ return {
150
+ "200": {
151
+ description: "Successful response",
152
+ content: {
153
+ "application/json": { schema: responseSchema }
154
+ }
155
+ }
156
+ };
157
+ }
158
+ function resolveResponseSchema(returns, schemaMap) {
159
+ if (!returns) return null;
160
+ let innerType = returns;
161
+ const promiseMatch = returns.match(/^Promise<(.+)>$/);
162
+ if (promiseMatch) {
163
+ innerType = promiseMatch[1];
164
+ }
165
+ const isArray = innerType.endsWith("[]");
166
+ const baseType = isArray ? innerType.slice(0, -2) : innerType;
167
+ if (["string", "number", "boolean"].includes(baseType)) {
168
+ const primitiveSchema = tsTypeToJsonSchema(baseType);
169
+ return isArray ? { type: "array", items: primitiveSchema } : primitiveSchema;
170
+ }
171
+ if (["void", "any", "unknown"].includes(baseType)) return null;
172
+ if (schemaMap[baseType]) {
173
+ const ref = { $ref: `#/components/schemas/${baseType}` };
174
+ return isArray ? { type: "array", items: ref } : ref;
175
+ }
176
+ return null;
177
+ }
178
+ function buildSchemaMap(schemas) {
179
+ const result = {};
180
+ for (const schemaInfo of schemas) {
181
+ const definition = schemaInfo.schema?.definitions?.[schemaInfo.type];
182
+ if (definition) {
183
+ result[schemaInfo.type] = definition;
184
+ }
185
+ }
186
+ return result;
187
+ }
188
+ function tsTypeToJsonSchema(tsType) {
189
+ switch (tsType) {
190
+ case "number":
191
+ return { type: "number" };
192
+ case "boolean":
193
+ return { type: "boolean" };
194
+ case "string":
195
+ default:
196
+ return { type: "string" };
197
+ }
198
+ }
199
+ function extractBaseTypeName(tsType) {
200
+ if (!tsType) return null;
201
+ let type = tsType;
202
+ const promiseMatch = type.match(/^Promise<(.+)>$/);
203
+ if (promiseMatch) type = promiseMatch[1];
204
+ type = type.replace(/\[\]$/, "");
205
+ const genericMatch = type.match(/^\w+<(\w+)>$/);
206
+ if (genericMatch) type = genericMatch[1];
207
+ if (["string", "number", "boolean", "any", "void", "unknown", "object"].includes(type)) {
208
+ return null;
209
+ }
210
+ return type;
211
+ }
212
+ function toOpenApiPath(expressPath) {
213
+ return expressPath.replace(/:(\w+)/g, "{$1}");
214
+ }
215
+ function buildFallbackPath(route) {
216
+ const parts = [route.prefix, route.version, route.route, route.path].filter((part) => part !== void 0 && part !== null && part !== "").map((part) => String(part).replace(/^\/+|\/+$/g, "")).filter((part) => part.length > 0);
217
+ const joined = parts.join("/");
218
+ return `/${joined}`;
219
+ }
220
+
221
+ // src/api-docs.plugin.ts
222
+ var DEFAULT_OPENAPI_ROUTE = "/openapi.json";
223
+ var DEFAULT_UI_ROUTE = "/docs";
224
+ var DEFAULT_UI_TITLE = "API Docs";
225
+ function isContextKey(artifact) {
226
+ return typeof artifact === "string";
227
+ }
228
+ function isArtifact(value) {
229
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
230
+ const obj = value;
231
+ return Array.isArray(obj.routes) && Array.isArray(obj.schemas);
232
+ }
233
+ var ApiDocsPlugin = class {
234
+ artifact;
235
+ openApiRoute;
236
+ uiRoute;
237
+ uiTitle;
238
+ reloadOnRequest;
239
+ genOptions;
240
+ app = null;
241
+ cachedSpec = null;
242
+ constructor(options) {
243
+ this.artifact = options.artifact;
244
+ this.openApiRoute = this.normalizeRoute(options.openApiRoute ?? DEFAULT_OPENAPI_ROUTE);
245
+ this.uiRoute = this.normalizeRoute(options.uiRoute ?? DEFAULT_UI_ROUTE);
246
+ this.uiTitle = options.uiTitle ?? DEFAULT_UI_TITLE;
247
+ this.reloadOnRequest = options.reloadOnRequest ?? false;
248
+ this.genOptions = {
249
+ title: options.title,
250
+ version: options.version,
251
+ description: options.description,
252
+ servers: options.servers
253
+ };
254
+ }
255
+ afterModulesRegistered = async (app, hono) => {
256
+ this.app = app;
257
+ hono.get(this.openApiRoute, async (c) => {
258
+ try {
259
+ const spec = await this.resolveSpec();
260
+ return c.json(spec);
261
+ } catch (error) {
262
+ return c.json(
263
+ {
264
+ error: "Failed to load OpenAPI spec",
265
+ message: this.toErrorMessage(error)
266
+ },
267
+ 500
268
+ );
269
+ }
270
+ });
271
+ hono.get(this.uiRoute, (c) => {
272
+ return c.html(this.renderSwaggerUiHtml());
273
+ });
274
+ };
275
+ normalizeRoute(input) {
276
+ const trimmed = input.trim();
277
+ if (!trimmed) return "/";
278
+ let normalized = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
279
+ if (normalized.length > 1) {
280
+ normalized = normalized.replace(/\/+$/g, "");
281
+ }
282
+ return normalized || "/";
283
+ }
284
+ async resolveSpec() {
285
+ if (!this.reloadOnRequest && this.cachedSpec) {
286
+ return this.cachedSpec;
287
+ }
288
+ let artifact;
289
+ if (isContextKey(this.artifact)) {
290
+ if (!this.app) {
291
+ throw new Error("ApiDocsPlugin: app not available when resolving artifact from context");
292
+ }
293
+ const value = this.app.getContext().get(this.artifact);
294
+ if (value === void 0) {
295
+ throw new Error(
296
+ `ApiDocsPlugin: no artifact at context key '${this.artifact}'. Ensure RPC plugin (or another producer) runs before ApiDocs and writes to this key.`
297
+ );
298
+ }
299
+ if (!isArtifact(value)) {
300
+ throw new Error(
301
+ `ApiDocsPlugin: value at '${this.artifact}' is not a valid artifact (expected object with routes and schemas)`
302
+ );
303
+ }
304
+ artifact = value;
305
+ } else {
306
+ artifact = this.artifact;
307
+ }
308
+ const spec = fromArtifactSync(artifact, this.genOptions);
309
+ if (!this.reloadOnRequest) this.cachedSpec = spec;
310
+ return spec;
311
+ }
312
+ renderSwaggerUiHtml() {
313
+ const title = this.escapeHtml(this.uiTitle);
314
+ const openApiRoute = this.escapeJsString(this.openApiRoute);
315
+ return `<!doctype html>
316
+ <html lang="en">
317
+ <head>
318
+ <meta charset="utf-8" />
319
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
320
+ <title>${title}</title>
321
+ <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
322
+ </head>
323
+ <body>
324
+ <div id="swagger-ui"></div>
325
+ <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
326
+ <script>
327
+ window.onload = function () {
328
+ window.ui = SwaggerUIBundle({
329
+ url: '${openApiRoute}',
330
+ dom_id: '#swagger-ui'
331
+ });
332
+ };
333
+ </script>
334
+ </body>
335
+ </html>`;
336
+ }
337
+ escapeHtml(value) {
338
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
339
+ }
340
+ escapeJsString(value) {
341
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
342
+ }
343
+ toErrorMessage(error) {
344
+ return error instanceof Error ? error.message : String(error);
345
+ }
346
+ };
347
+ // Annotate the CommonJS export names for ESM import in node:
348
+ 0 && (module.exports = {
349
+ ApiDocsPlugin,
350
+ fromArtifact,
351
+ fromArtifactSync,
352
+ write
353
+ });
354
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/openapi.generator.ts","../src/api-docs.plugin.ts"],"sourcesContent":["export { ApiDocsPlugin } 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 = 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\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'`) resolved via app.getContext().get(key). OpenAPI is always generated from the artifact. */\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\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,sBAAe;AACf,kBAAiB;AAmDjB,SAAS,eAAe,UAAoC,CAAC,GAA2B;AACvF,SAAO;AAAA,IACN,OAAO,QAAQ,SAAS;AAAA,IACxB,SAAS,QAAQ,WAAW;AAAA,IAC5B,aAAa,QAAQ,eAAe;AAAA,IACpC,SAAS,QAAQ,WAAW,CAAC;AAAA,EAC9B;AACD;AAEO,SAAS,iBACf,UACA,UAAoC,CAAC,GACnB;AAClB,QAAM,WAAW,eAAe,OAAO;AACvC,QAAM,YAAY,eAAe,SAAS,OAAO;AACjD,QAAM,OAA2B;AAAA,IAChC,SAAS;AAAA,IACT,MAAM;AAAA,MACL,OAAO,SAAS;AAAA,MAChB,SAAS,SAAS;AAAA,MAClB,aAAa,SAAS;AAAA,IACvB;AAAA,IACA,OAAO,CAAC;AAAA,IACR,YAAY;AAAA,MACX,SAAS;AAAA,IACV;AAAA,EACD;AAEA,MAAI,SAAS,QAAQ,SAAS,GAAG;AAChC,SAAK,UAAU,SAAS,QAAQ,IAAI,CAAC,YAAY,EAAE,GAAG,OAAO,EAAE;AAAA,EAChE;AAEA,aAAW,SAAS,SAAS,QAAQ;AACpC,UAAM,YAAY,MAAM,YAAY,kBAAkB,KAAK;AAC3D,UAAM,cAAc,cAAc,SAAS;AAC3C,UAAM,SAAS,MAAM,OAAO,YAAY;AACxC,QAAI,CAAC,KAAK,MAAM,WAAW,GAAG;AAC7B,WAAK,MAAM,WAAW,IAAI,CAAC;AAAA,IAC5B;AACA;AAAC,IAAC,KAAK,MAAM,WAAW,EAAgD,MAAM,IAAI;AAAA,MACjF;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;AAEA,eAAsB,aACrB,UACA,UAAoC,CAAC,GACV;AAC3B,SAAO,iBAAiB,UAAU,OAAO;AAC1C;AAEA,eAAsB,MAAM,SAA0B,YAAqC;AAC1F,QAAM,WAAW,YAAAA,QAAK,WAAW,UAAU,IAAI,aAAa,YAAAA,QAAK,QAAQ,QAAQ,IAAI,GAAG,UAAU;AAClG,QAAM,gBAAAC,QAAG,MAAM,YAAAD,QAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAC1D,QAAM,gBAAAC,QAAG,UAAU,UAAU,KAAK,UAAU,SAAS,MAAM,CAAC,GAAG,OAAO;AACtE,SAAO;AACR;AAEA,SAAS,eACR,OACA,WAC4B;AAC5B,QAAM,iBAAiB,MAAM,WAAW,QAAQ,eAAe,EAAE;AACjE,QAAM,aAAa,MAAM,cAAc,CAAC;AACxC,QAAM,YAAuC;AAAA,IAC5C,aAAa,MAAM;AAAA,IACnB,MAAM,CAAC,cAAc;AAAA,IACrB,WAAW,eAAe,MAAM,SAAS,SAAS;AAAA,EACnD;AAEA,QAAM,oBAAoB,gBAAgB,UAAU;AACpD,MAAI,kBAAkB,SAAS,GAAG;AACjC,cAAU,aAAa;AAAA,EACxB;AAEA,QAAM,cAAc,iBAAiB,YAAY,SAAS;AAC1D,MAAI,aAAa;AAChB,cAAU,cAAc;AAAA,EACzB;AAEA,SAAO;AACR;AAEA,SAAS,gBAAgB,YAA2E;AACnG,QAAM,SAAsC,CAAC;AAE7C,aAAW,SAAS,YAAY;AAC/B,QAAI,MAAM,kBAAkB,SAAS;AACpC,aAAO,KAAK;AAAA,QACX,MAAM,MAAM,QAAQ,MAAM;AAAA,QAC1B,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,QAAQ,mBAAmB,MAAM,IAAI;AAAA,MACtC,CAAC;AAAA,IACF,WAAW,MAAM,kBAAkB,SAAS;AAC3C,aAAO,KAAK;AAAA,QACX,MAAM,MAAM,QAAQ,MAAM;AAAA,QAC1B,IAAI;AAAA,QACJ,UAAU,MAAM,aAAa;AAAA,QAC7B,QAAQ,mBAAmB,MAAM,IAAI;AAAA,MACtC,CAAC;AAAA,IACF;AAAA,EACD;AAEA,SAAO;AACR;AAEA,SAAS,iBACR,YACA,WACqC;AACrC,QAAM,YAAY,WAAW,KAAK,CAAC,UAAU,MAAM,kBAAkB,MAAM;AAC3E,MAAI,CAAC,UAAW,QAAO;AAEvB,QAAM,WAAW,oBAAoB,UAAU,IAAI;AACnD,QAAM,SACL,YAAY,UAAU,QAAQ,IAC3B,EAAE,MAAM,wBAAwB,QAAQ,GAAG,IAC3C,EAAE,MAAM,SAAkB;AAE9B,SAAO;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,MACR,oBAAoB,EAAE,OAAO;AAAA,IAC9B;AAAA,EACD;AACD;AAEA,SAAS,eACR,SACA,WAC4B;AAC5B,QAAM,iBAAiB,sBAAsB,SAAS,SAAS;AAE/D,MAAI,CAAC,gBAAgB;AACpB,WAAO,EAAE,OAAO,EAAE,aAAa,sBAAsB,EAAE;AAAA,EACxD;AAEA,SAAO;AAAA,IACN,OAAO;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,QACR,oBAAoB,EAAE,QAAQ,eAAe;AAAA,MAC9C;AAAA,IACD;AAAA,EACD;AACD;AAEA,SAAS,sBACR,SACA,WAC6B;AAC7B,MAAI,CAAC,QAAS,QAAO;AAErB,MAAI,YAAY;AAChB,QAAM,eAAe,QAAQ,MAAM,iBAAiB;AACpD,MAAI,cAAc;AACjB,gBAAY,aAAa,CAAC;AAAA,EAC3B;AAEA,QAAM,UAAU,UAAU,SAAS,IAAI;AACvC,QAAM,WAAW,UAAU,UAAU,MAAM,GAAG,EAAE,IAAI;AACpD,MAAI,CAAC,UAAU,UAAU,SAAS,EAAE,SAAS,QAAQ,GAAG;AACvD,UAAM,kBAAkB,mBAAmB,QAAQ;AACnD,WAAO,UAAU,EAAE,MAAM,SAAS,OAAO,gBAAgB,IAAI;AAAA,EAC9D;AACA,MAAI,CAAC,QAAQ,OAAO,SAAS,EAAE,SAAS,QAAQ,EAAG,QAAO;AAC1D,MAAI,UAAU,QAAQ,GAAG;AACxB,UAAM,MAAM,EAAE,MAAM,wBAAwB,QAAQ,GAAG;AACvD,WAAO,UAAU,EAAE,MAAM,SAAS,OAAO,IAAI,IAAI;AAAA,EAClD;AACA,SAAO;AACR;AAEA,SAAS,eAAe,SAA6E;AACpG,QAAM,SAA8C,CAAC;AACrD,aAAW,cAAc,SAAS;AACjC,UAAM,aAAa,WAAW,QAAQ,cAAc,WAAW,IAAI;AACnE,QAAI,YAAY;AACf,aAAO,WAAW,IAAI,IAAI;AAAA,IAC3B;AAAA,EACD;AACA,SAAO;AACR;AAEA,SAAS,mBAAmB,QAAyC;AACpE,UAAQ,QAAQ;AAAA,IACf,KAAK;AACJ,aAAO,EAAE,MAAM,SAAkB;AAAA,IAClC,KAAK;AACJ,aAAO,EAAE,MAAM,UAAmB;AAAA,IACnC,KAAK;AAAA,IACL;AACC,aAAO,EAAE,MAAM,SAAkB;AAAA,EACnC;AACD;AAEA,SAAS,oBAAoB,QAA+B;AAC3D,MAAI,CAAC,OAAQ,QAAO;AAEpB,MAAI,OAAO;AACX,QAAM,eAAe,KAAK,MAAM,iBAAiB;AACjD,MAAI,aAAc,QAAO,aAAa,CAAC;AACvC,SAAO,KAAK,QAAQ,SAAS,EAAE;AAC/B,QAAM,eAAe,KAAK,MAAM,cAAc;AAC9C,MAAI,aAAc,QAAO,aAAa,CAAC;AACvC,MAAI,CAAC,UAAU,UAAU,WAAW,OAAO,QAAQ,WAAW,QAAQ,EAAE,SAAS,IAAI,GAAG;AACvF,WAAO;AAAA,EACR;AACA,SAAO;AACR;AAEA,SAAS,cAAc,aAA6B;AACnD,SAAO,YAAY,QAAQ,WAAW,MAAM;AAC7C;AAEA,SAAS,kBAAkB,OAAkC;AAC5D,QAAM,QAAQ,CAAC,MAAM,QAAQ,MAAM,SAAS,MAAM,OAAO,MAAM,IAAI,EACjE,OAAO,CAAC,SAAS,SAAS,UAAa,SAAS,QAAQ,SAAS,EAAE,EACnE,IAAI,CAAC,SAAS,OAAO,IAAI,EAAE,QAAQ,cAAc,EAAE,CAAC,EACpD,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;AAClC,QAAM,SAAS,MAAM,KAAK,GAAG;AAC7B,SAAO,IAAI,MAAM;AAClB;;;ACjRA,IAAM,wBAAwB;AAC9B,IAAM,mBAAmB;AACzB,IAAM,mBAAmB;AAIzB,SAAS,aAAa,UAA6C;AAClE,SAAO,OAAO,aAAa;AAC5B;AAEA,SAAS,WAAW,OAA+C;AAClE,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,EAAG,QAAO;AACxE,QAAM,MAAM;AACZ,SAAO,MAAM,QAAQ,IAAI,MAAM,KAAK,MAAM,QAAQ,IAAI,OAAO;AAC9D;AAWO,IAAM,gBAAN,MAAuC;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,MAA0B;AAAA,EAC1B,aAA6C;AAAA,EAErD,YAAY,SAA+B;AAC1C,SAAK,WAAW,QAAQ;AACxB,SAAK,eAAe,KAAK,eAAe,QAAQ,gBAAgB,qBAAqB;AACrF,SAAK,UAAU,KAAK,eAAe,QAAQ,WAAW,gBAAgB;AACtE,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,kBAAkB,QAAQ,mBAAmB;AAClD,SAAK,aAAa;AAAA,MACjB,OAAO,QAAQ;AAAA,MACf,SAAS,QAAQ;AAAA,MACjB,aAAa,QAAQ;AAAA,MACrB,SAAS,QAAQ;AAAA,IAClB;AAAA,EACD;AAAA,EAEA,yBAAyB,OAAO,KAAkB,SAA8B;AAC/E,SAAK,MAAM;AAEX,SAAK,IAAI,KAAK,cAAc,OAAO,MAAM;AACxC,UAAI;AACH,cAAM,OAAO,MAAM,KAAK,YAAY;AACpC,eAAO,EAAE,KAAK,IAAI;AAAA,MACnB,SAAS,OAAO;AACf,eAAO,EAAE;AAAA,UACR;AAAA,YACC,OAAO;AAAA,YACP,SAAS,KAAK,eAAe,KAAK;AAAA,UACnC;AAAA,UACA;AAAA,QACD;AAAA,MACD;AAAA,IACD,CAAC;AAED,SAAK,IAAI,KAAK,SAAS,CAAC,MAAM;AAC7B,aAAO,EAAE,KAAK,KAAK,oBAAoB,CAAC;AAAA,IACzC,CAAC;AAAA,EACF;AAAA,EAEQ,eAAe,OAAuB;AAC7C,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,QAAS,QAAO;AACrB,QAAI,aAAa,QAAQ,WAAW,GAAG,IAAI,UAAU,IAAI,OAAO;AAChE,QAAI,WAAW,SAAS,GAAG;AAC1B,mBAAa,WAAW,QAAQ,SAAS,EAAE;AAAA,IAC5C;AACA,WAAO,cAAc;AAAA,EACtB;AAAA,EAEA,MAAc,cAAgD;AAC7D,QAAI,CAAC,KAAK,mBAAmB,KAAK,YAAY;AAC7C,aAAO,KAAK;AAAA,IACb;AAEA,QAAI;AAEJ,QAAI,aAAa,KAAK,QAAQ,GAAG;AAChC,UAAI,CAAC,KAAK,KAAK;AACd,cAAM,IAAI,MAAM,uEAAuE;AAAA,MACxF;AACA,YAAM,QAAQ,KAAK,IAAI,WAAW,EAAE,IAAa,KAAK,QAAQ;AAC9D,UAAI,UAAU,QAAW;AACxB,cAAM,IAAI;AAAA,UACT,8CAA8C,KAAK,QAAQ;AAAA,QAC5D;AAAA,MACD;AACA,UAAI,CAAC,WAAW,KAAK,GAAG;AACvB,cAAM,IAAI;AAAA,UACT,4BAA4B,KAAK,QAAQ;AAAA,QAC1C;AAAA,MACD;AACA,iBAAW;AAAA,IACZ,OAAO;AACN,iBAAW,KAAK;AAAA,IACjB;AAEA,UAAM,OAAO,iBAAiB,UAAU,KAAK,UAAU;AACvD,QAAI,CAAC,KAAK,gBAAiB,MAAK,aAAa;AAC7C,WAAO;AAAA,EACR;AAAA,EAEQ,sBAA8B;AACrC,UAAM,QAAQ,KAAK,WAAW,KAAK,OAAO;AAC1C,UAAM,eAAe,KAAK,eAAe,KAAK,YAAY;AAE1D,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA,UAKC,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YASH,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOvB;AAAA,EAEQ,WAAW,OAAuB;AACzC,WAAO,MACL,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,OAAO;AAAA,EACxB;AAAA,EAEQ,eAAe,OAAuB;AAC7C,WAAO,MAAM,QAAQ,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAK;AAAA,EACxD;AAAA,EAEQ,eAAe,OAAwB;AAC9C,WAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,EAC7D;AACD;","names":["path","fs"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,314 @@
1
+ // src/openapi.generator.ts
2
+ import fs from "fs/promises";
3
+ import path from "path";
4
+ function resolveOptions(options = {}) {
5
+ return {
6
+ title: options.title ?? "API",
7
+ version: options.version ?? "1.0.0",
8
+ description: options.description ?? "",
9
+ servers: options.servers ?? []
10
+ };
11
+ }
12
+ function fromArtifactSync(artifact, options = {}) {
13
+ const resolved = resolveOptions(options);
14
+ const schemaMap = buildSchemaMap(artifact.schemas);
15
+ const spec = {
16
+ openapi: "3.0.3",
17
+ info: {
18
+ title: resolved.title,
19
+ version: resolved.version,
20
+ description: resolved.description
21
+ },
22
+ paths: {},
23
+ components: {
24
+ schemas: schemaMap
25
+ }
26
+ };
27
+ if (resolved.servers.length > 0) {
28
+ spec.servers = resolved.servers.map((server) => ({ ...server }));
29
+ }
30
+ for (const route of artifact.routes) {
31
+ const routePath = route.fullPath || buildFallbackPath(route);
32
+ const openApiPath = toOpenApiPath(routePath);
33
+ const method = route.method.toLowerCase();
34
+ if (!spec.paths[openApiPath]) {
35
+ spec.paths[openApiPath] = {};
36
+ }
37
+ ;
38
+ spec.paths[openApiPath][method] = buildOperation(
39
+ route,
40
+ schemaMap
41
+ );
42
+ }
43
+ return spec;
44
+ }
45
+ async function fromArtifact(artifact, options = {}) {
46
+ return fromArtifactSync(artifact, options);
47
+ }
48
+ async function write(openapi, outputPath) {
49
+ const absolute = path.isAbsolute(outputPath) ? outputPath : path.resolve(process.cwd(), outputPath);
50
+ await fs.mkdir(path.dirname(absolute), { recursive: true });
51
+ await fs.writeFile(absolute, JSON.stringify(openapi, null, 2), "utf-8");
52
+ return absolute;
53
+ }
54
+ function buildOperation(route, schemaMap) {
55
+ const controllerName = route.controller.replace(/Controller$/, "");
56
+ const parameters = route.parameters ?? [];
57
+ const operation = {
58
+ operationId: route.handler,
59
+ tags: [controllerName],
60
+ responses: buildResponses(route.returns, schemaMap)
61
+ };
62
+ const openApiParameters = buildParameters(parameters);
63
+ if (openApiParameters.length > 0) {
64
+ operation.parameters = openApiParameters;
65
+ }
66
+ const requestBody = buildRequestBody(parameters, schemaMap);
67
+ if (requestBody) {
68
+ operation.requestBody = requestBody;
69
+ }
70
+ return operation;
71
+ }
72
+ function buildParameters(parameters) {
73
+ const result = [];
74
+ for (const param of parameters) {
75
+ if (param.decoratorType === "param") {
76
+ result.push({
77
+ name: param.data ?? param.name,
78
+ in: "path",
79
+ required: true,
80
+ schema: tsTypeToJsonSchema(param.type)
81
+ });
82
+ } else if (param.decoratorType === "query") {
83
+ result.push({
84
+ name: param.data ?? param.name,
85
+ in: "query",
86
+ required: param.required === true,
87
+ schema: tsTypeToJsonSchema(param.type)
88
+ });
89
+ }
90
+ }
91
+ return result;
92
+ }
93
+ function buildRequestBody(parameters, schemaMap) {
94
+ const bodyParam = parameters.find((param) => param.decoratorType === "body");
95
+ if (!bodyParam) return null;
96
+ const typeName = extractBaseTypeName(bodyParam.type);
97
+ const schema = typeName && schemaMap[typeName] ? { $ref: `#/components/schemas/${typeName}` } : { type: "object" };
98
+ return {
99
+ required: true,
100
+ content: {
101
+ "application/json": { schema }
102
+ }
103
+ };
104
+ }
105
+ function buildResponses(returns, schemaMap) {
106
+ const responseSchema = resolveResponseSchema(returns, schemaMap);
107
+ if (!responseSchema) {
108
+ return { "200": { description: "Successful response" } };
109
+ }
110
+ return {
111
+ "200": {
112
+ description: "Successful response",
113
+ content: {
114
+ "application/json": { schema: responseSchema }
115
+ }
116
+ }
117
+ };
118
+ }
119
+ function resolveResponseSchema(returns, schemaMap) {
120
+ if (!returns) return null;
121
+ let innerType = returns;
122
+ const promiseMatch = returns.match(/^Promise<(.+)>$/);
123
+ if (promiseMatch) {
124
+ innerType = promiseMatch[1];
125
+ }
126
+ const isArray = innerType.endsWith("[]");
127
+ const baseType = isArray ? innerType.slice(0, -2) : innerType;
128
+ if (["string", "number", "boolean"].includes(baseType)) {
129
+ const primitiveSchema = tsTypeToJsonSchema(baseType);
130
+ return isArray ? { type: "array", items: primitiveSchema } : primitiveSchema;
131
+ }
132
+ if (["void", "any", "unknown"].includes(baseType)) return null;
133
+ if (schemaMap[baseType]) {
134
+ const ref = { $ref: `#/components/schemas/${baseType}` };
135
+ return isArray ? { type: "array", items: ref } : ref;
136
+ }
137
+ return null;
138
+ }
139
+ function buildSchemaMap(schemas) {
140
+ const result = {};
141
+ for (const schemaInfo of schemas) {
142
+ const definition = schemaInfo.schema?.definitions?.[schemaInfo.type];
143
+ if (definition) {
144
+ result[schemaInfo.type] = definition;
145
+ }
146
+ }
147
+ return result;
148
+ }
149
+ function tsTypeToJsonSchema(tsType) {
150
+ switch (tsType) {
151
+ case "number":
152
+ return { type: "number" };
153
+ case "boolean":
154
+ return { type: "boolean" };
155
+ case "string":
156
+ default:
157
+ return { type: "string" };
158
+ }
159
+ }
160
+ function extractBaseTypeName(tsType) {
161
+ if (!tsType) return null;
162
+ let type = tsType;
163
+ const promiseMatch = type.match(/^Promise<(.+)>$/);
164
+ if (promiseMatch) type = promiseMatch[1];
165
+ type = type.replace(/\[\]$/, "");
166
+ const genericMatch = type.match(/^\w+<(\w+)>$/);
167
+ if (genericMatch) type = genericMatch[1];
168
+ if (["string", "number", "boolean", "any", "void", "unknown", "object"].includes(type)) {
169
+ return null;
170
+ }
171
+ return type;
172
+ }
173
+ function toOpenApiPath(expressPath) {
174
+ return expressPath.replace(/:(\w+)/g, "{$1}");
175
+ }
176
+ function buildFallbackPath(route) {
177
+ const parts = [route.prefix, route.version, route.route, route.path].filter((part) => part !== void 0 && part !== null && part !== "").map((part) => String(part).replace(/^\/+|\/+$/g, "")).filter((part) => part.length > 0);
178
+ const joined = parts.join("/");
179
+ return `/${joined}`;
180
+ }
181
+
182
+ // src/api-docs.plugin.ts
183
+ var DEFAULT_OPENAPI_ROUTE = "/openapi.json";
184
+ var DEFAULT_UI_ROUTE = "/docs";
185
+ var DEFAULT_UI_TITLE = "API Docs";
186
+ function isContextKey(artifact) {
187
+ return typeof artifact === "string";
188
+ }
189
+ function isArtifact(value) {
190
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
191
+ const obj = value;
192
+ return Array.isArray(obj.routes) && Array.isArray(obj.schemas);
193
+ }
194
+ var ApiDocsPlugin = class {
195
+ artifact;
196
+ openApiRoute;
197
+ uiRoute;
198
+ uiTitle;
199
+ reloadOnRequest;
200
+ genOptions;
201
+ app = null;
202
+ cachedSpec = null;
203
+ constructor(options) {
204
+ this.artifact = options.artifact;
205
+ this.openApiRoute = this.normalizeRoute(options.openApiRoute ?? DEFAULT_OPENAPI_ROUTE);
206
+ this.uiRoute = this.normalizeRoute(options.uiRoute ?? DEFAULT_UI_ROUTE);
207
+ this.uiTitle = options.uiTitle ?? DEFAULT_UI_TITLE;
208
+ this.reloadOnRequest = options.reloadOnRequest ?? false;
209
+ this.genOptions = {
210
+ title: options.title,
211
+ version: options.version,
212
+ description: options.description,
213
+ servers: options.servers
214
+ };
215
+ }
216
+ afterModulesRegistered = async (app, hono) => {
217
+ this.app = app;
218
+ hono.get(this.openApiRoute, async (c) => {
219
+ try {
220
+ const spec = await this.resolveSpec();
221
+ return c.json(spec);
222
+ } catch (error) {
223
+ return c.json(
224
+ {
225
+ error: "Failed to load OpenAPI spec",
226
+ message: this.toErrorMessage(error)
227
+ },
228
+ 500
229
+ );
230
+ }
231
+ });
232
+ hono.get(this.uiRoute, (c) => {
233
+ return c.html(this.renderSwaggerUiHtml());
234
+ });
235
+ };
236
+ normalizeRoute(input) {
237
+ const trimmed = input.trim();
238
+ if (!trimmed) return "/";
239
+ let normalized = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
240
+ if (normalized.length > 1) {
241
+ normalized = normalized.replace(/\/+$/g, "");
242
+ }
243
+ return normalized || "/";
244
+ }
245
+ async resolveSpec() {
246
+ if (!this.reloadOnRequest && this.cachedSpec) {
247
+ return this.cachedSpec;
248
+ }
249
+ let artifact;
250
+ if (isContextKey(this.artifact)) {
251
+ if (!this.app) {
252
+ throw new Error("ApiDocsPlugin: app not available when resolving artifact from context");
253
+ }
254
+ const value = this.app.getContext().get(this.artifact);
255
+ if (value === void 0) {
256
+ throw new Error(
257
+ `ApiDocsPlugin: no artifact at context key '${this.artifact}'. Ensure RPC plugin (or another producer) runs before ApiDocs and writes to this key.`
258
+ );
259
+ }
260
+ if (!isArtifact(value)) {
261
+ throw new Error(
262
+ `ApiDocsPlugin: value at '${this.artifact}' is not a valid artifact (expected object with routes and schemas)`
263
+ );
264
+ }
265
+ artifact = value;
266
+ } else {
267
+ artifact = this.artifact;
268
+ }
269
+ const spec = fromArtifactSync(artifact, this.genOptions);
270
+ if (!this.reloadOnRequest) this.cachedSpec = spec;
271
+ return spec;
272
+ }
273
+ renderSwaggerUiHtml() {
274
+ const title = this.escapeHtml(this.uiTitle);
275
+ const openApiRoute = this.escapeJsString(this.openApiRoute);
276
+ return `<!doctype html>
277
+ <html lang="en">
278
+ <head>
279
+ <meta charset="utf-8" />
280
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
281
+ <title>${title}</title>
282
+ <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
283
+ </head>
284
+ <body>
285
+ <div id="swagger-ui"></div>
286
+ <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
287
+ <script>
288
+ window.onload = function () {
289
+ window.ui = SwaggerUIBundle({
290
+ url: '${openApiRoute}',
291
+ dom_id: '#swagger-ui'
292
+ });
293
+ };
294
+ </script>
295
+ </body>
296
+ </html>`;
297
+ }
298
+ escapeHtml(value) {
299
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
300
+ }
301
+ escapeJsString(value) {
302
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
303
+ }
304
+ toErrorMessage(error) {
305
+ return error instanceof Error ? error.message : String(error);
306
+ }
307
+ };
308
+ export {
309
+ ApiDocsPlugin,
310
+ fromArtifact,
311
+ fromArtifactSync,
312
+ write
313
+ };
314
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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 = 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\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'`) resolved via app.getContext().get(key). OpenAPI is always generated from the artifact. */\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\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":[]}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@honestjs/api-docs-plugin",
3
+ "version": "1.0.0",
4
+ "description": "API Docs plugin for HonestJS - generates OpenAPI from artifact object or context key and serves Swagger UI",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "keywords": [
19
+ "honestjs",
20
+ "hono",
21
+ "swagger",
22
+ "openapi",
23
+ "api-docs"
24
+ ],
25
+ "author": "Orkhan Karimov <karimovok1@gmail.com> (https://github.com/kerimovok)",
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/honestjs/plugins.git",
30
+ "directory": "packages/api-docs-plugin"
31
+ },
32
+ "devDependencies": {
33
+ "honestjs": "^0.1.9",
34
+ "hono": "^4.12.6",
35
+ "reflect-metadata": "^0.2.2",
36
+ "vitest": "^3.2.4"
37
+ },
38
+ "dependencies": {
39
+ "openapi-types": "^12.1.3"
40
+ },
41
+ "peerDependencies": {
42
+ "honestjs": "^0.1.9",
43
+ "hono": "^4.0.0",
44
+ "typescript": "^5.x"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup",
48
+ "dev": "tsup --watch",
49
+ "clean": "rm -rf dist",
50
+ "type-check": "tsc --noEmit",
51
+ "test": "vitest run",
52
+ "test:watch": "vitest"
53
+ }
54
+ }