@morojs/moro 1.6.2 → 1.6.3
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/core/http/http-server.js +12 -9
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/uws-http-server.js +1 -1
- package/dist/core/http/uws-http-server.js.map +1 -1
- package/dist/core/middleware/built-in/auth/core.d.ts +78 -0
- package/dist/core/middleware/built-in/auth/core.js +358 -0
- package/dist/core/middleware/built-in/auth/core.js.map +1 -0
- package/dist/core/middleware/built-in/{auth-helpers.js → auth/helpers.js} +1 -1
- package/dist/core/middleware/built-in/auth/helpers.js.map +1 -0
- package/dist/core/middleware/built-in/auth/hook.d.ts +30 -0
- package/dist/core/middleware/built-in/auth/hook.js +99 -0
- package/dist/core/middleware/built-in/auth/hook.js.map +1 -0
- package/dist/core/middleware/built-in/auth/index.d.ts +7 -0
- package/dist/core/middleware/built-in/auth/index.js +15 -0
- package/dist/core/middleware/built-in/auth/index.js.map +1 -0
- package/dist/core/middleware/built-in/{jwt-helpers.js → auth/jwt-helpers.js} +1 -1
- package/dist/core/middleware/built-in/auth/jwt-helpers.js.map +1 -0
- package/dist/core/middleware/built-in/auth/middleware.d.ts +23 -0
- package/dist/core/middleware/built-in/auth/middleware.js +71 -0
- package/dist/core/middleware/built-in/auth/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/{auth-providers.d.ts → auth/providers.d.ts} +1 -1
- package/dist/core/middleware/built-in/{auth-providers.js → auth/providers.js} +1 -1
- package/dist/core/middleware/built-in/auth/providers.js.map +1 -0
- package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/file.d.ts +1 -1
- package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/file.js +1 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/file.js.map +1 -0
- package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/index.d.ts +1 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/index.js.map +1 -0
- package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/memory.d.ts +1 -1
- package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/memory.js +1 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/memory.js.map +1 -0
- package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/redis.d.ts +1 -1
- package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/redis.js +2 -2
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.js.map +1 -0
- package/dist/core/middleware/built-in/{adapters → cache/adapters}/index.d.ts +0 -2
- package/{src/core/middleware/built-in/adapters/index.ts → dist/core/middleware/built-in/cache/adapters/index.js} +1 -3
- package/dist/core/middleware/built-in/cache/adapters/index.js.map +1 -0
- package/dist/core/middleware/built-in/cache/core.d.ts +37 -0
- package/dist/core/middleware/built-in/cache/core.js +87 -0
- package/dist/core/middleware/built-in/cache/core.js.map +1 -0
- package/dist/core/middleware/built-in/cache/hook.d.ts +20 -0
- package/dist/core/middleware/built-in/{cache.js → cache/hook.js} +22 -5
- package/dist/core/middleware/built-in/cache/hook.js.map +1 -0
- package/dist/core/middleware/built-in/cache/index.d.ts +3 -0
- package/dist/core/middleware/built-in/cache/index.js +9 -0
- package/dist/core/middleware/built-in/cache/index.js.map +1 -0
- package/dist/core/middleware/built-in/cache/middleware.d.ts +17 -0
- package/dist/core/middleware/built-in/cache/middleware.js +44 -0
- package/dist/core/middleware/built-in/cache/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/azure.d.ts +1 -1
- package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/azure.js +1 -1
- package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.js.map +1 -0
- package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/cloudflare.d.ts +1 -1
- package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/cloudflare.js +1 -1
- package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.js.map +1 -0
- package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/cloudfront.d.ts +1 -1
- package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/cloudfront.js +2 -2
- package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudfront.js.map +1 -0
- package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/index.d.ts +1 -1
- package/dist/core/middleware/built-in/cdn/adapters/cdn/index.js.map +1 -0
- package/dist/core/middleware/built-in/cdn/adapters/index.d.ts +2 -0
- package/dist/core/middleware/built-in/{adapters → cdn/adapters}/index.js +0 -2
- package/dist/core/middleware/built-in/cdn/adapters/index.js.map +1 -0
- package/dist/core/middleware/built-in/cdn/core.d.ts +43 -0
- package/dist/core/middleware/built-in/cdn/core.js +144 -0
- package/dist/core/middleware/built-in/cdn/core.js.map +1 -0
- package/dist/core/middleware/built-in/cdn/hook.d.ts +22 -0
- package/dist/core/middleware/built-in/cdn/hook.js +70 -0
- package/dist/core/middleware/built-in/cdn/hook.js.map +1 -0
- package/dist/core/middleware/built-in/cdn/index.d.ts +5 -0
- package/dist/core/middleware/built-in/cdn/index.js +11 -0
- package/dist/core/middleware/built-in/cdn/index.js.map +1 -0
- package/dist/core/middleware/built-in/cdn/middleware.d.ts +21 -0
- package/dist/core/middleware/built-in/cdn/middleware.js +52 -0
- package/dist/core/middleware/built-in/cdn/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/cookie/core.d.ts +37 -0
- package/dist/core/middleware/built-in/cookie/core.js +83 -0
- package/dist/core/middleware/built-in/cookie/core.js.map +1 -0
- package/dist/core/middleware/built-in/cookie/hook.d.ts +20 -0
- package/dist/core/middleware/built-in/cookie/hook.js +47 -0
- package/dist/core/middleware/built-in/cookie/hook.js.map +1 -0
- package/dist/core/middleware/built-in/cookie/index.d.ts +3 -0
- package/dist/core/middleware/built-in/cookie/index.js +9 -0
- package/dist/core/middleware/built-in/cookie/index.js.map +1 -0
- package/dist/core/middleware/built-in/cookie/middleware.d.ts +17 -0
- package/dist/core/middleware/built-in/cookie/middleware.js +36 -0
- package/dist/core/middleware/built-in/cookie/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/cors/core.d.ts +23 -0
- package/dist/core/middleware/built-in/cors/core.js +51 -0
- package/dist/core/middleware/built-in/cors/core.js.map +1 -0
- package/dist/core/middleware/built-in/cors/hook.d.ts +17 -0
- package/dist/core/middleware/built-in/cors/hook.js +37 -0
- package/dist/core/middleware/built-in/cors/hook.js.map +1 -0
- package/dist/core/middleware/built-in/cors/index.d.ts +3 -0
- package/dist/core/middleware/built-in/cors/index.js +9 -0
- package/dist/core/middleware/built-in/cors/index.js.map +1 -0
- package/dist/core/middleware/built-in/cors/middleware.d.ts +16 -0
- package/dist/core/middleware/built-in/cors/middleware.js +22 -0
- package/dist/core/middleware/built-in/cors/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/csp/core.d.ts +45 -0
- package/dist/core/middleware/built-in/csp/core.js +88 -0
- package/dist/core/middleware/built-in/csp/core.js.map +1 -0
- package/dist/core/middleware/built-in/csp/hook.d.ts +22 -0
- package/dist/core/middleware/built-in/csp/hook.js +47 -0
- package/dist/core/middleware/built-in/csp/hook.js.map +1 -0
- package/dist/core/middleware/built-in/csp/index.d.ts +3 -0
- package/dist/core/middleware/built-in/csp/index.js +9 -0
- package/dist/core/middleware/built-in/csp/index.js.map +1 -0
- package/dist/core/middleware/built-in/csp/middleware.d.ts +19 -0
- package/dist/core/middleware/built-in/csp/middleware.js +29 -0
- package/dist/core/middleware/built-in/csp/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/csrf/core.d.ts +28 -0
- package/dist/core/middleware/built-in/csrf/core.js +69 -0
- package/dist/core/middleware/built-in/csrf/core.js.map +1 -0
- package/dist/core/middleware/built-in/csrf/hook.d.ts +17 -0
- package/dist/core/middleware/built-in/csrf/hook.js +45 -0
- package/dist/core/middleware/built-in/csrf/hook.js.map +1 -0
- package/dist/core/middleware/built-in/csrf/index.d.ts +3 -0
- package/dist/core/middleware/built-in/csrf/index.js +9 -0
- package/dist/core/middleware/built-in/csrf/index.js.map +1 -0
- package/dist/core/middleware/built-in/csrf/middleware.d.ts +16 -0
- package/dist/core/middleware/built-in/csrf/middleware.js +34 -0
- package/dist/core/middleware/built-in/csrf/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/error-tracker/index.d.ts +1 -0
- package/dist/core/middleware/built-in/error-tracker/index.js +4 -0
- package/dist/core/middleware/built-in/error-tracker/index.js.map +1 -0
- package/dist/core/middleware/built-in/error-tracker/middleware.d.ts +12 -0
- package/dist/core/middleware/built-in/{error-tracker.js → error-tracker/middleware.js} +14 -3
- package/dist/core/middleware/built-in/error-tracker/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/index.d.ts +25 -59
- package/dist/core/middleware/built-in/index.js +31 -31
- package/dist/core/middleware/built-in/index.js.map +1 -1
- package/dist/core/middleware/built-in/performance-monitor/index.d.ts +1 -0
- package/dist/core/middleware/built-in/performance-monitor/index.js +4 -0
- package/dist/core/middleware/built-in/performance-monitor/index.js.map +1 -0
- package/dist/core/middleware/built-in/performance-monitor/middleware.d.ts +12 -0
- package/dist/core/middleware/built-in/{performance-monitor.js → performance-monitor/middleware.js} +14 -3
- package/dist/core/middleware/built-in/performance-monitor/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/rate-limit/core.d.ts +33 -0
- package/dist/core/middleware/built-in/rate-limit/core.js +86 -0
- package/dist/core/middleware/built-in/rate-limit/core.js.map +1 -0
- package/dist/core/middleware/built-in/rate-limit/hook.d.ts +20 -0
- package/dist/core/middleware/built-in/{rate-limit.js → rate-limit/hook.js} +22 -16
- package/dist/core/middleware/built-in/rate-limit/hook.js.map +1 -0
- package/dist/core/middleware/built-in/rate-limit/index.d.ts +3 -0
- package/dist/core/middleware/built-in/rate-limit/index.js +9 -0
- package/dist/core/middleware/built-in/rate-limit/index.js.map +1 -0
- package/dist/core/middleware/built-in/rate-limit/middleware.d.ts +16 -0
- package/dist/core/middleware/built-in/rate-limit/middleware.js +35 -0
- package/dist/core/middleware/built-in/rate-limit/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/request-logger/index.d.ts +1 -0
- package/dist/core/middleware/built-in/request-logger/index.js +4 -0
- package/dist/core/middleware/built-in/request-logger/index.js.map +1 -0
- package/dist/core/middleware/built-in/request-logger/middleware.d.ts +12 -0
- package/dist/core/middleware/built-in/{request-logger.js → request-logger/middleware.js} +14 -3
- package/dist/core/middleware/built-in/request-logger/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/session/core.d.ts +73 -0
- package/dist/core/middleware/built-in/session/core.js +227 -0
- package/dist/core/middleware/built-in/session/core.js.map +1 -0
- package/dist/core/middleware/built-in/session/hook.d.ts +17 -0
- package/dist/core/middleware/built-in/session/hook.js +53 -0
- package/dist/core/middleware/built-in/session/hook.js.map +1 -0
- package/dist/core/middleware/built-in/session/index.d.ts +3 -0
- package/dist/core/middleware/built-in/session/index.js +9 -0
- package/dist/core/middleware/built-in/session/index.js.map +1 -0
- package/dist/core/middleware/built-in/session/middleware.d.ts +17 -0
- package/dist/core/middleware/built-in/session/middleware.js +38 -0
- package/dist/core/middleware/built-in/session/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/sse/core.d.ts +44 -0
- package/dist/core/middleware/built-in/sse/core.js +117 -0
- package/dist/core/middleware/built-in/sse/core.js.map +1 -0
- package/dist/core/middleware/built-in/sse/hook.d.ts +18 -0
- package/dist/core/middleware/built-in/sse/hook.js +60 -0
- package/dist/core/middleware/built-in/sse/hook.js.map +1 -0
- package/dist/core/middleware/built-in/sse/index.d.ts +3 -0
- package/dist/core/middleware/built-in/sse/index.js +9 -0
- package/dist/core/middleware/built-in/sse/index.js.map +1 -0
- package/dist/core/middleware/built-in/sse/middleware.d.ts +18 -0
- package/dist/core/middleware/built-in/sse/middleware.js +43 -0
- package/dist/core/middleware/built-in/sse/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/validation/core.d.ts +23 -0
- package/dist/core/middleware/built-in/validation/core.js +93 -0
- package/dist/core/middleware/built-in/validation/core.js.map +1 -0
- package/dist/core/middleware/built-in/validation/hook.d.ts +13 -0
- package/dist/core/middleware/built-in/{validation.js → validation/hook.js} +14 -3
- package/dist/core/middleware/built-in/validation/hook.js.map +1 -0
- package/dist/core/middleware/built-in/validation/index.d.ts +3 -0
- package/dist/core/middleware/built-in/validation/index.js +9 -0
- package/dist/core/middleware/built-in/validation/index.js.map +1 -0
- package/dist/core/middleware/built-in/validation/middleware.d.ts +16 -0
- package/dist/core/middleware/built-in/validation/middleware.js +27 -0
- package/dist/core/middleware/built-in/validation/middleware.js.map +1 -0
- package/dist/core/middleware/index.js +6 -0
- package/dist/core/middleware/index.js.map +1 -1
- package/dist/core/routing/unified-router.d.ts +4 -20
- package/dist/core/routing/unified-router.js +61 -106
- package/dist/core/routing/unified-router.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/moro.js +12 -18
- package/dist/moro.js.map +1 -1
- package/dist/types/hooks.d.ts +3 -0
- package/package.json +2 -6
- package/dist/core/middleware/built-in/adapters/cache/file.js.map +0 -1
- package/dist/core/middleware/built-in/adapters/cache/index.js.map +0 -1
- package/dist/core/middleware/built-in/adapters/cache/memory.js.map +0 -1
- package/dist/core/middleware/built-in/adapters/cache/redis.js.map +0 -1
- package/dist/core/middleware/built-in/adapters/cdn/azure.js.map +0 -1
- package/dist/core/middleware/built-in/adapters/cdn/cloudflare.js.map +0 -1
- package/dist/core/middleware/built-in/adapters/cdn/cloudfront.js.map +0 -1
- package/dist/core/middleware/built-in/adapters/cdn/index.js.map +0 -1
- package/dist/core/middleware/built-in/adapters/index.js.map +0 -1
- package/dist/core/middleware/built-in/auth-helpers.js.map +0 -1
- package/dist/core/middleware/built-in/auth-providers.js.map +0 -1
- package/dist/core/middleware/built-in/auth.d.ts +0 -30
- package/dist/core/middleware/built-in/auth.js +0 -348
- package/dist/core/middleware/built-in/auth.js.map +0 -1
- package/dist/core/middleware/built-in/cache.d.ts +0 -3
- package/dist/core/middleware/built-in/cache.js.map +0 -1
- package/dist/core/middleware/built-in/cdn.d.ts +0 -3
- package/dist/core/middleware/built-in/cdn.js +0 -109
- package/dist/core/middleware/built-in/cdn.js.map +0 -1
- package/dist/core/middleware/built-in/cookie.d.ts +0 -14
- package/dist/core/middleware/built-in/cookie.js +0 -64
- package/dist/core/middleware/built-in/cookie.js.map +0 -1
- package/dist/core/middleware/built-in/cors.d.ts +0 -2
- package/dist/core/middleware/built-in/cors.js +0 -25
- package/dist/core/middleware/built-in/cors.js.map +0 -1
- package/dist/core/middleware/built-in/csp.d.ts +0 -22
- package/dist/core/middleware/built-in/csp.js +0 -68
- package/dist/core/middleware/built-in/csp.js.map +0 -1
- package/dist/core/middleware/built-in/csrf.d.ts +0 -9
- package/dist/core/middleware/built-in/csrf.js +0 -60
- package/dist/core/middleware/built-in/csrf.js.map +0 -1
- package/dist/core/middleware/built-in/error-tracker.d.ts +0 -1
- package/dist/core/middleware/built-in/error-tracker.js.map +0 -1
- package/dist/core/middleware/built-in/jwt-helpers.js.map +0 -1
- package/dist/core/middleware/built-in/performance-monitor.d.ts +0 -1
- package/dist/core/middleware/built-in/performance-monitor.js.map +0 -1
- package/dist/core/middleware/built-in/rate-limit.d.ts +0 -6
- package/dist/core/middleware/built-in/rate-limit.js.map +0 -1
- package/dist/core/middleware/built-in/request-logger.d.ts +0 -1
- package/dist/core/middleware/built-in/request-logger.js.map +0 -1
- package/dist/core/middleware/built-in/session.d.ts +0 -41
- package/dist/core/middleware/built-in/session.js +0 -205
- package/dist/core/middleware/built-in/session.js.map +0 -1
- package/dist/core/middleware/built-in/sse.d.ts +0 -6
- package/dist/core/middleware/built-in/sse.js +0 -69
- package/dist/core/middleware/built-in/sse.js.map +0 -1
- package/dist/core/middleware/built-in/validation.d.ts +0 -2
- package/dist/core/middleware/built-in/validation.js.map +0 -1
- package/jest.config.mjs +0 -41
- package/src/core/auth/README.md +0 -339
- package/src/core/auth/morojs-adapter.ts +0 -415
- package/src/core/config/config-manager.ts +0 -133
- package/src/core/config/config-sources.ts +0 -600
- package/src/core/config/config-validator.ts +0 -1116
- package/src/core/config/file-loader.ts +0 -150
- package/src/core/config/index.ts +0 -109
- package/src/core/config/schema.ts +0 -164
- package/src/core/config/utils.ts +0 -244
- package/src/core/database/README.md +0 -238
- package/src/core/database/adapters/drizzle.ts +0 -415
- package/src/core/database/adapters/index.ts +0 -42
- package/src/core/database/adapters/mongodb.ts +0 -317
- package/src/core/database/adapters/mysql.ts +0 -235
- package/src/core/database/adapters/postgresql.ts +0 -226
- package/src/core/database/adapters/redis.ts +0 -379
- package/src/core/database/adapters/sqlite.ts +0 -263
- package/src/core/database/index.ts +0 -3
- package/src/core/docs/index.ts +0 -231
- package/src/core/docs/openapi-generator.ts +0 -576
- package/src/core/docs/schema-to-openapi.ts +0 -145
- package/src/core/docs/simple-docs.ts +0 -295
- package/src/core/docs/swagger-ui.ts +0 -354
- package/src/core/docs/zod-to-openapi.ts +0 -532
- package/src/core/events/event-bus.ts +0 -231
- package/src/core/events/index.ts +0 -12
- package/src/core/framework.ts +0 -885
- package/src/core/http/http-server.ts +0 -1847
- package/src/core/http/index.ts +0 -7
- package/src/core/http/uws-http-server.ts +0 -591
- package/src/core/logger/filters.ts +0 -153
- package/src/core/logger/index.ts +0 -21
- package/src/core/logger/logger.ts +0 -1033
- package/src/core/logger/outputs.ts +0 -132
- package/src/core/middleware/built-in/adapters/cache/file.ts +0 -104
- package/src/core/middleware/built-in/adapters/cache/index.ts +0 -23
- package/src/core/middleware/built-in/adapters/cache/memory.ts +0 -73
- package/src/core/middleware/built-in/adapters/cache/redis.ts +0 -114
- package/src/core/middleware/built-in/adapters/cdn/azure.ts +0 -60
- package/src/core/middleware/built-in/adapters/cdn/cloudflare.ts +0 -83
- package/src/core/middleware/built-in/adapters/cdn/cloudfront.ts +0 -94
- package/src/core/middleware/built-in/adapters/cdn/index.ts +0 -23
- package/src/core/middleware/built-in/auth-helpers.ts +0 -401
- package/src/core/middleware/built-in/auth-providers.ts +0 -480
- package/src/core/middleware/built-in/auth.ts +0 -410
- package/src/core/middleware/built-in/cache.ts +0 -213
- package/src/core/middleware/built-in/cdn.ts +0 -124
- package/src/core/middleware/built-in/cookie.ts +0 -85
- package/src/core/middleware/built-in/cors.ts +0 -38
- package/src/core/middleware/built-in/csp.ts +0 -101
- package/src/core/middleware/built-in/csrf.ts +0 -82
- package/src/core/middleware/built-in/error-tracker.ts +0 -16
- package/src/core/middleware/built-in/index.ts +0 -87
- package/src/core/middleware/built-in/jwt-helpers.ts +0 -243
- package/src/core/middleware/built-in/performance-monitor.ts +0 -25
- package/src/core/middleware/built-in/rate-limit.ts +0 -60
- package/src/core/middleware/built-in/request-logger.ts +0 -16
- package/src/core/middleware/built-in/session.ts +0 -287
- package/src/core/middleware/built-in/sse.ts +0 -88
- package/src/core/middleware/built-in/validation.ts +0 -33
- package/src/core/middleware/index.ts +0 -177
- package/src/core/modules/auto-discovery.ts +0 -726
- package/src/core/modules/index.ts +0 -3
- package/src/core/modules/modules.ts +0 -135
- package/src/core/networking/adapters/index.ts +0 -17
- package/src/core/networking/adapters/socketio-adapter.ts +0 -254
- package/src/core/networking/adapters/uws-adapter.ts +0 -619
- package/src/core/networking/adapters/ws-adapter.ts +0 -429
- package/src/core/networking/index.ts +0 -4
- package/src/core/networking/service-discovery.ts +0 -303
- package/src/core/networking/websocket-adapter.ts +0 -217
- package/src/core/networking/websocket-manager.ts +0 -308
- package/src/core/pooling/object-pool-manager.ts +0 -630
- package/src/core/routing/app-integration.ts +0 -164
- package/src/core/routing/index.ts +0 -261
- package/src/core/routing/path-matcher.ts +0 -222
- package/src/core/routing/router.ts +0 -97
- package/src/core/routing/unified-router.ts +0 -870
- package/src/core/runtime/aws-lambda-adapter.ts +0 -147
- package/src/core/runtime/base-adapter.ts +0 -130
- package/src/core/runtime/cloudflare-workers-adapter.ts +0 -152
- package/src/core/runtime/index.ts +0 -62
- package/src/core/runtime/node-adapter.ts +0 -202
- package/src/core/runtime/vercel-edge-adapter.ts +0 -114
- package/src/core/utilities/circuit-breaker.ts +0 -46
- package/src/core/utilities/container.ts +0 -736
- package/src/core/utilities/hooks.ts +0 -142
- package/src/core/utilities/index.ts +0 -17
- package/src/core/utilities/package-utils.ts +0 -59
- package/src/core/validation/adapters.ts +0 -147
- package/src/core/validation/index.ts +0 -258
- package/src/core/validation/schema-interface.ts +0 -100
- package/src/index.ts +0 -233
- package/src/moro.ts +0 -1728
- package/src/types/auth.ts +0 -440
- package/src/types/cache.ts +0 -38
- package/src/types/cdn.ts +0 -22
- package/src/types/config.ts +0 -229
- package/src/types/core.ts +0 -58
- package/src/types/database.ts +0 -32
- package/src/types/discovery.ts +0 -7
- package/src/types/events.ts +0 -82
- package/src/types/hooks.ts +0 -47
- package/src/types/http.ts +0 -89
- package/src/types/logger.ts +0 -102
- package/src/types/module.ts +0 -99
- package/src/types/runtime.ts +0 -76
- package/src/types/session.ts +0 -89
- package/tsconfig.json +0 -23
- /package/dist/core/middleware/built-in/{auth-helpers.d.ts → auth/helpers.d.ts} +0 -0
- /package/dist/core/middleware/built-in/{jwt-helpers.d.ts → auth/jwt-helpers.d.ts} +0 -0
- /package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/index.js +0 -0
- /package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/index.js +0 -0
package/src/moro.ts
DELETED
|
@@ -1,1728 +0,0 @@
|
|
|
1
|
-
// Moro Framework - Modern TypeScript API Framework
|
|
2
|
-
// Built for developers who demand performance, elegance, and zero compromises
|
|
3
|
-
// Event-driven • Modular • Enterprise-ready • Developer-first
|
|
4
|
-
import { Moro as MoroCore } from './core/framework.js';
|
|
5
|
-
import { HttpRequest, HttpResponse, middleware } from './core/http/index.js';
|
|
6
|
-
import { ModuleConfig, InternalRouteDefinition } from './types/module.js';
|
|
7
|
-
import { MoroOptions } from './types/core.js';
|
|
8
|
-
import { ModuleDefaultsConfig } from './types/config.js';
|
|
9
|
-
import { MoroEventBus } from './core/events/index.js';
|
|
10
|
-
import { createFrameworkLogger, applyLoggingConfiguration } from './core/logger/index.js';
|
|
11
|
-
import { Logger } from './types/logger.js';
|
|
12
|
-
import { MiddlewareManager } from './core/middleware/index.js';
|
|
13
|
-
import { IntelligentRoutingManager } from './core/routing/app-integration.js';
|
|
14
|
-
import { RouteSchema } from './core/routing/index.js';
|
|
15
|
-
import {
|
|
16
|
-
UnifiedRouter,
|
|
17
|
-
RouteBuilder as UnifiedRouteBuilder,
|
|
18
|
-
} from './core/routing/unified-router.js';
|
|
19
|
-
import { AppDocumentationManager, DocsConfig } from './core/docs/index.js';
|
|
20
|
-
import { EventEmitter } from 'events';
|
|
21
|
-
import cluster from 'cluster';
|
|
22
|
-
import os from 'os';
|
|
23
|
-
import { normalizeValidationError } from './core/validation/schema-interface.js';
|
|
24
|
-
// Configuration System Integration
|
|
25
|
-
import { initializeConfig, type AppConfig } from './core/config/index.js';
|
|
26
|
-
// Runtime System Integration
|
|
27
|
-
import { RuntimeAdapter, RuntimeType, createRuntimeAdapter } from './core/runtime/index.js';
|
|
28
|
-
|
|
29
|
-
export class Moro extends EventEmitter {
|
|
30
|
-
private coreFramework!: MoroCore;
|
|
31
|
-
private routes: InternalRouteDefinition[] = [];
|
|
32
|
-
private moduleCounter = 0;
|
|
33
|
-
private loadedModules = new Set<string>();
|
|
34
|
-
private lazyModules = new Map<string, ModuleConfig>();
|
|
35
|
-
private routeHandlers: Record<string, Function> = {};
|
|
36
|
-
private moduleDiscovery?: any; // Store for cleanup
|
|
37
|
-
private autoDiscoveryOptions: MoroOptions | null = null;
|
|
38
|
-
private autoDiscoveryInitialized = false;
|
|
39
|
-
private autoDiscoveryPromise: Promise<void> | null = null;
|
|
40
|
-
// Enterprise event system integration
|
|
41
|
-
private eventBus!: MoroEventBus;
|
|
42
|
-
// Application logger
|
|
43
|
-
private logger!: Logger;
|
|
44
|
-
// Unified routing system (singleton - shared across all routers)
|
|
45
|
-
private unifiedRouter!: UnifiedRouter;
|
|
46
|
-
// Legacy intelligent routing (kept for backward compatibility, now a facade)
|
|
47
|
-
private intelligentRouting!: IntelligentRoutingManager;
|
|
48
|
-
// Documentation system
|
|
49
|
-
private documentation = new AppDocumentationManager();
|
|
50
|
-
// Configuration system
|
|
51
|
-
private config!: AppConfig;
|
|
52
|
-
// Track if user explicitly set logger options (for worker log level handling)
|
|
53
|
-
private userSetLogger = false;
|
|
54
|
-
// Runtime system
|
|
55
|
-
private runtimeAdapter!: RuntimeAdapter;
|
|
56
|
-
private runtimeType!: RuntimeType;
|
|
57
|
-
// Middleware system
|
|
58
|
-
private middlewareManager!: MiddlewareManager;
|
|
59
|
-
// Queued WebSocket registrations (for async adapter detection)
|
|
60
|
-
private queuedWebSocketRegistrations: Array<{
|
|
61
|
-
namespace: string;
|
|
62
|
-
handlers: Record<string, Function>;
|
|
63
|
-
processed: boolean;
|
|
64
|
-
}> = [];
|
|
65
|
-
|
|
66
|
-
constructor(options: MoroOptions = {}) {
|
|
67
|
-
super(); // Call EventEmitter constructor
|
|
68
|
-
|
|
69
|
-
// Track if user explicitly set logger/logging options
|
|
70
|
-
this.userSetLogger = !!(options.logger || options.logging);
|
|
71
|
-
|
|
72
|
-
// Apply logging configuration BEFORE config loading to avoid DEBUG spam
|
|
73
|
-
// 1. Environment variables (base level)
|
|
74
|
-
const envLogLevel = process.env.LOG_LEVEL || process.env.MORO_LOG_LEVEL;
|
|
75
|
-
if (envLogLevel) {
|
|
76
|
-
applyLoggingConfiguration({ level: envLogLevel }, undefined);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// 2. createApp logger options (highest precedence)
|
|
80
|
-
if (options.logger !== undefined) {
|
|
81
|
-
applyLoggingConfiguration(undefined, options.logger);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Create logger AFTER initial configuration
|
|
85
|
-
this.logger = createFrameworkLogger('App');
|
|
86
|
-
|
|
87
|
-
// Use simplified global configuration system
|
|
88
|
-
this.config = initializeConfig(options);
|
|
89
|
-
|
|
90
|
-
// Apply final config logging (this includes normalized logger → logging conversion)
|
|
91
|
-
// Always apply this as it's the authoritative merged config
|
|
92
|
-
if (this.config.logging) {
|
|
93
|
-
applyLoggingConfiguration(this.config.logging, undefined);
|
|
94
|
-
// Recreate logger with updated config
|
|
95
|
-
this.logger = createFrameworkLogger('App');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// NOW initialize routing systems AFTER logger is configured
|
|
99
|
-
this.unifiedRouter = UnifiedRouter.getInstance();
|
|
100
|
-
this.intelligentRouting = new IntelligentRoutingManager();
|
|
101
|
-
|
|
102
|
-
this.logger.info(
|
|
103
|
-
`Configuration system initialized: ${process.env.NODE_ENV || 'development'}:${this.config.server.port}`
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
// Initialize runtime system
|
|
107
|
-
this.runtimeType = options.runtime?.type || 'node';
|
|
108
|
-
this.runtimeAdapter = options.runtime?.adapter || createRuntimeAdapter(this.runtimeType);
|
|
109
|
-
|
|
110
|
-
this.logger.info(`Runtime system initialized: ${this.runtimeType}`, 'Runtime');
|
|
111
|
-
|
|
112
|
-
// Pass configuration from config to framework
|
|
113
|
-
const frameworkOptions: any = {
|
|
114
|
-
...options,
|
|
115
|
-
logger: this.config.logging,
|
|
116
|
-
// Enable websockets if either config has it enabled OR user passed websocket options
|
|
117
|
-
websocket:
|
|
118
|
-
this.config.websocket.enabled || options.websocket
|
|
119
|
-
? options.websocket || this.config.websocket || {}
|
|
120
|
-
: false,
|
|
121
|
-
config: this.config,
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
this.coreFramework = new MoroCore(frameworkOptions);
|
|
125
|
-
|
|
126
|
-
// Initialize middleware system
|
|
127
|
-
this.middlewareManager = new MiddlewareManager();
|
|
128
|
-
|
|
129
|
-
// Integrate hooks system with HTTP server
|
|
130
|
-
const httpServer = (this.coreFramework as any).httpServer;
|
|
131
|
-
if (httpServer && httpServer.setHookManager) {
|
|
132
|
-
httpServer.setHookManager((this.middlewareManager as any).hooks);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Configure HTTP server performance based on config
|
|
136
|
-
if (httpServer && httpServer.configurePerformance) {
|
|
137
|
-
const performanceConfig = this.config.performance;
|
|
138
|
-
httpServer.configurePerformance({
|
|
139
|
-
compression: performanceConfig?.compression || { enabled: true },
|
|
140
|
-
minimal: performanceConfig?.compression?.enabled === false, // Enable minimal mode if compression disabled
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Access enterprise event bus from core framework
|
|
145
|
-
this.eventBus = (this.coreFramework as any).eventBus;
|
|
146
|
-
|
|
147
|
-
// Setup default middleware if enabled - use config defaults with options override
|
|
148
|
-
this.setupDefaultMiddleware({
|
|
149
|
-
...this.getDefaultOptionsFromConfig(),
|
|
150
|
-
...options,
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
// Store auto-discovery options for later initialization
|
|
154
|
-
// IMPORTANT: Auto-discovery is deferred to ensure user middleware (like auth)
|
|
155
|
-
// is registered before module middleware that might bypass it
|
|
156
|
-
this.autoDiscoveryOptions = options.autoDiscover !== false ? options : null;
|
|
157
|
-
|
|
158
|
-
// Emit initialization event through enterprise event bus
|
|
159
|
-
this.eventBus.emit('framework:initialized', {
|
|
160
|
-
options,
|
|
161
|
-
config: this.config,
|
|
162
|
-
runtime: this.runtimeType,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Get configuration object
|
|
168
|
-
*/
|
|
169
|
-
getConfig(): AppConfig {
|
|
170
|
-
return this.config;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Get runtime adapter
|
|
175
|
-
*/
|
|
176
|
-
getRuntime(): RuntimeAdapter {
|
|
177
|
-
return this.runtimeAdapter;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Get runtime type
|
|
182
|
-
*/
|
|
183
|
-
getRuntimeType(): RuntimeType {
|
|
184
|
-
return this.runtimeType;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Extract default options from configuration
|
|
189
|
-
*/
|
|
190
|
-
private getDefaultOptionsFromConfig(): Partial<MoroOptions> {
|
|
191
|
-
return {
|
|
192
|
-
cors: this.config.security.cors.enabled,
|
|
193
|
-
compression: this.config.performance.compression.enabled,
|
|
194
|
-
helmet: this.config.security.helmet.enabled,
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Intelligent route methods - chainable with automatic middleware ordering
|
|
199
|
-
// Overloads for better TypeScript inference
|
|
200
|
-
get(path: string): UnifiedRouteBuilder;
|
|
201
|
-
get(path: string, handler: (req: HttpRequest, res: HttpResponse) => any, options?: any): this;
|
|
202
|
-
get(
|
|
203
|
-
path: string,
|
|
204
|
-
handler?: (req: HttpRequest, res: HttpResponse) => any,
|
|
205
|
-
options?: any
|
|
206
|
-
): UnifiedRouteBuilder | this {
|
|
207
|
-
if (handler) {
|
|
208
|
-
// Direct route registration
|
|
209
|
-
return this.addRoute('GET', path, handler, options);
|
|
210
|
-
}
|
|
211
|
-
// Use unified router for chainable API
|
|
212
|
-
return this.unifiedRouter.get(path);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
post(path: string): UnifiedRouteBuilder;
|
|
216
|
-
post(path: string, handler: (req: HttpRequest, res: HttpResponse) => any, options?: any): this;
|
|
217
|
-
post(
|
|
218
|
-
path: string,
|
|
219
|
-
handler?: (req: HttpRequest, res: HttpResponse) => any,
|
|
220
|
-
options?: any
|
|
221
|
-
): UnifiedRouteBuilder | this {
|
|
222
|
-
if (handler) {
|
|
223
|
-
// Direct route registration
|
|
224
|
-
return this.addRoute('POST', path, handler, options);
|
|
225
|
-
}
|
|
226
|
-
// Use unified router for chainable API
|
|
227
|
-
return this.unifiedRouter.post(path);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
put(path: string): UnifiedRouteBuilder;
|
|
231
|
-
put(path: string, handler: (req: HttpRequest, res: HttpResponse) => any, options?: any): this;
|
|
232
|
-
put(
|
|
233
|
-
path: string,
|
|
234
|
-
handler?: (req: HttpRequest, res: HttpResponse) => any,
|
|
235
|
-
options?: any
|
|
236
|
-
): UnifiedRouteBuilder | this {
|
|
237
|
-
if (handler) {
|
|
238
|
-
// Direct route registration
|
|
239
|
-
return this.addRoute('PUT', path, handler, options);
|
|
240
|
-
}
|
|
241
|
-
// Use unified router for chainable API
|
|
242
|
-
return this.unifiedRouter.put(path);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
delete(path: string): UnifiedRouteBuilder;
|
|
246
|
-
delete(path: string, handler: (req: HttpRequest, res: HttpResponse) => any, options?: any): this;
|
|
247
|
-
delete(
|
|
248
|
-
path: string,
|
|
249
|
-
handler?: (req: HttpRequest, res: HttpResponse) => any,
|
|
250
|
-
options?: any
|
|
251
|
-
): UnifiedRouteBuilder | this {
|
|
252
|
-
if (handler) {
|
|
253
|
-
// Direct route registration
|
|
254
|
-
return this.addRoute('DELETE', path, handler, options);
|
|
255
|
-
}
|
|
256
|
-
// Use unified router for chainable API
|
|
257
|
-
return this.unifiedRouter.delete(path);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
patch(path: string): UnifiedRouteBuilder;
|
|
261
|
-
patch(path: string, handler: (req: HttpRequest, res: HttpResponse) => any, options?: any): this;
|
|
262
|
-
patch(
|
|
263
|
-
path: string,
|
|
264
|
-
handler?: (req: HttpRequest, res: HttpResponse) => any,
|
|
265
|
-
options?: any
|
|
266
|
-
): UnifiedRouteBuilder | this {
|
|
267
|
-
if (handler) {
|
|
268
|
-
// Direct route registration
|
|
269
|
-
return this.addRoute('PATCH', path, handler, options);
|
|
270
|
-
}
|
|
271
|
-
// Use unified router for chainable API
|
|
272
|
-
return this.unifiedRouter.patch(path);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Schema-first route method
|
|
276
|
-
route(schema: RouteSchema): void {
|
|
277
|
-
// Use unified router for schema-first registration
|
|
278
|
-
this.unifiedRouter.route(schema);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Enable automatic API documentation
|
|
282
|
-
enableDocs(config: DocsConfig): void {
|
|
283
|
-
this.documentation.enableDocs(config, this.intelligentRouting);
|
|
284
|
-
|
|
285
|
-
this.logger.info(`API Documentation enabled at ${config.basePath || '/docs'}`, 'Documentation');
|
|
286
|
-
this.eventBus.emit('docs:enabled', { config });
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Get OpenAPI specification
|
|
290
|
-
getOpenAPISpec() {
|
|
291
|
-
return this.documentation.getOpenAPISpec();
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Get documentation as JSON
|
|
295
|
-
getDocsJSON(): string {
|
|
296
|
-
return this.documentation.getDocsJSON();
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// Get documentation as YAML
|
|
300
|
-
getDocsYAML(): string {
|
|
301
|
-
return this.documentation.getDocsYAML();
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Refresh documentation (useful after adding routes dynamically)
|
|
305
|
-
refreshDocs(): void {
|
|
306
|
-
this.documentation.refreshDocs();
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Universal middleware system - seamlessly handles standard and advanced middleware
|
|
310
|
-
async use(middlewareOrFunction: any, config?: any) {
|
|
311
|
-
// Standard middleware integration (req, res, next pattern)
|
|
312
|
-
if (typeof middlewareOrFunction === 'function' && middlewareOrFunction.length >= 3) {
|
|
313
|
-
this.coreFramework.addMiddleware(middlewareOrFunction);
|
|
314
|
-
this.eventBus.emit('middleware:registered', {
|
|
315
|
-
type: 'standard',
|
|
316
|
-
middleware: middlewareOrFunction,
|
|
317
|
-
});
|
|
318
|
-
return this;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Function-style middleware execution
|
|
322
|
-
if (typeof middlewareOrFunction === 'function' && middlewareOrFunction.length <= 1) {
|
|
323
|
-
await middlewareOrFunction(this);
|
|
324
|
-
this.eventBus.emit('middleware:executed', {
|
|
325
|
-
type: 'function',
|
|
326
|
-
middleware: middlewareOrFunction,
|
|
327
|
-
});
|
|
328
|
-
return this;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Advanced middleware pipeline integration - check if it's a MiddlewareInterface
|
|
332
|
-
if (
|
|
333
|
-
middlewareOrFunction &&
|
|
334
|
-
typeof middlewareOrFunction === 'object' &&
|
|
335
|
-
middlewareOrFunction.install &&
|
|
336
|
-
middlewareOrFunction.metadata
|
|
337
|
-
) {
|
|
338
|
-
// This is a MiddlewareInterface object - install it with the MiddlewareManager
|
|
339
|
-
this.logger.debug(
|
|
340
|
-
`Installing MiddlewareInterface: ${middlewareOrFunction.metadata.name}`,
|
|
341
|
-
'Middleware'
|
|
342
|
-
);
|
|
343
|
-
this.middlewareManager.install(middlewareOrFunction, config);
|
|
344
|
-
return this;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Fallback: emit event for unknown middleware types
|
|
348
|
-
this.eventBus.emit('middleware:advanced', {
|
|
349
|
-
middleware: middlewareOrFunction,
|
|
350
|
-
config,
|
|
351
|
-
});
|
|
352
|
-
this.logger.debug(
|
|
353
|
-
'Advanced middleware integration - enhanced capabilities loading...',
|
|
354
|
-
'Middleware'
|
|
355
|
-
);
|
|
356
|
-
return this;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Plugin compatibility layer - unified middleware interface
|
|
360
|
-
async plugin(middleware: any, options?: any): Promise<this> {
|
|
361
|
-
return this.use(middleware, options);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Module loading with events
|
|
365
|
-
async loadModule(moduleOrPath: ModuleConfig | string) {
|
|
366
|
-
this.eventBus.emit('module:loading', {
|
|
367
|
-
moduleId: typeof moduleOrPath === 'string' ? moduleOrPath : moduleOrPath.name,
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
if (typeof moduleOrPath === 'string') {
|
|
371
|
-
const module = await this.importModule(moduleOrPath);
|
|
372
|
-
await this.coreFramework.loadModule(module);
|
|
373
|
-
this.loadedModules.add(moduleOrPath);
|
|
374
|
-
this.eventBus.emit('module:loaded', {
|
|
375
|
-
moduleId: module.name,
|
|
376
|
-
version: module.version || '1.0.0',
|
|
377
|
-
});
|
|
378
|
-
} else {
|
|
379
|
-
await this.coreFramework.loadModule(moduleOrPath);
|
|
380
|
-
this.loadedModules.add(moduleOrPath.name);
|
|
381
|
-
this.eventBus.emit('module:loaded', {
|
|
382
|
-
moduleId: moduleOrPath.name,
|
|
383
|
-
version: moduleOrPath.version || '1.0.0',
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// IMPORTANT: If modules are loaded manually after auto-discovery,
|
|
388
|
-
// ensure the final module handler is set up to maintain middleware order
|
|
389
|
-
if (this.autoDiscoveryInitialized) {
|
|
390
|
-
this.coreFramework.setupFinalModuleHandler();
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return this;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// Database helper with events
|
|
397
|
-
database(adapter: any) {
|
|
398
|
-
this.eventBus.emit('database:connected', {
|
|
399
|
-
adapter: adapter.constructor.name,
|
|
400
|
-
config: 'hidden',
|
|
401
|
-
});
|
|
402
|
-
this.coreFramework.registerDatabase(adapter);
|
|
403
|
-
return this;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// WebSocket helper with events
|
|
407
|
-
websocket(namespace: string, handlers: Record<string, Function>) {
|
|
408
|
-
// Queue the registration to be processed after adapter initialization
|
|
409
|
-
const registration = { namespace, handlers, processed: false };
|
|
410
|
-
this.queuedWebSocketRegistrations.push(registration);
|
|
411
|
-
|
|
412
|
-
// Try to process immediately if adapter is already ready
|
|
413
|
-
const adapter = this.coreFramework.getWebSocketAdapter();
|
|
414
|
-
if (adapter && !registration.processed) {
|
|
415
|
-
// Adapter is ready, process immediately
|
|
416
|
-
this.processWebSocketRegistration(namespace, handlers, adapter);
|
|
417
|
-
registration.processed = true;
|
|
418
|
-
}
|
|
419
|
-
// Otherwise, it will be processed when the server starts
|
|
420
|
-
|
|
421
|
-
return this;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
private processWebSocketRegistration(
|
|
425
|
-
namespace: string,
|
|
426
|
-
handlers: Record<string, Function>,
|
|
427
|
-
adapter: any
|
|
428
|
-
) {
|
|
429
|
-
this.emit('websocket:registering', { namespace, handlers });
|
|
430
|
-
|
|
431
|
-
const ns = adapter.createNamespace(namespace);
|
|
432
|
-
|
|
433
|
-
Object.entries(handlers).forEach(([event, handler]) => {
|
|
434
|
-
ns.on('connection', (socket: any) => {
|
|
435
|
-
this.emit('websocket:connection', { namespace, event, socket });
|
|
436
|
-
|
|
437
|
-
socket.on(event, (data: any, callback: any) => {
|
|
438
|
-
this.emit('websocket:event', { namespace, event, data });
|
|
439
|
-
|
|
440
|
-
Promise.resolve(handler(socket, data))
|
|
441
|
-
.then((result: any) => {
|
|
442
|
-
this.emit('websocket:response', { namespace, event, result });
|
|
443
|
-
if (callback) callback(result);
|
|
444
|
-
else if (result) socket.emit(`${event}:response`, result);
|
|
445
|
-
})
|
|
446
|
-
.catch((error: any) => {
|
|
447
|
-
this.emit('websocket:error', { namespace, event, error });
|
|
448
|
-
const errorResponse = { success: false, error: error.message };
|
|
449
|
-
if (callback) callback(errorResponse);
|
|
450
|
-
else socket.emit('error', errorResponse);
|
|
451
|
-
});
|
|
452
|
-
});
|
|
453
|
-
});
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
this.emit('websocket:registered', { namespace, handlers });
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
private async processQueuedWebSocketRegistrations() {
|
|
460
|
-
// Wait for WebSocket adapter to be ready
|
|
461
|
-
await this.coreFramework.ensureWebSocketReady();
|
|
462
|
-
|
|
463
|
-
const adapter = this.coreFramework.getWebSocketAdapter();
|
|
464
|
-
|
|
465
|
-
// Check if any unprocessed registrations exist
|
|
466
|
-
const unprocessedRegistrations = this.queuedWebSocketRegistrations.filter(r => !r.processed);
|
|
467
|
-
|
|
468
|
-
if (!adapter && unprocessedRegistrations.length > 0) {
|
|
469
|
-
throw new Error(
|
|
470
|
-
'WebSocket features require a WebSocket adapter.\n\n' +
|
|
471
|
-
'Option 1: Install socket.io (auto-detected):\n' +
|
|
472
|
-
' npm install socket.io\n' +
|
|
473
|
-
' const app = new Moro({ websocket: {} });\n\n' +
|
|
474
|
-
'Option 2: Configure a specific adapter:\n' +
|
|
475
|
-
" import { SocketIOAdapter } from '@morojs/moro';\n" +
|
|
476
|
-
' const app = new Moro({ websocket: { adapter: new SocketIOAdapter() } });\n\n' +
|
|
477
|
-
'Option 3: Enable in config file (moro.config.js):\n' +
|
|
478
|
-
' export default { websocket: { enabled: true } };'
|
|
479
|
-
);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (adapter) {
|
|
483
|
-
// Process all unprocessed registrations
|
|
484
|
-
for (const registration of this.queuedWebSocketRegistrations) {
|
|
485
|
-
if (!registration.processed) {
|
|
486
|
-
this.processWebSocketRegistration(registration.namespace, registration.handlers, adapter);
|
|
487
|
-
registration.processed = true;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
// Start server with events (Node.js only)
|
|
494
|
-
listen(callback?: () => void): void;
|
|
495
|
-
listen(port: number, callback?: () => void): void;
|
|
496
|
-
listen(port: number, host: string, callback?: () => void): void;
|
|
497
|
-
listen(
|
|
498
|
-
portOrCallback?: number | (() => void),
|
|
499
|
-
hostOrCallback?: string | (() => void),
|
|
500
|
-
callback?: () => void
|
|
501
|
-
) {
|
|
502
|
-
// Only available for Node.js runtime
|
|
503
|
-
if (this.runtimeType !== 'node') {
|
|
504
|
-
throw new Error(
|
|
505
|
-
`listen() is only available for Node.js runtime. Current runtime: ${this.runtimeType}. Use getHandler() for other runtimes.`
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// Handle overloaded parameters - supports:
|
|
510
|
-
// listen(callback)
|
|
511
|
-
// listen(port, callback)
|
|
512
|
-
// listen(port, host, callback)
|
|
513
|
-
let port: number;
|
|
514
|
-
let host: string | undefined;
|
|
515
|
-
|
|
516
|
-
if (typeof portOrCallback === 'function') {
|
|
517
|
-
// listen(callback) - use port from config
|
|
518
|
-
callback = portOrCallback;
|
|
519
|
-
port = this.config.server.port;
|
|
520
|
-
host = this.config.server.host;
|
|
521
|
-
} else if (typeof portOrCallback === 'number') {
|
|
522
|
-
// listen(port, ...) variants
|
|
523
|
-
port = portOrCallback;
|
|
524
|
-
if (typeof hostOrCallback === 'function') {
|
|
525
|
-
// listen(port, callback)
|
|
526
|
-
callback = hostOrCallback;
|
|
527
|
-
host = undefined;
|
|
528
|
-
} else {
|
|
529
|
-
// listen(port, host, callback)
|
|
530
|
-
host = hostOrCallback;
|
|
531
|
-
}
|
|
532
|
-
} else {
|
|
533
|
-
// listen() - use config defaults
|
|
534
|
-
port = this.config.server.port;
|
|
535
|
-
host = this.config.server.host;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// Validate that we have a valid port
|
|
539
|
-
if (!port || typeof port !== 'number') {
|
|
540
|
-
throw new Error(
|
|
541
|
-
'Port not specified and not found in configuration. Please provide a port number or configure it in moro.config.js/ts'
|
|
542
|
-
);
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// Check if clustering is enabled for massive performance gains
|
|
546
|
-
// NOTE: uWebSockets.js does NOT support Node.js clustering - it's single-threaded only
|
|
547
|
-
const usingUWebSockets = this.config.server?.useUWebSockets || false;
|
|
548
|
-
|
|
549
|
-
if (this.config.performance?.clustering?.enabled) {
|
|
550
|
-
if (usingUWebSockets) {
|
|
551
|
-
this.logger.warn(
|
|
552
|
-
'Clustering is not supported with uWebSockets.js - running in single-threaded mode. ' +
|
|
553
|
-
'uWebSockets is so fast that single-threaded performance often exceeds multi-threaded Node.js!',
|
|
554
|
-
'Cluster'
|
|
555
|
-
);
|
|
556
|
-
// Continue without clustering
|
|
557
|
-
} else {
|
|
558
|
-
this.startWithClustering(port, host as string, callback);
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
this.eventBus.emit('server:starting', { port, runtime: this.runtimeType });
|
|
563
|
-
|
|
564
|
-
// Add documentation middleware first (if enabled)
|
|
565
|
-
try {
|
|
566
|
-
const docsMiddleware = this.documentation.getDocsMiddleware();
|
|
567
|
-
this.coreFramework.addMiddleware(docsMiddleware);
|
|
568
|
-
this.logger.debug('Documentation middleware added', 'Documentation');
|
|
569
|
-
} catch (error) {
|
|
570
|
-
// Documentation not enabled, that's fine
|
|
571
|
-
this.logger.debug('Documentation not enabled', 'Documentation');
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// Add unified routing middleware (handles both chainable and direct routes)
|
|
575
|
-
// Optimized: call router without extra async wrapper when possible
|
|
576
|
-
this.coreFramework.addMiddleware((req: HttpRequest, res: HttpResponse, next: () => void) => {
|
|
577
|
-
// Try unified router first (handles all route types)
|
|
578
|
-
const handled = this.unifiedRouter.handleRequest(req, res);
|
|
579
|
-
|
|
580
|
-
// Check if it's a promise (async route) or sync
|
|
581
|
-
if (handled && typeof (handled as any).then === 'function') {
|
|
582
|
-
// Async - await the result
|
|
583
|
-
(handled as Promise<boolean>)
|
|
584
|
-
.then(isHandled => {
|
|
585
|
-
if (!isHandled) {
|
|
586
|
-
next(); // Fall back to legacy routes if any
|
|
587
|
-
}
|
|
588
|
-
})
|
|
589
|
-
.catch(() => next());
|
|
590
|
-
} else {
|
|
591
|
-
// Sync - check immediately
|
|
592
|
-
if (!(handled as boolean)) {
|
|
593
|
-
next();
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
});
|
|
597
|
-
|
|
598
|
-
// Register legacy direct routes with the HTTP server (for backward compatibility)
|
|
599
|
-
if (this.routes.length > 0) {
|
|
600
|
-
this.registerDirectRoutes();
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
const startServer = () => {
|
|
604
|
-
const actualCallback = () => {
|
|
605
|
-
const displayHost = host || 'localhost';
|
|
606
|
-
this.logger.info('Moro Server Started', 'Server');
|
|
607
|
-
this.logger.info(`Runtime: ${this.runtimeType}`, 'Server');
|
|
608
|
-
this.logger.info(`HTTP API: http://${displayHost}:${port}`, 'Server');
|
|
609
|
-
if (this.config.websocket.enabled) {
|
|
610
|
-
this.logger.info(`WebSocket: ws://${displayHost}:${port}`, 'Server');
|
|
611
|
-
}
|
|
612
|
-
this.logger.info('Learn more at https://morojs.com', 'Server');
|
|
613
|
-
|
|
614
|
-
// Log unified router stats
|
|
615
|
-
const routeCount = this.unifiedRouter.getRouteCount();
|
|
616
|
-
if (routeCount > 0) {
|
|
617
|
-
this.logger.info(`Unified Router: ${routeCount} routes registered`, 'Server');
|
|
618
|
-
// Log performance stats
|
|
619
|
-
this.unifiedRouter.logPerformanceStats();
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
this.eventBus.emit('server:started', { port, runtime: this.runtimeType });
|
|
623
|
-
if (callback) callback();
|
|
624
|
-
};
|
|
625
|
-
|
|
626
|
-
if (host && typeof host === 'string') {
|
|
627
|
-
this.coreFramework.listen(port, host, actualCallback);
|
|
628
|
-
} else {
|
|
629
|
-
this.coreFramework.listen(port, actualCallback);
|
|
630
|
-
}
|
|
631
|
-
};
|
|
632
|
-
|
|
633
|
-
// Ensure auto-discovery and WebSocket setup is complete before starting server
|
|
634
|
-
Promise.all([this.ensureAutoDiscoveryComplete(), this.processQueuedWebSocketRegistrations()])
|
|
635
|
-
.then(() => {
|
|
636
|
-
startServer();
|
|
637
|
-
})
|
|
638
|
-
.catch(error => {
|
|
639
|
-
this.logger.error('Initialization failed during server start', 'Framework', {
|
|
640
|
-
error: error instanceof Error ? error.message : String(error),
|
|
641
|
-
});
|
|
642
|
-
// For auto-discovery failures, start server anyway
|
|
643
|
-
// For WebSocket failures with queued registrations, error will propagate
|
|
644
|
-
if (
|
|
645
|
-
error instanceof Error &&
|
|
646
|
-
error.message.includes('WebSocket features require a WebSocket adapter')
|
|
647
|
-
) {
|
|
648
|
-
throw error;
|
|
649
|
-
}
|
|
650
|
-
startServer();
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// Public method to manually initialize auto-discovery
|
|
655
|
-
// Useful for ensuring auth middleware is registered before auto-discovery
|
|
656
|
-
async initializeAutoDiscoveryNow(): Promise<void> {
|
|
657
|
-
return this.ensureAutoDiscoveryComplete();
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
// Public API: Initialize modules explicitly after middleware setup
|
|
661
|
-
// This provides users with explicit control over module loading timing
|
|
662
|
-
// IMPORTANT: This forces module loading even if autoDiscovery.enabled is false
|
|
663
|
-
// Usage: app.initModules() or app.initModules({ paths: ['./my-modules'] })
|
|
664
|
-
initModules(options?: {
|
|
665
|
-
paths?: string[];
|
|
666
|
-
patterns?: string[];
|
|
667
|
-
recursive?: boolean;
|
|
668
|
-
loadingStrategy?: 'eager' | 'lazy' | 'conditional';
|
|
669
|
-
watchForChanges?: boolean;
|
|
670
|
-
ignorePatterns?: string[];
|
|
671
|
-
loadOrder?: 'alphabetical' | 'dependency' | 'custom';
|
|
672
|
-
failOnError?: boolean;
|
|
673
|
-
maxDepth?: number;
|
|
674
|
-
}): void {
|
|
675
|
-
this.logger.info('User-requested module initialization', 'ModuleSystem');
|
|
676
|
-
|
|
677
|
-
// If already initialized, do nothing
|
|
678
|
-
if (this.autoDiscoveryInitialized) {
|
|
679
|
-
this.logger.debug('Auto-discovery already completed, skipping', 'ModuleSystem');
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
// Store the options and mark that we want to force initialization
|
|
684
|
-
this.autoDiscoveryOptions = {
|
|
685
|
-
autoDiscover: {
|
|
686
|
-
enabled: true, // Force enabled regardless of original config
|
|
687
|
-
paths: options?.paths || ['./modules', './src/modules'],
|
|
688
|
-
patterns: options?.patterns || [
|
|
689
|
-
'**/*.module.{ts,js}',
|
|
690
|
-
'**/index.{ts,js}',
|
|
691
|
-
'**/*.config.{ts,js}',
|
|
692
|
-
],
|
|
693
|
-
recursive: options?.recursive ?? true,
|
|
694
|
-
loadingStrategy: options?.loadingStrategy || ('eager' as const),
|
|
695
|
-
watchForChanges: options?.watchForChanges ?? false,
|
|
696
|
-
ignorePatterns: options?.ignorePatterns || [
|
|
697
|
-
'**/*.test.{ts,js}',
|
|
698
|
-
'**/*.spec.{ts,js}',
|
|
699
|
-
'**/node_modules/**',
|
|
700
|
-
],
|
|
701
|
-
loadOrder: options?.loadOrder || ('dependency' as const),
|
|
702
|
-
failOnError: options?.failOnError ?? false,
|
|
703
|
-
maxDepth: options?.maxDepth ?? 5,
|
|
704
|
-
},
|
|
705
|
-
};
|
|
706
|
-
|
|
707
|
-
this.logger.debug(
|
|
708
|
-
'Module initialization options stored, will execute on next listen/getHandler call',
|
|
709
|
-
'ModuleSystem'
|
|
710
|
-
);
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// Robust method to ensure auto-discovery is complete, handling race conditions
|
|
714
|
-
private async ensureAutoDiscoveryComplete(): Promise<void> {
|
|
715
|
-
// If already initialized, nothing to do
|
|
716
|
-
if (this.autoDiscoveryInitialized) {
|
|
717
|
-
return;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
// If auto-discovery is disabled, mark as initialized
|
|
721
|
-
if (!this.autoDiscoveryOptions) {
|
|
722
|
-
this.autoDiscoveryInitialized = true;
|
|
723
|
-
return;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
// If already in progress, wait for it to complete
|
|
727
|
-
if (this.autoDiscoveryPromise) {
|
|
728
|
-
return this.autoDiscoveryPromise;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
// Start auto-discovery
|
|
732
|
-
this.autoDiscoveryPromise = this.performAutoDiscovery();
|
|
733
|
-
|
|
734
|
-
try {
|
|
735
|
-
await this.autoDiscoveryPromise;
|
|
736
|
-
this.autoDiscoveryInitialized = true;
|
|
737
|
-
} catch (error) {
|
|
738
|
-
// Reset promise on error so it can be retried
|
|
739
|
-
this.autoDiscoveryPromise = null;
|
|
740
|
-
throw error;
|
|
741
|
-
} finally {
|
|
742
|
-
this.autoDiscoveryOptions = null; // Clear after attempt
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// Perform the actual auto-discovery work
|
|
747
|
-
private async performAutoDiscovery(optionsOverride?: MoroOptions): Promise<void> {
|
|
748
|
-
const optionsToUse = optionsOverride || this.autoDiscoveryOptions;
|
|
749
|
-
if (!optionsToUse) return;
|
|
750
|
-
|
|
751
|
-
this.logger.debug('Starting auto-discovery initialization', 'AutoDiscovery');
|
|
752
|
-
|
|
753
|
-
await this.initializeAutoDiscovery(optionsToUse);
|
|
754
|
-
|
|
755
|
-
this.logger.debug('Auto-discovery initialization completed', 'AutoDiscovery');
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
// Get handler for non-Node.js runtimes
|
|
759
|
-
getHandler() {
|
|
760
|
-
// Ensure auto-discovery is complete for non-Node.js runtimes
|
|
761
|
-
// This handles the case where users call getHandler() immediately after createApp()
|
|
762
|
-
this.ensureAutoDiscoveryComplete().catch(error => {
|
|
763
|
-
this.logger.error('Auto-discovery initialization failed for runtime handler', 'Framework', {
|
|
764
|
-
error: error instanceof Error ? error.message : String(error),
|
|
765
|
-
});
|
|
766
|
-
});
|
|
767
|
-
|
|
768
|
-
// Create a unified request handler that works with the runtime adapter
|
|
769
|
-
const handler = async (req: HttpRequest, res: HttpResponse) => {
|
|
770
|
-
// Add documentation middleware first (if enabled)
|
|
771
|
-
try {
|
|
772
|
-
const docsMiddleware = this.documentation.getDocsMiddleware();
|
|
773
|
-
await docsMiddleware(req, res, () => {});
|
|
774
|
-
if (res.headersSent) return;
|
|
775
|
-
} catch (error) {
|
|
776
|
-
// Documentation not enabled, that's fine
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
// Try unified router first (handles all routes)
|
|
780
|
-
const handled = await this.unifiedRouter.handleRequest(req, res);
|
|
781
|
-
if (handled) return;
|
|
782
|
-
|
|
783
|
-
// Handle legacy direct routes (backward compatibility)
|
|
784
|
-
if (this.routes.length > 0) {
|
|
785
|
-
await this.handleDirectRoutes(req, res);
|
|
786
|
-
}
|
|
787
|
-
};
|
|
788
|
-
|
|
789
|
-
// Use the runtime adapter to create the appropriate handler
|
|
790
|
-
return this.runtimeAdapter.createServer(handler);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
// Handle direct routes for runtime adapters
|
|
794
|
-
private async handleDirectRoutes(req: HttpRequest, res: HttpResponse) {
|
|
795
|
-
// Find matching route
|
|
796
|
-
const route = this.findMatchingRoute(req.method!, req.path);
|
|
797
|
-
if (!route) {
|
|
798
|
-
(res as any).status(404).json({ success: false, error: 'Not found' });
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
try {
|
|
803
|
-
// Extract path parameters
|
|
804
|
-
const matches = req.path.match(route.pattern);
|
|
805
|
-
if (matches) {
|
|
806
|
-
req.params = {};
|
|
807
|
-
route.paramNames.forEach((name: string, index: number) => {
|
|
808
|
-
req.params[name] = matches[index + 1];
|
|
809
|
-
});
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
// Get handler function
|
|
813
|
-
const handler = this.routeHandlers[route.handler];
|
|
814
|
-
if (!handler) {
|
|
815
|
-
(res as any).status(500).json({ success: false, error: 'Handler not found' });
|
|
816
|
-
return;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
// Execute validation if present
|
|
820
|
-
if (route.validation) {
|
|
821
|
-
try {
|
|
822
|
-
const validated = route.validation.parse(req.body);
|
|
823
|
-
req.body = validated;
|
|
824
|
-
} catch (error: any) {
|
|
825
|
-
if (error.issues) {
|
|
826
|
-
(res as any).status(400).json({
|
|
827
|
-
success: false,
|
|
828
|
-
error: 'Validation failed',
|
|
829
|
-
details: error.issues.map((issue: any) => ({
|
|
830
|
-
field: issue.path.length > 0 ? issue.path.join('.') : 'body',
|
|
831
|
-
message: issue.message,
|
|
832
|
-
code: issue.code,
|
|
833
|
-
})),
|
|
834
|
-
});
|
|
835
|
-
return;
|
|
836
|
-
}
|
|
837
|
-
throw error;
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
// Execute rate limiting if present
|
|
842
|
-
if (route.rateLimit) {
|
|
843
|
-
const clientId = req.ip || 'unknown';
|
|
844
|
-
const key = `${route.method}:${route.path}:${clientId}`;
|
|
845
|
-
|
|
846
|
-
if (!this.checkRateLimit(key, route.rateLimit)) {
|
|
847
|
-
(res as any).status(429).json({
|
|
848
|
-
success: false,
|
|
849
|
-
error: 'Rate limit exceeded',
|
|
850
|
-
retryAfter: Math.ceil(route.rateLimit.window / 1000),
|
|
851
|
-
});
|
|
852
|
-
return;
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
// Execute the handler
|
|
857
|
-
const result = await handler(req, res);
|
|
858
|
-
if (result && !(res as any).headersSent) {
|
|
859
|
-
(res as any).json(result);
|
|
860
|
-
}
|
|
861
|
-
} catch (error) {
|
|
862
|
-
if (!(res as any).headersSent) {
|
|
863
|
-
(res as any).status(500).json({
|
|
864
|
-
success: false,
|
|
865
|
-
error: error instanceof Error ? error.message : 'Internal server error',
|
|
866
|
-
});
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
// Advanced route matching with caching and optimization
|
|
872
|
-
private routeCache = new Map<string, { pattern: RegExp; paramNames: string[] }>();
|
|
873
|
-
private staticRouteMap = new Map<string, any>();
|
|
874
|
-
private dynamicRoutesBySegments = new Map<number, any[]>();
|
|
875
|
-
|
|
876
|
-
private findMatchingRoute(method: string, path: string) {
|
|
877
|
-
// Phase 1: O(1) static route lookup
|
|
878
|
-
const staticKey = `${method}:${path}`;
|
|
879
|
-
const staticRoute = this.staticRouteMap.get(staticKey);
|
|
880
|
-
if (staticRoute) {
|
|
881
|
-
return {
|
|
882
|
-
...staticRoute,
|
|
883
|
-
pattern: /^.*$/, // Dummy pattern for static routes
|
|
884
|
-
paramNames: [],
|
|
885
|
-
};
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
// Phase 2: Optimized dynamic route matching by segment count
|
|
889
|
-
const segments = path.split('/').filter(s => s.length > 0);
|
|
890
|
-
const segmentCount = segments.length;
|
|
891
|
-
const candidateRoutes = this.dynamicRoutesBySegments.get(segmentCount) || [];
|
|
892
|
-
|
|
893
|
-
for (const route of candidateRoutes) {
|
|
894
|
-
if (route.method === method) {
|
|
895
|
-
const cacheKey = `${method}:${route.path}`;
|
|
896
|
-
let pattern = this.routeCache.get(cacheKey);
|
|
897
|
-
|
|
898
|
-
if (!pattern) {
|
|
899
|
-
pattern = this.pathToRegex(route.path);
|
|
900
|
-
this.routeCache.set(cacheKey, pattern);
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
if (pattern.pattern.test(path)) {
|
|
904
|
-
return {
|
|
905
|
-
...route,
|
|
906
|
-
pattern: pattern.pattern,
|
|
907
|
-
paramNames: pattern.paramNames,
|
|
908
|
-
};
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
return null;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
// Convert path to regex (simplified version)
|
|
917
|
-
private pathToRegex(path: string): { pattern: RegExp; paramNames: string[] } {
|
|
918
|
-
const paramNames: string[] = [];
|
|
919
|
-
const regexPath = path.replace(/\//g, '\\/').replace(/:([^/]+)/g, (match, paramName) => {
|
|
920
|
-
paramNames.push(paramName);
|
|
921
|
-
return '([^/]+)';
|
|
922
|
-
});
|
|
923
|
-
|
|
924
|
-
return {
|
|
925
|
-
pattern: new RegExp(`^${regexPath}$`),
|
|
926
|
-
paramNames,
|
|
927
|
-
};
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
// Access enterprise event system for advanced integrations
|
|
931
|
-
get events() {
|
|
932
|
-
return this.eventBus;
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
// Access to core framework for advanced usage
|
|
936
|
-
get core() {
|
|
937
|
-
return this.coreFramework;
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
// Force cleanup of pooled objects
|
|
941
|
-
forceCleanup(): void {
|
|
942
|
-
const httpServer = (this.coreFramework as any).httpServer;
|
|
943
|
-
if (httpServer && httpServer.forceCleanup) {
|
|
944
|
-
httpServer.forceCleanup();
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
// Private methods
|
|
949
|
-
private addRoute(method: string, path: string, handler: Function, options: any = {}) {
|
|
950
|
-
// Register with unified router (primary routing system)
|
|
951
|
-
this.unifiedRouter.addRoute(method as any, path, handler as any, options.middleware || []);
|
|
952
|
-
|
|
953
|
-
// Also store in legacy routes array for backward compatibility
|
|
954
|
-
const handlerName = `handler_${this.routes.length}`;
|
|
955
|
-
const route = {
|
|
956
|
-
method: method as any,
|
|
957
|
-
path,
|
|
958
|
-
handler: handlerName,
|
|
959
|
-
validation: options.validation,
|
|
960
|
-
rateLimit: options.rateLimit,
|
|
961
|
-
cache: options.cache,
|
|
962
|
-
middleware: options.middleware,
|
|
963
|
-
};
|
|
964
|
-
|
|
965
|
-
this.routes.push(route);
|
|
966
|
-
|
|
967
|
-
// Organize routes for optimal lookup (legacy)
|
|
968
|
-
this.organizeRouteForLookup(route);
|
|
969
|
-
|
|
970
|
-
// Store handler for later module creation (legacy)
|
|
971
|
-
this.routeHandlers[handlerName] = handler;
|
|
972
|
-
|
|
973
|
-
return this;
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
private organizeRouteForLookup(route: any): void {
|
|
977
|
-
if (!route.path.includes(':')) {
|
|
978
|
-
// Static route - add to static map for O(1) lookup
|
|
979
|
-
const staticKey = `${route.method}:${route.path}`;
|
|
980
|
-
this.staticRouteMap.set(staticKey, route);
|
|
981
|
-
} else {
|
|
982
|
-
// Dynamic route - organize by segment count
|
|
983
|
-
const segments = route.path.split('/').filter((s: string) => s.length > 0);
|
|
984
|
-
const segmentCount = segments.length;
|
|
985
|
-
|
|
986
|
-
if (!this.dynamicRoutesBySegments.has(segmentCount)) {
|
|
987
|
-
this.dynamicRoutesBySegments.set(segmentCount, []);
|
|
988
|
-
}
|
|
989
|
-
this.dynamicRoutesBySegments.get(segmentCount)!.push(route);
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
private registerDirectRoutes() {
|
|
994
|
-
// Register routes directly with the HTTP server for optimal performance
|
|
995
|
-
// This provides the intuitive developer experience users expect
|
|
996
|
-
for (const route of this.routes) {
|
|
997
|
-
const handler = this.routeHandlers[route.handler];
|
|
998
|
-
|
|
999
|
-
// Get direct access to the HTTP server through the core framework
|
|
1000
|
-
const httpServer = (this.coreFramework as any).httpServer;
|
|
1001
|
-
|
|
1002
|
-
// Create a wrapper handler that handles validation, rate limiting, and return values
|
|
1003
|
-
const wrappedHandler = async (req: any, res: any) => {
|
|
1004
|
-
try {
|
|
1005
|
-
// Enhance request with events property for direct routes
|
|
1006
|
-
req.events = this.eventBus;
|
|
1007
|
-
|
|
1008
|
-
// Universal validation middleware (works with any ValidationSchema)
|
|
1009
|
-
if (route.validation) {
|
|
1010
|
-
try {
|
|
1011
|
-
const validated = await route.validation.parseAsync(req.body);
|
|
1012
|
-
req.body = validated;
|
|
1013
|
-
} catch (error: any) {
|
|
1014
|
-
// Handle universal validation errors
|
|
1015
|
-
const normalizedError = normalizeValidationError(error);
|
|
1016
|
-
res.status(400).json({
|
|
1017
|
-
success: false,
|
|
1018
|
-
error: 'Validation failed',
|
|
1019
|
-
details: normalizedError.issues.map((issue: any) => ({
|
|
1020
|
-
field: issue.path.length > 0 ? issue.path.join('.') : 'body',
|
|
1021
|
-
message: issue.message,
|
|
1022
|
-
code: issue.code,
|
|
1023
|
-
})),
|
|
1024
|
-
requestId: req.requestId,
|
|
1025
|
-
});
|
|
1026
|
-
return;
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
// Rate limiting middleware
|
|
1031
|
-
if (route.rateLimit) {
|
|
1032
|
-
const clientId = req.ip || req.connection.remoteAddress || 'unknown';
|
|
1033
|
-
const key = `${route.method}:${route.path}:${clientId}`;
|
|
1034
|
-
|
|
1035
|
-
if (!this.checkRateLimit(key, route.rateLimit)) {
|
|
1036
|
-
res.status(429).json({
|
|
1037
|
-
success: false,
|
|
1038
|
-
error: 'Rate limit exceeded',
|
|
1039
|
-
retryAfter: Math.ceil(route.rateLimit.window / 1000),
|
|
1040
|
-
});
|
|
1041
|
-
return;
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
// Execute the actual handler
|
|
1046
|
-
const result = await handler(req, res);
|
|
1047
|
-
if (result && !res.headersSent) {
|
|
1048
|
-
res.json(result);
|
|
1049
|
-
}
|
|
1050
|
-
} catch (error) {
|
|
1051
|
-
if (!res.headersSent) {
|
|
1052
|
-
res.status(500).json({
|
|
1053
|
-
success: false,
|
|
1054
|
-
error: error instanceof Error ? error.message : 'Internal server error',
|
|
1055
|
-
});
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
};
|
|
1059
|
-
|
|
1060
|
-
// Register with the appropriate HTTP method
|
|
1061
|
-
const method = route.method.toLowerCase();
|
|
1062
|
-
if (httpServer && httpServer[method]) {
|
|
1063
|
-
httpServer[method](route.path, wrappedHandler);
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
// Simple rate limiting for direct routes
|
|
1069
|
-
private rateLimitStore = new Map<string, { count: number; resetTime: number }>();
|
|
1070
|
-
|
|
1071
|
-
private checkRateLimit(key: string, config: { requests: number; window: number }): boolean {
|
|
1072
|
-
const now = Date.now();
|
|
1073
|
-
const bucket = this.rateLimitStore.get(key);
|
|
1074
|
-
|
|
1075
|
-
if (!bucket || now > bucket.resetTime) {
|
|
1076
|
-
// Create new bucket or reset expired bucket
|
|
1077
|
-
this.rateLimitStore.set(key, {
|
|
1078
|
-
count: 1,
|
|
1079
|
-
resetTime: now + config.window,
|
|
1080
|
-
});
|
|
1081
|
-
return true;
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
if (bucket.count >= config.requests) {
|
|
1085
|
-
return false; // Rate limit exceeded
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
bucket.count++;
|
|
1089
|
-
return true;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
private setupDefaultMiddleware(options: MoroOptions) {
|
|
1093
|
-
// CORS - check config enabled property OR options.security.cors.enabled === true
|
|
1094
|
-
if (this.config.security.cors.enabled || options.security?.cors?.enabled === true) {
|
|
1095
|
-
const corsOptions =
|
|
1096
|
-
typeof options.cors === 'object'
|
|
1097
|
-
? options.cors
|
|
1098
|
-
: this.config.security.cors
|
|
1099
|
-
? this.config.security.cors
|
|
1100
|
-
: {};
|
|
1101
|
-
this.use(middleware.cors(corsOptions));
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
// Helmet - check config enabled property OR options.security.helmet.enabled === true
|
|
1105
|
-
if (this.config.security.helmet.enabled || options.security?.helmet?.enabled === true) {
|
|
1106
|
-
this.use(middleware.helmet());
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
// Compression - check config enabled property OR options.performance.compression.enabled === true
|
|
1110
|
-
if (
|
|
1111
|
-
this.config.performance.compression.enabled ||
|
|
1112
|
-
options.performance?.compression?.enabled === true
|
|
1113
|
-
) {
|
|
1114
|
-
const compressionOptions =
|
|
1115
|
-
typeof options.compression === 'object'
|
|
1116
|
-
? options.compression
|
|
1117
|
-
: this.config.performance.compression
|
|
1118
|
-
? this.config.performance.compression
|
|
1119
|
-
: {};
|
|
1120
|
-
this.use(middleware.compression(compressionOptions));
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
// Body size limiting
|
|
1124
|
-
this.use(middleware.bodySize({ limit: '10mb' }));
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
// Enhanced auto-discovery initialization
|
|
1128
|
-
private async initializeAutoDiscovery(options: MoroOptions): Promise<void> {
|
|
1129
|
-
const { ModuleDiscovery } = await import('./core/modules/auto-discovery.js');
|
|
1130
|
-
|
|
1131
|
-
// Merge auto-discovery configuration
|
|
1132
|
-
const autoDiscoveryConfig = this.mergeAutoDiscoveryConfig(options);
|
|
1133
|
-
|
|
1134
|
-
if (!autoDiscoveryConfig.enabled) {
|
|
1135
|
-
return;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
this.moduleDiscovery = new ModuleDiscovery(process.cwd());
|
|
1139
|
-
|
|
1140
|
-
try {
|
|
1141
|
-
// Discover modules based on configuration
|
|
1142
|
-
const modules = await this.moduleDiscovery.discoverModulesAdvanced(autoDiscoveryConfig);
|
|
1143
|
-
|
|
1144
|
-
// Load modules based on strategy
|
|
1145
|
-
await this.loadDiscoveredModules(modules, autoDiscoveryConfig);
|
|
1146
|
-
|
|
1147
|
-
// Setup final module handler to run after user middleware (like auth)
|
|
1148
|
-
this.coreFramework.setupFinalModuleHandler();
|
|
1149
|
-
|
|
1150
|
-
// Setup file watching if enabled
|
|
1151
|
-
if (autoDiscoveryConfig.watchForChanges) {
|
|
1152
|
-
this.moduleDiscovery.watchModulesAdvanced(
|
|
1153
|
-
autoDiscoveryConfig,
|
|
1154
|
-
async (updatedModules: ModuleConfig[]) => {
|
|
1155
|
-
await this.handleModuleChanges(updatedModules);
|
|
1156
|
-
}
|
|
1157
|
-
);
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
this.logger.info(
|
|
1161
|
-
`Auto-discovery completed: ${modules.length} modules loaded`,
|
|
1162
|
-
'ModuleDiscovery'
|
|
1163
|
-
);
|
|
1164
|
-
} catch (error) {
|
|
1165
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1166
|
-
|
|
1167
|
-
if (autoDiscoveryConfig.failOnError) {
|
|
1168
|
-
throw new Error(`Module auto-discovery failed: ${errorMsg}`);
|
|
1169
|
-
} else {
|
|
1170
|
-
this.logger.warn(`Module auto-discovery failed: ${errorMsg}`, 'ModuleDiscovery');
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
// Merge auto-discovery configuration from multiple sources
|
|
1176
|
-
private mergeAutoDiscoveryConfig(options: MoroOptions) {
|
|
1177
|
-
const defaultConfig = this.config.modules.autoDiscovery;
|
|
1178
|
-
|
|
1179
|
-
// Handle legacy modulesPath option
|
|
1180
|
-
if (options.modulesPath && !options.autoDiscover) {
|
|
1181
|
-
return {
|
|
1182
|
-
...defaultConfig,
|
|
1183
|
-
paths: [options.modulesPath],
|
|
1184
|
-
};
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
// Handle boolean autoDiscover option
|
|
1188
|
-
if (typeof options.autoDiscover === 'boolean') {
|
|
1189
|
-
return {
|
|
1190
|
-
...defaultConfig,
|
|
1191
|
-
enabled: options.autoDiscover,
|
|
1192
|
-
};
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
// Handle object autoDiscover option
|
|
1196
|
-
if (typeof options.autoDiscover === 'object') {
|
|
1197
|
-
return {
|
|
1198
|
-
...defaultConfig,
|
|
1199
|
-
...options.autoDiscover,
|
|
1200
|
-
};
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
return defaultConfig;
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
// Load discovered modules based on strategy
|
|
1207
|
-
private async loadDiscoveredModules(
|
|
1208
|
-
modules: ModuleConfig[],
|
|
1209
|
-
config: ModuleDefaultsConfig['autoDiscovery']
|
|
1210
|
-
): Promise<void> {
|
|
1211
|
-
switch (config.loadingStrategy) {
|
|
1212
|
-
case 'eager':
|
|
1213
|
-
// Load all modules immediately
|
|
1214
|
-
for (const module of modules) {
|
|
1215
|
-
await this.loadModule(module);
|
|
1216
|
-
}
|
|
1217
|
-
break;
|
|
1218
|
-
|
|
1219
|
-
case 'lazy':
|
|
1220
|
-
// Register modules for lazy loading
|
|
1221
|
-
this.registerLazyModules(modules);
|
|
1222
|
-
break;
|
|
1223
|
-
|
|
1224
|
-
case 'conditional':
|
|
1225
|
-
// Load modules based on conditions
|
|
1226
|
-
await this.loadConditionalModules(modules);
|
|
1227
|
-
break;
|
|
1228
|
-
|
|
1229
|
-
default:
|
|
1230
|
-
// Default to eager loading
|
|
1231
|
-
for (const module of modules) {
|
|
1232
|
-
await this.loadModule(module);
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
// Register modules for lazy loading
|
|
1238
|
-
private registerLazyModules(modules: ModuleConfig[]): void {
|
|
1239
|
-
modules.forEach(module => {
|
|
1240
|
-
// Store module for lazy loading when first route is accessed
|
|
1241
|
-
this.lazyModules.set(module.name, module);
|
|
1242
|
-
|
|
1243
|
-
// Register placeholder routes that trigger lazy loading
|
|
1244
|
-
if (module.routes) {
|
|
1245
|
-
module.routes.forEach(route => {
|
|
1246
|
-
const basePath = `/api/v${module.version}/${module.name}`;
|
|
1247
|
-
const fullPath = `${basePath}${route.path}`;
|
|
1248
|
-
|
|
1249
|
-
// Note: Lazy loading will be implemented when route is accessed
|
|
1250
|
-
// For now, we'll store the module for later loading
|
|
1251
|
-
this.logger.debug(
|
|
1252
|
-
`Registered lazy route: ${route.method} ${fullPath}`,
|
|
1253
|
-
'ModuleDiscovery'
|
|
1254
|
-
);
|
|
1255
|
-
});
|
|
1256
|
-
}
|
|
1257
|
-
});
|
|
1258
|
-
|
|
1259
|
-
this.logger.info(`Registered ${modules.length} modules for lazy loading`, 'ModuleDiscovery');
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
// Load modules conditionally based on environment or configuration
|
|
1263
|
-
private async loadConditionalModules(modules: ModuleConfig[]): Promise<void> {
|
|
1264
|
-
for (const module of modules) {
|
|
1265
|
-
const shouldLoad = this.shouldLoadModule(module);
|
|
1266
|
-
|
|
1267
|
-
if (shouldLoad) {
|
|
1268
|
-
await this.loadModule(module);
|
|
1269
|
-
} else {
|
|
1270
|
-
this.logger.debug(`Skipping module ${module.name} due to conditions`, 'ModuleDiscovery');
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
// Determine if a module should be loaded based on conditions
|
|
1276
|
-
private shouldLoadModule(module: ModuleConfig): boolean {
|
|
1277
|
-
const moduleConfig = module.config as any;
|
|
1278
|
-
|
|
1279
|
-
// Check environment conditions
|
|
1280
|
-
if (moduleConfig?.conditions?.environment) {
|
|
1281
|
-
const requiredEnv = moduleConfig.conditions.environment;
|
|
1282
|
-
const currentEnv = process.env.NODE_ENV || 'development';
|
|
1283
|
-
|
|
1284
|
-
if (Array.isArray(requiredEnv)) {
|
|
1285
|
-
if (!requiredEnv.includes(currentEnv)) {
|
|
1286
|
-
return false;
|
|
1287
|
-
}
|
|
1288
|
-
} else if (requiredEnv !== currentEnv) {
|
|
1289
|
-
return false;
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
// Check feature flags
|
|
1294
|
-
if (moduleConfig?.conditions?.features) {
|
|
1295
|
-
const requiredFeatures = moduleConfig.conditions.features;
|
|
1296
|
-
|
|
1297
|
-
for (const feature of requiredFeatures) {
|
|
1298
|
-
if (!process.env[`FEATURE_${feature.toUpperCase()}`]) {
|
|
1299
|
-
return false;
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
// Check custom conditions
|
|
1305
|
-
if (moduleConfig?.conditions?.custom) {
|
|
1306
|
-
const customCondition = moduleConfig.conditions.custom;
|
|
1307
|
-
|
|
1308
|
-
if (typeof customCondition === 'function') {
|
|
1309
|
-
return customCondition();
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
return true;
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
// Handle module changes during development
|
|
1317
|
-
private async handleModuleChanges(modules: ModuleConfig[]): Promise<void> {
|
|
1318
|
-
this.logger.info('Module changes detected, reloading...', 'ModuleDiscovery');
|
|
1319
|
-
|
|
1320
|
-
// Unload existing modules (if supported)
|
|
1321
|
-
// For now, just log the change
|
|
1322
|
-
this.eventBus.emit('modules:changed', {
|
|
1323
|
-
modules: modules.map(m => ({ name: m.name, version: m.version })),
|
|
1324
|
-
timestamp: new Date(),
|
|
1325
|
-
});
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
// Legacy method for backward compatibility
|
|
1329
|
-
private autoDiscoverModules(modulesPath: string) {
|
|
1330
|
-
// Redirect to new system
|
|
1331
|
-
this.initializeAutoDiscovery({
|
|
1332
|
-
autoDiscover: {
|
|
1333
|
-
enabled: true,
|
|
1334
|
-
paths: [modulesPath],
|
|
1335
|
-
},
|
|
1336
|
-
});
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
private async importModule(modulePath: string): Promise<ModuleConfig> {
|
|
1340
|
-
const module = await import(modulePath);
|
|
1341
|
-
return module.default || module;
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
/**
|
|
1345
|
-
* Node.js Clustering Implementation
|
|
1346
|
-
* This clustering algorithm is based on published research and Node.js best practices.
|
|
1347
|
-
*
|
|
1348
|
-
* IPC (Inter-Process Communication) Considerations:
|
|
1349
|
-
* - Excessive workers create IPC bottlenecks (Source: BetterStack Node.js Guide)
|
|
1350
|
-
* - Round-robin scheduling provides better load distribution (Node.js Documentation)
|
|
1351
|
-
* - Message passing overhead increases significantly with worker count
|
|
1352
|
-
*
|
|
1353
|
-
* Memory Management:
|
|
1354
|
-
* - ~2GB per worker prevents memory pressure and GC overhead
|
|
1355
|
-
* - Conservative heap limits reduce memory fragmentation
|
|
1356
|
-
*
|
|
1357
|
-
* References:
|
|
1358
|
-
* - Node.js Cluster Documentation: https://nodejs.org/api/cluster.html
|
|
1359
|
-
* - BetterStack Node.js Clustering: https://betterstack.com/community/guides/scaling-nodejs/node-clustering/
|
|
1360
|
-
*/
|
|
1361
|
-
private clusterWorkers = new Map<number, any>();
|
|
1362
|
-
|
|
1363
|
-
private startWithClustering(port: number, host?: string, callback?: () => void): void {
|
|
1364
|
-
// Worker count calculation - respect user choice
|
|
1365
|
-
let workerCount = this.config.performance?.clustering?.workers || os.cpus().length;
|
|
1366
|
-
|
|
1367
|
-
// Only auto-optimize if user hasn't specified a number or set it to 'auto'
|
|
1368
|
-
if (workerCount === 'auto') {
|
|
1369
|
-
const cpuCount = os.cpus().length;
|
|
1370
|
-
const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);
|
|
1371
|
-
|
|
1372
|
-
// Get memory per worker from config - if not set by user, calculate dynamically
|
|
1373
|
-
let memoryPerWorkerGB = this.config.performance?.clustering?.memoryPerWorkerGB;
|
|
1374
|
-
|
|
1375
|
-
if (!memoryPerWorkerGB) {
|
|
1376
|
-
// Dynamic calculation: (Total RAM - 4GB headroom) / CPU cores
|
|
1377
|
-
const headroomGB = 4;
|
|
1378
|
-
memoryPerWorkerGB = Math.max(0.5, Math.floor((totalMemoryGB - headroomGB) / cpuCount));
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
// Conservative formula based on general guidelines:
|
|
1382
|
-
// - Don't exceed CPU cores
|
|
1383
|
-
// - Respect user's memory allocation preference
|
|
1384
|
-
// - Let the system resources determine the limit
|
|
1385
|
-
workerCount = Math.min(
|
|
1386
|
-
cpuCount, // Don't exceed CPU cores
|
|
1387
|
-
Math.floor(totalMemoryGB / memoryPerWorkerGB) // User-configurable memory per worker
|
|
1388
|
-
);
|
|
1389
|
-
|
|
1390
|
-
this.logger.info(
|
|
1391
|
-
`Auto-calculated worker count: ${workerCount} (CPU: ${cpuCount}, RAM: ${totalMemoryGB.toFixed(1)}GB, ${memoryPerWorkerGB}GB per worker)`,
|
|
1392
|
-
'Cluster'
|
|
1393
|
-
);
|
|
1394
|
-
} else if (typeof workerCount === 'number') {
|
|
1395
|
-
// User specified a number - respect their choice
|
|
1396
|
-
this.logger.info(`Using user-specified worker count: ${workerCount}`, 'Cluster');
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
if (cluster.isPrimary) {
|
|
1400
|
-
this.logger.info(`Starting ${workerCount} workers`, 'Cluster');
|
|
1401
|
-
|
|
1402
|
-
// Optimize cluster scheduling for high concurrency
|
|
1403
|
-
// Round-robin is the default on all platforms except Windows (Node.js docs)
|
|
1404
|
-
// Provides better load distribution than shared socket approach
|
|
1405
|
-
cluster.schedulingPolicy = cluster.SCHED_RR;
|
|
1406
|
-
|
|
1407
|
-
// Set cluster settings for better performance
|
|
1408
|
-
cluster.setupMaster({
|
|
1409
|
-
exec: process.argv[1] || process.execPath,
|
|
1410
|
-
args: process.argv.slice(2),
|
|
1411
|
-
silent: false,
|
|
1412
|
-
});
|
|
1413
|
-
|
|
1414
|
-
// IPC Optimization: Reduce communication overhead between master and workers
|
|
1415
|
-
// Research shows excessive IPC can create bottlenecks in clustered applications
|
|
1416
|
-
// (Source: BetterStack - Node.js Clustering Guide)
|
|
1417
|
-
process.env.NODE_CLUSTER_SCHED_POLICY = 'rr'; // Ensure round-robin
|
|
1418
|
-
process.env.NODE_DISABLE_COLORS = '1'; // Reduce IPC message size by disabling color codes
|
|
1419
|
-
|
|
1420
|
-
// Graceful shutdown handler
|
|
1421
|
-
const gracefulShutdown = () => {
|
|
1422
|
-
this.logger.info('Gracefully shutting down cluster...', 'Cluster');
|
|
1423
|
-
|
|
1424
|
-
// Clean up all workers
|
|
1425
|
-
for (const [pid, worker] of this.clusterWorkers) {
|
|
1426
|
-
worker.removeAllListeners();
|
|
1427
|
-
worker.kill('SIGTERM');
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
// Clean up cluster listeners
|
|
1431
|
-
cluster.removeAllListeners();
|
|
1432
|
-
process.exit(0);
|
|
1433
|
-
};
|
|
1434
|
-
|
|
1435
|
-
// Handle process signals for graceful shutdown
|
|
1436
|
-
process.on('SIGINT', gracefulShutdown);
|
|
1437
|
-
process.on('SIGTERM', gracefulShutdown);
|
|
1438
|
-
|
|
1439
|
-
// Fork workers with basic tracking
|
|
1440
|
-
for (let i = 0; i < workerCount; i++) {
|
|
1441
|
-
const worker = cluster.fork();
|
|
1442
|
-
this.clusterWorkers.set(worker.process.pid!, worker);
|
|
1443
|
-
this.logger.info(`Worker ${worker.process.pid} started`, 'Cluster');
|
|
1444
|
-
|
|
1445
|
-
// Handle individual worker messages
|
|
1446
|
-
worker.on('message', this.handleWorkerMessage.bind(this));
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
// Simple worker exit handling
|
|
1450
|
-
cluster.on('exit', (worker: any, code: number, signal: string) => {
|
|
1451
|
-
const pid = worker.process.pid;
|
|
1452
|
-
this.clusterWorkers.delete(pid);
|
|
1453
|
-
|
|
1454
|
-
if (code !== 0 && !worker.exitedAfterDisconnect) {
|
|
1455
|
-
this.logger.warn(
|
|
1456
|
-
`Worker ${pid} died unexpectedly (${signal || code}). Restarting...`,
|
|
1457
|
-
'Cluster'
|
|
1458
|
-
);
|
|
1459
|
-
|
|
1460
|
-
// Simple restart
|
|
1461
|
-
const newWorker = cluster.fork();
|
|
1462
|
-
this.clusterWorkers.set(newWorker.process.pid!, newWorker);
|
|
1463
|
-
this.logger.info(`Worker ${newWorker.process.pid} restarted`, 'Cluster');
|
|
1464
|
-
}
|
|
1465
|
-
});
|
|
1466
|
-
|
|
1467
|
-
// Master process callback
|
|
1468
|
-
if (callback) callback();
|
|
1469
|
-
} else {
|
|
1470
|
-
// Worker process - start the actual server with proper cleanup
|
|
1471
|
-
this.logger.info(`Worker ${process.pid} initializing`, 'Worker');
|
|
1472
|
-
|
|
1473
|
-
// Worker-specific optimizations for high concurrency
|
|
1474
|
-
process.env.UV_THREADPOOL_SIZE = '64';
|
|
1475
|
-
|
|
1476
|
-
// Reduce logging contention in workers (major bottleneck)
|
|
1477
|
-
// Multiple workers writing to same log files creates I/O contention
|
|
1478
|
-
// ONLY reduce log level if user didn't explicitly set one
|
|
1479
|
-
if (!this.userSetLogger) {
|
|
1480
|
-
// Workers log less frequently to reduce I/O contention (only if not explicitly configured)
|
|
1481
|
-
applyLoggingConfiguration(undefined, { level: 'warn' }); // Only warnings and errors
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
// Research-based memory optimization for workers
|
|
1485
|
-
const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);
|
|
1486
|
-
const workerCount = Object.keys(cluster.workers || {}).length || 1;
|
|
1487
|
-
|
|
1488
|
-
// Conservative memory allocation
|
|
1489
|
-
const heapSizePerWorkerMB = Math.min(
|
|
1490
|
-
Math.floor(((totalMemoryGB * 1024) / workerCount) * 0.8), // 80% of available memory
|
|
1491
|
-
1536 // Cap at 1.5GB (GC efficiency threshold from research)
|
|
1492
|
-
);
|
|
1493
|
-
|
|
1494
|
-
process.env.NODE_OPTIONS = `--max-old-space-size=${heapSizePerWorkerMB}`;
|
|
1495
|
-
|
|
1496
|
-
this.logger.debug(
|
|
1497
|
-
`Worker memory allocated: ${heapSizePerWorkerMB}MB heap (${workerCount} workers, ${totalMemoryGB.toFixed(1)}GB total)`,
|
|
1498
|
-
'Worker'
|
|
1499
|
-
);
|
|
1500
|
-
|
|
1501
|
-
// Optimize V8 flags for better performance
|
|
1502
|
-
if (process.env.NODE_ENV === 'production') {
|
|
1503
|
-
// Aggressive V8 optimizations for maximum performance
|
|
1504
|
-
const v8Flags = [
|
|
1505
|
-
'--optimize-for-size', // Trade memory for speed
|
|
1506
|
-
'--always-opt', // Always optimize functions
|
|
1507
|
-
'--turbo-fast-api-calls', // Optimize API calls
|
|
1508
|
-
'--turbo-escape-analysis', // Escape analysis optimization
|
|
1509
|
-
'--turbo-inline-api-calls', // Inline API calls
|
|
1510
|
-
'--max-old-space-size=1024', // Limit memory to prevent GC pressure
|
|
1511
|
-
];
|
|
1512
|
-
process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS || '') + ' ' + v8Flags.join(' ');
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
// Optimize garbage collection for workers
|
|
1516
|
-
// eslint-disable-next-line no-undef
|
|
1517
|
-
if ((global as any).gc) {
|
|
1518
|
-
setInterval(() => {
|
|
1519
|
-
// eslint-disable-next-line no-undef
|
|
1520
|
-
if ((global as any).gc) (global as any).gc();
|
|
1521
|
-
}, 60000); // GC every 60 seconds (less frequent)
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
// Graceful shutdown for worker
|
|
1525
|
-
const workerShutdown = () => {
|
|
1526
|
-
this.logger.info(`Worker ${process.pid} shutting down gracefully...`, 'Worker');
|
|
1527
|
-
|
|
1528
|
-
// Clean up event listeners
|
|
1529
|
-
this.eventBus.removeAllListeners();
|
|
1530
|
-
this.removeAllListeners();
|
|
1531
|
-
|
|
1532
|
-
// Close server gracefully
|
|
1533
|
-
if (this.coreFramework) {
|
|
1534
|
-
const server = (this.coreFramework as any).server;
|
|
1535
|
-
if (server) {
|
|
1536
|
-
server.close(() => {
|
|
1537
|
-
process.exit(0);
|
|
1538
|
-
});
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
};
|
|
1542
|
-
|
|
1543
|
-
// Handle worker shutdown signals
|
|
1544
|
-
process.on('SIGTERM', workerShutdown);
|
|
1545
|
-
process.on('SIGINT', workerShutdown);
|
|
1546
|
-
|
|
1547
|
-
// Continue with normal server startup for this worker
|
|
1548
|
-
this.eventBus.emit('server:starting', {
|
|
1549
|
-
port,
|
|
1550
|
-
runtime: this.runtimeType,
|
|
1551
|
-
worker: process.pid,
|
|
1552
|
-
});
|
|
1553
|
-
|
|
1554
|
-
// Add documentation middleware first (if enabled)
|
|
1555
|
-
try {
|
|
1556
|
-
const docsMiddleware = this.documentation.getDocsMiddleware();
|
|
1557
|
-
this.coreFramework.addMiddleware(docsMiddleware);
|
|
1558
|
-
} catch (error) {
|
|
1559
|
-
// Documentation not enabled, that's fine
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
// Add unified routing middleware (handles both chainable and direct routes)
|
|
1563
|
-
// Optimized: call router without extra async wrapper when possible
|
|
1564
|
-
this.coreFramework.addMiddleware((req: HttpRequest, res: HttpResponse, next: () => void) => {
|
|
1565
|
-
// Try unified router first (handles all route types)
|
|
1566
|
-
const handled = this.unifiedRouter.handleRequest(req, res);
|
|
1567
|
-
|
|
1568
|
-
// Check if it's a promise (async route) or sync
|
|
1569
|
-
if (handled && typeof (handled as any).then === 'function') {
|
|
1570
|
-
// Async - await the result
|
|
1571
|
-
(handled as Promise<boolean>)
|
|
1572
|
-
.then(isHandled => {
|
|
1573
|
-
if (!isHandled) {
|
|
1574
|
-
next(); // Fall back to legacy routes if any
|
|
1575
|
-
}
|
|
1576
|
-
})
|
|
1577
|
-
.catch(() => next());
|
|
1578
|
-
} else {
|
|
1579
|
-
// Sync - check immediately
|
|
1580
|
-
if (!(handled as boolean)) {
|
|
1581
|
-
next();
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
});
|
|
1585
|
-
|
|
1586
|
-
// Register legacy direct routes with the HTTP server (for backward compatibility)
|
|
1587
|
-
if (this.routes.length > 0) {
|
|
1588
|
-
this.registerDirectRoutes();
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
|
-
const workerCallback = () => {
|
|
1592
|
-
const displayHost = host || 'localhost';
|
|
1593
|
-
this.logger.info(`Worker ${process.pid} ready on ${displayHost}:${port}`, 'Worker');
|
|
1594
|
-
this.eventBus.emit('server:started', {
|
|
1595
|
-
port,
|
|
1596
|
-
runtime: this.runtimeType,
|
|
1597
|
-
worker: process.pid,
|
|
1598
|
-
});
|
|
1599
|
-
};
|
|
1600
|
-
|
|
1601
|
-
// Ensure WebSocket setup is complete before starting worker
|
|
1602
|
-
this.processQueuedWebSocketRegistrations()
|
|
1603
|
-
.then(() => {
|
|
1604
|
-
if (host) {
|
|
1605
|
-
this.coreFramework.listen(port, host, workerCallback);
|
|
1606
|
-
} else {
|
|
1607
|
-
this.coreFramework.listen(port, workerCallback);
|
|
1608
|
-
}
|
|
1609
|
-
})
|
|
1610
|
-
.catch(error => {
|
|
1611
|
-
this.logger.error('WebSocket initialization failed in worker', 'Worker', {
|
|
1612
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1613
|
-
});
|
|
1614
|
-
// For WebSocket failures with queued registrations, error will propagate
|
|
1615
|
-
if (
|
|
1616
|
-
error instanceof Error &&
|
|
1617
|
-
error.message.includes('WebSocket features require a WebSocket adapter')
|
|
1618
|
-
) {
|
|
1619
|
-
throw error;
|
|
1620
|
-
}
|
|
1621
|
-
// Start anyway for other errors
|
|
1622
|
-
if (host) {
|
|
1623
|
-
this.coreFramework.listen(port, host, workerCallback);
|
|
1624
|
-
} else {
|
|
1625
|
-
this.coreFramework.listen(port, workerCallback);
|
|
1626
|
-
}
|
|
1627
|
-
});
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
|
-
// Simple worker message handler
|
|
1632
|
-
private handleWorkerMessage(message: any): void {
|
|
1633
|
-
// Handle inter-worker communication if needed
|
|
1634
|
-
if (message.type === 'health-check') {
|
|
1635
|
-
// Worker health check response
|
|
1636
|
-
return;
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
|
-
// Log other worker messages
|
|
1640
|
-
this.logger.debug(`Worker message: ${JSON.stringify(message)}`, 'Cluster');
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
/**
|
|
1644
|
-
* Gracefully close the application and clean up resources
|
|
1645
|
-
* This should be called in tests and during shutdown
|
|
1646
|
-
*/
|
|
1647
|
-
async close(): Promise<void> {
|
|
1648
|
-
this.logger.debug('Closing Moro application...');
|
|
1649
|
-
|
|
1650
|
-
// Flush logger buffer before shutdown
|
|
1651
|
-
try {
|
|
1652
|
-
// Use flushBuffer for immediate synchronous flush
|
|
1653
|
-
this.logger.flushBuffer();
|
|
1654
|
-
} catch (error) {
|
|
1655
|
-
// Ignore flush errors during shutdown
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
// Close the core framework with timeout
|
|
1659
|
-
if (this.coreFramework && (this.coreFramework as any).httpServer) {
|
|
1660
|
-
try {
|
|
1661
|
-
await Promise.race([
|
|
1662
|
-
new Promise<void>(resolve => {
|
|
1663
|
-
(this.coreFramework as any).httpServer.close(() => {
|
|
1664
|
-
resolve();
|
|
1665
|
-
});
|
|
1666
|
-
}),
|
|
1667
|
-
new Promise<void>(resolve => setTimeout(resolve, 2000)), // 2 second timeout
|
|
1668
|
-
]);
|
|
1669
|
-
} catch (error) {
|
|
1670
|
-
// Force close if graceful close fails
|
|
1671
|
-
this.logger.warn('Force closing HTTP server due to timeout');
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
|
|
1675
|
-
// Clean up module discovery watchers
|
|
1676
|
-
if (this.moduleDiscovery && typeof this.moduleDiscovery.cleanup === 'function') {
|
|
1677
|
-
try {
|
|
1678
|
-
this.moduleDiscovery.cleanup();
|
|
1679
|
-
} catch (error) {
|
|
1680
|
-
// Ignore cleanup errors
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
// Clean up event listeners
|
|
1685
|
-
try {
|
|
1686
|
-
this.eventBus.removeAllListeners();
|
|
1687
|
-
this.removeAllListeners();
|
|
1688
|
-
} catch (error) {
|
|
1689
|
-
// Ignore cleanup errors
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
this.logger.debug('Moro application closed successfully');
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
|
|
1696
|
-
// Export convenience function
|
|
1697
|
-
export function createApp(options?: MoroOptions): Moro {
|
|
1698
|
-
return new Moro(options);
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
|
-
// Runtime-specific convenience functions
|
|
1702
|
-
export function createAppNode(options?: Omit<MoroOptions, 'runtime'>): Moro {
|
|
1703
|
-
return new Moro({
|
|
1704
|
-
...options,
|
|
1705
|
-
runtime: { type: 'node' },
|
|
1706
|
-
});
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
export function createAppEdge(options?: Omit<MoroOptions, 'runtime'>): Moro {
|
|
1710
|
-
return new Moro({
|
|
1711
|
-
...options,
|
|
1712
|
-
runtime: { type: 'vercel-edge' },
|
|
1713
|
-
});
|
|
1714
|
-
}
|
|
1715
|
-
|
|
1716
|
-
export function createAppLambda(options?: Omit<MoroOptions, 'runtime'>): Moro {
|
|
1717
|
-
return new Moro({
|
|
1718
|
-
...options,
|
|
1719
|
-
runtime: { type: 'aws-lambda' },
|
|
1720
|
-
});
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
export function createAppWorker(options?: Omit<MoroOptions, 'runtime'>): Moro {
|
|
1724
|
-
return new Moro({
|
|
1725
|
-
...options,
|
|
1726
|
-
runtime: { type: 'cloudflare-workers' },
|
|
1727
|
-
});
|
|
1728
|
-
}
|