@hono-di/swagger 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +226 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +83 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.js +217 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@hono-di/core');
|
|
4
|
+
require('reflect-metadata');
|
|
5
|
+
|
|
6
|
+
// src/document-builder.ts
|
|
7
|
+
var DocumentBuilder = class {
|
|
8
|
+
document = {
|
|
9
|
+
openapi: "3.0.0",
|
|
10
|
+
info: {
|
|
11
|
+
title: "",
|
|
12
|
+
description: "",
|
|
13
|
+
version: "1.0.0",
|
|
14
|
+
contact: {}
|
|
15
|
+
},
|
|
16
|
+
tags: [],
|
|
17
|
+
servers: [],
|
|
18
|
+
paths: {},
|
|
19
|
+
components: {
|
|
20
|
+
securitySchemes: {}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
setTitle(title) {
|
|
24
|
+
this.document.info.title = title;
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
setDescription(description) {
|
|
28
|
+
this.document.info.description = description;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
setVersion(version) {
|
|
32
|
+
this.document.info.version = version;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
setContact(name, url, email) {
|
|
36
|
+
this.document.info.contact = { name, url, email };
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
addServer(url, description) {
|
|
40
|
+
this.document.servers.push({ url, description });
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
addTag(name, description) {
|
|
44
|
+
this.document.tags.push({ name, description });
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
addBearerAuth(options = {}, name = "bearer") {
|
|
48
|
+
this.document.components.securitySchemes[name] = {
|
|
49
|
+
type: "http",
|
|
50
|
+
scheme: "bearer",
|
|
51
|
+
bearerFormat: "JWT",
|
|
52
|
+
...options
|
|
53
|
+
};
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
build() {
|
|
57
|
+
return this.document;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var DECORATORS_PREFIX = "swagger";
|
|
61
|
+
var DECORATORS = {
|
|
62
|
+
API_TAGS: `${DECORATORS_PREFIX}/apiTags`,
|
|
63
|
+
API_OPERATION: `${DECORATORS_PREFIX}/apiOperation`,
|
|
64
|
+
API_RESPONSE: `${DECORATORS_PREFIX}/apiResponse`,
|
|
65
|
+
API_PARAM: `${DECORATORS_PREFIX}/apiParam`,
|
|
66
|
+
API_BODY: `${DECORATORS_PREFIX}/apiBody`,
|
|
67
|
+
API_PROPERTY: `${DECORATORS_PREFIX}/apiProperty`
|
|
68
|
+
};
|
|
69
|
+
function ApiTags(...tags) {
|
|
70
|
+
return (target) => {
|
|
71
|
+
Reflect.defineMetadata(DECORATORS.API_TAGS, tags, target);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function ApiOperation(options) {
|
|
75
|
+
return (target, propertyKey, descriptor) => {
|
|
76
|
+
Reflect.defineMetadata(DECORATORS.API_OPERATION, options, descriptor.value);
|
|
77
|
+
return descriptor;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function ApiResponse(options) {
|
|
81
|
+
return (target, propertyKey, descriptor) => {
|
|
82
|
+
const responses = Reflect.getMetadata(DECORATORS.API_RESPONSE, descriptor.value) || {};
|
|
83
|
+
responses[options.status] = options;
|
|
84
|
+
Reflect.defineMetadata(DECORATORS.API_RESPONSE, responses, descriptor.value);
|
|
85
|
+
return descriptor;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function ApiProperty(options = {}) {
|
|
89
|
+
return (target, propertyKey) => {
|
|
90
|
+
const properties = Reflect.getMetadata(DECORATORS.API_PROPERTY, target.constructor) || [];
|
|
91
|
+
properties.push({ key: propertyKey, ...options });
|
|
92
|
+
Reflect.defineMetadata(DECORATORS.API_PROPERTY, properties, target.constructor);
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/explorers/api-scanner.ts
|
|
97
|
+
var SwaggerScanner = class {
|
|
98
|
+
constructor(container) {
|
|
99
|
+
this.container = container;
|
|
100
|
+
}
|
|
101
|
+
scan(document) {
|
|
102
|
+
const modules = this.container.getModules();
|
|
103
|
+
modules.forEach((module) => {
|
|
104
|
+
module.controllers.forEach((wrapper) => {
|
|
105
|
+
this.scanController(wrapper.metatype, document);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
return document;
|
|
109
|
+
}
|
|
110
|
+
scanController(controller, document) {
|
|
111
|
+
if (!controller) return;
|
|
112
|
+
const { prefix } = Reflect.getMetadata(core.METADATA_KEYS.CONTROLLER, controller) || { prefix: "" };
|
|
113
|
+
const routes = Reflect.getMetadata(core.METADATA_KEYS.ROUTES, controller) || [];
|
|
114
|
+
const apiTags = Reflect.getMetadata(DECORATORS.API_TAGS, controller) || [];
|
|
115
|
+
apiTags.forEach((tag) => {
|
|
116
|
+
if (!document.tags.find((t) => t.name === tag)) {
|
|
117
|
+
document.tags.push({ name: tag });
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
routes.forEach((route) => {
|
|
121
|
+
const method = route.requestMethod;
|
|
122
|
+
const path = this.normalizePath(prefix, route.path);
|
|
123
|
+
const descriptor = Object.getOwnPropertyDescriptor(controller.prototype, route.methodName);
|
|
124
|
+
const handler = descriptor?.value;
|
|
125
|
+
if (!handler) return;
|
|
126
|
+
const operation = this.createOperation(handler, apiTags);
|
|
127
|
+
if (!document.paths[path]) {
|
|
128
|
+
document.paths[path] = {};
|
|
129
|
+
}
|
|
130
|
+
document.paths[path][method] = operation;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
createOperation(handler, controllerTags) {
|
|
134
|
+
const apiOperation = Reflect.getMetadata(DECORATORS.API_OPERATION, handler) || {};
|
|
135
|
+
const apiResponses = Reflect.getMetadata(DECORATORS.API_RESPONSE, handler) || {};
|
|
136
|
+
const responses = {};
|
|
137
|
+
Object.keys(apiResponses).forEach((status) => {
|
|
138
|
+
responses[status] = {
|
|
139
|
+
description: apiResponses[status].description
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
if (Object.keys(responses).length === 0) {
|
|
143
|
+
responses["200"] = { description: "Successful operation" };
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
summary: apiOperation.summary || "",
|
|
147
|
+
description: apiOperation.description || "",
|
|
148
|
+
tags: controllerTags,
|
|
149
|
+
responses
|
|
150
|
+
// TODO: Add parameters and request body scanning
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
normalizePath(prefix, path) {
|
|
154
|
+
const cleanPrefix = prefix ? prefix.replace(/^\/+/, "").replace(/\/+$/, "") : "";
|
|
155
|
+
const cleanPath = path ? path.replace(/^\/+/, "").replace(/\/+$/, "") : "";
|
|
156
|
+
let result = "";
|
|
157
|
+
if (cleanPrefix) result += `/${cleanPrefix}`;
|
|
158
|
+
if (cleanPath) result += `/${cleanPath}`;
|
|
159
|
+
return (result || "/").replace(/:([^\/]+)/g, "{$1}");
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// src/swagger-module.ts
|
|
164
|
+
var SwaggerModule = class {
|
|
165
|
+
static createDocument(app, config) {
|
|
166
|
+
const container = app.getContainer();
|
|
167
|
+
const scanner = new SwaggerScanner(container);
|
|
168
|
+
const document = config;
|
|
169
|
+
return scanner.scan(document);
|
|
170
|
+
}
|
|
171
|
+
static setup(path, app, document, options) {
|
|
172
|
+
const httpAdapter = app.getHttpAdapter();
|
|
173
|
+
httpAdapter.get(`${path}-json`, (c) => c.json(document));
|
|
174
|
+
httpAdapter.get(path, (c) => {
|
|
175
|
+
const html = `
|
|
176
|
+
<!DOCTYPE html>
|
|
177
|
+
<html lang="en">
|
|
178
|
+
<head>
|
|
179
|
+
<meta charset="UTF-8">
|
|
180
|
+
<title>${document.info.title}</title>
|
|
181
|
+
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
|
|
182
|
+
<style>
|
|
183
|
+
html { box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; }
|
|
184
|
+
*, *:before, *:after { box-sizing: inherit; }
|
|
185
|
+
body { margin: 0; background: #fafafa; }
|
|
186
|
+
</style>
|
|
187
|
+
</head>
|
|
188
|
+
<body>
|
|
189
|
+
<div id="swagger-ui"></div>
|
|
190
|
+
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js" charset="UTF-8"> </script>
|
|
191
|
+
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
|
|
192
|
+
<script>
|
|
193
|
+
window.onload = function() {
|
|
194
|
+
const ui = SwaggerUIBundle({
|
|
195
|
+
spec: ${JSON.stringify(document)},
|
|
196
|
+
dom_id: '#swagger-ui',
|
|
197
|
+
deepLinking: true,
|
|
198
|
+
presets: [
|
|
199
|
+
SwaggerUIBundle.presets.apis,
|
|
200
|
+
SwaggerUIStandalonePreset
|
|
201
|
+
],
|
|
202
|
+
plugins: [
|
|
203
|
+
SwaggerUIBundle.plugins.DownloadUrl
|
|
204
|
+
],
|
|
205
|
+
layout: "StandaloneLayout"
|
|
206
|
+
});
|
|
207
|
+
window.ui = ui;
|
|
208
|
+
};
|
|
209
|
+
</script>
|
|
210
|
+
</body>
|
|
211
|
+
</html>`;
|
|
212
|
+
return c.html(html);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
exports.ApiOperation = ApiOperation;
|
|
218
|
+
exports.ApiProperty = ApiProperty;
|
|
219
|
+
exports.ApiResponse = ApiResponse;
|
|
220
|
+
exports.ApiTags = ApiTags;
|
|
221
|
+
exports.DECORATORS = DECORATORS;
|
|
222
|
+
exports.DECORATORS_PREFIX = DECORATORS_PREFIX;
|
|
223
|
+
exports.DocumentBuilder = DocumentBuilder;
|
|
224
|
+
exports.SwaggerModule = SwaggerModule;
|
|
225
|
+
//# sourceMappingURL=index.cjs.map
|
|
226
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/document-builder.ts","../src/decorators.ts","../src/explorers/api-scanner.ts","../src/swagger-module.ts"],"names":["METADATA_KEYS"],"mappings":"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","file":"index.cjs","sourcesContent":["export class DocumentBuilder {\n private readonly document: any = {\n openapi: '3.0.0',\n info: {\n title: '',\n description: '',\n version: '1.0.0',\n contact: {},\n },\n tags: [],\n servers: [],\n paths: {},\n components: {\n securitySchemes: {},\n },\n };\n\n setTitle(title: string): this {\n this.document.info.title = title;\n return this;\n }\n\n setDescription(description: string): this {\n this.document.info.description = description;\n return this;\n }\n\n setVersion(version: string): this {\n this.document.info.version = version;\n return this;\n }\n\n setContact(name: string, url: string, email: string): this {\n this.document.info.contact = { name, url, email };\n return this;\n }\n\n addServer(url: string, description?: string): this {\n this.document.servers.push({ url, description });\n return this;\n }\n\n addTag(name: string, description?: string): this {\n this.document.tags.push({ name, description });\n return this;\n }\n\n addBearerAuth(options: any = {}, name = 'bearer'): this {\n this.document.components.securitySchemes[name] = {\n type: 'http',\n scheme: 'bearer',\n bearerFormat: 'JWT',\n ...options,\n };\n return this;\n }\n\n build(): any {\n return this.document;\n }\n}\n","import 'reflect-metadata';\n\nexport const DECORATORS_PREFIX = 'swagger';\nexport const DECORATORS = {\n API_TAGS: `${DECORATORS_PREFIX}/apiTags`,\n API_OPERATION: `${DECORATORS_PREFIX}/apiOperation`,\n API_RESPONSE: `${DECORATORS_PREFIX}/apiResponse`,\n API_PARAM: `${DECORATORS_PREFIX}/apiParam`,\n API_BODY: `${DECORATORS_PREFIX}/apiBody`,\n API_PROPERTY: `${DECORATORS_PREFIX}/apiProperty`,\n};\n\nexport function ApiTags(...tags: string[]): ClassDecorator {\n return (target: any) => {\n Reflect.defineMetadata(DECORATORS.API_TAGS, tags, target);\n };\n}\n\nexport function ApiOperation(options: { summary?: string; description?: string; deprecated?: boolean }): MethodDecorator {\n return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {\n Reflect.defineMetadata(DECORATORS.API_OPERATION, options, descriptor.value);\n return descriptor;\n };\n}\n\nexport function ApiResponse(options: { status: number; description: string; type?: any }): MethodDecorator {\n return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {\n const responses = Reflect.getMetadata(DECORATORS.API_RESPONSE, descriptor.value) || {};\n responses[options.status] = options;\n Reflect.defineMetadata(DECORATORS.API_RESPONSE, responses, descriptor.value);\n return descriptor;\n };\n}\n\nexport function ApiProperty(options: { description?: string; type?: any; required?: boolean; example?: any } = {}): PropertyDecorator {\n return (target: any, propertyKey: string | symbol) => {\n const properties = Reflect.getMetadata(DECORATORS.API_PROPERTY, target.constructor) || [];\n properties.push({ key: propertyKey, ...options });\n Reflect.defineMetadata(DECORATORS.API_PROPERTY, properties, target.constructor);\n };\n}\n","import { Container, METADATA_KEYS, RequestMethod } from '@hono-di/core';\nimport { DECORATORS } from '../decorators';\nimport { SwaggerDocument } from '../interfaces';\n\nexport class SwaggerScanner {\n constructor(private readonly container: Container) { }\n\n scan(document: SwaggerDocument): SwaggerDocument {\n const modules = this.container.getModules();\n\n modules.forEach((module) => {\n module.controllers.forEach((wrapper) => {\n this.scanController(wrapper.metatype, document);\n });\n });\n\n return document;\n }\n\n private scanController(controller: any, document: SwaggerDocument) {\n if (!controller) return;\n\n const { prefix } = Reflect.getMetadata(METADATA_KEYS.CONTROLLER, controller) || { prefix: '' };\n const routes = Reflect.getMetadata(METADATA_KEYS.ROUTES, controller) || [];\n const apiTags = Reflect.getMetadata(DECORATORS.API_TAGS, controller) || [];\n\n // Add tags to document\n apiTags.forEach((tag: string) => {\n if (!document.tags.find(t => t.name === tag)) {\n document.tags.push({ name: tag });\n }\n });\n\n routes.forEach((route: any) => {\n const method = route.requestMethod;\n const path = this.normalizePath(prefix, route.path);\n const descriptor = Object.getOwnPropertyDescriptor(controller.prototype, route.methodName);\n const handler = descriptor?.value;\n\n if (!handler) return;\n\n const operation = this.createOperation(handler, apiTags);\n\n if (!document.paths[path]) {\n document.paths[path] = {};\n }\n document.paths[path][method] = operation;\n });\n }\n\n private createOperation(handler: any, controllerTags: string[]) {\n const apiOperation = Reflect.getMetadata(DECORATORS.API_OPERATION, handler) || {};\n const apiResponses = Reflect.getMetadata(DECORATORS.API_RESPONSE, handler) || {};\n\n const responses: Record<string, any> = {};\n Object.keys(apiResponses).forEach(status => {\n responses[status] = {\n description: apiResponses[status].description,\n };\n });\n\n // Default response if none provided\n if (Object.keys(responses).length === 0) {\n responses['200'] = { description: 'Successful operation' };\n }\n\n return {\n summary: apiOperation.summary || '',\n description: apiOperation.description || '',\n tags: controllerTags,\n responses,\n // TODO: Add parameters and request body scanning\n };\n }\n\n private normalizePath(prefix: string, path: string): string {\n const cleanPrefix = prefix ? prefix.replace(/^\\/+/, '').replace(/\\/+$/, '') : '';\n const cleanPath = path ? path.replace(/^\\/+/, '').replace(/\\/+$/, '') : '';\n let result = '';\n if (cleanPrefix) result += `/${cleanPrefix}`;\n if (cleanPath) result += `/${cleanPath}`;\n return (result || '/').replace(/:([^\\/]+)/g, '{$1}'); // Convert :param to {param}\n }\n}\n","import { HonoDiApplication } from '@hono-di/core';\nimport { DocumentBuilder } from './document-builder';\nimport { SwaggerDocument, SwaggerCustomOptions } from './interfaces';\nimport { SwaggerScanner } from './explorers/api-scanner';\n\nexport class SwaggerModule {\n static createDocument(app: HonoDiApplication, config: SwaggerDocument): SwaggerDocument {\n const container = app.getContainer();\n const scanner = new SwaggerScanner(container);\n const document = config;\n return scanner.scan(document);\n }\n\n static setup(path: string, app: HonoDiApplication, document: SwaggerDocument, options?: SwaggerCustomOptions) {\n const httpAdapter = app.getHttpAdapter();\n\n // Serve Swagger JSON\n httpAdapter.get(`${path}-json`, (c: any) => c.json(document));\n\n // Serve Swagger UI\n httpAdapter.get(path, (c: any) => {\n const html = `\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <title>${document.info.title}</title>\n <link rel=\"stylesheet\" type=\"text/css\" href=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui.css\" />\n <style>\n html { box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; }\n *, *:before, *:after { box-sizing: inherit; }\n body { margin: 0; background: #fafafa; }\n </style>\n </head>\n <body>\n <div id=\"swagger-ui\"></div>\n <script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js\" charset=\"UTF-8\"> </script>\n <script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js\" charset=\"UTF-8\"> </script>\n <script>\n window.onload = function() {\n const ui = SwaggerUIBundle({\n spec: ${JSON.stringify(document)},\n dom_id: '#swagger-ui',\n deepLinking: true,\n presets: [\n SwaggerUIBundle.presets.apis,\n SwaggerUIStandalonePreset\n ],\n plugins: [\n SwaggerUIBundle.plugins.DownloadUrl\n ],\n layout: \"StandaloneLayout\"\n });\n window.ui = ui;\n };\n </script>\n </body>\n </html>`;\n return c.html(html);\n });\n }\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { HonoDiApplication } from '@hono-di/core';
|
|
2
|
+
|
|
3
|
+
declare class DocumentBuilder {
|
|
4
|
+
private readonly document;
|
|
5
|
+
setTitle(title: string): this;
|
|
6
|
+
setDescription(description: string): this;
|
|
7
|
+
setVersion(version: string): this;
|
|
8
|
+
setContact(name: string, url: string, email: string): this;
|
|
9
|
+
addServer(url: string, description?: string): this;
|
|
10
|
+
addTag(name: string, description?: string): this;
|
|
11
|
+
addBearerAuth(options?: any, name?: string): this;
|
|
12
|
+
build(): any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SwaggerDocument {
|
|
16
|
+
openapi: string;
|
|
17
|
+
info: {
|
|
18
|
+
title: string;
|
|
19
|
+
description: string;
|
|
20
|
+
version: string;
|
|
21
|
+
contact?: {
|
|
22
|
+
name: string;
|
|
23
|
+
url: string;
|
|
24
|
+
email: string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
tags: {
|
|
28
|
+
name: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
}[];
|
|
31
|
+
servers: {
|
|
32
|
+
url: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
}[];
|
|
35
|
+
paths: Record<string, Record<string, any>>;
|
|
36
|
+
components: {
|
|
37
|
+
schemas: Record<string, any>;
|
|
38
|
+
securitySchemes: Record<string, any>;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
interface SwaggerCustomOptions {
|
|
42
|
+
explorer?: boolean;
|
|
43
|
+
swaggerOptions?: Record<string, any>;
|
|
44
|
+
customCss?: string;
|
|
45
|
+
customCssUrl?: string;
|
|
46
|
+
customJs?: string;
|
|
47
|
+
customJsUrl?: string;
|
|
48
|
+
customSiteTitle?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
declare class SwaggerModule {
|
|
52
|
+
static createDocument(app: HonoDiApplication, config: SwaggerDocument): SwaggerDocument;
|
|
53
|
+
static setup(path: string, app: HonoDiApplication, document: SwaggerDocument, options?: SwaggerCustomOptions): void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
declare const DECORATORS_PREFIX = "swagger";
|
|
57
|
+
declare const DECORATORS: {
|
|
58
|
+
API_TAGS: string;
|
|
59
|
+
API_OPERATION: string;
|
|
60
|
+
API_RESPONSE: string;
|
|
61
|
+
API_PARAM: string;
|
|
62
|
+
API_BODY: string;
|
|
63
|
+
API_PROPERTY: string;
|
|
64
|
+
};
|
|
65
|
+
declare function ApiTags(...tags: string[]): ClassDecorator;
|
|
66
|
+
declare function ApiOperation(options: {
|
|
67
|
+
summary?: string;
|
|
68
|
+
description?: string;
|
|
69
|
+
deprecated?: boolean;
|
|
70
|
+
}): MethodDecorator;
|
|
71
|
+
declare function ApiResponse(options: {
|
|
72
|
+
status: number;
|
|
73
|
+
description: string;
|
|
74
|
+
type?: any;
|
|
75
|
+
}): MethodDecorator;
|
|
76
|
+
declare function ApiProperty(options?: {
|
|
77
|
+
description?: string;
|
|
78
|
+
type?: any;
|
|
79
|
+
required?: boolean;
|
|
80
|
+
example?: any;
|
|
81
|
+
}): PropertyDecorator;
|
|
82
|
+
|
|
83
|
+
export { ApiOperation, ApiProperty, ApiResponse, ApiTags, DECORATORS, DECORATORS_PREFIX, DocumentBuilder, type SwaggerCustomOptions, type SwaggerDocument, SwaggerModule };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { HonoDiApplication } from '@hono-di/core';
|
|
2
|
+
|
|
3
|
+
declare class DocumentBuilder {
|
|
4
|
+
private readonly document;
|
|
5
|
+
setTitle(title: string): this;
|
|
6
|
+
setDescription(description: string): this;
|
|
7
|
+
setVersion(version: string): this;
|
|
8
|
+
setContact(name: string, url: string, email: string): this;
|
|
9
|
+
addServer(url: string, description?: string): this;
|
|
10
|
+
addTag(name: string, description?: string): this;
|
|
11
|
+
addBearerAuth(options?: any, name?: string): this;
|
|
12
|
+
build(): any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SwaggerDocument {
|
|
16
|
+
openapi: string;
|
|
17
|
+
info: {
|
|
18
|
+
title: string;
|
|
19
|
+
description: string;
|
|
20
|
+
version: string;
|
|
21
|
+
contact?: {
|
|
22
|
+
name: string;
|
|
23
|
+
url: string;
|
|
24
|
+
email: string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
tags: {
|
|
28
|
+
name: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
}[];
|
|
31
|
+
servers: {
|
|
32
|
+
url: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
}[];
|
|
35
|
+
paths: Record<string, Record<string, any>>;
|
|
36
|
+
components: {
|
|
37
|
+
schemas: Record<string, any>;
|
|
38
|
+
securitySchemes: Record<string, any>;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
interface SwaggerCustomOptions {
|
|
42
|
+
explorer?: boolean;
|
|
43
|
+
swaggerOptions?: Record<string, any>;
|
|
44
|
+
customCss?: string;
|
|
45
|
+
customCssUrl?: string;
|
|
46
|
+
customJs?: string;
|
|
47
|
+
customJsUrl?: string;
|
|
48
|
+
customSiteTitle?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
declare class SwaggerModule {
|
|
52
|
+
static createDocument(app: HonoDiApplication, config: SwaggerDocument): SwaggerDocument;
|
|
53
|
+
static setup(path: string, app: HonoDiApplication, document: SwaggerDocument, options?: SwaggerCustomOptions): void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
declare const DECORATORS_PREFIX = "swagger";
|
|
57
|
+
declare const DECORATORS: {
|
|
58
|
+
API_TAGS: string;
|
|
59
|
+
API_OPERATION: string;
|
|
60
|
+
API_RESPONSE: string;
|
|
61
|
+
API_PARAM: string;
|
|
62
|
+
API_BODY: string;
|
|
63
|
+
API_PROPERTY: string;
|
|
64
|
+
};
|
|
65
|
+
declare function ApiTags(...tags: string[]): ClassDecorator;
|
|
66
|
+
declare function ApiOperation(options: {
|
|
67
|
+
summary?: string;
|
|
68
|
+
description?: string;
|
|
69
|
+
deprecated?: boolean;
|
|
70
|
+
}): MethodDecorator;
|
|
71
|
+
declare function ApiResponse(options: {
|
|
72
|
+
status: number;
|
|
73
|
+
description: string;
|
|
74
|
+
type?: any;
|
|
75
|
+
}): MethodDecorator;
|
|
76
|
+
declare function ApiProperty(options?: {
|
|
77
|
+
description?: string;
|
|
78
|
+
type?: any;
|
|
79
|
+
required?: boolean;
|
|
80
|
+
example?: any;
|
|
81
|
+
}): PropertyDecorator;
|
|
82
|
+
|
|
83
|
+
export { ApiOperation, ApiProperty, ApiResponse, ApiTags, DECORATORS, DECORATORS_PREFIX, DocumentBuilder, type SwaggerCustomOptions, type SwaggerDocument, SwaggerModule };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { METADATA_KEYS } from '@hono-di/core';
|
|
2
|
+
import 'reflect-metadata';
|
|
3
|
+
|
|
4
|
+
// src/document-builder.ts
|
|
5
|
+
var DocumentBuilder = class {
|
|
6
|
+
document = {
|
|
7
|
+
openapi: "3.0.0",
|
|
8
|
+
info: {
|
|
9
|
+
title: "",
|
|
10
|
+
description: "",
|
|
11
|
+
version: "1.0.0",
|
|
12
|
+
contact: {}
|
|
13
|
+
},
|
|
14
|
+
tags: [],
|
|
15
|
+
servers: [],
|
|
16
|
+
paths: {},
|
|
17
|
+
components: {
|
|
18
|
+
securitySchemes: {}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
setTitle(title) {
|
|
22
|
+
this.document.info.title = title;
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
setDescription(description) {
|
|
26
|
+
this.document.info.description = description;
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
setVersion(version) {
|
|
30
|
+
this.document.info.version = version;
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
setContact(name, url, email) {
|
|
34
|
+
this.document.info.contact = { name, url, email };
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
addServer(url, description) {
|
|
38
|
+
this.document.servers.push({ url, description });
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
addTag(name, description) {
|
|
42
|
+
this.document.tags.push({ name, description });
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
addBearerAuth(options = {}, name = "bearer") {
|
|
46
|
+
this.document.components.securitySchemes[name] = {
|
|
47
|
+
type: "http",
|
|
48
|
+
scheme: "bearer",
|
|
49
|
+
bearerFormat: "JWT",
|
|
50
|
+
...options
|
|
51
|
+
};
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
build() {
|
|
55
|
+
return this.document;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var DECORATORS_PREFIX = "swagger";
|
|
59
|
+
var DECORATORS = {
|
|
60
|
+
API_TAGS: `${DECORATORS_PREFIX}/apiTags`,
|
|
61
|
+
API_OPERATION: `${DECORATORS_PREFIX}/apiOperation`,
|
|
62
|
+
API_RESPONSE: `${DECORATORS_PREFIX}/apiResponse`,
|
|
63
|
+
API_PARAM: `${DECORATORS_PREFIX}/apiParam`,
|
|
64
|
+
API_BODY: `${DECORATORS_PREFIX}/apiBody`,
|
|
65
|
+
API_PROPERTY: `${DECORATORS_PREFIX}/apiProperty`
|
|
66
|
+
};
|
|
67
|
+
function ApiTags(...tags) {
|
|
68
|
+
return (target) => {
|
|
69
|
+
Reflect.defineMetadata(DECORATORS.API_TAGS, tags, target);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function ApiOperation(options) {
|
|
73
|
+
return (target, propertyKey, descriptor) => {
|
|
74
|
+
Reflect.defineMetadata(DECORATORS.API_OPERATION, options, descriptor.value);
|
|
75
|
+
return descriptor;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function ApiResponse(options) {
|
|
79
|
+
return (target, propertyKey, descriptor) => {
|
|
80
|
+
const responses = Reflect.getMetadata(DECORATORS.API_RESPONSE, descriptor.value) || {};
|
|
81
|
+
responses[options.status] = options;
|
|
82
|
+
Reflect.defineMetadata(DECORATORS.API_RESPONSE, responses, descriptor.value);
|
|
83
|
+
return descriptor;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function ApiProperty(options = {}) {
|
|
87
|
+
return (target, propertyKey) => {
|
|
88
|
+
const properties = Reflect.getMetadata(DECORATORS.API_PROPERTY, target.constructor) || [];
|
|
89
|
+
properties.push({ key: propertyKey, ...options });
|
|
90
|
+
Reflect.defineMetadata(DECORATORS.API_PROPERTY, properties, target.constructor);
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/explorers/api-scanner.ts
|
|
95
|
+
var SwaggerScanner = class {
|
|
96
|
+
constructor(container) {
|
|
97
|
+
this.container = container;
|
|
98
|
+
}
|
|
99
|
+
scan(document) {
|
|
100
|
+
const modules = this.container.getModules();
|
|
101
|
+
modules.forEach((module) => {
|
|
102
|
+
module.controllers.forEach((wrapper) => {
|
|
103
|
+
this.scanController(wrapper.metatype, document);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
return document;
|
|
107
|
+
}
|
|
108
|
+
scanController(controller, document) {
|
|
109
|
+
if (!controller) return;
|
|
110
|
+
const { prefix } = Reflect.getMetadata(METADATA_KEYS.CONTROLLER, controller) || { prefix: "" };
|
|
111
|
+
const routes = Reflect.getMetadata(METADATA_KEYS.ROUTES, controller) || [];
|
|
112
|
+
const apiTags = Reflect.getMetadata(DECORATORS.API_TAGS, controller) || [];
|
|
113
|
+
apiTags.forEach((tag) => {
|
|
114
|
+
if (!document.tags.find((t) => t.name === tag)) {
|
|
115
|
+
document.tags.push({ name: tag });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
routes.forEach((route) => {
|
|
119
|
+
const method = route.requestMethod;
|
|
120
|
+
const path = this.normalizePath(prefix, route.path);
|
|
121
|
+
const descriptor = Object.getOwnPropertyDescriptor(controller.prototype, route.methodName);
|
|
122
|
+
const handler = descriptor?.value;
|
|
123
|
+
if (!handler) return;
|
|
124
|
+
const operation = this.createOperation(handler, apiTags);
|
|
125
|
+
if (!document.paths[path]) {
|
|
126
|
+
document.paths[path] = {};
|
|
127
|
+
}
|
|
128
|
+
document.paths[path][method] = operation;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
createOperation(handler, controllerTags) {
|
|
132
|
+
const apiOperation = Reflect.getMetadata(DECORATORS.API_OPERATION, handler) || {};
|
|
133
|
+
const apiResponses = Reflect.getMetadata(DECORATORS.API_RESPONSE, handler) || {};
|
|
134
|
+
const responses = {};
|
|
135
|
+
Object.keys(apiResponses).forEach((status) => {
|
|
136
|
+
responses[status] = {
|
|
137
|
+
description: apiResponses[status].description
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
if (Object.keys(responses).length === 0) {
|
|
141
|
+
responses["200"] = { description: "Successful operation" };
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
summary: apiOperation.summary || "",
|
|
145
|
+
description: apiOperation.description || "",
|
|
146
|
+
tags: controllerTags,
|
|
147
|
+
responses
|
|
148
|
+
// TODO: Add parameters and request body scanning
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
normalizePath(prefix, path) {
|
|
152
|
+
const cleanPrefix = prefix ? prefix.replace(/^\/+/, "").replace(/\/+$/, "") : "";
|
|
153
|
+
const cleanPath = path ? path.replace(/^\/+/, "").replace(/\/+$/, "") : "";
|
|
154
|
+
let result = "";
|
|
155
|
+
if (cleanPrefix) result += `/${cleanPrefix}`;
|
|
156
|
+
if (cleanPath) result += `/${cleanPath}`;
|
|
157
|
+
return (result || "/").replace(/:([^\/]+)/g, "{$1}");
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// src/swagger-module.ts
|
|
162
|
+
var SwaggerModule = class {
|
|
163
|
+
static createDocument(app, config) {
|
|
164
|
+
const container = app.getContainer();
|
|
165
|
+
const scanner = new SwaggerScanner(container);
|
|
166
|
+
const document = config;
|
|
167
|
+
return scanner.scan(document);
|
|
168
|
+
}
|
|
169
|
+
static setup(path, app, document, options) {
|
|
170
|
+
const httpAdapter = app.getHttpAdapter();
|
|
171
|
+
httpAdapter.get(`${path}-json`, (c) => c.json(document));
|
|
172
|
+
httpAdapter.get(path, (c) => {
|
|
173
|
+
const html = `
|
|
174
|
+
<!DOCTYPE html>
|
|
175
|
+
<html lang="en">
|
|
176
|
+
<head>
|
|
177
|
+
<meta charset="UTF-8">
|
|
178
|
+
<title>${document.info.title}</title>
|
|
179
|
+
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
|
|
180
|
+
<style>
|
|
181
|
+
html { box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; }
|
|
182
|
+
*, *:before, *:after { box-sizing: inherit; }
|
|
183
|
+
body { margin: 0; background: #fafafa; }
|
|
184
|
+
</style>
|
|
185
|
+
</head>
|
|
186
|
+
<body>
|
|
187
|
+
<div id="swagger-ui"></div>
|
|
188
|
+
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js" charset="UTF-8"> </script>
|
|
189
|
+
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
|
|
190
|
+
<script>
|
|
191
|
+
window.onload = function() {
|
|
192
|
+
const ui = SwaggerUIBundle({
|
|
193
|
+
spec: ${JSON.stringify(document)},
|
|
194
|
+
dom_id: '#swagger-ui',
|
|
195
|
+
deepLinking: true,
|
|
196
|
+
presets: [
|
|
197
|
+
SwaggerUIBundle.presets.apis,
|
|
198
|
+
SwaggerUIStandalonePreset
|
|
199
|
+
],
|
|
200
|
+
plugins: [
|
|
201
|
+
SwaggerUIBundle.plugins.DownloadUrl
|
|
202
|
+
],
|
|
203
|
+
layout: "StandaloneLayout"
|
|
204
|
+
});
|
|
205
|
+
window.ui = ui;
|
|
206
|
+
};
|
|
207
|
+
</script>
|
|
208
|
+
</body>
|
|
209
|
+
</html>`;
|
|
210
|
+
return c.html(html);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
export { ApiOperation, ApiProperty, ApiResponse, ApiTags, DECORATORS, DECORATORS_PREFIX, DocumentBuilder, SwaggerModule };
|
|
216
|
+
//# sourceMappingURL=index.js.map
|
|
217
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/document-builder.ts","../src/decorators.ts","../src/explorers/api-scanner.ts","../src/swagger-module.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export class DocumentBuilder {\n private readonly document: any = {\n openapi: '3.0.0',\n info: {\n title: '',\n description: '',\n version: '1.0.0',\n contact: {},\n },\n tags: [],\n servers: [],\n paths: {},\n components: {\n securitySchemes: {},\n },\n };\n\n setTitle(title: string): this {\n this.document.info.title = title;\n return this;\n }\n\n setDescription(description: string): this {\n this.document.info.description = description;\n return this;\n }\n\n setVersion(version: string): this {\n this.document.info.version = version;\n return this;\n }\n\n setContact(name: string, url: string, email: string): this {\n this.document.info.contact = { name, url, email };\n return this;\n }\n\n addServer(url: string, description?: string): this {\n this.document.servers.push({ url, description });\n return this;\n }\n\n addTag(name: string, description?: string): this {\n this.document.tags.push({ name, description });\n return this;\n }\n\n addBearerAuth(options: any = {}, name = 'bearer'): this {\n this.document.components.securitySchemes[name] = {\n type: 'http',\n scheme: 'bearer',\n bearerFormat: 'JWT',\n ...options,\n };\n return this;\n }\n\n build(): any {\n return this.document;\n }\n}\n","import 'reflect-metadata';\n\nexport const DECORATORS_PREFIX = 'swagger';\nexport const DECORATORS = {\n API_TAGS: `${DECORATORS_PREFIX}/apiTags`,\n API_OPERATION: `${DECORATORS_PREFIX}/apiOperation`,\n API_RESPONSE: `${DECORATORS_PREFIX}/apiResponse`,\n API_PARAM: `${DECORATORS_PREFIX}/apiParam`,\n API_BODY: `${DECORATORS_PREFIX}/apiBody`,\n API_PROPERTY: `${DECORATORS_PREFIX}/apiProperty`,\n};\n\nexport function ApiTags(...tags: string[]): ClassDecorator {\n return (target: any) => {\n Reflect.defineMetadata(DECORATORS.API_TAGS, tags, target);\n };\n}\n\nexport function ApiOperation(options: { summary?: string; description?: string; deprecated?: boolean }): MethodDecorator {\n return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {\n Reflect.defineMetadata(DECORATORS.API_OPERATION, options, descriptor.value);\n return descriptor;\n };\n}\n\nexport function ApiResponse(options: { status: number; description: string; type?: any }): MethodDecorator {\n return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {\n const responses = Reflect.getMetadata(DECORATORS.API_RESPONSE, descriptor.value) || {};\n responses[options.status] = options;\n Reflect.defineMetadata(DECORATORS.API_RESPONSE, responses, descriptor.value);\n return descriptor;\n };\n}\n\nexport function ApiProperty(options: { description?: string; type?: any; required?: boolean; example?: any } = {}): PropertyDecorator {\n return (target: any, propertyKey: string | symbol) => {\n const properties = Reflect.getMetadata(DECORATORS.API_PROPERTY, target.constructor) || [];\n properties.push({ key: propertyKey, ...options });\n Reflect.defineMetadata(DECORATORS.API_PROPERTY, properties, target.constructor);\n };\n}\n","import { Container, METADATA_KEYS, RequestMethod } from '@hono-di/core';\nimport { DECORATORS } from '../decorators';\nimport { SwaggerDocument } from '../interfaces';\n\nexport class SwaggerScanner {\n constructor(private readonly container: Container) { }\n\n scan(document: SwaggerDocument): SwaggerDocument {\n const modules = this.container.getModules();\n\n modules.forEach((module) => {\n module.controllers.forEach((wrapper) => {\n this.scanController(wrapper.metatype, document);\n });\n });\n\n return document;\n }\n\n private scanController(controller: any, document: SwaggerDocument) {\n if (!controller) return;\n\n const { prefix } = Reflect.getMetadata(METADATA_KEYS.CONTROLLER, controller) || { prefix: '' };\n const routes = Reflect.getMetadata(METADATA_KEYS.ROUTES, controller) || [];\n const apiTags = Reflect.getMetadata(DECORATORS.API_TAGS, controller) || [];\n\n // Add tags to document\n apiTags.forEach((tag: string) => {\n if (!document.tags.find(t => t.name === tag)) {\n document.tags.push({ name: tag });\n }\n });\n\n routes.forEach((route: any) => {\n const method = route.requestMethod;\n const path = this.normalizePath(prefix, route.path);\n const descriptor = Object.getOwnPropertyDescriptor(controller.prototype, route.methodName);\n const handler = descriptor?.value;\n\n if (!handler) return;\n\n const operation = this.createOperation(handler, apiTags);\n\n if (!document.paths[path]) {\n document.paths[path] = {};\n }\n document.paths[path][method] = operation;\n });\n }\n\n private createOperation(handler: any, controllerTags: string[]) {\n const apiOperation = Reflect.getMetadata(DECORATORS.API_OPERATION, handler) || {};\n const apiResponses = Reflect.getMetadata(DECORATORS.API_RESPONSE, handler) || {};\n\n const responses: Record<string, any> = {};\n Object.keys(apiResponses).forEach(status => {\n responses[status] = {\n description: apiResponses[status].description,\n };\n });\n\n // Default response if none provided\n if (Object.keys(responses).length === 0) {\n responses['200'] = { description: 'Successful operation' };\n }\n\n return {\n summary: apiOperation.summary || '',\n description: apiOperation.description || '',\n tags: controllerTags,\n responses,\n // TODO: Add parameters and request body scanning\n };\n }\n\n private normalizePath(prefix: string, path: string): string {\n const cleanPrefix = prefix ? prefix.replace(/^\\/+/, '').replace(/\\/+$/, '') : '';\n const cleanPath = path ? path.replace(/^\\/+/, '').replace(/\\/+$/, '') : '';\n let result = '';\n if (cleanPrefix) result += `/${cleanPrefix}`;\n if (cleanPath) result += `/${cleanPath}`;\n return (result || '/').replace(/:([^\\/]+)/g, '{$1}'); // Convert :param to {param}\n }\n}\n","import { HonoDiApplication } from '@hono-di/core';\nimport { DocumentBuilder } from './document-builder';\nimport { SwaggerDocument, SwaggerCustomOptions } from './interfaces';\nimport { SwaggerScanner } from './explorers/api-scanner';\n\nexport class SwaggerModule {\n static createDocument(app: HonoDiApplication, config: SwaggerDocument): SwaggerDocument {\n const container = app.getContainer();\n const scanner = new SwaggerScanner(container);\n const document = config;\n return scanner.scan(document);\n }\n\n static setup(path: string, app: HonoDiApplication, document: SwaggerDocument, options?: SwaggerCustomOptions) {\n const httpAdapter = app.getHttpAdapter();\n\n // Serve Swagger JSON\n httpAdapter.get(`${path}-json`, (c: any) => c.json(document));\n\n // Serve Swagger UI\n httpAdapter.get(path, (c: any) => {\n const html = `\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <title>${document.info.title}</title>\n <link rel=\"stylesheet\" type=\"text/css\" href=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui.css\" />\n <style>\n html { box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; }\n *, *:before, *:after { box-sizing: inherit; }\n body { margin: 0; background: #fafafa; }\n </style>\n </head>\n <body>\n <div id=\"swagger-ui\"></div>\n <script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js\" charset=\"UTF-8\"> </script>\n <script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js\" charset=\"UTF-8\"> </script>\n <script>\n window.onload = function() {\n const ui = SwaggerUIBundle({\n spec: ${JSON.stringify(document)},\n dom_id: '#swagger-ui',\n deepLinking: true,\n presets: [\n SwaggerUIBundle.presets.apis,\n SwaggerUIStandalonePreset\n ],\n plugins: [\n SwaggerUIBundle.plugins.DownloadUrl\n ],\n layout: \"StandaloneLayout\"\n });\n window.ui = ui;\n };\n </script>\n </body>\n </html>`;\n return c.html(html);\n });\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hono-di/swagger",
|
|
3
|
+
"version": "0.0.6",
|
|
4
|
+
"description": "OpenAPI (Swagger) module for Hono-DI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"test": "bun test"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@hono-di/core": "0.0.6",
|
|
25
|
+
"reflect-metadata": "^0.2.2"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"hono": "^4"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"tsup": "^8.5.1",
|
|
32
|
+
"typescript": "^5.4.0",
|
|
33
|
+
"@types/node": "^22.10.6"
|
|
34
|
+
}
|
|
35
|
+
}
|