@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,309 @@
|
|
|
1
|
+
// Service Discovery Client for Microservices
|
|
2
|
+
// Supports Consul, Kubernetes, and in-memory registry
|
|
3
|
+
|
|
4
|
+
export interface ServiceInfo {
|
|
5
|
+
name: string;
|
|
6
|
+
host: string;
|
|
7
|
+
port: number;
|
|
8
|
+
health?: string;
|
|
9
|
+
version?: string;
|
|
10
|
+
tags?: string[];
|
|
11
|
+
metadata?: Record<string, any>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ServiceDiscoveryOptions {
|
|
15
|
+
type: "consul" | "kubernetes" | "memory";
|
|
16
|
+
consulUrl?: string;
|
|
17
|
+
kubernetesNamespace?: string;
|
|
18
|
+
healthCheckInterval?: number;
|
|
19
|
+
tags?: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class ServiceRegistry {
|
|
23
|
+
private services = new Map<string, ServiceInfo[]>();
|
|
24
|
+
private options: ServiceDiscoveryOptions;
|
|
25
|
+
private healthCheckInterval?: NodeJS.Timeout;
|
|
26
|
+
|
|
27
|
+
constructor(options: ServiceDiscoveryOptions) {
|
|
28
|
+
this.options = options;
|
|
29
|
+
this.startHealthChecks();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async register(service: ServiceInfo): Promise<void> {
|
|
33
|
+
const { name } = service;
|
|
34
|
+
|
|
35
|
+
switch (this.options.type) {
|
|
36
|
+
case "consul":
|
|
37
|
+
await this.registerWithConsul(service);
|
|
38
|
+
break;
|
|
39
|
+
case "kubernetes":
|
|
40
|
+
await this.registerWithKubernetes(service);
|
|
41
|
+
break;
|
|
42
|
+
case "memory":
|
|
43
|
+
this.registerInMemory(service);
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(`Service registered: ${name}@${service.host}:${service.port}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async discover(serviceName: string): Promise<ServiceInfo[]> {
|
|
51
|
+
switch (this.options.type) {
|
|
52
|
+
case "consul":
|
|
53
|
+
return this.discoverFromConsul(serviceName);
|
|
54
|
+
case "kubernetes":
|
|
55
|
+
return this.discoverFromKubernetes(serviceName);
|
|
56
|
+
case "memory":
|
|
57
|
+
return this.discoverFromMemory(serviceName);
|
|
58
|
+
default:
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async deregister(serviceName: string): Promise<void> {
|
|
64
|
+
switch (this.options.type) {
|
|
65
|
+
case "consul":
|
|
66
|
+
await this.deregisterFromConsul(serviceName);
|
|
67
|
+
break;
|
|
68
|
+
case "kubernetes":
|
|
69
|
+
// K8s handles this automatically
|
|
70
|
+
break;
|
|
71
|
+
case "memory":
|
|
72
|
+
this.services.delete(serviceName);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log(`Service deregistered: ${serviceName}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// In-memory registry methods
|
|
80
|
+
private registerInMemory(service: ServiceInfo): void {
|
|
81
|
+
const existing = this.services.get(service.name) || [];
|
|
82
|
+
const updated = existing.filter(
|
|
83
|
+
(s) => s.host !== service.host || s.port !== service.port,
|
|
84
|
+
);
|
|
85
|
+
updated.push(service);
|
|
86
|
+
this.services.set(service.name, updated);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private discoverFromMemory(serviceName: string): ServiceInfo[] {
|
|
90
|
+
return this.services.get(serviceName) || [];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Consul integration
|
|
94
|
+
private async registerWithConsul(service: ServiceInfo): Promise<void> {
|
|
95
|
+
if (!this.options.consulUrl) {
|
|
96
|
+
throw new Error("Consul URL required for consul service discovery");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const consulService = {
|
|
100
|
+
ID: `${service.name}-${service.host}-${service.port}`,
|
|
101
|
+
Name: service.name,
|
|
102
|
+
Tags: service.tags || [],
|
|
103
|
+
Address: service.host,
|
|
104
|
+
Port: service.port,
|
|
105
|
+
Check: service.health
|
|
106
|
+
? {
|
|
107
|
+
HTTP: `http://${service.host}:${service.port}${service.health}`,
|
|
108
|
+
Interval: "30s",
|
|
109
|
+
Timeout: "10s",
|
|
110
|
+
}
|
|
111
|
+
: undefined,
|
|
112
|
+
Meta: service.metadata || {},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const response = await fetch(
|
|
117
|
+
`${this.options.consulUrl}/v1/agent/service/register`,
|
|
118
|
+
{
|
|
119
|
+
method: "PUT",
|
|
120
|
+
headers: { "Content-Type": "application/json" },
|
|
121
|
+
body: JSON.stringify(consulService),
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
throw new Error(`Consul registration failed: ${response.statusText}`);
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error("Failed to register with Consul:", error);
|
|
130
|
+
// Fallback to in-memory
|
|
131
|
+
this.registerInMemory(service);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private async discoverFromConsul(
|
|
136
|
+
serviceName: string,
|
|
137
|
+
): Promise<ServiceInfo[]> {
|
|
138
|
+
if (!this.options.consulUrl) {
|
|
139
|
+
return this.discoverFromMemory(serviceName);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const response = await fetch(
|
|
144
|
+
`${this.options.consulUrl}/v1/health/service/${serviceName}?passing=true`,
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
throw new Error(`Consul discovery failed: ${response.statusText}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const services = (await response.json()) as any[];
|
|
152
|
+
return services.map((entry: any) => ({
|
|
153
|
+
name: entry.Service.Service,
|
|
154
|
+
host: entry.Service.Address,
|
|
155
|
+
port: entry.Service.Port,
|
|
156
|
+
tags: entry.Service.Tags,
|
|
157
|
+
metadata: entry.Service.Meta,
|
|
158
|
+
}));
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error("Failed to discover from Consul:", error);
|
|
161
|
+
return this.discoverFromMemory(serviceName);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private async deregisterFromConsul(serviceName: string): Promise<void> {
|
|
166
|
+
if (!this.options.consulUrl) return;
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
await fetch(
|
|
170
|
+
`${this.options.consulUrl}/v1/agent/service/deregister/${serviceName}`,
|
|
171
|
+
{
|
|
172
|
+
method: "PUT",
|
|
173
|
+
},
|
|
174
|
+
);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error("Failed to deregister from Consul:", error);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Kubernetes integration
|
|
181
|
+
private async registerWithKubernetes(service: ServiceInfo): Promise<void> {
|
|
182
|
+
// In Kubernetes, services are registered via Service/Endpoints resources
|
|
183
|
+
// This would typically be handled by the K8s API, not application code
|
|
184
|
+
console.log(
|
|
185
|
+
`K8s service registration: ${service.name} (handled by Kubernetes)`,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Fallback to in-memory for local development
|
|
189
|
+
this.registerInMemory(service);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private async discoverFromKubernetes(
|
|
193
|
+
serviceName: string,
|
|
194
|
+
): Promise<ServiceInfo[]> {
|
|
195
|
+
// In K8s, we can discover services via DNS or the K8s API
|
|
196
|
+
const namespace = this.options.kubernetesNamespace || "default";
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
// Try K8s service DNS resolution
|
|
200
|
+
const host = `${serviceName}.${namespace}.svc.cluster.local`;
|
|
201
|
+
|
|
202
|
+
// For demo purposes, return the service info
|
|
203
|
+
// In production, you'd query the K8s API or use DNS
|
|
204
|
+
return [
|
|
205
|
+
{
|
|
206
|
+
name: serviceName,
|
|
207
|
+
host,
|
|
208
|
+
port: 80, // Default port, should be discovered from service definition
|
|
209
|
+
metadata: { discovered: "kubernetes" },
|
|
210
|
+
},
|
|
211
|
+
];
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error("Failed to discover from Kubernetes:", error);
|
|
214
|
+
return this.discoverFromMemory(serviceName);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Health checking
|
|
219
|
+
private startHealthChecks(): void {
|
|
220
|
+
if (this.options.healthCheckInterval) {
|
|
221
|
+
this.healthCheckInterval = setInterval(
|
|
222
|
+
() => this.performHealthChecks(),
|
|
223
|
+
this.options.healthCheckInterval,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private async performHealthChecks(): Promise<void> {
|
|
229
|
+
for (const [serviceName, services] of this.services.entries()) {
|
|
230
|
+
for (const service of services) {
|
|
231
|
+
if (service.health) {
|
|
232
|
+
try {
|
|
233
|
+
const response = await fetch(
|
|
234
|
+
`http://${service.host}:${service.port}${service.health}`,
|
|
235
|
+
{
|
|
236
|
+
timeout: 5000,
|
|
237
|
+
} as any,
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
if (!response.ok) {
|
|
241
|
+
console.warn(
|
|
242
|
+
`Health check failed for ${serviceName}: ${response.statusText}`,
|
|
243
|
+
);
|
|
244
|
+
// Remove unhealthy service
|
|
245
|
+
this.removeUnhealthyService(serviceName, service);
|
|
246
|
+
}
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.warn(`Health check failed for ${serviceName}:`, error);
|
|
249
|
+
this.removeUnhealthyService(serviceName, service);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private removeUnhealthyService(
|
|
257
|
+
serviceName: string,
|
|
258
|
+
unhealthyService: ServiceInfo,
|
|
259
|
+
): void {
|
|
260
|
+
const services = this.services.get(serviceName) || [];
|
|
261
|
+
const filtered = services.filter(
|
|
262
|
+
(s) =>
|
|
263
|
+
s.host !== unhealthyService.host || s.port !== unhealthyService.port,
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
if (filtered.length === 0) {
|
|
267
|
+
this.services.delete(serviceName);
|
|
268
|
+
} else {
|
|
269
|
+
this.services.set(serviceName, filtered);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Load balancing
|
|
274
|
+
selectService(
|
|
275
|
+
serviceName: string,
|
|
276
|
+
strategy: "round-robin" | "random" | "least-connections" = "round-robin",
|
|
277
|
+
): ServiceInfo | null {
|
|
278
|
+
const services = this.services.get(serviceName) || [];
|
|
279
|
+
|
|
280
|
+
if (services.length === 0) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
switch (strategy) {
|
|
285
|
+
case "random":
|
|
286
|
+
return services[Math.floor(Math.random() * services.length)];
|
|
287
|
+
case "round-robin":
|
|
288
|
+
// Simple round-robin (in production, maintain state)
|
|
289
|
+
return services[Date.now() % services.length];
|
|
290
|
+
case "least-connections":
|
|
291
|
+
// For demo, just return the first (in production, track connections)
|
|
292
|
+
return services[0];
|
|
293
|
+
default:
|
|
294
|
+
return services[0];
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Cleanup
|
|
299
|
+
destroy(): void {
|
|
300
|
+
if (this.healthCheckInterval) {
|
|
301
|
+
clearInterval(this.healthCheckInterval);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Get all registered services
|
|
306
|
+
getAllServices(): Record<string, ServiceInfo[]> {
|
|
307
|
+
return Object.fromEntries(this.services.entries());
|
|
308
|
+
}
|
|
309
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// src/core/websocket-manager.ts
|
|
2
|
+
import { Server as SocketIOServer, Socket, Namespace } from "socket.io";
|
|
3
|
+
import { Container } from "../utilities";
|
|
4
|
+
import { CircuitBreaker } from "../utilities";
|
|
5
|
+
import { ModuleConfig, WebSocketDefinition } from "../../types/module";
|
|
6
|
+
|
|
7
|
+
export class WebSocketManager {
|
|
8
|
+
private circuitBreakers = new Map<string, CircuitBreaker>();
|
|
9
|
+
private rateLimiters = new Map<
|
|
10
|
+
string,
|
|
11
|
+
Map<string, { count: number; resetTime: number }>
|
|
12
|
+
>();
|
|
13
|
+
private compressionEnabled = true;
|
|
14
|
+
private customIdGenerator?: () => string;
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
private io: SocketIOServer,
|
|
18
|
+
private container: Container,
|
|
19
|
+
) {
|
|
20
|
+
this.setupAdvancedFeatures();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private setupAdvancedFeatures() {
|
|
24
|
+
// Enable compression for WebSocket messages
|
|
25
|
+
if (this.compressionEnabled) {
|
|
26
|
+
(this.io.engine as any).compression = true;
|
|
27
|
+
(this.io.engine as any).perMessageDeflate = {
|
|
28
|
+
threshold: 1024,
|
|
29
|
+
concurrencyLimit: 10,
|
|
30
|
+
memLevel: 8,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Custom session ID generation if provided
|
|
35
|
+
if (this.customIdGenerator) {
|
|
36
|
+
(this.io.engine as any).generateId = this.customIdGenerator;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Global WebSocket middleware for advanced features
|
|
40
|
+
this.io.use((socket, next) => {
|
|
41
|
+
// Add binary message handling
|
|
42
|
+
socket.onAny((event, ...args) => {
|
|
43
|
+
// Handle binary frames efficiently
|
|
44
|
+
args.forEach((arg, index) => {
|
|
45
|
+
if (Buffer.isBuffer(arg)) {
|
|
46
|
+
// Process binary data with optimizations
|
|
47
|
+
args[index] = this.processBinaryData(arg);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Add compression per message
|
|
53
|
+
(socket as any).compressedEmit = (event: string, data: any) => {
|
|
54
|
+
if (this.compressionEnabled && this.shouldCompress(data)) {
|
|
55
|
+
socket.compress(true).emit(event, data);
|
|
56
|
+
} else {
|
|
57
|
+
socket.emit(event, data);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Add heartbeat mechanism
|
|
62
|
+
(socket as any).heartbeat = () => {
|
|
63
|
+
socket.emit("heartbeat", { timestamp: Date.now() });
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
next();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setCustomIdGenerator(generator: () => string) {
|
|
71
|
+
this.customIdGenerator = generator;
|
|
72
|
+
(this.io.engine as any).generateId = generator;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
enableCompression(options?: {
|
|
76
|
+
threshold?: number;
|
|
77
|
+
concurrencyLimit?: number;
|
|
78
|
+
memLevel?: number;
|
|
79
|
+
}) {
|
|
80
|
+
this.compressionEnabled = true;
|
|
81
|
+
if (options) {
|
|
82
|
+
(this.io.engine as any).perMessageDeflate = {
|
|
83
|
+
threshold: options.threshold || 1024,
|
|
84
|
+
concurrencyLimit: options.concurrencyLimit || 10,
|
|
85
|
+
memLevel: options.memLevel || 8,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private processBinaryData(buffer: Buffer): Buffer {
|
|
91
|
+
// Optimize binary data processing
|
|
92
|
+
// Could add compression, validation, etc.
|
|
93
|
+
return buffer;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private shouldCompress(data: any): boolean {
|
|
97
|
+
// Determine if data should be compressed
|
|
98
|
+
const serialized = JSON.stringify(data);
|
|
99
|
+
return serialized.length > 1024; // Compress if larger than 1KB
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async registerHandler(
|
|
103
|
+
namespace: Namespace,
|
|
104
|
+
wsConfig: WebSocketDefinition,
|
|
105
|
+
moduleConfig: ModuleConfig,
|
|
106
|
+
): Promise<void> {
|
|
107
|
+
namespace.on("connection", (socket: Socket) => {
|
|
108
|
+
console.log(`WebSocket connected to /${moduleConfig.name}: ${socket.id}`);
|
|
109
|
+
this.setupSocketHandlers(socket, wsConfig, moduleConfig);
|
|
110
|
+
this.setupSocketMiddleware(socket, moduleConfig.name);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private setupSocketHandlers(
|
|
115
|
+
socket: Socket,
|
|
116
|
+
wsConfig: WebSocketDefinition,
|
|
117
|
+
moduleConfig: ModuleConfig,
|
|
118
|
+
): void {
|
|
119
|
+
socket.on(wsConfig.event, async (data: any, callback?: Function) => {
|
|
120
|
+
const handlerKey = `${moduleConfig.name}.${wsConfig.handler}`;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
// Rate limiting
|
|
124
|
+
if (
|
|
125
|
+
wsConfig.rateLimit &&
|
|
126
|
+
!this.checkRateLimit(socket.id, handlerKey, wsConfig.rateLimit)
|
|
127
|
+
) {
|
|
128
|
+
const error = {
|
|
129
|
+
success: false,
|
|
130
|
+
error: "Rate limit exceeded",
|
|
131
|
+
code: "RATE_LIMIT",
|
|
132
|
+
};
|
|
133
|
+
if (callback) callback(error);
|
|
134
|
+
else socket.emit("error", error);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Validation (Zod-only)
|
|
139
|
+
if (wsConfig.validation) {
|
|
140
|
+
try {
|
|
141
|
+
data = wsConfig.validation.parse(data);
|
|
142
|
+
} catch (validationError: any) {
|
|
143
|
+
if (validationError.issues) {
|
|
144
|
+
const error = {
|
|
145
|
+
success: false,
|
|
146
|
+
error: "Validation failed",
|
|
147
|
+
details: validationError.issues.map((issue: any) => ({
|
|
148
|
+
field: issue.path.length > 0 ? issue.path.join(".") : "data",
|
|
149
|
+
message: issue.message,
|
|
150
|
+
code: issue.code,
|
|
151
|
+
})),
|
|
152
|
+
};
|
|
153
|
+
if (callback) callback(error);
|
|
154
|
+
else socket.emit("error", error);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
throw validationError;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Circuit breaker protection
|
|
162
|
+
const circuitBreaker = this.getCircuitBreaker(handlerKey);
|
|
163
|
+
|
|
164
|
+
const result = await circuitBreaker.execute(async () => {
|
|
165
|
+
const controller = this.container.resolve(moduleConfig.name);
|
|
166
|
+
return await (controller as any)[wsConfig.handler](socket, data);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Handle response
|
|
170
|
+
if (callback) {
|
|
171
|
+
callback({ success: true, data: result });
|
|
172
|
+
} else if (wsConfig.broadcast && result?.event) {
|
|
173
|
+
socket.broadcast.emit(result.event, result.data);
|
|
174
|
+
} else if (result) {
|
|
175
|
+
socket.emit(`${wsConfig.event}:response`, result);
|
|
176
|
+
}
|
|
177
|
+
} catch (error) {
|
|
178
|
+
const errorMessage =
|
|
179
|
+
error instanceof Error ? error.message : String(error);
|
|
180
|
+
const errorCode = (error as any)?.code || "INTERNAL_ERROR";
|
|
181
|
+
console.error(`WebSocket error in ${handlerKey}:`, errorMessage);
|
|
182
|
+
|
|
183
|
+
const errorResponse = {
|
|
184
|
+
success: false,
|
|
185
|
+
error: errorMessage,
|
|
186
|
+
code: errorCode,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
if (callback) {
|
|
190
|
+
callback(errorResponse);
|
|
191
|
+
} else {
|
|
192
|
+
socket.emit("error", errorResponse);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private setupSocketMiddleware(socket: Socket, moduleName: string): void {
|
|
199
|
+
socket.on("disconnect", (reason) => {
|
|
200
|
+
console.log(
|
|
201
|
+
`WebSocket disconnected from /${moduleName}: ${socket.id} (${reason})`,
|
|
202
|
+
);
|
|
203
|
+
this.cleanup(socket.id);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
socket.on("ping", () => {
|
|
207
|
+
socket.emit("pong");
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private checkRateLimit(
|
|
212
|
+
socketId: string,
|
|
213
|
+
handlerKey: string,
|
|
214
|
+
rateLimit: { requests: number; window: number },
|
|
215
|
+
): boolean {
|
|
216
|
+
if (!this.rateLimiters.has(handlerKey)) {
|
|
217
|
+
this.rateLimiters.set(handlerKey, new Map());
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const handlerLimiter = this.rateLimiters.get(handlerKey)!;
|
|
221
|
+
const now = Date.now();
|
|
222
|
+
const socketLimit = handlerLimiter.get(socketId);
|
|
223
|
+
|
|
224
|
+
if (!socketLimit || now > socketLimit.resetTime) {
|
|
225
|
+
handlerLimiter.set(socketId, {
|
|
226
|
+
count: 1,
|
|
227
|
+
resetTime: now + rateLimit.window,
|
|
228
|
+
});
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (socketLimit.count >= rateLimit.requests) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
socketLimit.count++;
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private getCircuitBreaker(key: string): CircuitBreaker {
|
|
241
|
+
if (!this.circuitBreakers.has(key)) {
|
|
242
|
+
this.circuitBreakers.set(
|
|
243
|
+
key,
|
|
244
|
+
new CircuitBreaker({
|
|
245
|
+
failureThreshold: 5,
|
|
246
|
+
resetTimeout: 30000,
|
|
247
|
+
monitoringPeriod: 10000,
|
|
248
|
+
}),
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
return this.circuitBreakers.get(key)!;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private cleanup(socketId: string): void {
|
|
255
|
+
this.rateLimiters.forEach((limiter) => {
|
|
256
|
+
limiter.delete(socketId);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|