@morojs/moro 1.6.1 → 1.6.2
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/README.md +74 -256
- package/dist/core/auth/morojs-adapter.js +20 -20
- package/dist/core/auth/morojs-adapter.js.map +1 -1
- package/dist/core/config/config-manager.d.ts +44 -0
- package/dist/core/config/config-manager.js +104 -0
- package/dist/core/config/config-manager.js.map +1 -0
- package/dist/core/config/config-sources.d.ts +21 -0
- package/dist/core/config/config-sources.js +503 -0
- package/dist/core/config/config-sources.js.map +1 -0
- package/dist/core/config/config-validator.d.ts +21 -0
- package/dist/core/config/config-validator.js +791 -0
- package/dist/core/config/config-validator.js.map +1 -0
- package/dist/core/config/file-loader.d.ts +1 -6
- package/dist/core/config/file-loader.js +21 -249
- package/dist/core/config/file-loader.js.map +1 -1
- package/dist/core/config/index.d.ts +41 -12
- package/dist/core/config/index.js +65 -54
- package/dist/core/config/index.js.map +1 -1
- package/dist/core/config/schema.d.ts +2 -2
- package/dist/core/config/schema.js +55 -44
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/config/utils.d.ts +10 -3
- package/dist/core/config/utils.js +31 -58
- package/dist/core/config/utils.js.map +1 -1
- package/dist/core/database/adapters/drizzle.d.ts +1 -1
- package/dist/core/database/adapters/drizzle.js +18 -11
- package/dist/core/database/adapters/drizzle.js.map +1 -1
- package/dist/core/database/adapters/index.d.ts +7 -7
- package/dist/core/database/adapters/index.js +19 -29
- package/dist/core/database/adapters/index.js.map +1 -1
- package/dist/core/database/adapters/mongodb.d.ts +13 -1
- package/dist/core/database/adapters/mongodb.js +46 -10
- package/dist/core/database/adapters/mongodb.js.map +1 -1
- package/dist/core/database/adapters/mysql.d.ts +14 -1
- package/dist/core/database/adapters/mysql.js +19 -9
- package/dist/core/database/adapters/mysql.js.map +1 -1
- package/dist/core/database/adapters/postgresql.d.ts +12 -2
- package/dist/core/database/adapters/postgresql.js +19 -9
- package/dist/core/database/adapters/postgresql.js.map +1 -1
- package/dist/core/database/adapters/redis.d.ts +12 -1
- package/dist/core/database/adapters/redis.js +48 -13
- package/dist/core/database/adapters/redis.js.map +1 -1
- package/dist/core/database/adapters/sqlite.d.ts +3 -1
- package/dist/core/database/adapters/sqlite.js +19 -8
- package/dist/core/database/adapters/sqlite.js.map +1 -1
- package/dist/core/database/index.d.ts +2 -2
- package/dist/core/database/index.js +2 -18
- package/dist/core/database/index.js.map +1 -1
- package/dist/core/docs/index.d.ts +9 -9
- package/dist/core/docs/index.js +14 -35
- package/dist/core/docs/index.js.map +1 -1
- package/dist/core/docs/openapi-generator.d.ts +2 -2
- package/dist/core/docs/openapi-generator.js +11 -16
- package/dist/core/docs/openapi-generator.js.map +1 -1
- package/dist/core/docs/schema-to-openapi.d.ts +2 -2
- package/dist/core/docs/schema-to-openapi.js +5 -11
- package/dist/core/docs/schema-to-openapi.js.map +1 -1
- package/dist/core/docs/simple-docs.d.ts +1 -1
- package/dist/core/docs/simple-docs.js +4 -9
- package/dist/core/docs/simple-docs.js.map +1 -1
- package/dist/core/docs/swagger-ui.d.ts +2 -2
- package/dist/core/docs/swagger-ui.js +26 -29
- package/dist/core/docs/swagger-ui.js.map +1 -1
- package/dist/core/docs/zod-to-openapi.js +31 -28
- package/dist/core/docs/zod-to-openapi.js.map +1 -1
- package/dist/core/events/event-bus.d.ts +1 -1
- package/dist/core/events/event-bus.js +7 -11
- package/dist/core/events/event-bus.js.map +1 -1
- package/dist/core/events/index.d.ts +2 -2
- package/dist/core/events/index.js +1 -5
- package/dist/core/events/index.js.map +1 -1
- package/dist/core/framework.d.ts +20 -13
- package/dist/core/framework.js +285 -102
- package/dist/core/framework.js.map +1 -1
- package/dist/core/http/http-server.d.ts +59 -7
- package/dist/core/http/http-server.js +190 -176
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/index.d.ts +4 -3
- package/dist/core/http/index.js +3 -8
- package/dist/core/http/index.js.map +1 -1
- package/dist/core/http/uws-http-server.d.ts +46 -0
- package/dist/core/http/uws-http-server.js +523 -0
- package/dist/core/http/uws-http-server.js.map +1 -0
- package/dist/core/logger/filters.d.ts +1 -1
- package/dist/core/logger/filters.js +20 -23
- package/dist/core/logger/filters.js.map +1 -1
- package/dist/core/logger/index.d.ts +3 -3
- package/dist/core/logger/index.js +2 -24
- package/dist/core/logger/index.js.map +1 -1
- package/dist/core/logger/logger.d.ts +30 -14
- package/dist/core/logger/logger.js +398 -223
- package/dist/core/logger/logger.js.map +1 -1
- package/dist/core/logger/outputs.d.ts +1 -1
- package/dist/core/logger/outputs.js +8 -17
- package/dist/core/logger/outputs.js.map +1 -1
- package/dist/core/middleware/built-in/adapters/cache/file.d.ts +1 -1
- package/dist/core/middleware/built-in/adapters/cache/file.js +10 -47
- package/dist/core/middleware/built-in/adapters/cache/file.js.map +1 -1
- package/dist/core/middleware/built-in/adapters/cache/index.d.ts +4 -4
- package/dist/core/middleware/built-in/adapters/cache/index.js +10 -17
- package/dist/core/middleware/built-in/adapters/cache/index.js.map +1 -1
- package/dist/core/middleware/built-in/adapters/cache/memory.d.ts +1 -1
- package/dist/core/middleware/built-in/adapters/cache/memory.js +3 -7
- package/dist/core/middleware/built-in/adapters/cache/memory.js.map +1 -1
- package/dist/core/middleware/built-in/adapters/cache/redis.d.ts +3 -1
- package/dist/core/middleware/built-in/adapters/cache/redis.js +11 -9
- package/dist/core/middleware/built-in/adapters/cache/redis.js.map +1 -1
- package/dist/core/middleware/built-in/adapters/cdn/azure.d.ts +1 -1
- package/dist/core/middleware/built-in/adapters/cdn/azure.js +3 -7
- package/dist/core/middleware/built-in/adapters/cdn/azure.js.map +1 -1
- package/dist/core/middleware/built-in/adapters/cdn/cloudflare.d.ts +1 -1
- package/dist/core/middleware/built-in/adapters/cdn/cloudflare.js +3 -7
- package/dist/core/middleware/built-in/adapters/cdn/cloudflare.js.map +1 -1
- package/dist/core/middleware/built-in/adapters/cdn/cloudfront.d.ts +3 -1
- package/dist/core/middleware/built-in/adapters/cdn/cloudfront.js +12 -10
- package/dist/core/middleware/built-in/adapters/cdn/cloudfront.js.map +1 -1
- package/dist/core/middleware/built-in/adapters/cdn/index.d.ts +4 -4
- package/dist/core/middleware/built-in/adapters/cdn/index.js +10 -17
- package/dist/core/middleware/built-in/adapters/cdn/index.js.map +1 -1
- package/dist/core/middleware/built-in/adapters/index.d.ts +4 -4
- package/dist/core/middleware/built-in/adapters/index.js +4 -23
- package/dist/core/middleware/built-in/adapters/index.js.map +1 -1
- package/dist/core/middleware/built-in/auth-helpers.js +11 -22
- package/dist/core/middleware/built-in/auth-helpers.js.map +1 -1
- package/dist/core/middleware/built-in/auth-providers.d.ts +1 -1
- package/dist/core/middleware/built-in/auth-providers.js +4 -9
- package/dist/core/middleware/built-in/auth-providers.js.map +1 -1
- package/dist/core/middleware/built-in/auth.d.ts +2 -2
- package/dist/core/middleware/built-in/auth.js +93 -26
- package/dist/core/middleware/built-in/auth.js.map +1 -1
- package/dist/core/middleware/built-in/cache.d.ts +2 -2
- package/dist/core/middleware/built-in/cache.js +11 -12
- package/dist/core/middleware/built-in/cache.js.map +1 -1
- package/dist/core/middleware/built-in/cdn.d.ts +2 -2
- package/dist/core/middleware/built-in/cdn.js +5 -9
- package/dist/core/middleware/built-in/cdn.js.map +1 -1
- package/dist/core/middleware/built-in/cookie.d.ts +1 -1
- package/dist/core/middleware/built-in/cookie.js +3 -7
- package/dist/core/middleware/built-in/cookie.js.map +1 -1
- package/dist/core/middleware/built-in/cors.d.ts +1 -1
- package/dist/core/middleware/built-in/cors.js +3 -7
- package/dist/core/middleware/built-in/cors.js.map +1 -1
- package/dist/core/middleware/built-in/csp.d.ts +1 -1
- package/dist/core/middleware/built-in/csp.js +5 -8
- package/dist/core/middleware/built-in/csp.js.map +1 -1
- package/dist/core/middleware/built-in/csrf.d.ts +1 -1
- package/dist/core/middleware/built-in/csrf.js +5 -8
- package/dist/core/middleware/built-in/csrf.js.map +1 -1
- package/dist/core/middleware/built-in/error-tracker.js +3 -7
- package/dist/core/middleware/built-in/error-tracker.js.map +1 -1
- package/dist/core/middleware/built-in/index.d.ts +28 -27
- package/dist/core/middleware/built-in/index.js +48 -78
- package/dist/core/middleware/built-in/index.js.map +1 -1
- package/dist/core/middleware/built-in/jwt-helpers.d.ts +118 -0
- package/dist/core/middleware/built-in/jwt-helpers.js +218 -0
- package/dist/core/middleware/built-in/jwt-helpers.js.map +1 -0
- package/dist/core/middleware/built-in/performance-monitor.js +3 -7
- package/dist/core/middleware/built-in/performance-monitor.js.map +1 -1
- package/dist/core/middleware/built-in/rate-limit.d.ts +1 -1
- package/dist/core/middleware/built-in/rate-limit.js +3 -7
- package/dist/core/middleware/built-in/rate-limit.js.map +1 -1
- package/dist/core/middleware/built-in/request-logger.js +5 -8
- package/dist/core/middleware/built-in/request-logger.js.map +1 -1
- package/dist/core/middleware/built-in/session.d.ts +2 -2
- package/dist/core/middleware/built-in/session.js +11 -15
- package/dist/core/middleware/built-in/session.js.map +1 -1
- package/dist/core/middleware/built-in/sse.d.ts +1 -1
- package/dist/core/middleware/built-in/sse.js +12 -14
- package/dist/core/middleware/built-in/sse.js.map +1 -1
- package/dist/core/middleware/built-in/validation.d.ts +1 -1
- package/dist/core/middleware/built-in/validation.js +3 -7
- package/dist/core/middleware/built-in/validation.js.map +1 -1
- package/dist/core/middleware/index.d.ts +4 -4
- package/dist/core/middleware/index.js +8 -28
- package/dist/core/middleware/index.js.map +1 -1
- package/dist/core/modules/auto-discovery.d.ts +19 -2
- package/dist/core/modules/auto-discovery.js +391 -74
- package/dist/core/modules/auto-discovery.js.map +1 -1
- package/dist/core/modules/index.d.ts +2 -2
- package/dist/core/modules/index.js +2 -9
- package/dist/core/modules/index.js.map +1 -1
- package/dist/core/modules/modules.d.ts +3 -3
- package/dist/core/modules/modules.js +23 -54
- package/dist/core/modules/modules.js.map +1 -1
- package/dist/core/networking/adapters/index.d.ts +4 -3
- package/dist/core/networking/adapters/index.js +3 -7
- package/dist/core/networking/adapters/index.js.map +1 -1
- package/dist/core/networking/adapters/socketio-adapter.d.ts +1 -1
- package/dist/core/networking/adapters/socketio-adapter.js +5 -40
- package/dist/core/networking/adapters/socketio-adapter.js.map +1 -1
- package/dist/core/networking/adapters/uws-adapter.d.ts +44 -0
- package/dist/core/networking/adapters/uws-adapter.js +513 -0
- package/dist/core/networking/adapters/uws-adapter.js.map +1 -0
- package/dist/core/networking/adapters/ws-adapter.d.ts +2 -2
- package/dist/core/networking/adapters/ws-adapter.js +8 -43
- package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
- package/dist/core/networking/index.d.ts +3 -2
- package/dist/core/networking/index.js +2 -7
- package/dist/core/networking/index.js.map +1 -1
- package/dist/core/networking/service-discovery.js +8 -12
- package/dist/core/networking/service-discovery.js.map +1 -1
- package/dist/core/networking/websocket-adapter.js +1 -2
- package/dist/core/networking/websocket-adapter.js.map +1 -1
- package/dist/core/networking/websocket-manager.d.ts +3 -3
- package/dist/core/networking/websocket-manager.js +9 -11
- package/dist/core/networking/websocket-manager.js.map +1 -1
- package/dist/core/pooling/object-pool-manager.d.ts +140 -0
- package/dist/core/pooling/object-pool-manager.js +502 -0
- package/dist/core/pooling/object-pool-manager.js.map +1 -0
- package/dist/core/routing/app-integration.d.ts +14 -12
- package/dist/core/routing/app-integration.js +49 -85
- package/dist/core/routing/app-integration.js.map +1 -1
- package/dist/core/routing/index.d.ts +17 -11
- package/dist/core/routing/index.js +48 -237
- package/dist/core/routing/index.js.map +1 -1
- package/dist/core/routing/path-matcher.d.ts +67 -0
- package/dist/core/routing/path-matcher.js +182 -0
- package/dist/core/routing/path-matcher.js.map +1 -0
- package/dist/core/routing/router.d.ts +38 -0
- package/dist/core/routing/router.js +68 -0
- package/dist/core/routing/router.js.map +1 -0
- package/dist/core/routing/unified-router.d.ts +148 -0
- package/dist/core/routing/unified-router.js +684 -0
- package/dist/core/routing/unified-router.js.map +1 -0
- package/dist/core/runtime/aws-lambda-adapter.d.ts +3 -3
- package/dist/core/runtime/aws-lambda-adapter.js +2 -6
- package/dist/core/runtime/aws-lambda-adapter.js.map +1 -1
- package/dist/core/runtime/base-adapter.d.ts +2 -2
- package/dist/core/runtime/base-adapter.js +3 -7
- package/dist/core/runtime/base-adapter.js.map +1 -1
- package/dist/core/runtime/cloudflare-workers-adapter.d.ts +3 -3
- package/dist/core/runtime/cloudflare-workers-adapter.js +2 -6
- package/dist/core/runtime/cloudflare-workers-adapter.js.map +1 -1
- package/dist/core/runtime/index.d.ts +12 -12
- package/dist/core/runtime/index.js +22 -35
- package/dist/core/runtime/index.js.map +1 -1
- package/dist/core/runtime/node-adapter.d.ts +4 -4
- package/dist/core/runtime/node-adapter.js +18 -49
- package/dist/core/runtime/node-adapter.js.map +1 -1
- package/dist/core/runtime/vercel-edge-adapter.d.ts +3 -3
- package/dist/core/runtime/vercel-edge-adapter.js +2 -6
- package/dist/core/runtime/vercel-edge-adapter.js.map +1 -1
- package/dist/core/utilities/circuit-breaker.js +1 -5
- package/dist/core/utilities/circuit-breaker.js.map +1 -1
- package/dist/core/utilities/container.js +12 -22
- package/dist/core/utilities/container.js.map +1 -1
- package/dist/core/utilities/hooks.d.ts +2 -2
- package/dist/core/utilities/hooks.js +7 -12
- package/dist/core/utilities/hooks.js.map +1 -1
- package/dist/core/utilities/index.d.ts +5 -4
- package/dist/core/utilities/index.js +5 -19
- package/dist/core/utilities/index.js.map +1 -1
- package/dist/core/utilities/package-utils.d.ts +38 -0
- package/dist/core/utilities/package-utils.js +57 -0
- package/dist/core/utilities/package-utils.js.map +1 -0
- package/dist/core/validation/adapters.d.ts +1 -1
- package/dist/core/validation/adapters.js +15 -26
- package/dist/core/validation/adapters.js.map +1 -1
- package/dist/core/validation/index.d.ts +6 -4
- package/dist/core/validation/index.js +57 -28
- package/dist/core/validation/index.js.map +1 -1
- package/dist/core/validation/schema-interface.js +3 -9
- package/dist/core/validation/schema-interface.js.map +1 -1
- package/dist/index.d.ts +51 -52
- package/dist/index.js +23 -132
- package/dist/index.js.map +1 -1
- package/dist/moro.d.ts +70 -16
- package/dist/moro.js +658 -271
- package/dist/moro.js.map +1 -1
- package/dist/types/auth.js +3 -9
- package/dist/types/auth.js.map +1 -1
- package/dist/types/cache.js +1 -2
- package/dist/types/cdn.js +1 -2
- package/dist/types/config.d.ts +73 -2
- package/dist/types/config.js +1 -2
- package/dist/types/config.js.map +1 -1
- package/dist/types/core.d.ts +36 -42
- package/dist/types/core.js +1 -2
- package/dist/types/database.js +1 -2
- package/dist/types/discovery.js +1 -2
- package/dist/types/events.js +1 -2
- package/dist/types/hooks.d.ts +1 -1
- package/dist/types/hooks.js +1 -2
- package/dist/types/http.d.ts +16 -1
- package/dist/types/http.js +1 -2
- package/dist/types/logger.d.ts +7 -0
- package/dist/types/logger.js +1 -2
- package/dist/types/module.d.ts +11 -0
- package/dist/types/module.js +1 -2
- package/dist/types/runtime.d.ts +1 -1
- package/dist/types/runtime.js +1 -2
- package/dist/types/session.js +1 -2
- package/jest.config.mjs +41 -0
- package/package.json +19 -52
- package/src/core/auth/morojs-adapter.ts +18 -13
- package/src/core/config/config-manager.ts +133 -0
- package/src/core/config/config-sources.ts +600 -0
- package/src/core/config/config-validator.ts +1116 -0
- package/src/core/config/file-loader.ts +16 -273
- package/src/core/config/index.ts +83 -34
- package/src/core/config/schema.ts +47 -33
- package/src/core/config/utils.ts +24 -31
- package/src/core/database/README.md +26 -16
- package/src/core/database/adapters/drizzle.ts +18 -6
- package/src/core/database/adapters/index.ts +13 -13
- package/src/core/database/adapters/mongodb.ts +53 -5
- package/src/core/database/adapters/mysql.ts +32 -4
- package/src/core/database/adapters/postgresql.ts +30 -5
- package/src/core/database/adapters/redis.ts +61 -8
- package/src/core/database/adapters/sqlite.ts +19 -3
- package/src/core/database/index.ts +2 -2
- package/src/core/docs/index.ts +8 -8
- package/src/core/docs/openapi-generator.ts +4 -4
- package/src/core/docs/schema-to-openapi.ts +3 -6
- package/src/core/docs/simple-docs.ts +2 -2
- package/src/core/docs/swagger-ui.ts +19 -16
- package/src/core/docs/zod-to-openapi.ts +34 -34
- package/src/core/events/event-bus.ts +3 -3
- package/src/core/events/index.ts +2 -2
- package/src/core/framework.ts +320 -71
- package/src/core/http/http-server.ts +203 -143
- package/src/core/http/index.ts +4 -3
- package/src/core/http/uws-http-server.ts +591 -0
- package/src/core/logger/filters.ts +13 -5
- package/src/core/logger/index.ts +4 -3
- package/src/core/logger/logger.ts +435 -216
- package/src/core/logger/outputs.ts +1 -3
- package/src/core/middleware/built-in/adapters/cache/file.ts +3 -3
- package/src/core/middleware/built-in/adapters/cache/index.ts +7 -7
- package/src/core/middleware/built-in/adapters/cache/memory.ts +2 -2
- package/src/core/middleware/built-in/adapters/cache/redis.ts +18 -4
- package/src/core/middleware/built-in/adapters/cdn/azure.ts +2 -2
- package/src/core/middleware/built-in/adapters/cdn/cloudflare.ts +2 -2
- package/src/core/middleware/built-in/adapters/cdn/cloudfront.ts +16 -5
- package/src/core/middleware/built-in/adapters/cdn/index.ts +7 -7
- package/src/core/middleware/built-in/adapters/index.ts +4 -4
- package/src/core/middleware/built-in/auth-helpers.ts +1 -1
- package/src/core/middleware/built-in/auth-providers.ts +1 -1
- package/src/core/middleware/built-in/auth.ts +102 -21
- package/src/core/middleware/built-in/cache.ts +8 -6
- package/src/core/middleware/built-in/cdn.ts +4 -4
- package/src/core/middleware/built-in/cookie.ts +2 -2
- package/src/core/middleware/built-in/cors.ts +2 -2
- package/src/core/middleware/built-in/csp.ts +3 -3
- package/src/core/middleware/built-in/csrf.ts +3 -3
- package/src/core/middleware/built-in/error-tracker.ts +1 -1
- package/src/core/middleware/built-in/index.ts +38 -30
- package/src/core/middleware/built-in/jwt-helpers.ts +243 -0
- package/src/core/middleware/built-in/performance-monitor.ts +1 -1
- package/src/core/middleware/built-in/rate-limit.ts +2 -2
- package/src/core/middleware/built-in/request-logger.ts +3 -1
- package/src/core/middleware/built-in/session.ts +7 -8
- package/src/core/middleware/built-in/sse.ts +11 -9
- package/src/core/middleware/built-in/validation.ts +2 -2
- package/src/core/middleware/index.ts +6 -6
- package/src/core/modules/auto-discovery.ts +478 -15
- package/src/core/modules/index.ts +2 -2
- package/src/core/modules/modules.ts +23 -12
- package/src/core/networking/adapters/index.ts +4 -3
- package/src/core/networking/adapters/socketio-adapter.ts +5 -3
- package/src/core/networking/adapters/uws-adapter.ts +619 -0
- package/src/core/networking/adapters/ws-adapter.ts +8 -9
- package/src/core/networking/index.ts +3 -2
- package/src/core/networking/service-discovery.ts +6 -7
- package/src/core/networking/websocket-manager.ts +7 -7
- package/src/core/pooling/object-pool-manager.ts +630 -0
- package/src/core/routing/app-integration.ts +60 -112
- package/src/core/routing/index.ts +66 -293
- package/src/core/routing/path-matcher.ts +222 -0
- package/src/core/routing/router.ts +97 -0
- package/src/core/routing/unified-router.ts +870 -0
- package/src/core/runtime/aws-lambda-adapter.ts +3 -3
- package/src/core/runtime/base-adapter.ts +2 -2
- package/src/core/runtime/cloudflare-workers-adapter.ts +3 -3
- package/src/core/runtime/index.ts +13 -13
- package/src/core/runtime/node-adapter.ts +16 -10
- package/src/core/runtime/vercel-edge-adapter.ts +3 -3
- package/src/core/utilities/hooks.ts +3 -3
- package/src/core/utilities/index.ts +5 -4
- package/src/core/utilities/package-utils.ts +59 -0
- package/src/core/validation/adapters.ts +1 -1
- package/src/core/validation/index.ts +68 -16
- package/src/index.ts +73 -66
- package/src/moro.ts +784 -253
- package/src/types/config.ts +74 -2
- package/src/types/core.ts +49 -47
- package/src/types/hooks.ts +1 -1
- package/src/types/http.ts +23 -1
- package/src/types/logger.ts +9 -0
- package/src/types/module.ts +12 -0
- package/src/types/runtime.ts +1 -1
- package/tsconfig.json +4 -2
- package/dist/core/config/loader.d.ts +0 -7
- package/dist/core/config/loader.js +0 -269
- package/dist/core/config/loader.js.map +0 -1
- package/dist/core/config/validation.d.ts +0 -17
- package/dist/core/config/validation.js +0 -131
- package/dist/core/config/validation.js.map +0 -1
- package/dist/core/http/router.d.ts +0 -14
- package/dist/core/http/router.js +0 -109
- package/dist/core/http/router.js.map +0 -1
- package/src/core/config/loader.ts +0 -633
- package/src/core/config/validation.ts +0 -140
- package/src/core/http/router.ts +0 -141
package/src/moro.ts
CHANGED
|
@@ -1,124 +1,106 @@
|
|
|
1
1
|
// Moro Framework - Modern TypeScript API Framework
|
|
2
2
|
// Built for developers who demand performance, elegance, and zero compromises
|
|
3
3
|
// Event-driven • Modular • Enterprise-ready • Developer-first
|
|
4
|
-
import { Moro as MoroCore } from './core/framework';
|
|
5
|
-
import { HttpRequest, HttpResponse, middleware } from './core/http';
|
|
6
|
-
import { ModuleConfig, InternalRouteDefinition } from './types/module';
|
|
7
|
-
import { MoroOptions } from './types/core';
|
|
8
|
-
import {
|
|
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';
|
|
9
15
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from './core/
|
|
14
|
-
import { MiddlewareManager } from './core/middleware';
|
|
15
|
-
import { IntelligentRoutingManager } from './core/routing/app-integration';
|
|
16
|
-
import { RouteBuilder, RouteSchema, CompiledRoute } from './core/routing';
|
|
17
|
-
import { AppDocumentationManager, DocsConfig } from './core/docs';
|
|
18
|
-
import { readdirSync, statSync } from 'fs';
|
|
19
|
-
import { join } from 'path';
|
|
16
|
+
UnifiedRouter,
|
|
17
|
+
RouteBuilder as UnifiedRouteBuilder,
|
|
18
|
+
} from './core/routing/unified-router.js';
|
|
19
|
+
import { AppDocumentationManager, DocsConfig } from './core/docs/index.js';
|
|
20
20
|
import { EventEmitter } from 'events';
|
|
21
|
+
import cluster from 'cluster';
|
|
22
|
+
import os from 'os';
|
|
23
|
+
import { normalizeValidationError } from './core/validation/schema-interface.js';
|
|
21
24
|
// Configuration System Integration
|
|
22
|
-
import { initializeConfig,
|
|
25
|
+
import { initializeConfig, type AppConfig } from './core/config/index.js';
|
|
23
26
|
// Runtime System Integration
|
|
24
|
-
import {
|
|
25
|
-
RuntimeAdapter,
|
|
26
|
-
RuntimeType,
|
|
27
|
-
createRuntimeAdapter,
|
|
28
|
-
NodeRuntimeAdapter,
|
|
29
|
-
} from './core/runtime';
|
|
27
|
+
import { RuntimeAdapter, RuntimeType, createRuntimeAdapter } from './core/runtime/index.js';
|
|
30
28
|
|
|
31
29
|
export class Moro extends EventEmitter {
|
|
32
|
-
private coreFramework
|
|
30
|
+
private coreFramework!: MoroCore;
|
|
33
31
|
private routes: InternalRouteDefinition[] = [];
|
|
34
32
|
private moduleCounter = 0;
|
|
35
33
|
private loadedModules = new Set<string>();
|
|
34
|
+
private lazyModules = new Map<string, ModuleConfig>();
|
|
36
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;
|
|
37
40
|
// Enterprise event system integration
|
|
38
|
-
private eventBus
|
|
41
|
+
private eventBus!: MoroEventBus;
|
|
39
42
|
// Application logger
|
|
40
|
-
private logger
|
|
41
|
-
//
|
|
42
|
-
private
|
|
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;
|
|
43
48
|
// Documentation system
|
|
44
49
|
private documentation = new AppDocumentationManager();
|
|
45
50
|
// Configuration system
|
|
46
|
-
private config
|
|
51
|
+
private config!: AppConfig;
|
|
52
|
+
// Track if user explicitly set logger options (for worker log level handling)
|
|
53
|
+
private userSetLogger = false;
|
|
47
54
|
// Runtime system
|
|
48
|
-
private runtimeAdapter
|
|
49
|
-
private runtimeType
|
|
55
|
+
private runtimeAdapter!: RuntimeAdapter;
|
|
56
|
+
private runtimeType!: RuntimeType;
|
|
50
57
|
// Middleware system
|
|
51
|
-
private middlewareManager
|
|
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
|
+
}> = [];
|
|
52
65
|
|
|
53
66
|
constructor(options: MoroOptions = {}) {
|
|
54
67
|
super(); // Call EventEmitter constructor
|
|
55
68
|
|
|
56
|
-
//
|
|
57
|
-
|
|
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)
|
|
58
74
|
const envLogLevel = process.env.LOG_LEVEL || process.env.MORO_LOG_LEVEL;
|
|
59
75
|
if (envLogLevel) {
|
|
60
76
|
applyLoggingConfiguration({ level: envLogLevel }, undefined);
|
|
61
77
|
}
|
|
62
78
|
|
|
63
|
-
//
|
|
64
|
-
this.config = JSON.parse(JSON.stringify(initializeConfig()));
|
|
65
|
-
|
|
66
|
-
// Apply logging configuration from the loaded config (this happens after config file processing)
|
|
67
|
-
if (this.config.logging) {
|
|
68
|
-
applyLoggingConfiguration(this.config.logging, undefined);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Apply additional logging configuration from createApp options (takes precedence)
|
|
79
|
+
// 2. createApp logger options (highest precedence)
|
|
72
80
|
if (options.logger !== undefined) {
|
|
73
81
|
applyLoggingConfiguration(undefined, options.logger);
|
|
74
82
|
}
|
|
75
83
|
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
if (options.performance.clustering) {
|
|
79
|
-
this.config.performance.clustering = {
|
|
80
|
-
...this.config.performance.clustering,
|
|
81
|
-
...options.performance.clustering,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
if (options.performance.compression) {
|
|
85
|
-
this.config.performance.compression = {
|
|
86
|
-
...this.config.performance.compression,
|
|
87
|
-
...options.performance.compression,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
if (options.performance.circuitBreaker) {
|
|
91
|
-
this.config.performance.circuitBreaker = {
|
|
92
|
-
...this.config.performance.circuitBreaker,
|
|
93
|
-
...options.performance.circuitBreaker,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
}
|
|
84
|
+
// Create logger AFTER initial configuration
|
|
85
|
+
this.logger = createFrameworkLogger('App');
|
|
97
86
|
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
this.config.modules.rateLimit = {
|
|
108
|
-
...this.config.modules.rateLimit,
|
|
109
|
-
...options.modules.rateLimit,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
if (options.modules.validation) {
|
|
113
|
-
this.config.modules.validation = {
|
|
114
|
-
...this.config.modules.validation,
|
|
115
|
-
...options.modules.validation,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
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');
|
|
118
96
|
}
|
|
119
97
|
|
|
98
|
+
// NOW initialize routing systems AFTER logger is configured
|
|
99
|
+
this.unifiedRouter = UnifiedRouter.getInstance();
|
|
100
|
+
this.intelligentRouting = new IntelligentRoutingManager();
|
|
101
|
+
|
|
120
102
|
this.logger.info(
|
|
121
|
-
`Configuration system initialized: ${
|
|
103
|
+
`Configuration system initialized: ${process.env.NODE_ENV || 'development'}:${this.config.server.port}`
|
|
122
104
|
);
|
|
123
105
|
|
|
124
106
|
// Initialize runtime system
|
|
@@ -127,10 +109,16 @@ export class Moro extends EventEmitter {
|
|
|
127
109
|
|
|
128
110
|
this.logger.info(`Runtime system initialized: ${this.runtimeType}`, 'Runtime');
|
|
129
111
|
|
|
130
|
-
// Pass
|
|
112
|
+
// Pass configuration from config to framework
|
|
131
113
|
const frameworkOptions: any = {
|
|
132
114
|
...options,
|
|
133
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,
|
|
134
122
|
};
|
|
135
123
|
|
|
136
124
|
this.coreFramework = new MoroCore(frameworkOptions);
|
|
@@ -162,10 +150,10 @@ export class Moro extends EventEmitter {
|
|
|
162
150
|
...options,
|
|
163
151
|
});
|
|
164
152
|
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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;
|
|
169
157
|
|
|
170
158
|
// Emit initialization event through enterprise event bus
|
|
171
159
|
this.eventBus.emit('framework:initialized', {
|
|
@@ -209,84 +197,85 @@ export class Moro extends EventEmitter {
|
|
|
209
197
|
|
|
210
198
|
// Intelligent route methods - chainable with automatic middleware ordering
|
|
211
199
|
// Overloads for better TypeScript inference
|
|
212
|
-
get(path: string):
|
|
200
|
+
get(path: string): UnifiedRouteBuilder;
|
|
213
201
|
get(path: string, handler: (req: HttpRequest, res: HttpResponse) => any, options?: any): this;
|
|
214
202
|
get(
|
|
215
203
|
path: string,
|
|
216
204
|
handler?: (req: HttpRequest, res: HttpResponse) => any,
|
|
217
205
|
options?: any
|
|
218
|
-
):
|
|
206
|
+
): UnifiedRouteBuilder | this {
|
|
219
207
|
if (handler) {
|
|
220
208
|
// Direct route registration
|
|
221
209
|
return this.addRoute('GET', path, handler, options);
|
|
222
210
|
}
|
|
223
|
-
//
|
|
224
|
-
return this.
|
|
211
|
+
// Use unified router for chainable API
|
|
212
|
+
return this.unifiedRouter.get(path);
|
|
225
213
|
}
|
|
226
214
|
|
|
227
|
-
post(path: string):
|
|
215
|
+
post(path: string): UnifiedRouteBuilder;
|
|
228
216
|
post(path: string, handler: (req: HttpRequest, res: HttpResponse) => any, options?: any): this;
|
|
229
217
|
post(
|
|
230
218
|
path: string,
|
|
231
219
|
handler?: (req: HttpRequest, res: HttpResponse) => any,
|
|
232
220
|
options?: any
|
|
233
|
-
):
|
|
221
|
+
): UnifiedRouteBuilder | this {
|
|
234
222
|
if (handler) {
|
|
235
223
|
// Direct route registration
|
|
236
224
|
return this.addRoute('POST', path, handler, options);
|
|
237
225
|
}
|
|
238
|
-
//
|
|
239
|
-
return this.
|
|
226
|
+
// Use unified router for chainable API
|
|
227
|
+
return this.unifiedRouter.post(path);
|
|
240
228
|
}
|
|
241
229
|
|
|
242
|
-
put(path: string):
|
|
230
|
+
put(path: string): UnifiedRouteBuilder;
|
|
243
231
|
put(path: string, handler: (req: HttpRequest, res: HttpResponse) => any, options?: any): this;
|
|
244
232
|
put(
|
|
245
233
|
path: string,
|
|
246
234
|
handler?: (req: HttpRequest, res: HttpResponse) => any,
|
|
247
235
|
options?: any
|
|
248
|
-
):
|
|
236
|
+
): UnifiedRouteBuilder | this {
|
|
249
237
|
if (handler) {
|
|
250
238
|
// Direct route registration
|
|
251
239
|
return this.addRoute('PUT', path, handler, options);
|
|
252
240
|
}
|
|
253
|
-
//
|
|
254
|
-
return this.
|
|
241
|
+
// Use unified router for chainable API
|
|
242
|
+
return this.unifiedRouter.put(path);
|
|
255
243
|
}
|
|
256
244
|
|
|
257
|
-
delete(path: string):
|
|
245
|
+
delete(path: string): UnifiedRouteBuilder;
|
|
258
246
|
delete(path: string, handler: (req: HttpRequest, res: HttpResponse) => any, options?: any): this;
|
|
259
247
|
delete(
|
|
260
248
|
path: string,
|
|
261
249
|
handler?: (req: HttpRequest, res: HttpResponse) => any,
|
|
262
250
|
options?: any
|
|
263
|
-
):
|
|
251
|
+
): UnifiedRouteBuilder | this {
|
|
264
252
|
if (handler) {
|
|
265
253
|
// Direct route registration
|
|
266
254
|
return this.addRoute('DELETE', path, handler, options);
|
|
267
255
|
}
|
|
268
|
-
//
|
|
269
|
-
return this.
|
|
256
|
+
// Use unified router for chainable API
|
|
257
|
+
return this.unifiedRouter.delete(path);
|
|
270
258
|
}
|
|
271
259
|
|
|
272
|
-
patch(path: string):
|
|
260
|
+
patch(path: string): UnifiedRouteBuilder;
|
|
273
261
|
patch(path: string, handler: (req: HttpRequest, res: HttpResponse) => any, options?: any): this;
|
|
274
262
|
patch(
|
|
275
263
|
path: string,
|
|
276
264
|
handler?: (req: HttpRequest, res: HttpResponse) => any,
|
|
277
265
|
options?: any
|
|
278
|
-
):
|
|
266
|
+
): UnifiedRouteBuilder | this {
|
|
279
267
|
if (handler) {
|
|
280
268
|
// Direct route registration
|
|
281
269
|
return this.addRoute('PATCH', path, handler, options);
|
|
282
270
|
}
|
|
283
|
-
//
|
|
284
|
-
return this.
|
|
271
|
+
// Use unified router for chainable API
|
|
272
|
+
return this.unifiedRouter.patch(path);
|
|
285
273
|
}
|
|
286
274
|
|
|
287
275
|
// Schema-first route method
|
|
288
|
-
route(schema: RouteSchema):
|
|
289
|
-
|
|
276
|
+
route(schema: RouteSchema): void {
|
|
277
|
+
// Use unified router for schema-first registration
|
|
278
|
+
this.unifiedRouter.route(schema);
|
|
290
279
|
}
|
|
291
280
|
|
|
292
281
|
// Enable automatic API documentation
|
|
@@ -394,6 +383,13 @@ export class Moro extends EventEmitter {
|
|
|
394
383
|
version: moduleOrPath.version || '1.0.0',
|
|
395
384
|
});
|
|
396
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
|
+
|
|
397
393
|
return this;
|
|
398
394
|
}
|
|
399
395
|
|
|
@@ -409,34 +405,45 @@ export class Moro extends EventEmitter {
|
|
|
409
405
|
|
|
410
406
|
// WebSocket helper with events
|
|
411
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
|
|
412
413
|
const adapter = this.coreFramework.getWebSocketAdapter();
|
|
413
|
-
if (!
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
'or\n' +
|
|
418
|
-
'new Moro({ websocket: { adapter: new SocketIOAdapter() } })'
|
|
419
|
-
);
|
|
414
|
+
if (adapter && !registration.processed) {
|
|
415
|
+
// Adapter is ready, process immediately
|
|
416
|
+
this.processWebSocketRegistration(namespace, handlers, adapter);
|
|
417
|
+
registration.processed = true;
|
|
420
418
|
}
|
|
419
|
+
// Otherwise, it will be processed when the server starts
|
|
421
420
|
|
|
421
|
+
return this;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
private processWebSocketRegistration(
|
|
425
|
+
namespace: string,
|
|
426
|
+
handlers: Record<string, Function>,
|
|
427
|
+
adapter: any
|
|
428
|
+
) {
|
|
422
429
|
this.emit('websocket:registering', { namespace, handlers });
|
|
423
430
|
|
|
424
431
|
const ns = adapter.createNamespace(namespace);
|
|
425
432
|
|
|
426
433
|
Object.entries(handlers).forEach(([event, handler]) => {
|
|
427
|
-
ns.on('connection', socket => {
|
|
434
|
+
ns.on('connection', (socket: any) => {
|
|
428
435
|
this.emit('websocket:connection', { namespace, event, socket });
|
|
429
436
|
|
|
430
|
-
socket.on(event, (data, callback) => {
|
|
437
|
+
socket.on(event, (data: any, callback: any) => {
|
|
431
438
|
this.emit('websocket:event', { namespace, event, data });
|
|
432
439
|
|
|
433
440
|
Promise.resolve(handler(socket, data))
|
|
434
|
-
.then(result => {
|
|
441
|
+
.then((result: any) => {
|
|
435
442
|
this.emit('websocket:response', { namespace, event, result });
|
|
436
443
|
if (callback) callback(result);
|
|
437
444
|
else if (result) socket.emit(`${event}:response`, result);
|
|
438
445
|
})
|
|
439
|
-
.catch(error => {
|
|
446
|
+
.catch((error: any) => {
|
|
440
447
|
this.emit('websocket:error', { namespace, event, error });
|
|
441
448
|
const errorResponse = { success: false, error: error.message };
|
|
442
449
|
if (callback) callback(errorResponse);
|
|
@@ -447,7 +454,40 @@ export class Moro extends EventEmitter {
|
|
|
447
454
|
});
|
|
448
455
|
|
|
449
456
|
this.emit('websocket:registered', { namespace, handlers });
|
|
450
|
-
|
|
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
|
+
}
|
|
451
491
|
}
|
|
452
492
|
|
|
453
493
|
// Start server with events (Node.js only)
|
|
@@ -503,9 +543,21 @@ export class Moro extends EventEmitter {
|
|
|
503
543
|
}
|
|
504
544
|
|
|
505
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
|
+
|
|
506
549
|
if (this.config.performance?.clustering?.enabled) {
|
|
507
|
-
|
|
508
|
-
|
|
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
|
+
}
|
|
509
561
|
}
|
|
510
562
|
this.eventBus.emit('server:starting', { port, runtime: this.runtimeType });
|
|
511
563
|
|
|
@@ -519,50 +571,200 @@ export class Moro extends EventEmitter {
|
|
|
519
571
|
this.logger.debug('Documentation not enabled', 'Documentation');
|
|
520
572
|
}
|
|
521
573
|
|
|
522
|
-
// Add
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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();
|
|
529
594
|
}
|
|
530
595
|
}
|
|
531
|
-
);
|
|
596
|
+
});
|
|
532
597
|
|
|
533
|
-
// Register direct routes with the HTTP server
|
|
598
|
+
// Register legacy direct routes with the HTTP server (for backward compatibility)
|
|
534
599
|
if (this.routes.length > 0) {
|
|
535
600
|
this.registerDirectRoutes();
|
|
536
601
|
}
|
|
537
602
|
|
|
538
|
-
const
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
this.
|
|
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);
|
|
551
630
|
}
|
|
631
|
+
};
|
|
552
632
|
|
|
553
|
-
|
|
554
|
-
|
|
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
|
+
},
|
|
555
705
|
};
|
|
556
706
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
|
561
743
|
}
|
|
562
744
|
}
|
|
563
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
|
+
|
|
564
758
|
// Get handler for non-Node.js runtimes
|
|
565
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
|
+
|
|
566
768
|
// Create a unified request handler that works with the runtime adapter
|
|
567
769
|
const handler = async (req: HttpRequest, res: HttpResponse) => {
|
|
568
770
|
// Add documentation middleware first (if enabled)
|
|
@@ -574,11 +776,11 @@ export class Moro extends EventEmitter {
|
|
|
574
776
|
// Documentation not enabled, that's fine
|
|
575
777
|
}
|
|
576
778
|
|
|
577
|
-
// Try
|
|
578
|
-
const handled = await this.
|
|
779
|
+
// Try unified router first (handles all routes)
|
|
780
|
+
const handled = await this.unifiedRouter.handleRequest(req, res);
|
|
579
781
|
if (handled) return;
|
|
580
782
|
|
|
581
|
-
// Handle direct routes
|
|
783
|
+
// Handle legacy direct routes (backward compatibility)
|
|
582
784
|
if (this.routes.length > 0) {
|
|
583
785
|
await this.handleDirectRoutes(req, res);
|
|
584
786
|
}
|
|
@@ -745,8 +947,11 @@ export class Moro extends EventEmitter {
|
|
|
745
947
|
|
|
746
948
|
// Private methods
|
|
747
949
|
private addRoute(method: string, path: string, handler: Function, options: any = {}) {
|
|
748
|
-
|
|
950
|
+
// Register with unified router (primary routing system)
|
|
951
|
+
this.unifiedRouter.addRoute(method as any, path, handler as any, options.middleware || []);
|
|
749
952
|
|
|
953
|
+
// Also store in legacy routes array for backward compatibility
|
|
954
|
+
const handlerName = `handler_${this.routes.length}`;
|
|
750
955
|
const route = {
|
|
751
956
|
method: method as any,
|
|
752
957
|
path,
|
|
@@ -759,10 +964,10 @@ export class Moro extends EventEmitter {
|
|
|
759
964
|
|
|
760
965
|
this.routes.push(route);
|
|
761
966
|
|
|
762
|
-
// Organize routes for optimal lookup
|
|
967
|
+
// Organize routes for optimal lookup (legacy)
|
|
763
968
|
this.organizeRouteForLookup(route);
|
|
764
969
|
|
|
765
|
-
// Store handler for later module creation
|
|
970
|
+
// Store handler for later module creation (legacy)
|
|
766
971
|
this.routeHandlers[handlerName] = handler;
|
|
767
972
|
|
|
768
973
|
return this;
|
|
@@ -807,7 +1012,6 @@ export class Moro extends EventEmitter {
|
|
|
807
1012
|
req.body = validated;
|
|
808
1013
|
} catch (error: any) {
|
|
809
1014
|
// Handle universal validation errors
|
|
810
|
-
const { normalizeValidationError } = require('./core/validation/schema-interface');
|
|
811
1015
|
const normalizedError = normalizeValidationError(error);
|
|
812
1016
|
res.status(400).json({
|
|
813
1017
|
success: false,
|
|
@@ -886,20 +1090,33 @@ export class Moro extends EventEmitter {
|
|
|
886
1090
|
}
|
|
887
1091
|
|
|
888
1092
|
private setupDefaultMiddleware(options: MoroOptions) {
|
|
889
|
-
// CORS
|
|
890
|
-
if (options.cors
|
|
891
|
-
const corsOptions =
|
|
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
|
+
: {};
|
|
892
1101
|
this.use(middleware.cors(corsOptions));
|
|
893
1102
|
}
|
|
894
1103
|
|
|
895
|
-
// Helmet
|
|
896
|
-
if (options.helmet
|
|
1104
|
+
// Helmet - check config enabled property OR options.security.helmet.enabled === true
|
|
1105
|
+
if (this.config.security.helmet.enabled || options.security?.helmet?.enabled === true) {
|
|
897
1106
|
this.use(middleware.helmet());
|
|
898
1107
|
}
|
|
899
1108
|
|
|
900
|
-
// Compression
|
|
901
|
-
if (
|
|
902
|
-
|
|
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
|
+
: {};
|
|
903
1120
|
this.use(middleware.compression(compressionOptions));
|
|
904
1121
|
}
|
|
905
1122
|
|
|
@@ -907,27 +1124,216 @@ export class Moro extends EventEmitter {
|
|
|
907
1124
|
this.use(middleware.bodySize({ limit: '10mb' }));
|
|
908
1125
|
}
|
|
909
1126
|
|
|
910
|
-
|
|
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
|
+
|
|
911
1140
|
try {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
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);
|
|
925
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);
|
|
926
1216
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
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
|
+
}
|
|
930
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
|
+
});
|
|
931
1337
|
}
|
|
932
1338
|
|
|
933
1339
|
private async importModule(modulePath: string): Promise<ModuleConfig> {
|
|
@@ -935,55 +1341,81 @@ export class Moro extends EventEmitter {
|
|
|
935
1341
|
return module.default || module;
|
|
936
1342
|
}
|
|
937
1343
|
|
|
938
|
-
|
|
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
|
+
*/
|
|
939
1361
|
private clusterWorkers = new Map<number, any>();
|
|
940
|
-
private startWithClustering(port: number, host?: string, callback?: () => void): void {
|
|
941
|
-
const cluster = require('cluster');
|
|
942
|
-
const os = require('os');
|
|
943
1362
|
|
|
944
|
-
|
|
1363
|
+
private startWithClustering(port: number, host?: string, callback?: () => void): void {
|
|
1364
|
+
// Worker count calculation - respect user choice
|
|
945
1365
|
let workerCount = this.config.performance?.clustering?.workers || os.cpus().length;
|
|
946
1366
|
|
|
947
|
-
//
|
|
948
|
-
if (workerCount === 'auto'
|
|
949
|
-
// For high-core machines, limit workers to prevent IPC/memory bottlenecks
|
|
1367
|
+
// Only auto-optimize if user hasn't specified a number or set it to 'auto'
|
|
1368
|
+
if (workerCount === 'auto') {
|
|
950
1369
|
const cpuCount = os.cpus().length;
|
|
951
1370
|
const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);
|
|
952
1371
|
|
|
953
|
-
//
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
} else {
|
|
961
|
-
// Low-core machines: use all cores
|
|
962
|
-
workerCount = cpuCount;
|
|
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));
|
|
963
1379
|
}
|
|
964
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
|
+
|
|
965
1390
|
this.logger.info(
|
|
966
|
-
`Auto-
|
|
1391
|
+
`Auto-calculated worker count: ${workerCount} (CPU: ${cpuCount}, RAM: ${totalMemoryGB.toFixed(1)}GB, ${memoryPerWorkerGB}GB per worker)`,
|
|
967
1392
|
'Cluster'
|
|
968
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');
|
|
969
1397
|
}
|
|
970
1398
|
|
|
971
1399
|
if (cluster.isPrimary) {
|
|
972
|
-
this.logger.info(
|
|
1400
|
+
this.logger.info(`Starting ${workerCount} workers`, 'Cluster');
|
|
973
1401
|
|
|
974
1402
|
// Optimize cluster scheduling for high concurrency
|
|
975
|
-
|
|
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;
|
|
976
1406
|
|
|
977
1407
|
// Set cluster settings for better performance
|
|
978
1408
|
cluster.setupMaster({
|
|
979
|
-
exec: process.argv[1],
|
|
1409
|
+
exec: process.argv[1] || process.execPath,
|
|
980
1410
|
args: process.argv.slice(2),
|
|
981
1411
|
silent: false,
|
|
982
1412
|
});
|
|
983
1413
|
|
|
984
|
-
//
|
|
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)
|
|
985
1417
|
process.env.NODE_CLUSTER_SCHED_POLICY = 'rr'; // Ensure round-robin
|
|
986
|
-
process.env.NODE_DISABLE_COLORS = '1'; // Reduce IPC message size
|
|
1418
|
+
process.env.NODE_DISABLE_COLORS = '1'; // Reduce IPC message size by disabling color codes
|
|
987
1419
|
|
|
988
1420
|
// Graceful shutdown handler
|
|
989
1421
|
const gracefulShutdown = () => {
|
|
@@ -1004,37 +1436,32 @@ export class Moro extends EventEmitter {
|
|
|
1004
1436
|
process.on('SIGINT', gracefulShutdown);
|
|
1005
1437
|
process.on('SIGTERM', gracefulShutdown);
|
|
1006
1438
|
|
|
1007
|
-
// Fork workers with
|
|
1439
|
+
// Fork workers with basic tracking
|
|
1008
1440
|
for (let i = 0; i < workerCount; i++) {
|
|
1009
|
-
const worker = cluster.fork(
|
|
1010
|
-
WORKER_ID: i,
|
|
1011
|
-
WORKER_CPU_AFFINITY: i % os.cpus().length, // Distribute workers across CPUs
|
|
1012
|
-
});
|
|
1441
|
+
const worker = cluster.fork();
|
|
1013
1442
|
this.clusterWorkers.set(worker.process.pid!, worker);
|
|
1014
|
-
this.logger.info(
|
|
1015
|
-
`Worker ${worker.process.pid} started (CPU ${i % os.cpus().length})`,
|
|
1016
|
-
'Cluster'
|
|
1017
|
-
);
|
|
1443
|
+
this.logger.info(`Worker ${worker.process.pid} started`, 'Cluster');
|
|
1018
1444
|
|
|
1019
|
-
// Handle individual worker messages
|
|
1445
|
+
// Handle individual worker messages
|
|
1020
1446
|
worker.on('message', this.handleWorkerMessage.bind(this));
|
|
1021
1447
|
}
|
|
1022
1448
|
|
|
1023
|
-
//
|
|
1449
|
+
// Simple worker exit handling
|
|
1024
1450
|
cluster.on('exit', (worker: any, code: number, signal: string) => {
|
|
1025
|
-
|
|
1026
|
-
this.clusterWorkers.delete(
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
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
|
+
}
|
|
1038
1465
|
});
|
|
1039
1466
|
|
|
1040
1467
|
// Master process callback
|
|
@@ -1047,17 +1474,33 @@ export class Moro extends EventEmitter {
|
|
|
1047
1474
|
process.env.UV_THREADPOOL_SIZE = '64';
|
|
1048
1475
|
|
|
1049
1476
|
// Reduce logging contention in workers (major bottleneck)
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
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
|
|
1053
1482
|
}
|
|
1054
1483
|
|
|
1055
|
-
//
|
|
1056
|
-
|
|
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}`;
|
|
1057
1495
|
|
|
1058
|
-
|
|
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
|
|
1059
1502
|
if (process.env.NODE_ENV === 'production') {
|
|
1060
|
-
//
|
|
1503
|
+
// Aggressive V8 optimizations for maximum performance
|
|
1061
1504
|
const v8Flags = [
|
|
1062
1505
|
'--optimize-for-size', // Trade memory for speed
|
|
1063
1506
|
'--always-opt', // Always optimize functions
|
|
@@ -1116,17 +1559,31 @@ export class Moro extends EventEmitter {
|
|
|
1116
1559
|
// Documentation not enabled, that's fine
|
|
1117
1560
|
}
|
|
1118
1561
|
|
|
1119
|
-
// Add
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
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)) {
|
|
1124
1581
|
next();
|
|
1125
1582
|
}
|
|
1126
1583
|
}
|
|
1127
|
-
);
|
|
1584
|
+
});
|
|
1128
1585
|
|
|
1129
|
-
// Register direct routes
|
|
1586
|
+
// Register legacy direct routes with the HTTP server (for backward compatibility)
|
|
1130
1587
|
if (this.routes.length > 0) {
|
|
1131
1588
|
this.registerDirectRoutes();
|
|
1132
1589
|
}
|
|
@@ -1141,15 +1598,37 @@ export class Moro extends EventEmitter {
|
|
|
1141
1598
|
});
|
|
1142
1599
|
};
|
|
1143
1600
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
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
|
+
});
|
|
1149
1628
|
}
|
|
1150
1629
|
}
|
|
1151
1630
|
|
|
1152
|
-
//
|
|
1631
|
+
// Simple worker message handler
|
|
1153
1632
|
private handleWorkerMessage(message: any): void {
|
|
1154
1633
|
// Handle inter-worker communication if needed
|
|
1155
1634
|
if (message.type === 'health-check') {
|
|
@@ -1160,6 +1639,58 @@ export class Moro extends EventEmitter {
|
|
|
1160
1639
|
// Log other worker messages
|
|
1161
1640
|
this.logger.debug(`Worker message: ${JSON.stringify(message)}`, 'Cluster');
|
|
1162
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
|
+
}
|
|
1163
1694
|
}
|
|
1164
1695
|
|
|
1165
1696
|
// Export convenience function
|