@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/dist/moro.js
CHANGED
|
@@ -1,154 +1,99 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.Moro = void 0;
|
|
37
|
-
exports.createApp = createApp;
|
|
38
|
-
exports.createAppNode = createAppNode;
|
|
39
|
-
exports.createAppEdge = createAppEdge;
|
|
40
|
-
exports.createAppLambda = createAppLambda;
|
|
41
|
-
exports.createAppWorker = createAppWorker;
|
|
42
1
|
// Moro Framework - Modern TypeScript API Framework
|
|
43
2
|
// Built for developers who demand performance, elegance, and zero compromises
|
|
44
3
|
// Event-driven • Modular • Enterprise-ready • Developer-first
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
4
|
+
import { Moro as MoroCore } from './core/framework.js';
|
|
5
|
+
import { middleware } from './core/http/index.js';
|
|
6
|
+
import { createFrameworkLogger, applyLoggingConfiguration } from './core/logger/index.js';
|
|
7
|
+
import { MiddlewareManager } from './core/middleware/index.js';
|
|
8
|
+
import { IntelligentRoutingManager } from './core/routing/app-integration.js';
|
|
9
|
+
import { UnifiedRouter, } from './core/routing/unified-router.js';
|
|
10
|
+
import { AppDocumentationManager } from './core/docs/index.js';
|
|
11
|
+
import { EventEmitter } from 'events';
|
|
12
|
+
import cluster from 'cluster';
|
|
13
|
+
import os from 'os';
|
|
14
|
+
import { normalizeValidationError } from './core/validation/schema-interface.js';
|
|
54
15
|
// Configuration System Integration
|
|
55
|
-
|
|
16
|
+
import { initializeConfig } from './core/config/index.js';
|
|
56
17
|
// Runtime System Integration
|
|
57
|
-
|
|
58
|
-
class Moro extends
|
|
18
|
+
import { createRuntimeAdapter } from './core/runtime/index.js';
|
|
19
|
+
export class Moro extends EventEmitter {
|
|
59
20
|
coreFramework;
|
|
60
21
|
routes = [];
|
|
61
22
|
moduleCounter = 0;
|
|
62
23
|
loadedModules = new Set();
|
|
24
|
+
lazyModules = new Map();
|
|
63
25
|
routeHandlers = {};
|
|
26
|
+
moduleDiscovery; // Store for cleanup
|
|
27
|
+
autoDiscoveryOptions = null;
|
|
28
|
+
autoDiscoveryInitialized = false;
|
|
29
|
+
autoDiscoveryPromise = null;
|
|
64
30
|
// Enterprise event system integration
|
|
65
31
|
eventBus;
|
|
66
32
|
// Application logger
|
|
67
|
-
logger
|
|
68
|
-
//
|
|
69
|
-
|
|
33
|
+
logger;
|
|
34
|
+
// Unified routing system (singleton - shared across all routers)
|
|
35
|
+
unifiedRouter;
|
|
36
|
+
// Legacy intelligent routing (kept for backward compatibility, now a facade)
|
|
37
|
+
intelligentRouting;
|
|
70
38
|
// Documentation system
|
|
71
|
-
documentation = new
|
|
39
|
+
documentation = new AppDocumentationManager();
|
|
72
40
|
// Configuration system
|
|
73
41
|
config;
|
|
42
|
+
// Track if user explicitly set logger options (for worker log level handling)
|
|
43
|
+
userSetLogger = false;
|
|
74
44
|
// Runtime system
|
|
75
45
|
runtimeAdapter;
|
|
76
46
|
runtimeType;
|
|
77
47
|
// Middleware system
|
|
78
48
|
middlewareManager;
|
|
49
|
+
// Queued WebSocket registrations (for async adapter detection)
|
|
50
|
+
queuedWebSocketRegistrations = [];
|
|
79
51
|
constructor(options = {}) {
|
|
80
52
|
super(); // Call EventEmitter constructor
|
|
81
|
-
//
|
|
82
|
-
|
|
53
|
+
// Track if user explicitly set logger/logging options
|
|
54
|
+
this.userSetLogger = !!(options.logger || options.logging);
|
|
55
|
+
// Apply logging configuration BEFORE config loading to avoid DEBUG spam
|
|
56
|
+
// 1. Environment variables (base level)
|
|
83
57
|
const envLogLevel = process.env.LOG_LEVEL || process.env.MORO_LOG_LEVEL;
|
|
84
58
|
if (envLogLevel) {
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
// Initialize configuration system - create a deep copy for this instance
|
|
88
|
-
this.config = JSON.parse(JSON.stringify((0, config_1.initializeConfig)()));
|
|
89
|
-
// Apply logging configuration from the loaded config (this happens after config file processing)
|
|
90
|
-
if (this.config.logging) {
|
|
91
|
-
(0, logger_1.applyLoggingConfiguration)(this.config.logging, undefined);
|
|
59
|
+
applyLoggingConfiguration({ level: envLogLevel }, undefined);
|
|
92
60
|
}
|
|
93
|
-
//
|
|
61
|
+
// 2. createApp logger options (highest precedence)
|
|
94
62
|
if (options.logger !== undefined) {
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
// Apply performance configuration from createApp options (takes precedence)
|
|
98
|
-
if (options.performance) {
|
|
99
|
-
if (options.performance.clustering) {
|
|
100
|
-
this.config.performance.clustering = {
|
|
101
|
-
...this.config.performance.clustering,
|
|
102
|
-
...options.performance.clustering,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
if (options.performance.compression) {
|
|
106
|
-
this.config.performance.compression = {
|
|
107
|
-
...this.config.performance.compression,
|
|
108
|
-
...options.performance.compression,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
if (options.performance.circuitBreaker) {
|
|
112
|
-
this.config.performance.circuitBreaker = {
|
|
113
|
-
...this.config.performance.circuitBreaker,
|
|
114
|
-
...options.performance.circuitBreaker,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
63
|
+
applyLoggingConfiguration(undefined, options.logger);
|
|
117
64
|
}
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
...this.config.modules.rateLimit,
|
|
129
|
-
...options.modules.rateLimit,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
if (options.modules.validation) {
|
|
133
|
-
this.config.modules.validation = {
|
|
134
|
-
...this.config.modules.validation,
|
|
135
|
-
...options.modules.validation,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
65
|
+
// Create logger AFTER initial configuration
|
|
66
|
+
this.logger = createFrameworkLogger('App');
|
|
67
|
+
// Use simplified global configuration system
|
|
68
|
+
this.config = initializeConfig(options);
|
|
69
|
+
// Apply final config logging (this includes normalized logger → logging conversion)
|
|
70
|
+
// Always apply this as it's the authoritative merged config
|
|
71
|
+
if (this.config.logging) {
|
|
72
|
+
applyLoggingConfiguration(this.config.logging, undefined);
|
|
73
|
+
// Recreate logger with updated config
|
|
74
|
+
this.logger = createFrameworkLogger('App');
|
|
138
75
|
}
|
|
139
|
-
|
|
76
|
+
// NOW initialize routing systems AFTER logger is configured
|
|
77
|
+
this.unifiedRouter = UnifiedRouter.getInstance();
|
|
78
|
+
this.intelligentRouting = new IntelligentRoutingManager();
|
|
79
|
+
this.logger.info(`Configuration system initialized: ${process.env.NODE_ENV || 'development'}:${this.config.server.port}`);
|
|
140
80
|
// Initialize runtime system
|
|
141
81
|
this.runtimeType = options.runtime?.type || 'node';
|
|
142
|
-
this.runtimeAdapter = options.runtime?.adapter ||
|
|
82
|
+
this.runtimeAdapter = options.runtime?.adapter || createRuntimeAdapter(this.runtimeType);
|
|
143
83
|
this.logger.info(`Runtime system initialized: ${this.runtimeType}`, 'Runtime');
|
|
144
|
-
// Pass
|
|
84
|
+
// Pass configuration from config to framework
|
|
145
85
|
const frameworkOptions = {
|
|
146
86
|
...options,
|
|
147
87
|
logger: this.config.logging,
|
|
88
|
+
// Enable websockets if either config has it enabled OR user passed websocket options
|
|
89
|
+
websocket: this.config.websocket.enabled || options.websocket
|
|
90
|
+
? options.websocket || this.config.websocket || {}
|
|
91
|
+
: false,
|
|
92
|
+
config: this.config,
|
|
148
93
|
};
|
|
149
|
-
this.coreFramework = new
|
|
94
|
+
this.coreFramework = new MoroCore(frameworkOptions);
|
|
150
95
|
// Initialize middleware system
|
|
151
|
-
this.middlewareManager = new
|
|
96
|
+
this.middlewareManager = new MiddlewareManager();
|
|
152
97
|
// Integrate hooks system with HTTP server
|
|
153
98
|
const httpServer = this.coreFramework.httpServer;
|
|
154
99
|
if (httpServer && httpServer.setHookManager) {
|
|
@@ -169,10 +114,10 @@ class Moro extends events_1.EventEmitter {
|
|
|
169
114
|
...this.getDefaultOptionsFromConfig(),
|
|
170
115
|
...options,
|
|
171
116
|
});
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
117
|
+
// Store auto-discovery options for later initialization
|
|
118
|
+
// IMPORTANT: Auto-discovery is deferred to ensure user middleware (like auth)
|
|
119
|
+
// is registered before module middleware that might bypass it
|
|
120
|
+
this.autoDiscoveryOptions = options.autoDiscover !== false ? options : null;
|
|
176
121
|
// Emit initialization event through enterprise event bus
|
|
177
122
|
this.eventBus.emit('framework:initialized', {
|
|
178
123
|
options,
|
|
@@ -213,44 +158,45 @@ class Moro extends events_1.EventEmitter {
|
|
|
213
158
|
// Direct route registration
|
|
214
159
|
return this.addRoute('GET', path, handler, options);
|
|
215
160
|
}
|
|
216
|
-
//
|
|
217
|
-
return this.
|
|
161
|
+
// Use unified router for chainable API
|
|
162
|
+
return this.unifiedRouter.get(path);
|
|
218
163
|
}
|
|
219
164
|
post(path, handler, options) {
|
|
220
165
|
if (handler) {
|
|
221
166
|
// Direct route registration
|
|
222
167
|
return this.addRoute('POST', path, handler, options);
|
|
223
168
|
}
|
|
224
|
-
//
|
|
225
|
-
return this.
|
|
169
|
+
// Use unified router for chainable API
|
|
170
|
+
return this.unifiedRouter.post(path);
|
|
226
171
|
}
|
|
227
172
|
put(path, handler, options) {
|
|
228
173
|
if (handler) {
|
|
229
174
|
// Direct route registration
|
|
230
175
|
return this.addRoute('PUT', path, handler, options);
|
|
231
176
|
}
|
|
232
|
-
//
|
|
233
|
-
return this.
|
|
177
|
+
// Use unified router for chainable API
|
|
178
|
+
return this.unifiedRouter.put(path);
|
|
234
179
|
}
|
|
235
180
|
delete(path, handler, options) {
|
|
236
181
|
if (handler) {
|
|
237
182
|
// Direct route registration
|
|
238
183
|
return this.addRoute('DELETE', path, handler, options);
|
|
239
184
|
}
|
|
240
|
-
//
|
|
241
|
-
return this.
|
|
185
|
+
// Use unified router for chainable API
|
|
186
|
+
return this.unifiedRouter.delete(path);
|
|
242
187
|
}
|
|
243
188
|
patch(path, handler, options) {
|
|
244
189
|
if (handler) {
|
|
245
190
|
// Direct route registration
|
|
246
191
|
return this.addRoute('PATCH', path, handler, options);
|
|
247
192
|
}
|
|
248
|
-
//
|
|
249
|
-
return this.
|
|
193
|
+
// Use unified router for chainable API
|
|
194
|
+
return this.unifiedRouter.patch(path);
|
|
250
195
|
}
|
|
251
196
|
// Schema-first route method
|
|
252
197
|
route(schema) {
|
|
253
|
-
|
|
198
|
+
// Use unified router for schema-first registration
|
|
199
|
+
this.unifiedRouter.route(schema);
|
|
254
200
|
}
|
|
255
201
|
// Enable automatic API documentation
|
|
256
202
|
enableDocs(config) {
|
|
@@ -338,6 +284,11 @@ class Moro extends events_1.EventEmitter {
|
|
|
338
284
|
version: moduleOrPath.version || '1.0.0',
|
|
339
285
|
});
|
|
340
286
|
}
|
|
287
|
+
// IMPORTANT: If modules are loaded manually after auto-discovery,
|
|
288
|
+
// ensure the final module handler is set up to maintain middleware order
|
|
289
|
+
if (this.autoDiscoveryInitialized) {
|
|
290
|
+
this.coreFramework.setupFinalModuleHandler();
|
|
291
|
+
}
|
|
341
292
|
return this;
|
|
342
293
|
}
|
|
343
294
|
// Database helper with events
|
|
@@ -351,29 +302,36 @@ class Moro extends events_1.EventEmitter {
|
|
|
351
302
|
}
|
|
352
303
|
// WebSocket helper with events
|
|
353
304
|
websocket(namespace, handlers) {
|
|
305
|
+
// Queue the registration to be processed after adapter initialization
|
|
306
|
+
const registration = { namespace, handlers, processed: false };
|
|
307
|
+
this.queuedWebSocketRegistrations.push(registration);
|
|
308
|
+
// Try to process immediately if adapter is already ready
|
|
354
309
|
const adapter = this.coreFramework.getWebSocketAdapter();
|
|
355
|
-
if (!
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
'new Moro({ websocket: { adapter: new SocketIOAdapter() } })');
|
|
310
|
+
if (adapter && !registration.processed) {
|
|
311
|
+
// Adapter is ready, process immediately
|
|
312
|
+
this.processWebSocketRegistration(namespace, handlers, adapter);
|
|
313
|
+
registration.processed = true;
|
|
360
314
|
}
|
|
315
|
+
// Otherwise, it will be processed when the server starts
|
|
316
|
+
return this;
|
|
317
|
+
}
|
|
318
|
+
processWebSocketRegistration(namespace, handlers, adapter) {
|
|
361
319
|
this.emit('websocket:registering', { namespace, handlers });
|
|
362
320
|
const ns = adapter.createNamespace(namespace);
|
|
363
321
|
Object.entries(handlers).forEach(([event, handler]) => {
|
|
364
|
-
ns.on('connection', socket => {
|
|
322
|
+
ns.on('connection', (socket) => {
|
|
365
323
|
this.emit('websocket:connection', { namespace, event, socket });
|
|
366
324
|
socket.on(event, (data, callback) => {
|
|
367
325
|
this.emit('websocket:event', { namespace, event, data });
|
|
368
326
|
Promise.resolve(handler(socket, data))
|
|
369
|
-
.then(result => {
|
|
327
|
+
.then((result) => {
|
|
370
328
|
this.emit('websocket:response', { namespace, event, result });
|
|
371
329
|
if (callback)
|
|
372
330
|
callback(result);
|
|
373
331
|
else if (result)
|
|
374
332
|
socket.emit(`${event}:response`, result);
|
|
375
333
|
})
|
|
376
|
-
.catch(error => {
|
|
334
|
+
.catch((error) => {
|
|
377
335
|
this.emit('websocket:error', { namespace, event, error });
|
|
378
336
|
const errorResponse = { success: false, error: error.message };
|
|
379
337
|
if (callback)
|
|
@@ -385,7 +343,33 @@ class Moro extends events_1.EventEmitter {
|
|
|
385
343
|
});
|
|
386
344
|
});
|
|
387
345
|
this.emit('websocket:registered', { namespace, handlers });
|
|
388
|
-
|
|
346
|
+
}
|
|
347
|
+
async processQueuedWebSocketRegistrations() {
|
|
348
|
+
// Wait for WebSocket adapter to be ready
|
|
349
|
+
await this.coreFramework.ensureWebSocketReady();
|
|
350
|
+
const adapter = this.coreFramework.getWebSocketAdapter();
|
|
351
|
+
// Check if any unprocessed registrations exist
|
|
352
|
+
const unprocessedRegistrations = this.queuedWebSocketRegistrations.filter(r => !r.processed);
|
|
353
|
+
if (!adapter && unprocessedRegistrations.length > 0) {
|
|
354
|
+
throw new Error('WebSocket features require a WebSocket adapter.\n\n' +
|
|
355
|
+
'Option 1: Install socket.io (auto-detected):\n' +
|
|
356
|
+
' npm install socket.io\n' +
|
|
357
|
+
' const app = new Moro({ websocket: {} });\n\n' +
|
|
358
|
+
'Option 2: Configure a specific adapter:\n' +
|
|
359
|
+
" import { SocketIOAdapter } from '@morojs/moro';\n" +
|
|
360
|
+
' const app = new Moro({ websocket: { adapter: new SocketIOAdapter() } });\n\n' +
|
|
361
|
+
'Option 3: Enable in config file (moro.config.js):\n' +
|
|
362
|
+
' export default { websocket: { enabled: true } };');
|
|
363
|
+
}
|
|
364
|
+
if (adapter) {
|
|
365
|
+
// Process all unprocessed registrations
|
|
366
|
+
for (const registration of this.queuedWebSocketRegistrations) {
|
|
367
|
+
if (!registration.processed) {
|
|
368
|
+
this.processWebSocketRegistration(registration.namespace, registration.handlers, adapter);
|
|
369
|
+
registration.processed = true;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
389
373
|
}
|
|
390
374
|
listen(portOrCallback, hostOrCallback, callback) {
|
|
391
375
|
// Only available for Node.js runtime
|
|
@@ -427,9 +411,18 @@ class Moro extends events_1.EventEmitter {
|
|
|
427
411
|
throw new Error('Port not specified and not found in configuration. Please provide a port number or configure it in moro.config.js/ts');
|
|
428
412
|
}
|
|
429
413
|
// Check if clustering is enabled for massive performance gains
|
|
414
|
+
// NOTE: uWebSockets.js does NOT support Node.js clustering - it's single-threaded only
|
|
415
|
+
const usingUWebSockets = this.config.server?.useUWebSockets || false;
|
|
430
416
|
if (this.config.performance?.clustering?.enabled) {
|
|
431
|
-
|
|
432
|
-
|
|
417
|
+
if (usingUWebSockets) {
|
|
418
|
+
this.logger.warn('Clustering is not supported with uWebSockets.js - running in single-threaded mode. ' +
|
|
419
|
+
'uWebSockets is so fast that single-threaded performance often exceeds multi-threaded Node.js!', 'Cluster');
|
|
420
|
+
// Continue without clustering
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
this.startWithClustering(port, host, callback);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
433
426
|
}
|
|
434
427
|
this.eventBus.emit('server:starting', { port, runtime: this.runtimeType });
|
|
435
428
|
// Add documentation middleware first (if enabled)
|
|
@@ -442,44 +435,168 @@ class Moro extends events_1.EventEmitter {
|
|
|
442
435
|
// Documentation not enabled, that's fine
|
|
443
436
|
this.logger.debug('Documentation not enabled', 'Documentation');
|
|
444
437
|
}
|
|
445
|
-
// Add
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
438
|
+
// Add unified routing middleware (handles both chainable and direct routes)
|
|
439
|
+
// Optimized: call router without extra async wrapper when possible
|
|
440
|
+
this.coreFramework.addMiddleware((req, res, next) => {
|
|
441
|
+
// Try unified router first (handles all route types)
|
|
442
|
+
const handled = this.unifiedRouter.handleRequest(req, res);
|
|
443
|
+
// Check if it's a promise (async route) or sync
|
|
444
|
+
if (handled && typeof handled.then === 'function') {
|
|
445
|
+
// Async - await the result
|
|
446
|
+
handled
|
|
447
|
+
.then(isHandled => {
|
|
448
|
+
if (!isHandled) {
|
|
449
|
+
next(); // Fall back to legacy routes if any
|
|
450
|
+
}
|
|
451
|
+
})
|
|
452
|
+
.catch(() => next());
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
// Sync - check immediately
|
|
456
|
+
if (!handled) {
|
|
457
|
+
next();
|
|
458
|
+
}
|
|
451
459
|
}
|
|
452
460
|
});
|
|
453
|
-
// Register direct routes with the HTTP server
|
|
461
|
+
// Register legacy direct routes with the HTTP server (for backward compatibility)
|
|
454
462
|
if (this.routes.length > 0) {
|
|
455
463
|
this.registerDirectRoutes();
|
|
456
464
|
}
|
|
457
|
-
const
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
this.
|
|
465
|
+
const startServer = () => {
|
|
466
|
+
const actualCallback = () => {
|
|
467
|
+
const displayHost = host || 'localhost';
|
|
468
|
+
this.logger.info('Moro Server Started', 'Server');
|
|
469
|
+
this.logger.info(`Runtime: ${this.runtimeType}`, 'Server');
|
|
470
|
+
this.logger.info(`HTTP API: http://${displayHost}:${port}`, 'Server');
|
|
471
|
+
if (this.config.websocket.enabled) {
|
|
472
|
+
this.logger.info(`WebSocket: ws://${displayHost}:${port}`, 'Server');
|
|
473
|
+
}
|
|
474
|
+
this.logger.info('Learn more at https://morojs.com', 'Server');
|
|
475
|
+
// Log unified router stats
|
|
476
|
+
const routeCount = this.unifiedRouter.getRouteCount();
|
|
477
|
+
if (routeCount > 0) {
|
|
478
|
+
this.logger.info(`Unified Router: ${routeCount} routes registered`, 'Server');
|
|
479
|
+
// Log performance stats
|
|
480
|
+
this.unifiedRouter.logPerformanceStats();
|
|
481
|
+
}
|
|
482
|
+
this.eventBus.emit('server:started', { port, runtime: this.runtimeType });
|
|
483
|
+
if (callback)
|
|
484
|
+
callback();
|
|
485
|
+
};
|
|
486
|
+
if (host && typeof host === 'string') {
|
|
487
|
+
this.coreFramework.listen(port, host, actualCallback);
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
this.coreFramework.listen(port, actualCallback);
|
|
469
491
|
}
|
|
470
|
-
this.eventBus.emit('server:started', { port, runtime: this.runtimeType });
|
|
471
|
-
if (callback)
|
|
472
|
-
callback();
|
|
473
492
|
};
|
|
474
|
-
|
|
475
|
-
|
|
493
|
+
// Ensure auto-discovery and WebSocket setup is complete before starting server
|
|
494
|
+
Promise.all([this.ensureAutoDiscoveryComplete(), this.processQueuedWebSocketRegistrations()])
|
|
495
|
+
.then(() => {
|
|
496
|
+
startServer();
|
|
497
|
+
})
|
|
498
|
+
.catch(error => {
|
|
499
|
+
this.logger.error('Initialization failed during server start', 'Framework', {
|
|
500
|
+
error: error instanceof Error ? error.message : String(error),
|
|
501
|
+
});
|
|
502
|
+
// For auto-discovery failures, start server anyway
|
|
503
|
+
// For WebSocket failures with queued registrations, error will propagate
|
|
504
|
+
if (error instanceof Error &&
|
|
505
|
+
error.message.includes('WebSocket features require a WebSocket adapter')) {
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
startServer();
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
// Public method to manually initialize auto-discovery
|
|
512
|
+
// Useful for ensuring auth middleware is registered before auto-discovery
|
|
513
|
+
async initializeAutoDiscoveryNow() {
|
|
514
|
+
return this.ensureAutoDiscoveryComplete();
|
|
515
|
+
}
|
|
516
|
+
// Public API: Initialize modules explicitly after middleware setup
|
|
517
|
+
// This provides users with explicit control over module loading timing
|
|
518
|
+
// IMPORTANT: This forces module loading even if autoDiscovery.enabled is false
|
|
519
|
+
// Usage: app.initModules() or app.initModules({ paths: ['./my-modules'] })
|
|
520
|
+
initModules(options) {
|
|
521
|
+
this.logger.info('User-requested module initialization', 'ModuleSystem');
|
|
522
|
+
// If already initialized, do nothing
|
|
523
|
+
if (this.autoDiscoveryInitialized) {
|
|
524
|
+
this.logger.debug('Auto-discovery already completed, skipping', 'ModuleSystem');
|
|
525
|
+
return;
|
|
476
526
|
}
|
|
477
|
-
|
|
478
|
-
|
|
527
|
+
// Store the options and mark that we want to force initialization
|
|
528
|
+
this.autoDiscoveryOptions = {
|
|
529
|
+
autoDiscover: {
|
|
530
|
+
enabled: true, // Force enabled regardless of original config
|
|
531
|
+
paths: options?.paths || ['./modules', './src/modules'],
|
|
532
|
+
patterns: options?.patterns || [
|
|
533
|
+
'**/*.module.{ts,js}',
|
|
534
|
+
'**/index.{ts,js}',
|
|
535
|
+
'**/*.config.{ts,js}',
|
|
536
|
+
],
|
|
537
|
+
recursive: options?.recursive ?? true,
|
|
538
|
+
loadingStrategy: options?.loadingStrategy || 'eager',
|
|
539
|
+
watchForChanges: options?.watchForChanges ?? false,
|
|
540
|
+
ignorePatterns: options?.ignorePatterns || [
|
|
541
|
+
'**/*.test.{ts,js}',
|
|
542
|
+
'**/*.spec.{ts,js}',
|
|
543
|
+
'**/node_modules/**',
|
|
544
|
+
],
|
|
545
|
+
loadOrder: options?.loadOrder || 'dependency',
|
|
546
|
+
failOnError: options?.failOnError ?? false,
|
|
547
|
+
maxDepth: options?.maxDepth ?? 5,
|
|
548
|
+
},
|
|
549
|
+
};
|
|
550
|
+
this.logger.debug('Module initialization options stored, will execute on next listen/getHandler call', 'ModuleSystem');
|
|
551
|
+
}
|
|
552
|
+
// Robust method to ensure auto-discovery is complete, handling race conditions
|
|
553
|
+
async ensureAutoDiscoveryComplete() {
|
|
554
|
+
// If already initialized, nothing to do
|
|
555
|
+
if (this.autoDiscoveryInitialized) {
|
|
556
|
+
return;
|
|
479
557
|
}
|
|
558
|
+
// If auto-discovery is disabled, mark as initialized
|
|
559
|
+
if (!this.autoDiscoveryOptions) {
|
|
560
|
+
this.autoDiscoveryInitialized = true;
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
// If already in progress, wait for it to complete
|
|
564
|
+
if (this.autoDiscoveryPromise) {
|
|
565
|
+
return this.autoDiscoveryPromise;
|
|
566
|
+
}
|
|
567
|
+
// Start auto-discovery
|
|
568
|
+
this.autoDiscoveryPromise = this.performAutoDiscovery();
|
|
569
|
+
try {
|
|
570
|
+
await this.autoDiscoveryPromise;
|
|
571
|
+
this.autoDiscoveryInitialized = true;
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
// Reset promise on error so it can be retried
|
|
575
|
+
this.autoDiscoveryPromise = null;
|
|
576
|
+
throw error;
|
|
577
|
+
}
|
|
578
|
+
finally {
|
|
579
|
+
this.autoDiscoveryOptions = null; // Clear after attempt
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
// Perform the actual auto-discovery work
|
|
583
|
+
async performAutoDiscovery(optionsOverride) {
|
|
584
|
+
const optionsToUse = optionsOverride || this.autoDiscoveryOptions;
|
|
585
|
+
if (!optionsToUse)
|
|
586
|
+
return;
|
|
587
|
+
this.logger.debug('Starting auto-discovery initialization', 'AutoDiscovery');
|
|
588
|
+
await this.initializeAutoDiscovery(optionsToUse);
|
|
589
|
+
this.logger.debug('Auto-discovery initialization completed', 'AutoDiscovery');
|
|
480
590
|
}
|
|
481
591
|
// Get handler for non-Node.js runtimes
|
|
482
592
|
getHandler() {
|
|
593
|
+
// Ensure auto-discovery is complete for non-Node.js runtimes
|
|
594
|
+
// This handles the case where users call getHandler() immediately after createApp()
|
|
595
|
+
this.ensureAutoDiscoveryComplete().catch(error => {
|
|
596
|
+
this.logger.error('Auto-discovery initialization failed for runtime handler', 'Framework', {
|
|
597
|
+
error: error instanceof Error ? error.message : String(error),
|
|
598
|
+
});
|
|
599
|
+
});
|
|
483
600
|
// Create a unified request handler that works with the runtime adapter
|
|
484
601
|
const handler = async (req, res) => {
|
|
485
602
|
// Add documentation middleware first (if enabled)
|
|
@@ -492,11 +609,11 @@ class Moro extends events_1.EventEmitter {
|
|
|
492
609
|
catch (error) {
|
|
493
610
|
// Documentation not enabled, that's fine
|
|
494
611
|
}
|
|
495
|
-
// Try
|
|
496
|
-
const handled = await this.
|
|
612
|
+
// Try unified router first (handles all routes)
|
|
613
|
+
const handled = await this.unifiedRouter.handleRequest(req, res);
|
|
497
614
|
if (handled)
|
|
498
615
|
return;
|
|
499
|
-
// Handle direct routes
|
|
616
|
+
// Handle legacy direct routes (backward compatibility)
|
|
500
617
|
if (this.routes.length > 0) {
|
|
501
618
|
await this.handleDirectRoutes(req, res);
|
|
502
619
|
}
|
|
@@ -644,6 +761,9 @@ class Moro extends events_1.EventEmitter {
|
|
|
644
761
|
}
|
|
645
762
|
// Private methods
|
|
646
763
|
addRoute(method, path, handler, options = {}) {
|
|
764
|
+
// Register with unified router (primary routing system)
|
|
765
|
+
this.unifiedRouter.addRoute(method, path, handler, options.middleware || []);
|
|
766
|
+
// Also store in legacy routes array for backward compatibility
|
|
647
767
|
const handlerName = `handler_${this.routes.length}`;
|
|
648
768
|
const route = {
|
|
649
769
|
method: method,
|
|
@@ -655,9 +775,9 @@ class Moro extends events_1.EventEmitter {
|
|
|
655
775
|
middleware: options.middleware,
|
|
656
776
|
};
|
|
657
777
|
this.routes.push(route);
|
|
658
|
-
// Organize routes for optimal lookup
|
|
778
|
+
// Organize routes for optimal lookup (legacy)
|
|
659
779
|
this.organizeRouteForLookup(route);
|
|
660
|
-
// Store handler for later module creation
|
|
780
|
+
// Store handler for later module creation (legacy)
|
|
661
781
|
this.routeHandlers[handlerName] = handler;
|
|
662
782
|
return this;
|
|
663
783
|
}
|
|
@@ -697,7 +817,6 @@ class Moro extends events_1.EventEmitter {
|
|
|
697
817
|
}
|
|
698
818
|
catch (error) {
|
|
699
819
|
// Handle universal validation errors
|
|
700
|
-
const { normalizeValidationError } = require('./core/validation/schema-interface');
|
|
701
820
|
const normalizedError = normalizeValidationError(error);
|
|
702
821
|
res.status(400).json({
|
|
703
822
|
success: false,
|
|
@@ -767,91 +886,266 @@ class Moro extends events_1.EventEmitter {
|
|
|
767
886
|
return true;
|
|
768
887
|
}
|
|
769
888
|
setupDefaultMiddleware(options) {
|
|
770
|
-
// CORS
|
|
771
|
-
if (options.cors
|
|
772
|
-
const corsOptions = typeof options.cors === 'object'
|
|
773
|
-
|
|
889
|
+
// CORS - check config enabled property OR options.security.cors.enabled === true
|
|
890
|
+
if (this.config.security.cors.enabled || options.security?.cors?.enabled === true) {
|
|
891
|
+
const corsOptions = typeof options.cors === 'object'
|
|
892
|
+
? options.cors
|
|
893
|
+
: this.config.security.cors
|
|
894
|
+
? this.config.security.cors
|
|
895
|
+
: {};
|
|
896
|
+
this.use(middleware.cors(corsOptions));
|
|
774
897
|
}
|
|
775
|
-
// Helmet
|
|
776
|
-
if (options.helmet
|
|
777
|
-
this.use(
|
|
898
|
+
// Helmet - check config enabled property OR options.security.helmet.enabled === true
|
|
899
|
+
if (this.config.security.helmet.enabled || options.security?.helmet?.enabled === true) {
|
|
900
|
+
this.use(middleware.helmet());
|
|
778
901
|
}
|
|
779
|
-
// Compression
|
|
780
|
-
if (
|
|
781
|
-
|
|
782
|
-
|
|
902
|
+
// Compression - check config enabled property OR options.performance.compression.enabled === true
|
|
903
|
+
if (this.config.performance.compression.enabled ||
|
|
904
|
+
options.performance?.compression?.enabled === true) {
|
|
905
|
+
const compressionOptions = typeof options.compression === 'object'
|
|
906
|
+
? options.compression
|
|
907
|
+
: this.config.performance.compression
|
|
908
|
+
? this.config.performance.compression
|
|
909
|
+
: {};
|
|
910
|
+
this.use(middleware.compression(compressionOptions));
|
|
783
911
|
}
|
|
784
912
|
// Body size limiting
|
|
785
|
-
this.use(
|
|
913
|
+
this.use(middleware.bodySize({ limit: '10mb' }));
|
|
786
914
|
}
|
|
787
|
-
|
|
915
|
+
// Enhanced auto-discovery initialization
|
|
916
|
+
async initializeAutoDiscovery(options) {
|
|
917
|
+
const { ModuleDiscovery } = await import('./core/modules/auto-discovery.js');
|
|
918
|
+
// Merge auto-discovery configuration
|
|
919
|
+
const autoDiscoveryConfig = this.mergeAutoDiscoveryConfig(options);
|
|
920
|
+
if (!autoDiscoveryConfig.enabled) {
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
this.moduleDiscovery = new ModuleDiscovery(process.cwd());
|
|
788
924
|
try {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
925
|
+
// Discover modules based on configuration
|
|
926
|
+
const modules = await this.moduleDiscovery.discoverModulesAdvanced(autoDiscoveryConfig);
|
|
927
|
+
// Load modules based on strategy
|
|
928
|
+
await this.loadDiscoveredModules(modules, autoDiscoveryConfig);
|
|
929
|
+
// Setup final module handler to run after user middleware (like auth)
|
|
930
|
+
this.coreFramework.setupFinalModuleHandler();
|
|
931
|
+
// Setup file watching if enabled
|
|
932
|
+
if (autoDiscoveryConfig.watchForChanges) {
|
|
933
|
+
this.moduleDiscovery.watchModulesAdvanced(autoDiscoveryConfig, async (updatedModules) => {
|
|
934
|
+
await this.handleModuleChanges(updatedModules);
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
this.logger.info(`Auto-discovery completed: ${modules.length} modules loaded`, 'ModuleDiscovery');
|
|
938
|
+
}
|
|
939
|
+
catch (error) {
|
|
940
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
941
|
+
if (autoDiscoveryConfig.failOnError) {
|
|
942
|
+
throw new Error(`Module auto-discovery failed: ${errorMsg}`);
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
this.logger.warn(`Module auto-discovery failed: ${errorMsg}`, 'ModuleDiscovery');
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
// Merge auto-discovery configuration from multiple sources
|
|
950
|
+
mergeAutoDiscoveryConfig(options) {
|
|
951
|
+
const defaultConfig = this.config.modules.autoDiscovery;
|
|
952
|
+
// Handle legacy modulesPath option
|
|
953
|
+
if (options.modulesPath && !options.autoDiscover) {
|
|
954
|
+
return {
|
|
955
|
+
...defaultConfig,
|
|
956
|
+
paths: [options.modulesPath],
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
// Handle boolean autoDiscover option
|
|
960
|
+
if (typeof options.autoDiscover === 'boolean') {
|
|
961
|
+
return {
|
|
962
|
+
...defaultConfig,
|
|
963
|
+
enabled: options.autoDiscover,
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
// Handle object autoDiscover option
|
|
967
|
+
if (typeof options.autoDiscover === 'object') {
|
|
968
|
+
return {
|
|
969
|
+
...defaultConfig,
|
|
970
|
+
...options.autoDiscover,
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
return defaultConfig;
|
|
974
|
+
}
|
|
975
|
+
// Load discovered modules based on strategy
|
|
976
|
+
async loadDiscoveredModules(modules, config) {
|
|
977
|
+
switch (config.loadingStrategy) {
|
|
978
|
+
case 'eager':
|
|
979
|
+
// Load all modules immediately
|
|
980
|
+
for (const module of modules) {
|
|
981
|
+
await this.loadModule(module);
|
|
982
|
+
}
|
|
983
|
+
break;
|
|
984
|
+
case 'lazy':
|
|
985
|
+
// Register modules for lazy loading
|
|
986
|
+
this.registerLazyModules(modules);
|
|
987
|
+
break;
|
|
988
|
+
case 'conditional':
|
|
989
|
+
// Load modules based on conditions
|
|
990
|
+
await this.loadConditionalModules(modules);
|
|
991
|
+
break;
|
|
992
|
+
default:
|
|
993
|
+
// Default to eager loading
|
|
994
|
+
for (const module of modules) {
|
|
995
|
+
await this.loadModule(module);
|
|
804
996
|
}
|
|
805
|
-
});
|
|
806
997
|
}
|
|
807
|
-
|
|
808
|
-
|
|
998
|
+
}
|
|
999
|
+
// Register modules for lazy loading
|
|
1000
|
+
registerLazyModules(modules) {
|
|
1001
|
+
modules.forEach(module => {
|
|
1002
|
+
// Store module for lazy loading when first route is accessed
|
|
1003
|
+
this.lazyModules.set(module.name, module);
|
|
1004
|
+
// Register placeholder routes that trigger lazy loading
|
|
1005
|
+
if (module.routes) {
|
|
1006
|
+
module.routes.forEach(route => {
|
|
1007
|
+
const basePath = `/api/v${module.version}/${module.name}`;
|
|
1008
|
+
const fullPath = `${basePath}${route.path}`;
|
|
1009
|
+
// Note: Lazy loading will be implemented when route is accessed
|
|
1010
|
+
// For now, we'll store the module for later loading
|
|
1011
|
+
this.logger.debug(`Registered lazy route: ${route.method} ${fullPath}`, 'ModuleDiscovery');
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
this.logger.info(`Registered ${modules.length} modules for lazy loading`, 'ModuleDiscovery');
|
|
1016
|
+
}
|
|
1017
|
+
// Load modules conditionally based on environment or configuration
|
|
1018
|
+
async loadConditionalModules(modules) {
|
|
1019
|
+
for (const module of modules) {
|
|
1020
|
+
const shouldLoad = this.shouldLoadModule(module);
|
|
1021
|
+
if (shouldLoad) {
|
|
1022
|
+
await this.loadModule(module);
|
|
1023
|
+
}
|
|
1024
|
+
else {
|
|
1025
|
+
this.logger.debug(`Skipping module ${module.name} due to conditions`, 'ModuleDiscovery');
|
|
1026
|
+
}
|
|
809
1027
|
}
|
|
810
1028
|
}
|
|
1029
|
+
// Determine if a module should be loaded based on conditions
|
|
1030
|
+
shouldLoadModule(module) {
|
|
1031
|
+
const moduleConfig = module.config;
|
|
1032
|
+
// Check environment conditions
|
|
1033
|
+
if (moduleConfig?.conditions?.environment) {
|
|
1034
|
+
const requiredEnv = moduleConfig.conditions.environment;
|
|
1035
|
+
const currentEnv = process.env.NODE_ENV || 'development';
|
|
1036
|
+
if (Array.isArray(requiredEnv)) {
|
|
1037
|
+
if (!requiredEnv.includes(currentEnv)) {
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
else if (requiredEnv !== currentEnv) {
|
|
1042
|
+
return false;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
// Check feature flags
|
|
1046
|
+
if (moduleConfig?.conditions?.features) {
|
|
1047
|
+
const requiredFeatures = moduleConfig.conditions.features;
|
|
1048
|
+
for (const feature of requiredFeatures) {
|
|
1049
|
+
if (!process.env[`FEATURE_${feature.toUpperCase()}`]) {
|
|
1050
|
+
return false;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
// Check custom conditions
|
|
1055
|
+
if (moduleConfig?.conditions?.custom) {
|
|
1056
|
+
const customCondition = moduleConfig.conditions.custom;
|
|
1057
|
+
if (typeof customCondition === 'function') {
|
|
1058
|
+
return customCondition();
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
return true;
|
|
1062
|
+
}
|
|
1063
|
+
// Handle module changes during development
|
|
1064
|
+
async handleModuleChanges(modules) {
|
|
1065
|
+
this.logger.info('Module changes detected, reloading...', 'ModuleDiscovery');
|
|
1066
|
+
// Unload existing modules (if supported)
|
|
1067
|
+
// For now, just log the change
|
|
1068
|
+
this.eventBus.emit('modules:changed', {
|
|
1069
|
+
modules: modules.map(m => ({ name: m.name, version: m.version })),
|
|
1070
|
+
timestamp: new Date(),
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
// Legacy method for backward compatibility
|
|
1074
|
+
autoDiscoverModules(modulesPath) {
|
|
1075
|
+
// Redirect to new system
|
|
1076
|
+
this.initializeAutoDiscovery({
|
|
1077
|
+
autoDiscover: {
|
|
1078
|
+
enabled: true,
|
|
1079
|
+
paths: [modulesPath],
|
|
1080
|
+
},
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
811
1083
|
async importModule(modulePath) {
|
|
812
|
-
const module = await
|
|
1084
|
+
const module = await import(modulePath);
|
|
813
1085
|
return module.default || module;
|
|
814
1086
|
}
|
|
815
|
-
|
|
1087
|
+
/**
|
|
1088
|
+
* Node.js Clustering Implementation
|
|
1089
|
+
* This clustering algorithm is based on published research and Node.js best practices.
|
|
1090
|
+
*
|
|
1091
|
+
* IPC (Inter-Process Communication) Considerations:
|
|
1092
|
+
* - Excessive workers create IPC bottlenecks (Source: BetterStack Node.js Guide)
|
|
1093
|
+
* - Round-robin scheduling provides better load distribution (Node.js Documentation)
|
|
1094
|
+
* - Message passing overhead increases significantly with worker count
|
|
1095
|
+
*
|
|
1096
|
+
* Memory Management:
|
|
1097
|
+
* - ~2GB per worker prevents memory pressure and GC overhead
|
|
1098
|
+
* - Conservative heap limits reduce memory fragmentation
|
|
1099
|
+
*
|
|
1100
|
+
* References:
|
|
1101
|
+
* - Node.js Cluster Documentation: https://nodejs.org/api/cluster.html
|
|
1102
|
+
* - BetterStack Node.js Clustering: https://betterstack.com/community/guides/scaling-nodejs/node-clustering/
|
|
1103
|
+
*/
|
|
816
1104
|
clusterWorkers = new Map();
|
|
817
1105
|
startWithClustering(port, host, callback) {
|
|
818
|
-
|
|
819
|
-
const os = require('os');
|
|
820
|
-
// Smart worker count calculation based on actual bottlenecks
|
|
1106
|
+
// Worker count calculation - respect user choice
|
|
821
1107
|
let workerCount = this.config.performance?.clustering?.workers || os.cpus().length;
|
|
822
|
-
//
|
|
823
|
-
if (workerCount === 'auto'
|
|
824
|
-
// For high-core machines, limit workers to prevent IPC/memory bottlenecks
|
|
1108
|
+
// Only auto-optimize if user hasn't specified a number or set it to 'auto'
|
|
1109
|
+
if (workerCount === 'auto') {
|
|
825
1110
|
const cpuCount = os.cpus().length;
|
|
826
1111
|
const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);
|
|
827
|
-
//
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
// Mid-range machines: balanced approach
|
|
834
|
-
workerCount = Math.min(cpuCount / 2, 4);
|
|
1112
|
+
// Get memory per worker from config - if not set by user, calculate dynamically
|
|
1113
|
+
let memoryPerWorkerGB = this.config.performance?.clustering?.memoryPerWorkerGB;
|
|
1114
|
+
if (!memoryPerWorkerGB) {
|
|
1115
|
+
// Dynamic calculation: (Total RAM - 4GB headroom) / CPU cores
|
|
1116
|
+
const headroomGB = 4;
|
|
1117
|
+
memoryPerWorkerGB = Math.max(0.5, Math.floor((totalMemoryGB - headroomGB) / cpuCount));
|
|
835
1118
|
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1119
|
+
// Conservative formula based on general guidelines:
|
|
1120
|
+
// - Don't exceed CPU cores
|
|
1121
|
+
// - Respect user's memory allocation preference
|
|
1122
|
+
// - Let the system resources determine the limit
|
|
1123
|
+
workerCount = Math.min(cpuCount, // Don't exceed CPU cores
|
|
1124
|
+
Math.floor(totalMemoryGB / memoryPerWorkerGB) // User-configurable memory per worker
|
|
1125
|
+
);
|
|
1126
|
+
this.logger.info(`Auto-calculated worker count: ${workerCount} (CPU: ${cpuCount}, RAM: ${totalMemoryGB.toFixed(1)}GB, ${memoryPerWorkerGB}GB per worker)`, 'Cluster');
|
|
1127
|
+
}
|
|
1128
|
+
else if (typeof workerCount === 'number') {
|
|
1129
|
+
// User specified a number - respect their choice
|
|
1130
|
+
this.logger.info(`Using user-specified worker count: ${workerCount}`, 'Cluster');
|
|
841
1131
|
}
|
|
842
1132
|
if (cluster.isPrimary) {
|
|
843
|
-
this.logger.info(
|
|
1133
|
+
this.logger.info(`Starting ${workerCount} workers`, 'Cluster');
|
|
844
1134
|
// Optimize cluster scheduling for high concurrency
|
|
845
|
-
|
|
1135
|
+
// Round-robin is the default on all platforms except Windows (Node.js docs)
|
|
1136
|
+
// Provides better load distribution than shared socket approach
|
|
1137
|
+
cluster.schedulingPolicy = cluster.SCHED_RR;
|
|
846
1138
|
// Set cluster settings for better performance
|
|
847
1139
|
cluster.setupMaster({
|
|
848
|
-
exec: process.argv[1],
|
|
1140
|
+
exec: process.argv[1] || process.execPath,
|
|
849
1141
|
args: process.argv.slice(2),
|
|
850
1142
|
silent: false,
|
|
851
1143
|
});
|
|
852
|
-
//
|
|
1144
|
+
// IPC Optimization: Reduce communication overhead between master and workers
|
|
1145
|
+
// Research shows excessive IPC can create bottlenecks in clustered applications
|
|
1146
|
+
// (Source: BetterStack - Node.js Clustering Guide)
|
|
853
1147
|
process.env.NODE_CLUSTER_SCHED_POLICY = 'rr'; // Ensure round-robin
|
|
854
|
-
process.env.NODE_DISABLE_COLORS = '1'; // Reduce IPC message size
|
|
1148
|
+
process.env.NODE_DISABLE_COLORS = '1'; // Reduce IPC message size by disabling color codes
|
|
855
1149
|
// Graceful shutdown handler
|
|
856
1150
|
const gracefulShutdown = () => {
|
|
857
1151
|
this.logger.info('Gracefully shutting down cluster...', 'Cluster');
|
|
@@ -867,27 +1161,25 @@ class Moro extends events_1.EventEmitter {
|
|
|
867
1161
|
// Handle process signals for graceful shutdown
|
|
868
1162
|
process.on('SIGINT', gracefulShutdown);
|
|
869
1163
|
process.on('SIGTERM', gracefulShutdown);
|
|
870
|
-
// Fork workers with
|
|
1164
|
+
// Fork workers with basic tracking
|
|
871
1165
|
for (let i = 0; i < workerCount; i++) {
|
|
872
|
-
const worker = cluster.fork(
|
|
873
|
-
WORKER_ID: i,
|
|
874
|
-
WORKER_CPU_AFFINITY: i % os.cpus().length, // Distribute workers across CPUs
|
|
875
|
-
});
|
|
1166
|
+
const worker = cluster.fork();
|
|
876
1167
|
this.clusterWorkers.set(worker.process.pid, worker);
|
|
877
|
-
this.logger.info(`Worker ${worker.process.pid} started
|
|
878
|
-
// Handle individual worker messages
|
|
1168
|
+
this.logger.info(`Worker ${worker.process.pid} started`, 'Cluster');
|
|
1169
|
+
// Handle individual worker messages
|
|
879
1170
|
worker.on('message', this.handleWorkerMessage.bind(this));
|
|
880
1171
|
}
|
|
881
|
-
//
|
|
1172
|
+
// Simple worker exit handling
|
|
882
1173
|
cluster.on('exit', (worker, code, signal) => {
|
|
883
|
-
|
|
884
|
-
this.clusterWorkers.delete(
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
1174
|
+
const pid = worker.process.pid;
|
|
1175
|
+
this.clusterWorkers.delete(pid);
|
|
1176
|
+
if (code !== 0 && !worker.exitedAfterDisconnect) {
|
|
1177
|
+
this.logger.warn(`Worker ${pid} died unexpectedly (${signal || code}). Restarting...`, 'Cluster');
|
|
1178
|
+
// Simple restart
|
|
1179
|
+
const newWorker = cluster.fork();
|
|
1180
|
+
this.clusterWorkers.set(newWorker.process.pid, newWorker);
|
|
1181
|
+
this.logger.info(`Worker ${newWorker.process.pid} restarted`, 'Cluster');
|
|
1182
|
+
}
|
|
891
1183
|
});
|
|
892
1184
|
// Master process callback
|
|
893
1185
|
if (callback)
|
|
@@ -899,15 +1191,24 @@ class Moro extends events_1.EventEmitter {
|
|
|
899
1191
|
// Worker-specific optimizations for high concurrency
|
|
900
1192
|
process.env.UV_THREADPOOL_SIZE = '64';
|
|
901
1193
|
// Reduce logging contention in workers (major bottleneck)
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1194
|
+
// Multiple workers writing to same log files creates I/O contention
|
|
1195
|
+
// ONLY reduce log level if user didn't explicitly set one
|
|
1196
|
+
if (!this.userSetLogger) {
|
|
1197
|
+
// Workers log less frequently to reduce I/O contention (only if not explicitly configured)
|
|
1198
|
+
applyLoggingConfiguration(undefined, { level: 'warn' }); // Only warnings and errors
|
|
905
1199
|
}
|
|
906
|
-
//
|
|
907
|
-
|
|
908
|
-
|
|
1200
|
+
// Research-based memory optimization for workers
|
|
1201
|
+
const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);
|
|
1202
|
+
const workerCount = Object.keys(cluster.workers || {}).length || 1;
|
|
1203
|
+
// Conservative memory allocation
|
|
1204
|
+
const heapSizePerWorkerMB = Math.min(Math.floor(((totalMemoryGB * 1024) / workerCount) * 0.8), // 80% of available memory
|
|
1205
|
+
1536 // Cap at 1.5GB (GC efficiency threshold from research)
|
|
1206
|
+
);
|
|
1207
|
+
process.env.NODE_OPTIONS = `--max-old-space-size=${heapSizePerWorkerMB}`;
|
|
1208
|
+
this.logger.debug(`Worker memory allocated: ${heapSizePerWorkerMB}MB heap (${workerCount} workers, ${totalMemoryGB.toFixed(1)}GB total)`, 'Worker');
|
|
1209
|
+
// Optimize V8 flags for better performance
|
|
909
1210
|
if (process.env.NODE_ENV === 'production') {
|
|
910
|
-
//
|
|
1211
|
+
// Aggressive V8 optimizations for maximum performance
|
|
911
1212
|
const v8Flags = [
|
|
912
1213
|
'--optimize-for-size', // Trade memory for speed
|
|
913
1214
|
'--always-opt', // Always optimize functions
|
|
@@ -960,14 +1261,30 @@ class Moro extends events_1.EventEmitter {
|
|
|
960
1261
|
catch (error) {
|
|
961
1262
|
// Documentation not enabled, that's fine
|
|
962
1263
|
}
|
|
963
|
-
// Add
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1264
|
+
// Add unified routing middleware (handles both chainable and direct routes)
|
|
1265
|
+
// Optimized: call router without extra async wrapper when possible
|
|
1266
|
+
this.coreFramework.addMiddleware((req, res, next) => {
|
|
1267
|
+
// Try unified router first (handles all route types)
|
|
1268
|
+
const handled = this.unifiedRouter.handleRequest(req, res);
|
|
1269
|
+
// Check if it's a promise (async route) or sync
|
|
1270
|
+
if (handled && typeof handled.then === 'function') {
|
|
1271
|
+
// Async - await the result
|
|
1272
|
+
handled
|
|
1273
|
+
.then(isHandled => {
|
|
1274
|
+
if (!isHandled) {
|
|
1275
|
+
next(); // Fall back to legacy routes if any
|
|
1276
|
+
}
|
|
1277
|
+
})
|
|
1278
|
+
.catch(() => next());
|
|
1279
|
+
}
|
|
1280
|
+
else {
|
|
1281
|
+
// Sync - check immediately
|
|
1282
|
+
if (!handled) {
|
|
1283
|
+
next();
|
|
1284
|
+
}
|
|
968
1285
|
}
|
|
969
1286
|
});
|
|
970
|
-
// Register direct routes
|
|
1287
|
+
// Register legacy direct routes with the HTTP server (for backward compatibility)
|
|
971
1288
|
if (this.routes.length > 0) {
|
|
972
1289
|
this.registerDirectRoutes();
|
|
973
1290
|
}
|
|
@@ -980,15 +1297,36 @@ class Moro extends events_1.EventEmitter {
|
|
|
980
1297
|
worker: process.pid,
|
|
981
1298
|
});
|
|
982
1299
|
};
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1300
|
+
// Ensure WebSocket setup is complete before starting worker
|
|
1301
|
+
this.processQueuedWebSocketRegistrations()
|
|
1302
|
+
.then(() => {
|
|
1303
|
+
if (host) {
|
|
1304
|
+
this.coreFramework.listen(port, host, workerCallback);
|
|
1305
|
+
}
|
|
1306
|
+
else {
|
|
1307
|
+
this.coreFramework.listen(port, workerCallback);
|
|
1308
|
+
}
|
|
1309
|
+
})
|
|
1310
|
+
.catch(error => {
|
|
1311
|
+
this.logger.error('WebSocket initialization failed in worker', 'Worker', {
|
|
1312
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1313
|
+
});
|
|
1314
|
+
// For WebSocket failures with queued registrations, error will propagate
|
|
1315
|
+
if (error instanceof Error &&
|
|
1316
|
+
error.message.includes('WebSocket features require a WebSocket adapter')) {
|
|
1317
|
+
throw error;
|
|
1318
|
+
}
|
|
1319
|
+
// Start anyway for other errors
|
|
1320
|
+
if (host) {
|
|
1321
|
+
this.coreFramework.listen(port, host, workerCallback);
|
|
1322
|
+
}
|
|
1323
|
+
else {
|
|
1324
|
+
this.coreFramework.listen(port, workerCallback);
|
|
1325
|
+
}
|
|
1326
|
+
});
|
|
989
1327
|
}
|
|
990
1328
|
}
|
|
991
|
-
//
|
|
1329
|
+
// Simple worker message handler
|
|
992
1330
|
handleWorkerMessage(message) {
|
|
993
1331
|
// Handle inter-worker communication if needed
|
|
994
1332
|
if (message.type === 'health-check') {
|
|
@@ -998,32 +1336,81 @@ class Moro extends events_1.EventEmitter {
|
|
|
998
1336
|
// Log other worker messages
|
|
999
1337
|
this.logger.debug(`Worker message: ${JSON.stringify(message)}`, 'Cluster');
|
|
1000
1338
|
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Gracefully close the application and clean up resources
|
|
1341
|
+
* This should be called in tests and during shutdown
|
|
1342
|
+
*/
|
|
1343
|
+
async close() {
|
|
1344
|
+
this.logger.debug('Closing Moro application...');
|
|
1345
|
+
// Flush logger buffer before shutdown
|
|
1346
|
+
try {
|
|
1347
|
+
// Use flushBuffer for immediate synchronous flush
|
|
1348
|
+
this.logger.flushBuffer();
|
|
1349
|
+
}
|
|
1350
|
+
catch (error) {
|
|
1351
|
+
// Ignore flush errors during shutdown
|
|
1352
|
+
}
|
|
1353
|
+
// Close the core framework with timeout
|
|
1354
|
+
if (this.coreFramework && this.coreFramework.httpServer) {
|
|
1355
|
+
try {
|
|
1356
|
+
await Promise.race([
|
|
1357
|
+
new Promise(resolve => {
|
|
1358
|
+
this.coreFramework.httpServer.close(() => {
|
|
1359
|
+
resolve();
|
|
1360
|
+
});
|
|
1361
|
+
}),
|
|
1362
|
+
new Promise(resolve => setTimeout(resolve, 2000)), // 2 second timeout
|
|
1363
|
+
]);
|
|
1364
|
+
}
|
|
1365
|
+
catch (error) {
|
|
1366
|
+
// Force close if graceful close fails
|
|
1367
|
+
this.logger.warn('Force closing HTTP server due to timeout');
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
// Clean up module discovery watchers
|
|
1371
|
+
if (this.moduleDiscovery && typeof this.moduleDiscovery.cleanup === 'function') {
|
|
1372
|
+
try {
|
|
1373
|
+
this.moduleDiscovery.cleanup();
|
|
1374
|
+
}
|
|
1375
|
+
catch (error) {
|
|
1376
|
+
// Ignore cleanup errors
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
// Clean up event listeners
|
|
1380
|
+
try {
|
|
1381
|
+
this.eventBus.removeAllListeners();
|
|
1382
|
+
this.removeAllListeners();
|
|
1383
|
+
}
|
|
1384
|
+
catch (error) {
|
|
1385
|
+
// Ignore cleanup errors
|
|
1386
|
+
}
|
|
1387
|
+
this.logger.debug('Moro application closed successfully');
|
|
1388
|
+
}
|
|
1001
1389
|
}
|
|
1002
|
-
exports.Moro = Moro;
|
|
1003
1390
|
// Export convenience function
|
|
1004
|
-
function createApp(options) {
|
|
1391
|
+
export function createApp(options) {
|
|
1005
1392
|
return new Moro(options);
|
|
1006
1393
|
}
|
|
1007
1394
|
// Runtime-specific convenience functions
|
|
1008
|
-
function createAppNode(options) {
|
|
1395
|
+
export function createAppNode(options) {
|
|
1009
1396
|
return new Moro({
|
|
1010
1397
|
...options,
|
|
1011
1398
|
runtime: { type: 'node' },
|
|
1012
1399
|
});
|
|
1013
1400
|
}
|
|
1014
|
-
function createAppEdge(options) {
|
|
1401
|
+
export function createAppEdge(options) {
|
|
1015
1402
|
return new Moro({
|
|
1016
1403
|
...options,
|
|
1017
1404
|
runtime: { type: 'vercel-edge' },
|
|
1018
1405
|
});
|
|
1019
1406
|
}
|
|
1020
|
-
function createAppLambda(options) {
|
|
1407
|
+
export function createAppLambda(options) {
|
|
1021
1408
|
return new Moro({
|
|
1022
1409
|
...options,
|
|
1023
1410
|
runtime: { type: 'aws-lambda' },
|
|
1024
1411
|
});
|
|
1025
1412
|
}
|
|
1026
|
-
function createAppWorker(options) {
|
|
1413
|
+
export function createAppWorker(options) {
|
|
1027
1414
|
return new Moro({
|
|
1028
1415
|
...options,
|
|
1029
1416
|
runtime: { type: 'cloudflare-workers' },
|