@morojs/moro 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 +21 -0
- package/README.md +233 -0
- package/dist/core/config/index.d.ts +19 -0
- package/dist/core/config/index.js +59 -0
- package/dist/core/config/index.js.map +1 -0
- package/dist/core/config/loader.d.ts +6 -0
- package/dist/core/config/loader.js +288 -0
- package/dist/core/config/loader.js.map +1 -0
- package/dist/core/config/schema.d.ts +335 -0
- package/dist/core/config/schema.js +286 -0
- package/dist/core/config/schema.js.map +1 -0
- package/dist/core/config/utils.d.ts +50 -0
- package/dist/core/config/utils.js +185 -0
- package/dist/core/config/utils.js.map +1 -0
- package/dist/core/database/adapters/drizzle.d.ts +29 -0
- package/dist/core/database/adapters/drizzle.js +366 -0
- package/dist/core/database/adapters/drizzle.js.map +1 -0
- package/dist/core/database/adapters/index.d.ts +8 -0
- package/dist/core/database/adapters/index.js +48 -0
- package/dist/core/database/adapters/index.js.map +1 -0
- package/dist/core/database/adapters/mongodb.d.ts +35 -0
- package/dist/core/database/adapters/mongodb.js +215 -0
- package/dist/core/database/adapters/mongodb.js.map +1 -0
- package/dist/core/database/adapters/mysql.d.ts +23 -0
- package/dist/core/database/adapters/mysql.js +149 -0
- package/dist/core/database/adapters/mysql.js.map +1 -0
- package/dist/core/database/adapters/postgresql.d.ts +24 -0
- package/dist/core/database/adapters/postgresql.js +160 -0
- package/dist/core/database/adapters/postgresql.js.map +1 -0
- package/dist/core/database/adapters/redis.d.ts +50 -0
- package/dist/core/database/adapters/redis.js +266 -0
- package/dist/core/database/adapters/redis.js.map +1 -0
- package/dist/core/database/adapters/sqlite.d.ts +23 -0
- package/dist/core/database/adapters/sqlite.js +194 -0
- package/dist/core/database/adapters/sqlite.js.map +1 -0
- package/dist/core/database/index.d.ts +2 -0
- package/dist/core/database/index.js +20 -0
- package/dist/core/database/index.js.map +1 -0
- package/dist/core/docs/index.d.ts +63 -0
- package/dist/core/docs/index.js +170 -0
- package/dist/core/docs/index.js.map +1 -0
- package/dist/core/docs/openapi-generator.d.ts +124 -0
- package/dist/core/docs/openapi-generator.js +413 -0
- package/dist/core/docs/openapi-generator.js.map +1 -0
- package/dist/core/docs/simple-docs.d.ts +21 -0
- package/dist/core/docs/simple-docs.js +268 -0
- package/dist/core/docs/simple-docs.js.map +1 -0
- package/dist/core/docs/swagger-ui.d.ts +28 -0
- package/dist/core/docs/swagger-ui.js +317 -0
- package/dist/core/docs/swagger-ui.js.map +1 -0
- package/dist/core/docs/zod-to-openapi.d.ts +29 -0
- package/dist/core/docs/zod-to-openapi.js +414 -0
- package/dist/core/docs/zod-to-openapi.js.map +1 -0
- package/dist/core/events/event-bus.d.ts +27 -0
- package/dist/core/events/event-bus.js +193 -0
- package/dist/core/events/event-bus.js.map +1 -0
- package/dist/core/events/index.d.ts +2 -0
- package/dist/core/events/index.js +7 -0
- package/dist/core/events/index.js.map +1 -0
- package/dist/core/framework.d.ts +57 -0
- package/dist/core/framework.js +432 -0
- package/dist/core/framework.js.map +1 -0
- package/dist/core/http/http-server.d.ts +114 -0
- package/dist/core/http/http-server.js +1154 -0
- package/dist/core/http/http-server.js.map +1 -0
- package/dist/core/http/index.d.ts +3 -0
- package/dist/core/http/index.js +10 -0
- package/dist/core/http/index.js.map +1 -0
- package/dist/core/http/router.d.ts +14 -0
- package/dist/core/http/router.js +113 -0
- package/dist/core/http/router.js.map +1 -0
- package/dist/core/logger/filters.d.ts +9 -0
- package/dist/core/logger/filters.js +134 -0
- package/dist/core/logger/filters.js.map +1 -0
- package/dist/core/logger/index.d.ts +3 -0
- package/dist/core/logger/index.js +26 -0
- package/dist/core/logger/index.js.map +1 -0
- package/dist/core/logger/logger.d.ts +49 -0
- package/dist/core/logger/logger.js +332 -0
- package/dist/core/logger/logger.js.map +1 -0
- package/dist/core/logger/outputs.d.ts +42 -0
- package/dist/core/logger/outputs.js +110 -0
- package/dist/core/logger/outputs.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/cache/file.d.ts +15 -0
- package/dist/core/middleware/built-in/adapters/cache/file.js +128 -0
- package/dist/core/middleware/built-in/adapters/cache/file.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/cache/index.d.ts +5 -0
- package/dist/core/middleware/built-in/adapters/cache/index.js +28 -0
- package/dist/core/middleware/built-in/adapters/cache/index.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/cache/memory.d.ts +11 -0
- package/dist/core/middleware/built-in/adapters/cache/memory.js +65 -0
- package/dist/core/middleware/built-in/adapters/cache/memory.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/cache/redis.d.ts +17 -0
- package/dist/core/middleware/built-in/adapters/cache/redis.js +91 -0
- package/dist/core/middleware/built-in/adapters/cache/redis.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/cdn/azure.d.ts +21 -0
- package/dist/core/middleware/built-in/adapters/cdn/azure.js +40 -0
- package/dist/core/middleware/built-in/adapters/cdn/azure.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/cdn/cloudflare.d.ts +14 -0
- package/dist/core/middleware/built-in/adapters/cdn/cloudflare.js +77 -0
- package/dist/core/middleware/built-in/adapters/cdn/cloudflare.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/cdn/cloudfront.d.ts +15 -0
- package/dist/core/middleware/built-in/adapters/cdn/cloudfront.js +73 -0
- package/dist/core/middleware/built-in/adapters/cdn/cloudfront.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/cdn/index.d.ts +5 -0
- package/dist/core/middleware/built-in/adapters/cdn/index.js +28 -0
- package/dist/core/middleware/built-in/adapters/cdn/index.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/index.d.ts +4 -0
- package/dist/core/middleware/built-in/adapters/index.js +26 -0
- package/dist/core/middleware/built-in/adapters/index.js.map +1 -0
- package/dist/core/middleware/built-in/auth.d.ts +2 -0
- package/dist/core/middleware/built-in/auth.js +38 -0
- package/dist/core/middleware/built-in/auth.js.map +1 -0
- package/dist/core/middleware/built-in/cache.d.ts +3 -0
- package/dist/core/middleware/built-in/cache.js +188 -0
- package/dist/core/middleware/built-in/cache.js.map +1 -0
- package/dist/core/middleware/built-in/cdn.d.ts +3 -0
- package/dist/core/middleware/built-in/cdn.js +115 -0
- package/dist/core/middleware/built-in/cdn.js.map +1 -0
- package/dist/core/middleware/built-in/cookie.d.ts +14 -0
- package/dist/core/middleware/built-in/cookie.js +68 -0
- package/dist/core/middleware/built-in/cookie.js.map +1 -0
- package/dist/core/middleware/built-in/cors.d.ts +2 -0
- package/dist/core/middleware/built-in/cors.js +29 -0
- package/dist/core/middleware/built-in/cors.js.map +1 -0
- package/dist/core/middleware/built-in/csp.d.ts +22 -0
- package/dist/core/middleware/built-in/csp.js +74 -0
- package/dist/core/middleware/built-in/csp.js.map +1 -0
- package/dist/core/middleware/built-in/csrf.d.ts +9 -0
- package/dist/core/middleware/built-in/csrf.js +66 -0
- package/dist/core/middleware/built-in/csrf.js.map +1 -0
- package/dist/core/middleware/built-in/error-tracker.d.ts +1 -0
- package/dist/core/middleware/built-in/error-tracker.js +19 -0
- package/dist/core/middleware/built-in/error-tracker.js.map +1 -0
- package/dist/core/middleware/built-in/index.d.ts +70 -0
- package/dist/core/middleware/built-in/index.js +70 -0
- package/dist/core/middleware/built-in/index.js.map +1 -0
- package/dist/core/middleware/built-in/performance-monitor.d.ts +1 -0
- package/dist/core/middleware/built-in/performance-monitor.js +22 -0
- package/dist/core/middleware/built-in/performance-monitor.js.map +1 -0
- package/dist/core/middleware/built-in/rate-limit.d.ts +6 -0
- package/dist/core/middleware/built-in/rate-limit.js +47 -0
- package/dist/core/middleware/built-in/rate-limit.js.map +1 -0
- package/dist/core/middleware/built-in/request-logger.d.ts +1 -0
- package/dist/core/middleware/built-in/request-logger.js +15 -0
- package/dist/core/middleware/built-in/request-logger.js.map +1 -0
- package/dist/core/middleware/built-in/session.d.ts +41 -0
- package/dist/core/middleware/built-in/session.js +209 -0
- package/dist/core/middleware/built-in/session.js.map +1 -0
- package/dist/core/middleware/built-in/sse.d.ts +6 -0
- package/dist/core/middleware/built-in/sse.js +73 -0
- package/dist/core/middleware/built-in/sse.js.map +1 -0
- package/dist/core/middleware/built-in/validation.d.ts +2 -0
- package/dist/core/middleware/built-in/validation.js +31 -0
- package/dist/core/middleware/built-in/validation.js.map +1 -0
- package/dist/core/middleware/index.d.ts +21 -0
- package/dist/core/middleware/index.js +152 -0
- package/dist/core/middleware/index.js.map +1 -0
- package/dist/core/modules/auto-discovery.d.ts +27 -0
- package/dist/core/modules/auto-discovery.js +255 -0
- package/dist/core/modules/auto-discovery.js.map +1 -0
- package/dist/core/modules/index.d.ts +2 -0
- package/dist/core/modules/index.js +11 -0
- package/dist/core/modules/index.js.map +1 -0
- package/dist/core/modules/modules.d.ts +10 -0
- package/dist/core/modules/modules.js +137 -0
- package/dist/core/modules/modules.js.map +1 -0
- package/dist/core/networking/index.d.ts +2 -0
- package/dist/core/networking/index.js +9 -0
- package/dist/core/networking/index.js.map +1 -0
- package/dist/core/networking/service-discovery.d.ts +38 -0
- package/dist/core/networking/service-discovery.js +233 -0
- package/dist/core/networking/service-discovery.js.map +1 -0
- package/dist/core/networking/websocket-manager.d.ts +27 -0
- package/dist/core/networking/websocket-manager.js +211 -0
- package/dist/core/networking/websocket-manager.js.map +1 -0
- package/dist/core/routing/app-integration.d.ts +42 -0
- package/dist/core/routing/app-integration.js +152 -0
- package/dist/core/routing/app-integration.js.map +1 -0
- package/dist/core/routing/index.d.ts +106 -0
- package/dist/core/routing/index.js +343 -0
- package/dist/core/routing/index.js.map +1 -0
- package/dist/core/runtime/aws-lambda-adapter.d.ts +43 -0
- package/dist/core/runtime/aws-lambda-adapter.js +108 -0
- package/dist/core/runtime/aws-lambda-adapter.js.map +1 -0
- package/dist/core/runtime/base-adapter.d.ts +16 -0
- package/dist/core/runtime/base-adapter.js +105 -0
- package/dist/core/runtime/base-adapter.js.map +1 -0
- package/dist/core/runtime/cloudflare-workers-adapter.d.ts +18 -0
- package/dist/core/runtime/cloudflare-workers-adapter.js +131 -0
- package/dist/core/runtime/cloudflare-workers-adapter.js.map +1 -0
- package/dist/core/runtime/index.d.ts +14 -0
- package/dist/core/runtime/index.js +56 -0
- package/dist/core/runtime/index.js.map +1 -0
- package/dist/core/runtime/node-adapter.d.ts +15 -0
- package/dist/core/runtime/node-adapter.js +204 -0
- package/dist/core/runtime/node-adapter.js.map +1 -0
- package/dist/core/runtime/vercel-edge-adapter.d.ts +10 -0
- package/dist/core/runtime/vercel-edge-adapter.js +106 -0
- package/dist/core/runtime/vercel-edge-adapter.js.map +1 -0
- package/dist/core/utilities/circuit-breaker.d.ts +14 -0
- package/dist/core/utilities/circuit-breaker.js +42 -0
- package/dist/core/utilities/circuit-breaker.js.map +1 -0
- package/dist/core/utilities/container.d.ts +116 -0
- package/dist/core/utilities/container.js +529 -0
- package/dist/core/utilities/container.js.map +1 -0
- package/dist/core/utilities/hooks.d.ts +24 -0
- package/dist/core/utilities/hooks.js +131 -0
- package/dist/core/utilities/hooks.js.map +1 -0
- package/dist/core/utilities/index.d.ts +4 -0
- package/dist/core/utilities/index.js +22 -0
- package/dist/core/utilities/index.js.map +1 -0
- package/dist/core/validation/index.d.ts +30 -0
- package/dist/core/validation/index.js +144 -0
- package/dist/core/validation/index.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/moro.d.ts +82 -0
- package/dist/moro.js +679 -0
- package/dist/moro.js.map +1 -0
- package/dist/types/cache.d.ts +34 -0
- package/dist/types/cache.js +3 -0
- package/dist/types/cache.js.map +1 -0
- package/dist/types/cdn.d.ts +19 -0
- package/dist/types/cdn.js +3 -0
- package/dist/types/cdn.js.map +1 -0
- package/dist/types/core.d.ts +13 -0
- package/dist/types/core.js +3 -0
- package/dist/types/core.js.map +1 -0
- package/dist/types/database.d.ts +29 -0
- package/dist/types/database.js +3 -0
- package/dist/types/database.js.map +1 -0
- package/dist/types/discovery.d.ts +6 -0
- package/dist/types/discovery.js +3 -0
- package/dist/types/discovery.js.map +1 -0
- package/dist/types/events.d.ts +116 -0
- package/dist/types/events.js +3 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/hooks.d.ts +38 -0
- package/dist/types/hooks.js +3 -0
- package/dist/types/hooks.js.map +1 -0
- package/dist/types/http.d.ts +51 -0
- package/dist/types/http.js +3 -0
- package/dist/types/http.js.map +1 -0
- package/dist/types/logger.d.ts +77 -0
- package/dist/types/logger.js +3 -0
- package/dist/types/logger.js.map +1 -0
- package/dist/types/module.d.ts +91 -0
- package/dist/types/module.js +3 -0
- package/dist/types/module.js.map +1 -0
- package/dist/types/runtime.d.ts +48 -0
- package/dist/types/runtime.js +3 -0
- package/dist/types/runtime.js.map +1 -0
- package/dist/types/session.d.ts +66 -0
- package/dist/types/session.js +3 -0
- package/dist/types/session.js.map +1 -0
- package/package.json +176 -0
- package/src/core/config/index.ts +47 -0
- package/src/core/config/loader.ts +366 -0
- package/src/core/config/schema.ts +346 -0
- package/src/core/config/utils.ts +220 -0
- package/src/core/database/README.md +228 -0
- package/src/core/database/adapters/drizzle.ts +425 -0
- package/src/core/database/adapters/index.ts +45 -0
- package/src/core/database/adapters/mongodb.ts +292 -0
- package/src/core/database/adapters/mysql.ts +217 -0
- package/src/core/database/adapters/postgresql.ts +211 -0
- package/src/core/database/adapters/redis.ts +331 -0
- package/src/core/database/adapters/sqlite.ts +255 -0
- package/src/core/database/index.ts +3 -0
- package/src/core/docs/index.ts +245 -0
- package/src/core/docs/openapi-generator.ts +588 -0
- package/src/core/docs/simple-docs.ts +305 -0
- package/src/core/docs/swagger-ui.ts +370 -0
- package/src/core/docs/zod-to-openapi.ts +532 -0
- package/src/core/events/event-bus.ts +249 -0
- package/src/core/events/index.ts +12 -0
- package/src/core/framework.ts +621 -0
- package/src/core/http/http-server.ts +1421 -0
- package/src/core/http/index.ts +11 -0
- package/src/core/http/router.ts +153 -0
- package/src/core/logger/filters.ts +148 -0
- package/src/core/logger/index.ts +20 -0
- package/src/core/logger/logger.ts +434 -0
- package/src/core/logger/outputs.ts +136 -0
- package/src/core/middleware/built-in/adapters/cache/file.ts +106 -0
- package/src/core/middleware/built-in/adapters/cache/index.ts +26 -0
- package/src/core/middleware/built-in/adapters/cache/memory.ts +73 -0
- package/src/core/middleware/built-in/adapters/cache/redis.ts +103 -0
- package/src/core/middleware/built-in/adapters/cdn/azure.ts +68 -0
- package/src/core/middleware/built-in/adapters/cdn/cloudflare.ts +100 -0
- package/src/core/middleware/built-in/adapters/cdn/cloudfront.ts +92 -0
- package/src/core/middleware/built-in/adapters/cdn/index.ts +23 -0
- package/src/core/middleware/built-in/adapters/index.ts +7 -0
- package/src/core/middleware/built-in/auth.ts +39 -0
- package/src/core/middleware/built-in/cache.ts +228 -0
- package/src/core/middleware/built-in/cdn.ts +151 -0
- package/src/core/middleware/built-in/cookie.ts +90 -0
- package/src/core/middleware/built-in/cors.ts +38 -0
- package/src/core/middleware/built-in/csp.ts +107 -0
- package/src/core/middleware/built-in/csrf.ts +87 -0
- package/src/core/middleware/built-in/error-tracker.ts +16 -0
- package/src/core/middleware/built-in/index.ts +57 -0
- package/src/core/middleware/built-in/performance-monitor.ts +25 -0
- package/src/core/middleware/built-in/rate-limit.ts +60 -0
- package/src/core/middleware/built-in/request-logger.ts +14 -0
- package/src/core/middleware/built-in/session.ts +311 -0
- package/src/core/middleware/built-in/sse.ts +91 -0
- package/src/core/middleware/built-in/validation.ts +33 -0
- package/src/core/middleware/index.ts +188 -0
- package/src/core/modules/auto-discovery.ts +265 -0
- package/src/core/modules/index.ts +6 -0
- package/src/core/modules/modules.ts +125 -0
- package/src/core/networking/index.ts +7 -0
- package/src/core/networking/service-discovery.ts +309 -0
- package/src/core/networking/websocket-manager.ts +259 -0
- package/src/core/routing/app-integration.ts +229 -0
- package/src/core/routing/index.ts +519 -0
- package/src/core/runtime/aws-lambda-adapter.ts +157 -0
- package/src/core/runtime/base-adapter.ts +140 -0
- package/src/core/runtime/cloudflare-workers-adapter.ts +166 -0
- package/src/core/runtime/index.ts +74 -0
- package/src/core/runtime/node-adapter.ts +210 -0
- package/src/core/runtime/vercel-edge-adapter.ts +125 -0
- package/src/core/utilities/circuit-breaker.ts +46 -0
- package/src/core/utilities/container.ts +760 -0
- package/src/core/utilities/hooks.ts +148 -0
- package/src/core/utilities/index.ts +16 -0
- package/src/core/validation/index.ts +216 -0
- package/src/index.ts +120 -0
- package/src/moro.ts +842 -0
- package/src/types/cache.ts +38 -0
- package/src/types/cdn.ts +22 -0
- package/src/types/core.ts +17 -0
- package/src/types/database.ts +40 -0
- package/src/types/discovery.ts +7 -0
- package/src/types/events.ts +90 -0
- package/src/types/hooks.ts +47 -0
- package/src/types/http.ts +70 -0
- package/src/types/logger.ts +109 -0
- package/src/types/module.ts +87 -0
- package/src/types/runtime.ts +91 -0
- package/src/types/session.ts +89 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// Simple Documentation Generator - Fallback for when Swagger UI has issues
|
|
2
|
+
// Generates clean, readable API documentation from routes
|
|
3
|
+
|
|
4
|
+
import { CompiledRoute, RouteSchema } from "../routing";
|
|
5
|
+
import { createFrameworkLogger } from "../logger";
|
|
6
|
+
|
|
7
|
+
const logger = createFrameworkLogger("SimpleDocs");
|
|
8
|
+
|
|
9
|
+
export interface SimpleDocsOptions {
|
|
10
|
+
title?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
basePath?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class SimpleDocsGenerator {
|
|
16
|
+
private routes: CompiledRoute[] = [];
|
|
17
|
+
|
|
18
|
+
constructor(private options: SimpleDocsOptions = {}) {
|
|
19
|
+
this.options = {
|
|
20
|
+
title: "API Documentation",
|
|
21
|
+
description: "API documentation generated from intelligent routes",
|
|
22
|
+
basePath: "/docs",
|
|
23
|
+
...options,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
addRoutes(routes: CompiledRoute[]): void {
|
|
28
|
+
this.routes = routes;
|
|
29
|
+
logger.debug(
|
|
30
|
+
`Added ${routes.length} routes to simple docs`,
|
|
31
|
+
"RouteAddition",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
generateHTML(): string {
|
|
36
|
+
const routesByTag = this.groupRoutesByTag();
|
|
37
|
+
|
|
38
|
+
return `<!DOCTYPE html>
|
|
39
|
+
<html lang="en">
|
|
40
|
+
<head>
|
|
41
|
+
<meta charset="UTF-8">
|
|
42
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
43
|
+
<title>${this.options.title}</title>
|
|
44
|
+
<style>
|
|
45
|
+
body {
|
|
46
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
47
|
+
line-height: 1.6;
|
|
48
|
+
margin: 0;
|
|
49
|
+
padding: 20px;
|
|
50
|
+
background: #f8f9fa;
|
|
51
|
+
}
|
|
52
|
+
.container {
|
|
53
|
+
max-width: 1200px;
|
|
54
|
+
margin: 0 auto;
|
|
55
|
+
background: white;
|
|
56
|
+
padding: 30px;
|
|
57
|
+
border-radius: 8px;
|
|
58
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
59
|
+
}
|
|
60
|
+
h1 { color: #2563eb; margin-bottom: 10px; }
|
|
61
|
+
h2 { color: #374151; border-bottom: 2px solid #e5e7eb; padding-bottom: 10px; }
|
|
62
|
+
h3 { color: #6b7280; }
|
|
63
|
+
.endpoint {
|
|
64
|
+
background: #f3f4f6;
|
|
65
|
+
border-left: 4px solid #2563eb;
|
|
66
|
+
padding: 15px;
|
|
67
|
+
margin: 15px 0;
|
|
68
|
+
border-radius: 4px;
|
|
69
|
+
}
|
|
70
|
+
.method {
|
|
71
|
+
display: inline-block;
|
|
72
|
+
padding: 4px 8px;
|
|
73
|
+
border-radius: 4px;
|
|
74
|
+
font-weight: bold;
|
|
75
|
+
margin-right: 10px;
|
|
76
|
+
color: white;
|
|
77
|
+
}
|
|
78
|
+
.method.GET { background: #10b981; }
|
|
79
|
+
.method.POST { background: #f59e0b; }
|
|
80
|
+
.method.PUT { background: #3b82f6; }
|
|
81
|
+
.method.DELETE { background: #ef4444; }
|
|
82
|
+
.method.PATCH { background: #8b5cf6; }
|
|
83
|
+
.path { font-family: monospace; font-size: 16px; font-weight: bold; }
|
|
84
|
+
.description { color: #6b7280; margin: 8px 0; }
|
|
85
|
+
.tags { margin: 8px 0; }
|
|
86
|
+
.tag {
|
|
87
|
+
display: inline-block;
|
|
88
|
+
background: #e5e7eb;
|
|
89
|
+
color: #374151;
|
|
90
|
+
padding: 2px 8px;
|
|
91
|
+
border-radius: 12px;
|
|
92
|
+
font-size: 12px;
|
|
93
|
+
margin-right: 5px;
|
|
94
|
+
}
|
|
95
|
+
.validation {
|
|
96
|
+
background: #fef3c7;
|
|
97
|
+
border: 1px solid #f59e0b;
|
|
98
|
+
padding: 10px;
|
|
99
|
+
border-radius: 4px;
|
|
100
|
+
margin: 10px 0;
|
|
101
|
+
}
|
|
102
|
+
.auth {
|
|
103
|
+
background: #fee2e2;
|
|
104
|
+
border: 1px solid #ef4444;
|
|
105
|
+
padding: 10px;
|
|
106
|
+
border-radius: 4px;
|
|
107
|
+
margin: 10px 0;
|
|
108
|
+
}
|
|
109
|
+
.rate-limit {
|
|
110
|
+
background: #e0f2fe;
|
|
111
|
+
border: 1px solid #0284c7;
|
|
112
|
+
padding: 10px;
|
|
113
|
+
border-radius: 4px;
|
|
114
|
+
margin: 10px 0;
|
|
115
|
+
}
|
|
116
|
+
.example {
|
|
117
|
+
background: #f3f4f6;
|
|
118
|
+
border: 1px solid #d1d5db;
|
|
119
|
+
padding: 10px;
|
|
120
|
+
border-radius: 4px;
|
|
121
|
+
margin: 10px 0;
|
|
122
|
+
font-family: monospace;
|
|
123
|
+
font-size: 14px;
|
|
124
|
+
}
|
|
125
|
+
.footer {
|
|
126
|
+
margin-top: 40px;
|
|
127
|
+
padding-top: 20px;
|
|
128
|
+
border-top: 1px solid #e5e7eb;
|
|
129
|
+
text-align: center;
|
|
130
|
+
color: #6b7280;
|
|
131
|
+
}
|
|
132
|
+
</style>
|
|
133
|
+
</head>
|
|
134
|
+
<body>
|
|
135
|
+
<div class="container">
|
|
136
|
+
<h1>${this.options.title}</h1>
|
|
137
|
+
<p>${this.options.description}</p>
|
|
138
|
+
|
|
139
|
+
<div class="example">
|
|
140
|
+
<strong>Interactive Swagger UI:</strong> <a href="${this.options.basePath}" target="_blank">${this.options.basePath}</a><br>
|
|
141
|
+
<strong>OpenAPI JSON:</strong> <a href="${this.options.basePath}/openapi.json" target="_blank">${this.options.basePath}/openapi.json</a>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
${this.generateRouteDocumentation(routesByTag)}
|
|
145
|
+
|
|
146
|
+
<div class="footer">
|
|
147
|
+
<p>Generated automatically from Moro Framework intelligent routes</p>
|
|
148
|
+
<p>Built with Moro Framework - Intelligent Routing + Type-Safe Validation</p>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</body>
|
|
152
|
+
</html>`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private groupRoutesByTag(): Map<string, CompiledRoute[]> {
|
|
156
|
+
const grouped = new Map<string, CompiledRoute[]>();
|
|
157
|
+
|
|
158
|
+
for (const route of this.routes) {
|
|
159
|
+
const tags = route.schema.tags || ["default"];
|
|
160
|
+
|
|
161
|
+
for (const tag of tags) {
|
|
162
|
+
if (!grouped.has(tag)) {
|
|
163
|
+
grouped.set(tag, []);
|
|
164
|
+
}
|
|
165
|
+
grouped.get(tag)!.push(route);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return grouped;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private generateRouteDocumentation(
|
|
173
|
+
routesByTag: Map<string, CompiledRoute[]>,
|
|
174
|
+
): string {
|
|
175
|
+
let html = "";
|
|
176
|
+
|
|
177
|
+
for (const [tag, routes] of routesByTag) {
|
|
178
|
+
html += `<h2>${tag.charAt(0).toUpperCase() + tag.slice(1)}</h2>`;
|
|
179
|
+
|
|
180
|
+
for (const route of routes) {
|
|
181
|
+
html += this.generateRouteSection(route.schema);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return html;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private generateRouteSection(route: RouteSchema): string {
|
|
189
|
+
const methodClass = route.method.toLowerCase();
|
|
190
|
+
|
|
191
|
+
const html = `
|
|
192
|
+
<div class="endpoint">
|
|
193
|
+
<div>
|
|
194
|
+
<span class="method ${route.method}">${route.method}</span>
|
|
195
|
+
<span class="path">${route.path}</span>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
${route.description ? `<div class="description">${route.description}</div>` : ""}
|
|
199
|
+
|
|
200
|
+
${route.tags ? `<div class="tags">${route.tags.map((tag) => `<span class="tag">${tag}</span>`).join("")}</div>` : ""}
|
|
201
|
+
|
|
202
|
+
${this.generateValidationInfo(route)}
|
|
203
|
+
${this.generateAuthInfo(route)}
|
|
204
|
+
${this.generateRateLimitInfo(route)}
|
|
205
|
+
${this.generateExamples(route)}
|
|
206
|
+
</div>`;
|
|
207
|
+
|
|
208
|
+
return html;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private generateValidationInfo(route: RouteSchema): string {
|
|
212
|
+
if (!route.validation) return "";
|
|
213
|
+
|
|
214
|
+
const validationTypes = [];
|
|
215
|
+
if (route.validation.body) validationTypes.push("Body");
|
|
216
|
+
if (route.validation.query) validationTypes.push("Query Parameters");
|
|
217
|
+
if (route.validation.params) validationTypes.push("Path Parameters");
|
|
218
|
+
if (route.validation.headers) validationTypes.push("Headers");
|
|
219
|
+
|
|
220
|
+
return `
|
|
221
|
+
<div class="validation">
|
|
222
|
+
<strong>Validation:</strong> ${validationTypes.join(", ")}
|
|
223
|
+
<br><small>Request will be validated with Zod schemas for type safety</small>
|
|
224
|
+
</div>`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private generateAuthInfo(route: RouteSchema): string {
|
|
228
|
+
if (!route.auth) return "";
|
|
229
|
+
|
|
230
|
+
const roles = route.auth.roles
|
|
231
|
+
? route.auth.roles.join(", ")
|
|
232
|
+
: "authenticated";
|
|
233
|
+
|
|
234
|
+
return `
|
|
235
|
+
<div class="auth">
|
|
236
|
+
<strong>Authentication Required:</strong> ${roles}
|
|
237
|
+
<br><small>Requires valid authentication token</small>
|
|
238
|
+
</div>`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private generateRateLimitInfo(route: RouteSchema): string {
|
|
242
|
+
if (!route.rateLimit) return "";
|
|
243
|
+
|
|
244
|
+
const { requests, window } = route.rateLimit;
|
|
245
|
+
const windowSeconds = Math.round(window / 1000);
|
|
246
|
+
|
|
247
|
+
return `
|
|
248
|
+
<div class="rate-limit">
|
|
249
|
+
<strong>Rate Limit:</strong> ${requests} requests per ${windowSeconds} seconds
|
|
250
|
+
<br><small>Automatic protection against abuse</small>
|
|
251
|
+
</div>`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private generateExamples(route: RouteSchema): string {
|
|
255
|
+
const baseUrl = "http://localhost:3001";
|
|
256
|
+
const fullPath = `${baseUrl}${route.path}`;
|
|
257
|
+
|
|
258
|
+
let example = "";
|
|
259
|
+
|
|
260
|
+
if (route.method === "GET") {
|
|
261
|
+
example = `curl "${fullPath}"`;
|
|
262
|
+
|
|
263
|
+
// Add query parameter example if validation exists
|
|
264
|
+
if (route.validation?.query) {
|
|
265
|
+
example = `curl "${fullPath}?limit=10&search=example"`;
|
|
266
|
+
}
|
|
267
|
+
} else if (["POST", "PUT", "PATCH"].includes(route.method)) {
|
|
268
|
+
example = `curl -X ${route.method} ${fullPath} \\
|
|
269
|
+
-H "Content-Type: application/json" \\
|
|
270
|
+
-d '{"example": "data"}'`;
|
|
271
|
+
} else {
|
|
272
|
+
example = `curl -X ${route.method} ${fullPath}`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return `
|
|
276
|
+
<div class="example">
|
|
277
|
+
<strong>Example:</strong><br>
|
|
278
|
+
<code>${example}</code>
|
|
279
|
+
</div>`;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Create middleware for simple docs
|
|
284
|
+
export function createSimpleDocsMiddleware(
|
|
285
|
+
routes: CompiledRoute[],
|
|
286
|
+
options: SimpleDocsOptions = {},
|
|
287
|
+
) {
|
|
288
|
+
const generator = new SimpleDocsGenerator(options);
|
|
289
|
+
generator.addRoutes(routes);
|
|
290
|
+
|
|
291
|
+
const basePath = options.basePath || "/docs";
|
|
292
|
+
|
|
293
|
+
return (req: any, res: any, next: () => void) => {
|
|
294
|
+
if (
|
|
295
|
+
req.path === `${basePath}/simple` ||
|
|
296
|
+
req.path === `${basePath}/simple/`
|
|
297
|
+
) {
|
|
298
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
299
|
+
res.send(generator.generateHTML());
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
next();
|
|
304
|
+
};
|
|
305
|
+
}
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
// Swagger UI Integration for Moro Framework
|
|
2
|
+
// Serves interactive API documentation using Swagger UI
|
|
3
|
+
|
|
4
|
+
import { readFileSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { HttpRequest, HttpResponse } from "../http";
|
|
7
|
+
import { OpenAPISpec } from "./openapi-generator";
|
|
8
|
+
import { createFrameworkLogger } from "../logger";
|
|
9
|
+
|
|
10
|
+
const logger = createFrameworkLogger("SwaggerUI");
|
|
11
|
+
|
|
12
|
+
// Swagger UI configuration options
|
|
13
|
+
export interface SwaggerUIOptions {
|
|
14
|
+
title?: string;
|
|
15
|
+
favicon?: string;
|
|
16
|
+
customCss?: string;
|
|
17
|
+
customJs?: string;
|
|
18
|
+
swaggerOptions?: Record<string, any>;
|
|
19
|
+
enableTryItOut?: boolean;
|
|
20
|
+
enableFilter?: boolean;
|
|
21
|
+
enableDeepLinking?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Swagger UI middleware
|
|
25
|
+
export class SwaggerUIMiddleware {
|
|
26
|
+
private swaggerUIAssetPath: string;
|
|
27
|
+
private openAPISpec: OpenAPISpec;
|
|
28
|
+
private options: SwaggerUIOptions;
|
|
29
|
+
|
|
30
|
+
constructor(openAPISpec: OpenAPISpec, options: SwaggerUIOptions = {}) {
|
|
31
|
+
this.openAPISpec = openAPISpec;
|
|
32
|
+
this.options = {
|
|
33
|
+
title: "API Documentation",
|
|
34
|
+
enableTryItOut: true,
|
|
35
|
+
enableFilter: true,
|
|
36
|
+
enableDeepLinking: true,
|
|
37
|
+
swaggerOptions: {
|
|
38
|
+
dom_id: "#swagger-ui",
|
|
39
|
+
presets: [
|
|
40
|
+
"SwaggerUIBundle.presets.apis",
|
|
41
|
+
"SwaggerUIBundle.presets.standalone",
|
|
42
|
+
],
|
|
43
|
+
plugins: ["SwaggerUIBundle.plugins.DownloadUrl"],
|
|
44
|
+
layout: "StandaloneLayout",
|
|
45
|
+
},
|
|
46
|
+
...options,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// Find swagger-ui-dist assets
|
|
51
|
+
this.swaggerUIAssetPath = require
|
|
52
|
+
.resolve("swagger-ui-dist/package.json")
|
|
53
|
+
.replace("/package.json", "");
|
|
54
|
+
logger.debug("Swagger UI assets found", "Initialization", {
|
|
55
|
+
assetPath: this.swaggerUIAssetPath,
|
|
56
|
+
});
|
|
57
|
+
} catch (error) {
|
|
58
|
+
logger.error("Failed to locate Swagger UI assets", "Initialization", {
|
|
59
|
+
error: error instanceof Error ? error.message : String(error),
|
|
60
|
+
});
|
|
61
|
+
throw new Error(
|
|
62
|
+
"swagger-ui-dist package not found. Install with: npm install swagger-ui-dist",
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Generate HTML page for Swagger UI
|
|
68
|
+
private generateHTML(basePath: string): string {
|
|
69
|
+
const swaggerOptions = {
|
|
70
|
+
...this.options.swaggerOptions,
|
|
71
|
+
url: `${basePath}/openapi.json`, // Relative URL to the OpenAPI spec
|
|
72
|
+
tryItOutEnabled: this.options.enableTryItOut,
|
|
73
|
+
filter: this.options.enableFilter,
|
|
74
|
+
deepLinking: this.options.enableDeepLinking,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return `<!DOCTYPE html>
|
|
78
|
+
<html lang="en">
|
|
79
|
+
<head>
|
|
80
|
+
<meta charset="UTF-8">
|
|
81
|
+
<title>${this.options.title}</title>
|
|
82
|
+
<link rel="stylesheet" type="text/css" href="${basePath}/swagger-ui.css" />
|
|
83
|
+
<style>
|
|
84
|
+
html {
|
|
85
|
+
box-sizing: border-box;
|
|
86
|
+
overflow: -moz-scrollbars-vertical;
|
|
87
|
+
overflow-y: scroll;
|
|
88
|
+
}
|
|
89
|
+
*, *:before, *:after {
|
|
90
|
+
box-sizing: inherit;
|
|
91
|
+
}
|
|
92
|
+
body {
|
|
93
|
+
margin:0;
|
|
94
|
+
background: #fafafa;
|
|
95
|
+
}
|
|
96
|
+
#loading-message {
|
|
97
|
+
padding: 20px;
|
|
98
|
+
text-align: center;
|
|
99
|
+
font-family: Arial, sans-serif;
|
|
100
|
+
color: #666;
|
|
101
|
+
}
|
|
102
|
+
#error-display {
|
|
103
|
+
display: none;
|
|
104
|
+
padding: 20px;
|
|
105
|
+
background: #ffebee;
|
|
106
|
+
border-left: 4px solid #f44336;
|
|
107
|
+
margin: 20px;
|
|
108
|
+
font-family: monospace;
|
|
109
|
+
color: #c62828;
|
|
110
|
+
}
|
|
111
|
+
${this.options.customCss || ""}
|
|
112
|
+
</style>
|
|
113
|
+
${this.options.favicon ? `<link rel="icon" type="image/png" href="${this.options.favicon}" sizes="32x32" />` : ""}
|
|
114
|
+
</head>
|
|
115
|
+
<body>
|
|
116
|
+
<div id="loading-message">
|
|
117
|
+
<h2>Loading Swagger UI...</h2>
|
|
118
|
+
<p>Please wait while the API documentation loads.</p>
|
|
119
|
+
</div>
|
|
120
|
+
<div id="swagger-ui"></div>
|
|
121
|
+
<div id="error-display">
|
|
122
|
+
<h3>Failed to Load Swagger UI</h3>
|
|
123
|
+
<div id="error-details"></div>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<script src="${basePath}/swagger-ui-bundle.js" charset="UTF-8"></script>
|
|
127
|
+
<script src="${basePath}/swagger-ui-standalone-preset.js" charset="UTF-8"></script>
|
|
128
|
+
<script>
|
|
129
|
+
console.log('Starting Swagger UI initialization...');
|
|
130
|
+
|
|
131
|
+
function showError(message, details) {
|
|
132
|
+
console.error('Swagger UI Error:', message, details);
|
|
133
|
+
document.getElementById('loading-message').style.display = 'none';
|
|
134
|
+
document.getElementById('error-display').style.display = 'block';
|
|
135
|
+
document.getElementById('error-details').innerHTML =
|
|
136
|
+
'<p><strong>Error:</strong> ' + message + '</p>' +
|
|
137
|
+
(details ? '<pre>' + JSON.stringify(details, null, 2) + '</pre>' : '');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function initializeSwaggerUI() {
|
|
141
|
+
console.log('Initializing Swagger UI...');
|
|
142
|
+
|
|
143
|
+
if (typeof SwaggerUIBundle === 'undefined') {
|
|
144
|
+
showError('SwaggerUIBundle not loaded', { SwaggerUIBundle: typeof SwaggerUIBundle });
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (typeof SwaggerUIStandalonePreset === 'undefined') {
|
|
149
|
+
showError('SwaggerUIStandalonePreset not loaded', { SwaggerUIStandalonePreset: typeof SwaggerUIStandalonePreset });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
console.log('Creating SwaggerUIBundle...');
|
|
155
|
+
|
|
156
|
+
const ui = SwaggerUIBundle({
|
|
157
|
+
url: '${basePath}/openapi.json',
|
|
158
|
+
dom_id: '#swagger-ui',
|
|
159
|
+
deepLinking: ${this.options.enableDeepLinking},
|
|
160
|
+
presets: [
|
|
161
|
+
SwaggerUIBundle.presets.apis,
|
|
162
|
+
SwaggerUIStandalonePreset
|
|
163
|
+
],
|
|
164
|
+
plugins: [
|
|
165
|
+
SwaggerUIBundle.plugins.DownloadUrl
|
|
166
|
+
],
|
|
167
|
+
layout: "StandaloneLayout",
|
|
168
|
+
tryItOutEnabled: ${this.options.enableTryItOut},
|
|
169
|
+
filter: ${this.options.enableFilter},
|
|
170
|
+
onComplete: function() {
|
|
171
|
+
console.log('Swagger UI loaded successfully');
|
|
172
|
+
document.getElementById('loading-message').style.display = 'none';
|
|
173
|
+
},
|
|
174
|
+
onFailure: function(error) {
|
|
175
|
+
console.error('Swagger UI failed to load:', error);
|
|
176
|
+
showError('Swagger UI initialization failed', error);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
window.ui = ui;
|
|
181
|
+
console.log('SwaggerUIBundle created successfully');
|
|
182
|
+
|
|
183
|
+
// Hide loading message after timeout if onComplete doesn't fire
|
|
184
|
+
setTimeout(function() {
|
|
185
|
+
var loadingEl = document.getElementById('loading-message');
|
|
186
|
+
if (loadingEl && loadingEl.style.display !== 'none') {
|
|
187
|
+
console.log('Hiding loading message after timeout');
|
|
188
|
+
loadingEl.style.display = 'none';
|
|
189
|
+
}
|
|
190
|
+
}, 5000);
|
|
191
|
+
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error('Error creating SwaggerUIBundle:', error);
|
|
194
|
+
showError('Failed to create SwaggerUIBundle', {
|
|
195
|
+
name: error.name,
|
|
196
|
+
message: error.message
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Initialize when DOM is ready and scripts are loaded
|
|
202
|
+
if (document.readyState === 'loading') {
|
|
203
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
204
|
+
setTimeout(initializeSwaggerUI, 100);
|
|
205
|
+
});
|
|
206
|
+
} else {
|
|
207
|
+
setTimeout(initializeSwaggerUI, 100);
|
|
208
|
+
}
|
|
209
|
+
</script>
|
|
210
|
+
</body>
|
|
211
|
+
</html>`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Create middleware function that serves Swagger UI
|
|
215
|
+
createMiddleware(basePath: string = "/docs") {
|
|
216
|
+
return (req: HttpRequest, res: HttpResponse, next: () => void) => {
|
|
217
|
+
const path = req.path;
|
|
218
|
+
|
|
219
|
+
logger.debug(`Docs middleware handling: ${path}`, "DocsMiddleware", {
|
|
220
|
+
basePath,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Serve the main HTML page
|
|
224
|
+
if (path === basePath || path === `${basePath}/`) {
|
|
225
|
+
logger.debug("Serving Swagger UI HTML", "DocsServing");
|
|
226
|
+
|
|
227
|
+
// Set CSP headers to allow Swagger UI to work
|
|
228
|
+
res.setHeader(
|
|
229
|
+
"Content-Security-Policy",
|
|
230
|
+
"default-src 'self'; " +
|
|
231
|
+
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; " +
|
|
232
|
+
"style-src 'self' 'unsafe-inline'; " +
|
|
233
|
+
"img-src 'self' data: https:; " +
|
|
234
|
+
"font-src 'self' data:; " +
|
|
235
|
+
"connect-src 'self'",
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
239
|
+
res.send(this.generateHTML(basePath));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Serve the OpenAPI JSON spec
|
|
244
|
+
if (path === `${basePath}/openapi.json`) {
|
|
245
|
+
logger.debug("Serving OpenAPI JSON spec", "DocsServing");
|
|
246
|
+
res.setHeader("Content-Type", "application/json");
|
|
247
|
+
res.json(this.openAPISpec);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Serve Swagger UI assets
|
|
252
|
+
if (path.startsWith(`${basePath}/`)) {
|
|
253
|
+
const assetName = path.replace(`${basePath}/`, "");
|
|
254
|
+
|
|
255
|
+
logger.debug(
|
|
256
|
+
`Attempting to serve asset: ${assetName}`,
|
|
257
|
+
"AssetServing",
|
|
258
|
+
{
|
|
259
|
+
fullPath: path,
|
|
260
|
+
basePath,
|
|
261
|
+
assetPath: this.swaggerUIAssetPath,
|
|
262
|
+
},
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
// Security: only allow specific asset files
|
|
266
|
+
const allowedAssets = [
|
|
267
|
+
"swagger-ui-bundle.js",
|
|
268
|
+
"swagger-ui.css",
|
|
269
|
+
"swagger-ui-standalone-preset.js",
|
|
270
|
+
"favicon-16x16.png",
|
|
271
|
+
"favicon-32x32.png",
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
if (allowedAssets.includes(assetName)) {
|
|
275
|
+
try {
|
|
276
|
+
const assetPath = join(this.swaggerUIAssetPath, assetName);
|
|
277
|
+
logger.debug(`Reading asset from: ${assetPath}`, "AssetServing");
|
|
278
|
+
|
|
279
|
+
const content = readFileSync(assetPath);
|
|
280
|
+
|
|
281
|
+
// Set appropriate content type
|
|
282
|
+
const contentType = this.getContentType(assetName);
|
|
283
|
+
res.setHeader("Content-Type", contentType);
|
|
284
|
+
res.setHeader("Cache-Control", "public, max-age=86400"); // Cache for 1 day
|
|
285
|
+
|
|
286
|
+
logger.debug(
|
|
287
|
+
`Serving asset: ${assetName} (${content.length} bytes)`,
|
|
288
|
+
"AssetServing",
|
|
289
|
+
);
|
|
290
|
+
res.send(content);
|
|
291
|
+
return;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
logger.error(
|
|
294
|
+
`Failed to serve Swagger UI asset: ${assetName}`,
|
|
295
|
+
"AssetServing",
|
|
296
|
+
{
|
|
297
|
+
error: error instanceof Error ? error.message : String(error),
|
|
298
|
+
assetPath: join(this.swaggerUIAssetPath, assetName),
|
|
299
|
+
},
|
|
300
|
+
);
|
|
301
|
+
res.status(404);
|
|
302
|
+
res.send(`Asset not found: ${assetName}`);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
logger.warn(`Asset not allowed: ${assetName}`, "AssetServing", {
|
|
307
|
+
allowedAssets,
|
|
308
|
+
});
|
|
309
|
+
res.status(404);
|
|
310
|
+
res.send(`Asset not allowed: ${assetName}`);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Not a docs request, continue to next middleware
|
|
316
|
+
next();
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Get content type for asset files
|
|
321
|
+
private getContentType(filename: string): string {
|
|
322
|
+
if (filename.endsWith(".js")) return "application/javascript";
|
|
323
|
+
if (filename.endsWith(".css")) return "text/css";
|
|
324
|
+
if (filename.endsWith(".png")) return "image/png";
|
|
325
|
+
if (filename.endsWith(".ico")) return "image/x-icon";
|
|
326
|
+
return "text/plain";
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Update the OpenAPI spec (useful for dynamic updates)
|
|
330
|
+
updateSpec(newSpec: OpenAPISpec): void {
|
|
331
|
+
this.openAPISpec = newSpec;
|
|
332
|
+
logger.debug("OpenAPI specification updated", "SpecUpdate", {
|
|
333
|
+
pathCount: Object.keys(newSpec.paths).length,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Convenience function to create documentation middleware
|
|
339
|
+
export function createDocsMiddleware(
|
|
340
|
+
openAPISpec: OpenAPISpec,
|
|
341
|
+
options: SwaggerUIOptions = {},
|
|
342
|
+
) {
|
|
343
|
+
const middleware = new SwaggerUIMiddleware(openAPISpec, options);
|
|
344
|
+
return middleware.createMiddleware();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Helper function to generate documentation for an app
|
|
348
|
+
export function generateDocsForApp(
|
|
349
|
+
routes: any[],
|
|
350
|
+
info: { title: string; version: string; description?: string },
|
|
351
|
+
options: SwaggerUIOptions = {},
|
|
352
|
+
) {
|
|
353
|
+
const openAPISpec: OpenAPISpec = {
|
|
354
|
+
openapi: "3.0.3",
|
|
355
|
+
info,
|
|
356
|
+
servers: [
|
|
357
|
+
{ url: "http://localhost:3000", description: "Development server" },
|
|
358
|
+
],
|
|
359
|
+
paths: {},
|
|
360
|
+
tags: [],
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// This will be enhanced when integrated with the routing system
|
|
364
|
+
logger.info("Documentation generated for app", "AppDocumentation", {
|
|
365
|
+
routeCount: routes.length,
|
|
366
|
+
title: info.title,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
return createDocsMiddleware(openAPISpec, options);
|
|
370
|
+
}
|