@morojs/moro 1.6.6 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -4
- package/dist/core/auth/morojs-adapter.js +33 -20
- package/dist/core/auth/morojs-adapter.js.map +1 -1
- package/dist/core/config/config-sources.js +71 -15
- package/dist/core/config/config-sources.js.map +1 -1
- package/dist/core/config/config-validator.js +201 -6
- package/dist/core/config/config-validator.js.map +1 -1
- package/dist/core/database/adapters/drizzle.js +5 -5
- package/dist/core/database/adapters/drizzle.js.map +1 -1
- package/dist/core/database/adapters/mongodb.js +5 -1
- package/dist/core/database/adapters/mongodb.js.map +1 -1
- package/dist/core/database/adapters/mysql.js +5 -1
- package/dist/core/database/adapters/mysql.js.map +1 -1
- package/dist/core/database/adapters/postgresql.js +1 -1
- package/dist/core/database/adapters/postgresql.js.map +1 -1
- package/dist/core/database/adapters/redis.js +2 -2
- package/dist/core/database/adapters/redis.js.map +1 -1
- package/dist/core/database/adapters/sqlite.js +5 -1
- package/dist/core/database/adapters/sqlite.js.map +1 -1
- package/dist/core/docs/index.js.map +1 -1
- package/dist/core/docs/openapi-generator.js +8 -9
- package/dist/core/docs/openapi-generator.js.map +1 -1
- package/dist/core/docs/simple-docs.js +2 -1
- package/dist/core/docs/simple-docs.js.map +1 -1
- package/dist/core/docs/swagger-ui.js +1 -0
- package/dist/core/docs/swagger-ui.js.map +1 -1
- package/dist/core/docs/zod-to-openapi.js +4 -0
- 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 +2 -1
- package/dist/core/events/event-bus.js.map +1 -1
- package/dist/core/framework.d.ts +5 -3
- package/dist/core/framework.js +28 -25
- package/dist/core/framework.js.map +1 -1
- package/dist/core/graphql/adapter.d.ts +73 -0
- package/dist/core/graphql/adapter.js +2 -0
- package/dist/core/graphql/adapter.js.map +1 -0
- package/dist/core/graphql/adapters/graphql-js-adapter.d.ts +26 -0
- package/dist/core/graphql/adapters/graphql-js-adapter.js +229 -0
- package/dist/core/graphql/adapters/graphql-js-adapter.js.map +1 -0
- package/dist/core/graphql/core.d.ts +60 -0
- package/dist/core/graphql/core.js +191 -0
- package/dist/core/graphql/core.js.map +1 -0
- package/dist/core/graphql/index.d.ts +4 -0
- package/dist/core/graphql/index.js +4 -0
- package/dist/core/graphql/index.js.map +1 -0
- package/dist/core/graphql/loader.d.ts +9 -0
- package/dist/core/graphql/loader.js +32 -0
- package/dist/core/graphql/loader.js.map +1 -0
- package/dist/core/graphql/types.d.ts +211 -0
- package/dist/core/graphql/types.js +2 -0
- package/dist/core/graphql/types.js.map +1 -0
- package/dist/core/grpc/adapters/grpc-js-adapter.d.ts +28 -0
- package/dist/core/grpc/adapters/grpc-js-adapter.js +449 -0
- package/dist/core/grpc/adapters/grpc-js-adapter.js.map +1 -0
- package/dist/core/grpc/adapters/index.d.ts +1 -0
- package/dist/core/grpc/adapters/index.js +6 -0
- package/dist/core/grpc/adapters/index.js.map +1 -0
- package/dist/core/grpc/grpc-adapter.d.ts +47 -0
- package/dist/core/grpc/grpc-adapter.js +4 -0
- package/dist/core/grpc/grpc-adapter.js.map +1 -0
- package/dist/core/grpc/grpc-manager.d.ts +59 -0
- package/dist/core/grpc/grpc-manager.js +218 -0
- package/dist/core/grpc/grpc-manager.js.map +1 -0
- package/dist/core/grpc/index.d.ts +7 -0
- package/dist/core/grpc/index.js +10 -0
- package/dist/core/grpc/index.js.map +1 -0
- package/dist/core/grpc/middleware/auth.d.ts +22 -0
- package/dist/core/grpc/middleware/auth.js +126 -0
- package/dist/core/grpc/middleware/auth.js.map +1 -0
- package/dist/core/grpc/middleware/logging.d.ts +19 -0
- package/dist/core/grpc/middleware/logging.js +57 -0
- package/dist/core/grpc/middleware/logging.js.map +1 -0
- package/dist/core/grpc/middleware/validation.d.ts +18 -0
- package/dist/core/grpc/middleware/validation.js +126 -0
- package/dist/core/grpc/middleware/validation.js.map +1 -0
- package/dist/core/grpc/types.d.ts +233 -0
- package/dist/core/grpc/types.js +36 -0
- package/dist/core/grpc/types.js.map +1 -0
- package/dist/core/http/http-server.d.ts +13 -84
- package/dist/core/http/http-server.js +216 -781
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/http2-server.d.ts +131 -0
- package/dist/core/http/http2-server.js +803 -0
- package/dist/core/http/http2-server.js.map +1 -0
- package/dist/core/http/index.d.ts +3 -1
- package/dist/core/http/index.js +2 -1
- package/dist/core/http/index.js.map +1 -1
- package/dist/core/http/utils/uws-worker-clustering.d.ts +28 -0
- package/dist/core/http/utils/uws-worker-clustering.js +313 -0
- package/dist/core/http/utils/uws-worker-clustering.js.map +1 -0
- package/dist/core/http/uws-http-server.d.ts +3 -1
- package/dist/core/http/uws-http-server.js +58 -34
- package/dist/core/http/uws-http-server.js.map +1 -1
- package/dist/core/jobs/cron-parser.d.ts +62 -0
- package/dist/core/jobs/cron-parser.js +239 -0
- package/dist/core/jobs/cron-parser.js.map +1 -0
- package/dist/core/jobs/index.d.ts +12 -0
- package/dist/core/jobs/index.js +9 -0
- package/dist/core/jobs/index.js.map +1 -0
- package/dist/core/jobs/job-executor.d.ts +134 -0
- package/dist/core/jobs/job-executor.js +418 -0
- package/dist/core/jobs/job-executor.js.map +1 -0
- package/dist/core/jobs/job-scheduler.d.ts +214 -0
- package/dist/core/jobs/job-scheduler.js +554 -0
- package/dist/core/jobs/job-scheduler.js.map +1 -0
- package/dist/core/jobs/job-state-manager.d.ts +158 -0
- package/dist/core/jobs/job-state-manager.js +444 -0
- package/dist/core/jobs/job-state-manager.js.map +1 -0
- package/dist/core/jobs/leader-election.d.ts +124 -0
- package/dist/core/jobs/leader-election.js +482 -0
- package/dist/core/jobs/leader-election.js.map +1 -0
- package/dist/core/jobs/types.d.ts +151 -0
- package/dist/core/jobs/types.js +4 -0
- package/dist/core/jobs/types.js.map +1 -0
- package/dist/core/jobs/utils.d.ts +95 -0
- package/dist/core/jobs/utils.js +258 -0
- package/dist/core/jobs/utils.js.map +1 -0
- package/dist/core/logger/filters.js +2 -0
- package/dist/core/logger/filters.js.map +1 -1
- package/dist/core/logger/logger.js +48 -21
- package/dist/core/logger/logger.js.map +1 -1
- package/dist/core/logger/outputs.js +11 -3
- package/dist/core/logger/outputs.js.map +1 -1
- package/dist/core/mail/adapters/console-adapter.d.ts +14 -0
- package/dist/core/mail/adapters/console-adapter.js +89 -0
- package/dist/core/mail/adapters/console-adapter.js.map +1 -0
- package/dist/core/mail/adapters/index.d.ts +5 -0
- package/dist/core/mail/adapters/index.js +8 -0
- package/dist/core/mail/adapters/index.js.map +1 -0
- package/dist/core/mail/adapters/nodemailer-adapter.d.ts +18 -0
- package/dist/core/mail/adapters/nodemailer-adapter.js +188 -0
- package/dist/core/mail/adapters/nodemailer-adapter.js.map +1 -0
- package/dist/core/mail/adapters/resend-adapter.d.ts +18 -0
- package/dist/core/mail/adapters/resend-adapter.js +169 -0
- package/dist/core/mail/adapters/resend-adapter.js.map +1 -0
- package/dist/core/mail/adapters/sendgrid-adapter.d.ts +19 -0
- package/dist/core/mail/adapters/sendgrid-adapter.js +186 -0
- package/dist/core/mail/adapters/sendgrid-adapter.js.map +1 -0
- package/dist/core/mail/adapters/ses-adapter.d.ts +18 -0
- package/dist/core/mail/adapters/ses-adapter.js +167 -0
- package/dist/core/mail/adapters/ses-adapter.js.map +1 -0
- package/dist/core/mail/index.d.ts +5 -0
- package/dist/core/mail/index.js +8 -0
- package/dist/core/mail/index.js.map +1 -0
- package/dist/core/mail/mail-adapter.d.ts +62 -0
- package/dist/core/mail/mail-adapter.js +83 -0
- package/dist/core/mail/mail-adapter.js.map +1 -0
- package/dist/core/mail/mail-manager.d.ts +63 -0
- package/dist/core/mail/mail-manager.js +302 -0
- package/dist/core/mail/mail-manager.js.map +1 -0
- package/dist/core/mail/template-engine.d.ts +43 -0
- package/dist/core/mail/template-engine.js +239 -0
- package/dist/core/mail/template-engine.js.map +1 -0
- package/dist/core/mail/types.d.ts +237 -0
- package/dist/core/mail/types.js +4 -0
- package/dist/core/mail/types.js.map +1 -0
- package/dist/core/middleware/built-in/auth/helpers.js +1 -1
- package/dist/core/middleware/built-in/auth/helpers.js.map +1 -1
- package/dist/core/middleware/built-in/auth/jwt-helpers.js +1 -1
- package/dist/core/middleware/built-in/auth/jwt-helpers.js.map +1 -1
- package/dist/core/middleware/built-in/auth/providers.js +1 -1
- package/dist/core/middleware/built-in/auth/providers.js.map +1 -1
- package/dist/core/middleware/built-in/body-size/core.d.ts +12 -0
- package/dist/core/middleware/built-in/body-size/core.js +52 -0
- package/dist/core/middleware/built-in/body-size/core.js.map +1 -0
- package/dist/core/middleware/built-in/body-size/hook.d.ts +2 -0
- package/dist/core/middleware/built-in/body-size/hook.js +12 -0
- package/dist/core/middleware/built-in/body-size/hook.js.map +1 -0
- package/dist/core/middleware/built-in/body-size/index.d.ts +6 -0
- package/dist/core/middleware/built-in/body-size/index.js +7 -0
- package/dist/core/middleware/built-in/body-size/index.js.map +1 -0
- package/dist/core/middleware/built-in/body-size/middleware.d.ts +14 -0
- package/dist/core/middleware/built-in/body-size/middleware.js +22 -0
- package/dist/core/middleware/built-in/body-size/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/cache/adapters/cache/file.js +3 -3
- package/dist/core/middleware/built-in/cache/adapters/cache/file.js.map +1 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/memory.js +1 -0
- package/dist/core/middleware/built-in/cache/adapters/cache/memory.js.map +1 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.js +1 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.js.map +1 -1
- package/dist/core/middleware/built-in/cache/core.d.ts +20 -1
- package/dist/core/middleware/built-in/cache/core.js.map +1 -1
- package/dist/core/middleware/built-in/cache/hook.d.ts +38 -1
- package/dist/core/middleware/built-in/cache/hook.js +202 -16
- package/dist/core/middleware/built-in/cache/hook.js.map +1 -1
- package/dist/core/middleware/built-in/cache/index.js +1 -1
- package/dist/core/middleware/built-in/cache/index.js.map +1 -1
- package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.d.ts +8 -0
- package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.js +100 -7
- package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.js.map +1 -1
- package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.d.ts +6 -0
- package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.js +97 -13
- package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.js.map +1 -1
- package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudfront.js +1 -1
- package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudfront.js.map +1 -1
- package/dist/core/middleware/built-in/compression/core.d.ts +16 -0
- package/dist/core/middleware/built-in/compression/core.js +75 -0
- package/dist/core/middleware/built-in/compression/core.js.map +1 -0
- package/dist/core/middleware/built-in/compression/hook.d.ts +2 -0
- package/dist/core/middleware/built-in/compression/hook.js +14 -0
- package/dist/core/middleware/built-in/compression/hook.js.map +1 -0
- package/dist/core/middleware/built-in/compression/index.d.ts +6 -0
- package/dist/core/middleware/built-in/compression/index.js +7 -0
- package/dist/core/middleware/built-in/compression/index.js.map +1 -0
- package/dist/core/middleware/built-in/compression/middleware.d.ts +20 -0
- package/dist/core/middleware/built-in/compression/middleware.js +28 -0
- package/dist/core/middleware/built-in/compression/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/cookie/core.js +37 -9
- package/dist/core/middleware/built-in/cookie/core.js.map +1 -1
- package/dist/core/middleware/built-in/cookie/hook.d.ts +1 -1
- package/dist/core/middleware/built-in/cookie/hook.js +2 -2
- package/dist/core/middleware/built-in/cookie/hook.js.map +1 -1
- package/dist/core/middleware/built-in/csrf/core.js +1 -0
- package/dist/core/middleware/built-in/csrf/core.js.map +1 -1
- package/dist/core/middleware/built-in/graphql/core.d.ts +11 -0
- package/dist/core/middleware/built-in/graphql/core.js +24 -0
- package/dist/core/middleware/built-in/graphql/core.js.map +1 -0
- package/dist/core/middleware/built-in/graphql/helpers.d.ts +69 -0
- package/dist/core/middleware/built-in/graphql/helpers.js +187 -0
- package/dist/core/middleware/built-in/graphql/helpers.js.map +1 -0
- package/dist/core/middleware/built-in/graphql/hook.d.ts +7 -0
- package/dist/core/middleware/built-in/graphql/hook.js +78 -0
- package/dist/core/middleware/built-in/graphql/hook.js.map +1 -0
- package/dist/core/middleware/built-in/graphql/index.d.ts +5 -0
- package/dist/core/middleware/built-in/graphql/index.js +5 -0
- package/dist/core/middleware/built-in/graphql/index.js.map +1 -0
- package/dist/core/middleware/built-in/graphql/middleware.d.ts +7 -0
- package/dist/core/middleware/built-in/graphql/middleware.js +54 -0
- package/dist/core/middleware/built-in/graphql/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/graphql/subscriptions.d.ts +20 -0
- package/dist/core/middleware/built-in/graphql/subscriptions.js +37 -0
- package/dist/core/middleware/built-in/graphql/subscriptions.js.map +1 -0
- package/dist/core/middleware/built-in/helmet/core.d.ts +19 -0
- package/dist/core/middleware/built-in/helmet/core.js +70 -0
- package/dist/core/middleware/built-in/helmet/core.js.map +1 -0
- package/dist/core/middleware/built-in/helmet/hook.d.ts +2 -0
- package/dist/core/middleware/built-in/helmet/hook.js +12 -0
- package/dist/core/middleware/built-in/helmet/hook.js.map +1 -0
- package/dist/core/middleware/built-in/helmet/index.d.ts +6 -0
- package/dist/core/middleware/built-in/helmet/index.js +7 -0
- package/dist/core/middleware/built-in/helmet/index.js.map +1 -0
- package/dist/core/middleware/built-in/helmet/middleware.d.ts +22 -0
- package/dist/core/middleware/built-in/helmet/middleware.js +28 -0
- package/dist/core/middleware/built-in/helmet/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/http2/core.d.ts +35 -0
- package/dist/core/middleware/built-in/http2/core.js +128 -0
- package/dist/core/middleware/built-in/http2/core.js.map +1 -0
- package/dist/core/middleware/built-in/http2/hook.d.ts +5 -0
- package/dist/core/middleware/built-in/http2/hook.js +34 -0
- package/dist/core/middleware/built-in/http2/hook.js.map +1 -0
- package/dist/core/middleware/built-in/http2/index.d.ts +8 -0
- package/dist/core/middleware/built-in/http2/index.js +10 -0
- package/dist/core/middleware/built-in/http2/index.js.map +1 -0
- package/dist/core/middleware/built-in/http2/middleware.d.ts +20 -0
- package/dist/core/middleware/built-in/http2/middleware.js +31 -0
- package/dist/core/middleware/built-in/http2/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/index.d.ts +20 -1
- package/dist/core/middleware/built-in/index.js +31 -0
- package/dist/core/middleware/built-in/index.js.map +1 -1
- package/dist/core/middleware/built-in/range/core.d.ts +16 -0
- package/dist/core/middleware/built-in/range/core.js +112 -0
- package/dist/core/middleware/built-in/range/core.js.map +1 -0
- package/dist/core/middleware/built-in/range/hook.d.ts +2 -0
- package/dist/core/middleware/built-in/range/hook.js +12 -0
- package/dist/core/middleware/built-in/range/hook.js.map +1 -0
- package/dist/core/middleware/built-in/range/index.d.ts +6 -0
- package/dist/core/middleware/built-in/range/index.js +7 -0
- package/dist/core/middleware/built-in/range/index.js.map +1 -0
- package/dist/core/middleware/built-in/range/middleware.d.ts +21 -0
- package/dist/core/middleware/built-in/range/middleware.js +27 -0
- package/dist/core/middleware/built-in/range/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/session/core.js +14 -1
- package/dist/core/middleware/built-in/session/core.js.map +1 -1
- package/dist/core/middleware/built-in/static/core.d.ts +20 -0
- package/dist/core/middleware/built-in/static/core.js +143 -0
- package/dist/core/middleware/built-in/static/core.js.map +1 -0
- package/dist/core/middleware/built-in/static/hook.d.ts +2 -0
- package/dist/core/middleware/built-in/static/hook.js +12 -0
- package/dist/core/middleware/built-in/static/hook.js.map +1 -0
- package/dist/core/middleware/built-in/static/index.d.ts +6 -0
- package/dist/core/middleware/built-in/static/index.js +7 -0
- package/dist/core/middleware/built-in/static/index.js.map +1 -0
- package/dist/core/middleware/built-in/static/middleware.d.ts +18 -0
- package/dist/core/middleware/built-in/static/middleware.js +26 -0
- package/dist/core/middleware/built-in/static/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/template/core.d.ts +19 -0
- package/dist/core/middleware/built-in/template/core.js +108 -0
- package/dist/core/middleware/built-in/template/core.js.map +1 -0
- package/dist/core/middleware/built-in/template/hook.d.ts +2 -0
- package/dist/core/middleware/built-in/template/hook.js +12 -0
- package/dist/core/middleware/built-in/template/hook.js.map +1 -0
- package/dist/core/middleware/built-in/template/index.d.ts +6 -0
- package/dist/core/middleware/built-in/template/index.js +7 -0
- package/dist/core/middleware/built-in/template/index.js.map +1 -0
- package/dist/core/middleware/built-in/template/middleware.d.ts +21 -0
- package/dist/core/middleware/built-in/template/middleware.js +27 -0
- package/dist/core/middleware/built-in/template/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/upload/core.d.ts +29 -0
- package/dist/core/middleware/built-in/upload/core.js +66 -0
- package/dist/core/middleware/built-in/upload/core.js.map +1 -0
- package/dist/core/middleware/built-in/upload/hook.d.ts +2 -0
- package/dist/core/middleware/built-in/upload/hook.js +25 -0
- package/dist/core/middleware/built-in/upload/hook.js.map +1 -0
- package/dist/core/middleware/built-in/upload/index.d.ts +6 -0
- package/dist/core/middleware/built-in/upload/index.js +7 -0
- package/dist/core/middleware/built-in/upload/index.js.map +1 -0
- package/dist/core/middleware/built-in/upload/middleware.d.ts +18 -0
- package/dist/core/middleware/built-in/upload/middleware.js +41 -0
- package/dist/core/middleware/built-in/upload/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/validation/core.js +4 -2
- package/dist/core/middleware/built-in/validation/core.js.map +1 -1
- package/dist/core/middleware/built-in/validation/middleware.js +2 -1
- package/dist/core/middleware/built-in/validation/middleware.js.map +1 -1
- package/dist/core/middleware/index.js +1 -0
- package/dist/core/middleware/index.js.map +1 -1
- package/dist/core/modules/auto-discovery.js +5 -4
- package/dist/core/modules/auto-discovery.js.map +1 -1
- package/dist/core/modules/modules.js.map +1 -1
- package/dist/core/networking/adapters/socketio-adapter.js +1 -1
- package/dist/core/networking/adapters/socketio-adapter.js.map +1 -1
- package/dist/core/networking/adapters/uws-adapter.js +61 -8
- package/dist/core/networking/adapters/uws-adapter.js.map +1 -1
- package/dist/core/networking/adapters/ws-adapter.js +61 -19
- package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
- package/dist/core/networking/websocket-manager.js +2 -0
- package/dist/core/networking/websocket-manager.js.map +1 -1
- package/dist/core/pooling/object-pool-manager.js +12 -1
- package/dist/core/pooling/object-pool-manager.js.map +1 -1
- package/dist/core/queue/adapters/bull-adapter.d.ts +86 -0
- package/dist/core/queue/adapters/bull-adapter.js +330 -0
- package/dist/core/queue/adapters/bull-adapter.js.map +1 -0
- package/dist/core/queue/adapters/index.d.ts +9 -0
- package/dist/core/queue/adapters/index.js +10 -0
- package/dist/core/queue/adapters/index.js.map +1 -0
- package/dist/core/queue/adapters/kafka-adapter.d.ts +86 -0
- package/dist/core/queue/adapters/kafka-adapter.js +462 -0
- package/dist/core/queue/adapters/kafka-adapter.js.map +1 -0
- package/dist/core/queue/adapters/memory-adapter.d.ts +87 -0
- package/dist/core/queue/adapters/memory-adapter.js +415 -0
- package/dist/core/queue/adapters/memory-adapter.js.map +1 -0
- package/dist/core/queue/adapters/rabbitmq-adapter.d.ts +86 -0
- package/dist/core/queue/adapters/rabbitmq-adapter.js +436 -0
- package/dist/core/queue/adapters/rabbitmq-adapter.js.map +1 -0
- package/dist/core/queue/adapters/sqs-adapter.d.ts +102 -0
- package/dist/core/queue/adapters/sqs-adapter.js +522 -0
- package/dist/core/queue/adapters/sqs-adapter.js.map +1 -0
- package/dist/core/queue/index.d.ts +11 -0
- package/dist/core/queue/index.js +14 -0
- package/dist/core/queue/index.js.map +1 -0
- package/dist/core/queue/middleware/index.d.ts +7 -0
- package/dist/core/queue/middleware/index.js +8 -0
- package/dist/core/queue/middleware/index.js.map +1 -0
- package/dist/core/queue/middleware/monitoring.d.ts +84 -0
- package/dist/core/queue/middleware/monitoring.js +145 -0
- package/dist/core/queue/middleware/monitoring.js.map +1 -0
- package/dist/core/queue/middleware/priority.d.ts +61 -0
- package/dist/core/queue/middleware/priority.js +90 -0
- package/dist/core/queue/middleware/priority.js.map +1 -0
- package/dist/core/queue/middleware/rate-limit.d.ts +34 -0
- package/dist/core/queue/middleware/rate-limit.js +109 -0
- package/dist/core/queue/middleware/rate-limit.js.map +1 -0
- package/dist/core/queue/queue-adapter.d.ts +73 -0
- package/dist/core/queue/queue-adapter.js +20 -0
- package/dist/core/queue/queue-adapter.js.map +1 -0
- package/dist/core/queue/queue-manager.d.ts +92 -0
- package/dist/core/queue/queue-manager.js +327 -0
- package/dist/core/queue/queue-manager.js.map +1 -0
- package/dist/core/queue/types.d.ts +205 -0
- package/dist/core/queue/types.js +6 -0
- package/dist/core/queue/types.js.map +1 -0
- package/dist/core/routing/app-integration.d.ts +3 -3
- package/dist/core/routing/app-integration.js +1 -1
- package/dist/core/routing/app-integration.js.map +1 -1
- package/dist/core/routing/index.d.ts +1 -1
- package/dist/core/routing/index.js +42 -11
- package/dist/core/routing/index.js.map +1 -1
- package/dist/core/routing/radix-tree.d.ts +48 -0
- package/dist/core/routing/radix-tree.js +211 -0
- package/dist/core/routing/radix-tree.js.map +1 -0
- package/dist/core/routing/router.d.ts +10 -9
- package/dist/core/routing/router.js +3 -1
- package/dist/core/routing/router.js.map +1 -1
- package/dist/core/routing/unified-router.d.ts +18 -12
- package/dist/core/routing/unified-router.js +220 -163
- package/dist/core/routing/unified-router.js.map +1 -1
- package/dist/core/runtime/aws-lambda-adapter.js +21 -10
- package/dist/core/runtime/aws-lambda-adapter.js.map +1 -1
- package/dist/core/runtime/base-adapter.js +18 -8
- package/dist/core/runtime/base-adapter.js.map +1 -1
- package/dist/core/runtime/cloudflare-workers-adapter.js +36 -13
- package/dist/core/runtime/cloudflare-workers-adapter.js.map +1 -1
- package/dist/core/runtime/node-adapter.d.ts +1 -1
- package/dist/core/runtime/node-adapter.js +7 -4
- package/dist/core/runtime/node-adapter.js.map +1 -1
- package/dist/core/runtime/vercel-edge-adapter.js +17 -6
- package/dist/core/runtime/vercel-edge-adapter.js.map +1 -1
- package/dist/core/utilities/circuit-breaker.d.ts +9 -2
- package/dist/core/utilities/circuit-breaker.js +32 -3
- package/dist/core/utilities/circuit-breaker.js.map +1 -1
- package/dist/core/utilities/container.js +9 -1
- package/dist/core/utilities/container.js.map +1 -1
- package/dist/core/utilities/hooks.js +4 -0
- package/dist/core/utilities/hooks.js.map +1 -1
- package/dist/core/validation/index.js +6 -1
- package/dist/core/validation/index.js.map +1 -1
- package/dist/core/workers/facade.d.ts +74 -0
- package/dist/core/workers/facade.js +98 -0
- package/dist/core/workers/facade.js.map +1 -0
- package/dist/core/workers/index.d.ts +2 -0
- package/dist/core/workers/index.js +6 -0
- package/dist/core/workers/index.js.map +1 -0
- package/dist/core/workers/worker-manager.d.ts +124 -0
- package/dist/core/workers/worker-manager.js +299 -0
- package/dist/core/workers/worker-manager.js.map +1 -0
- package/dist/core/workers/worker.d.ts +1 -0
- package/dist/core/workers/worker.js +225 -0
- package/dist/core/workers/worker.js.map +1 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/moro.d.ts +486 -1
- package/dist/moro.js +1194 -28
- package/dist/moro.js.map +1 -1
- package/dist/types/cache.d.ts +4 -0
- package/dist/types/config.d.ts +70 -0
- package/dist/types/core.d.ts +19 -1
- package/dist/types/events.d.ts +1 -1
- package/dist/types/events.js +1 -0
- package/dist/types/events.js.map +1 -1
- package/dist/types/module.d.ts +2 -2
- package/package.json +110 -16
package/dist/moro.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// Built for developers who demand performance, elegance, and zero compromises
|
|
3
3
|
// Event-driven • Modular • Enterprise-ready • Developer-first
|
|
4
4
|
import { Moro as MoroCore } from './core/framework.js';
|
|
5
|
-
import { middleware } from './core/http/index.js';
|
|
6
5
|
import { createFrameworkLogger, applyLoggingConfiguration } from './core/logger/index.js';
|
|
7
6
|
import { MiddlewareManager } from './core/middleware/index.js';
|
|
8
7
|
import { IntelligentRoutingManager } from './core/routing/app-integration.js';
|
|
@@ -17,6 +16,17 @@ import { normalizeValidationError } from './core/validation/schema-interface.js'
|
|
|
17
16
|
import { initializeConfig } from './core/config/index.js';
|
|
18
17
|
// Runtime System Integration
|
|
19
18
|
import { createRuntimeAdapter } from './core/runtime/index.js';
|
|
19
|
+
// Job System Integration
|
|
20
|
+
import { JobScheduler, JobHealthChecker, everyInterval, cronSchedule, } from './core/jobs/index.js';
|
|
21
|
+
// Worker Threads Integration (optional - lazy loaded when needed)
|
|
22
|
+
import { WorkerThreadsFacade } from './core/workers/index.js';
|
|
23
|
+
// Built-in middleware integration
|
|
24
|
+
import { cors, helmet, compression, bodySize } from './core/middleware/built-in/index.js';
|
|
25
|
+
// Lazy imports for GraphQL to avoid crashes when not installed
|
|
26
|
+
let GraphQLCore;
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
28
|
+
let GraphQLSubscriptionManager;
|
|
29
|
+
let setupGraphQLSubscriptions;
|
|
20
30
|
export class Moro extends EventEmitter {
|
|
21
31
|
coreFramework;
|
|
22
32
|
routes = [];
|
|
@@ -49,6 +59,31 @@ export class Moro extends EventEmitter {
|
|
|
49
59
|
middlewareManager;
|
|
50
60
|
// Queued WebSocket registrations (for async adapter detection)
|
|
51
61
|
queuedWebSocketRegistrations = [];
|
|
62
|
+
// Job scheduling system
|
|
63
|
+
jobScheduler;
|
|
64
|
+
jobHealthChecker;
|
|
65
|
+
jobsStarted = false;
|
|
66
|
+
// GraphQL system
|
|
67
|
+
graphqlCore; // Use any to avoid import dependency
|
|
68
|
+
graphqlSubscriptionManager;
|
|
69
|
+
graphqlInitPromise;
|
|
70
|
+
graphqlConfig; // Store config for lazy initialization
|
|
71
|
+
// gRPC system (lazy loaded)
|
|
72
|
+
grpcManager; // Use any to avoid import dependency
|
|
73
|
+
grpcInitPromise;
|
|
74
|
+
grpcStarted = false;
|
|
75
|
+
grpcConfig; // Store config for lazy initialization
|
|
76
|
+
// Queue system (lazy loaded)
|
|
77
|
+
queueManager; // Use any to avoid import dependency
|
|
78
|
+
queueInitialized = false;
|
|
79
|
+
queueInitPromise; // Track initialization promise
|
|
80
|
+
queueConfigs = new Map(); // Store queue configs for lazy initialization
|
|
81
|
+
// Worker threads system (lazy loaded facade)
|
|
82
|
+
workerFacade;
|
|
83
|
+
// Mail system (lazy loaded)
|
|
84
|
+
mailManager; // Use any to avoid import dependency
|
|
85
|
+
mailInitialized = false;
|
|
86
|
+
mailConfig; // Store config for lazy initialization
|
|
52
87
|
constructor(options = {}) {
|
|
53
88
|
super(); // Call EventEmitter constructor
|
|
54
89
|
// Track if user explicitly set logger/logging options
|
|
@@ -110,6 +145,35 @@ export class Moro extends EventEmitter {
|
|
|
110
145
|
}
|
|
111
146
|
// Access enterprise event bus from core framework
|
|
112
147
|
this.eventBus = this.coreFramework.eventBus;
|
|
148
|
+
// Initialize job scheduler if enabled in config
|
|
149
|
+
// Default to enabled (true) unless explicitly disabled
|
|
150
|
+
const jobsEnabled = this.config.jobs?.enabled !== false && options.jobs?.enabled !== false;
|
|
151
|
+
if (jobsEnabled) {
|
|
152
|
+
const leaderElectionOptions = this.config.jobs?.leaderElection?.enabled !== false
|
|
153
|
+
? {
|
|
154
|
+
strategy: this.config.jobs?.leaderElection?.strategy ?? 'file',
|
|
155
|
+
lockPath: this.config.jobs?.leaderElection?.lockPath,
|
|
156
|
+
lockTimeout: this.config.jobs?.leaderElection?.lockTimeout,
|
|
157
|
+
heartbeatInterval: this.config.jobs?.leaderElection?.heartbeatInterval,
|
|
158
|
+
}
|
|
159
|
+
: undefined;
|
|
160
|
+
const jobSchedulerOptions = {
|
|
161
|
+
maxConcurrentJobs: this.config.jobs?.maxConcurrentJobs,
|
|
162
|
+
enableLeaderElection: this.config.jobs?.leaderElection?.enabled !== false,
|
|
163
|
+
leaderElection: leaderElectionOptions,
|
|
164
|
+
executor: this.config.jobs?.executor,
|
|
165
|
+
stateManager: this.config.jobs?.stateManager,
|
|
166
|
+
gracefulShutdownTimeout: this.config.jobs?.gracefulShutdownTimeout,
|
|
167
|
+
};
|
|
168
|
+
this.jobScheduler = new JobScheduler(createFrameworkLogger('Jobs'), jobSchedulerOptions);
|
|
169
|
+
this.jobHealthChecker = new JobHealthChecker(this.jobScheduler, createFrameworkLogger('JobHealth'));
|
|
170
|
+
this.logger.info('Job scheduler initialized', 'Jobs', {
|
|
171
|
+
maxConcurrentJobs: jobSchedulerOptions.maxConcurrentJobs,
|
|
172
|
+
leaderElection: jobSchedulerOptions.enableLeaderElection,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
// Initialize worker threads facade (optional - lazy loaded)
|
|
176
|
+
this.workerFacade = new WorkerThreadsFacade();
|
|
113
177
|
// Setup default middleware if enabled - use config defaults with options override
|
|
114
178
|
this.setupDefaultMiddleware({
|
|
115
179
|
...this.getDefaultOptionsFromConfig(),
|
|
@@ -412,18 +476,10 @@ export class Moro extends EventEmitter {
|
|
|
412
476
|
throw new Error('Port not specified and not found in configuration. Please provide a port number or configure it in moro.config.js/ts');
|
|
413
477
|
}
|
|
414
478
|
// Check if clustering is enabled for massive performance gains
|
|
415
|
-
// NOTE: uWebSockets.js does NOT support Node.js clustering - it's single-threaded only
|
|
416
|
-
const usingUWebSockets = this.config.server?.useUWebSockets || false;
|
|
417
479
|
if (this.config.performance?.clustering?.enabled) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
// Continue without clustering
|
|
422
|
-
}
|
|
423
|
-
else {
|
|
424
|
-
this.startWithClustering(port, host, callback);
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
480
|
+
this.logger.info('Clustering enabled - using Node.js cluster', 'Cluster');
|
|
481
|
+
this.startWithClustering(port, host, callback);
|
|
482
|
+
return;
|
|
427
483
|
}
|
|
428
484
|
this.eventBus.emit('server:starting', { port, runtime: this.runtimeType });
|
|
429
485
|
// Add documentation middleware first (if enabled)
|
|
@@ -432,12 +488,12 @@ export class Moro extends EventEmitter {
|
|
|
432
488
|
this.coreFramework.addMiddleware(docsMiddleware);
|
|
433
489
|
this.logger.debug('Documentation middleware added', 'Documentation');
|
|
434
490
|
}
|
|
435
|
-
catch
|
|
491
|
+
catch {
|
|
436
492
|
// Documentation not enabled, that's fine
|
|
437
493
|
this.logger.debug('Documentation not enabled', 'Documentation');
|
|
438
494
|
}
|
|
439
495
|
// Add unified routing middleware (handles both chainable and direct routes)
|
|
440
|
-
//
|
|
496
|
+
// Call router without extra async wrapper when possible
|
|
441
497
|
this.coreFramework.addMiddleware(async (req, res, next) => {
|
|
442
498
|
// Try unified router first (handles all route types)
|
|
443
499
|
const handled = this.unifiedRouter.handleRequest(req, res);
|
|
@@ -478,6 +534,20 @@ export class Moro extends EventEmitter {
|
|
|
478
534
|
this.unifiedRouter.logPerformanceStats();
|
|
479
535
|
}
|
|
480
536
|
this.eventBus.emit('server:started', { port, runtime: this.runtimeType });
|
|
537
|
+
// Start job scheduler after server starts
|
|
538
|
+
this.startJobScheduler().catch(err => {
|
|
539
|
+
this.logger.error(`Failed to start job scheduler: ${String(err)}`);
|
|
540
|
+
});
|
|
541
|
+
// Initialize GraphQL if configured
|
|
542
|
+
this.ensureGraphQLInitialized().catch(error => {
|
|
543
|
+
this.logger.error(`Failed to initialize GraphQL: ${error}`, 'GraphQL');
|
|
544
|
+
});
|
|
545
|
+
// Start gRPC server if initialized
|
|
546
|
+
if (this.grpcManager && !this.grpcStarted) {
|
|
547
|
+
this.startGrpc().catch(error => {
|
|
548
|
+
this.logger.error(`Failed to start gRPC server: ${error}`, 'GRPC');
|
|
549
|
+
});
|
|
550
|
+
}
|
|
481
551
|
if (callback)
|
|
482
552
|
callback();
|
|
483
553
|
};
|
|
@@ -604,7 +674,7 @@ export class Moro extends EventEmitter {
|
|
|
604
674
|
if (res.headersSent)
|
|
605
675
|
return;
|
|
606
676
|
}
|
|
607
|
-
catch
|
|
677
|
+
catch {
|
|
608
678
|
// Documentation not enabled, that's fine
|
|
609
679
|
}
|
|
610
680
|
// Try unified router first (handles all routes)
|
|
@@ -622,6 +692,7 @@ export class Moro extends EventEmitter {
|
|
|
622
692
|
// Handle direct routes for runtime adapters
|
|
623
693
|
async handleDirectRoutes(req, res) {
|
|
624
694
|
// Find matching route
|
|
695
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
625
696
|
const route = this.findMatchingRoute(req.method, req.path);
|
|
626
697
|
if (!route) {
|
|
627
698
|
res.status(404).json({ success: false, error: 'Not found' });
|
|
@@ -697,7 +768,7 @@ export class Moro extends EventEmitter {
|
|
|
697
768
|
staticRouteMap = new Map();
|
|
698
769
|
dynamicRoutesBySegments = new Map();
|
|
699
770
|
findMatchingRoute(method, path) {
|
|
700
|
-
//
|
|
771
|
+
// O(1) static route lookup
|
|
701
772
|
const staticKey = `${method}:${path}`;
|
|
702
773
|
const staticRoute = this.staticRouteMap.get(staticKey);
|
|
703
774
|
if (staticRoute) {
|
|
@@ -707,7 +778,7 @@ export class Moro extends EventEmitter {
|
|
|
707
778
|
paramNames: [],
|
|
708
779
|
};
|
|
709
780
|
}
|
|
710
|
-
//
|
|
781
|
+
// Dynamic route matching by segment count
|
|
711
782
|
const segmentCount = PathMatcher.countSegments(path);
|
|
712
783
|
const candidateRoutes = this.dynamicRoutesBySegments.get(segmentCount) || [];
|
|
713
784
|
for (const route of candidateRoutes) {
|
|
@@ -790,6 +861,7 @@ export class Moro extends EventEmitter {
|
|
|
790
861
|
if (!this.dynamicRoutesBySegments.has(segmentCount)) {
|
|
791
862
|
this.dynamicRoutesBySegments.set(segmentCount, []);
|
|
792
863
|
}
|
|
864
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
793
865
|
this.dynamicRoutesBySegments.get(segmentCount).push(route);
|
|
794
866
|
}
|
|
795
867
|
}
|
|
@@ -889,11 +961,11 @@ export class Moro extends EventEmitter {
|
|
|
889
961
|
: this.config.security.cors
|
|
890
962
|
? this.config.security.cors
|
|
891
963
|
: {};
|
|
892
|
-
this.use(
|
|
964
|
+
this.use(cors(corsOptions));
|
|
893
965
|
}
|
|
894
966
|
// Helmet - check config enabled property OR options.security.helmet.enabled === true
|
|
895
967
|
if (this.config.security.helmet.enabled || options.security?.helmet?.enabled === true) {
|
|
896
|
-
this.use(
|
|
968
|
+
this.use(helmet());
|
|
897
969
|
}
|
|
898
970
|
// Compression - check config enabled property OR options.performance.compression.enabled === true
|
|
899
971
|
if (this.config.performance.compression.enabled ||
|
|
@@ -903,10 +975,10 @@ export class Moro extends EventEmitter {
|
|
|
903
975
|
: this.config.performance.compression
|
|
904
976
|
? this.config.performance.compression
|
|
905
977
|
: {};
|
|
906
|
-
this.use(
|
|
978
|
+
this.use(compression(compressionOptions));
|
|
907
979
|
}
|
|
908
980
|
// Body size limiting
|
|
909
|
-
this.use(
|
|
981
|
+
this.use(bodySize({ limit: '10mb' }));
|
|
910
982
|
}
|
|
911
983
|
// Enhanced auto-discovery initialization
|
|
912
984
|
async initializeAutoDiscovery(options) {
|
|
@@ -1146,6 +1218,7 @@ export class Moro extends EventEmitter {
|
|
|
1146
1218
|
const gracefulShutdown = () => {
|
|
1147
1219
|
this.logger.info('Gracefully shutting down cluster...', 'Cluster');
|
|
1148
1220
|
// Clean up all workers
|
|
1221
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1149
1222
|
for (const [pid, worker] of this.clusterWorkers) {
|
|
1150
1223
|
worker.removeAllListeners();
|
|
1151
1224
|
worker.kill('SIGTERM');
|
|
@@ -1160,6 +1233,7 @@ export class Moro extends EventEmitter {
|
|
|
1160
1233
|
// Fork workers with basic tracking
|
|
1161
1234
|
for (let i = 0; i < workerCount; i++) {
|
|
1162
1235
|
const worker = cluster.fork();
|
|
1236
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1163
1237
|
this.clusterWorkers.set(worker.process.pid, worker);
|
|
1164
1238
|
this.logger.info(`Worker ${worker.process.pid} started`, 'Cluster');
|
|
1165
1239
|
// Handle individual worker messages
|
|
@@ -1173,6 +1247,7 @@ export class Moro extends EventEmitter {
|
|
|
1173
1247
|
this.logger.warn(`Worker ${pid} died unexpectedly (${signal || code}). Restarting...`, 'Cluster');
|
|
1174
1248
|
// Simple restart
|
|
1175
1249
|
const newWorker = cluster.fork();
|
|
1250
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1176
1251
|
this.clusterWorkers.set(newWorker.process.pid, newWorker);
|
|
1177
1252
|
this.logger.info(`Worker ${newWorker.process.pid} restarted`, 'Cluster');
|
|
1178
1253
|
}
|
|
@@ -1254,11 +1329,11 @@ export class Moro extends EventEmitter {
|
|
|
1254
1329
|
const docsMiddleware = this.documentation.getDocsMiddleware();
|
|
1255
1330
|
this.coreFramework.addMiddleware(docsMiddleware);
|
|
1256
1331
|
}
|
|
1257
|
-
catch
|
|
1332
|
+
catch {
|
|
1258
1333
|
// Documentation not enabled, that's fine
|
|
1259
1334
|
}
|
|
1260
1335
|
// Add unified routing middleware (handles both chainable and direct routes)
|
|
1261
|
-
//
|
|
1336
|
+
// Call router without extra async wrapper when possible
|
|
1262
1337
|
this.coreFramework.addMiddleware(async (req, res, next) => {
|
|
1263
1338
|
// Try unified router first (handles all route types)
|
|
1264
1339
|
const handled = this.unifiedRouter.handleRequest(req, res);
|
|
@@ -1335,12 +1410,51 @@ export class Moro extends EventEmitter {
|
|
|
1335
1410
|
*/
|
|
1336
1411
|
async close() {
|
|
1337
1412
|
this.logger.debug('Closing Moro application...');
|
|
1413
|
+
// Shutdown worker threads first (fastest)
|
|
1414
|
+
try {
|
|
1415
|
+
this.logger.info('Shutting down worker threads...');
|
|
1416
|
+
await this.workerFacade.shutdown();
|
|
1417
|
+
}
|
|
1418
|
+
catch {
|
|
1419
|
+
// Workers might not be initialized or available
|
|
1420
|
+
this.logger.debug('Worker threads not available for shutdown');
|
|
1421
|
+
}
|
|
1422
|
+
// Shutdown job scheduler
|
|
1423
|
+
if (this.jobScheduler) {
|
|
1424
|
+
try {
|
|
1425
|
+
this.logger.info('Shutting down job scheduler...');
|
|
1426
|
+
await this.jobScheduler.shutdown();
|
|
1427
|
+
this.jobsStarted = false;
|
|
1428
|
+
}
|
|
1429
|
+
catch (err) {
|
|
1430
|
+
this.logger.error(`Error shutting down job scheduler: ${String(err)}`);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
// Shutdown gRPC server
|
|
1434
|
+
if (this.grpcManager && this.grpcStarted) {
|
|
1435
|
+
try {
|
|
1436
|
+
this.logger.info('Shutting down gRPC server...');
|
|
1437
|
+
await this.stopGrpc();
|
|
1438
|
+
}
|
|
1439
|
+
catch (err) {
|
|
1440
|
+
this.logger.error(`Error shutting down gRPC server: ${String(err)}`);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
// Cleanup GraphQL adapter resources
|
|
1444
|
+
if (this.graphqlCore) {
|
|
1445
|
+
try {
|
|
1446
|
+
await this.graphqlCore.cleanup();
|
|
1447
|
+
}
|
|
1448
|
+
catch (err) {
|
|
1449
|
+
this.logger.error(`Error cleaning up GraphQL: ${String(err)}`);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1338
1452
|
// Flush logger buffer before shutdown
|
|
1339
1453
|
try {
|
|
1340
1454
|
// Use flushBuffer for immediate synchronous flush
|
|
1341
1455
|
this.logger.flushBuffer();
|
|
1342
1456
|
}
|
|
1343
|
-
catch
|
|
1457
|
+
catch {
|
|
1344
1458
|
// Ignore flush errors during shutdown
|
|
1345
1459
|
}
|
|
1346
1460
|
// Close the core framework with timeout
|
|
@@ -1352,10 +1466,13 @@ export class Moro extends EventEmitter {
|
|
|
1352
1466
|
resolve();
|
|
1353
1467
|
});
|
|
1354
1468
|
}),
|
|
1355
|
-
new Promise(resolve =>
|
|
1469
|
+
new Promise(resolve => {
|
|
1470
|
+
const timer = setTimeout(resolve, 2000); // 2 second timeout
|
|
1471
|
+
timer.unref(); // Don't keep process alive
|
|
1472
|
+
}),
|
|
1356
1473
|
]);
|
|
1357
1474
|
}
|
|
1358
|
-
catch
|
|
1475
|
+
catch {
|
|
1359
1476
|
// Force close if graceful close fails
|
|
1360
1477
|
this.logger.warn('Force closing HTTP server due to timeout');
|
|
1361
1478
|
}
|
|
@@ -1365,20 +1482,1069 @@ export class Moro extends EventEmitter {
|
|
|
1365
1482
|
try {
|
|
1366
1483
|
this.moduleDiscovery.cleanup();
|
|
1367
1484
|
}
|
|
1368
|
-
catch
|
|
1485
|
+
catch {
|
|
1369
1486
|
// Ignore cleanup errors
|
|
1370
1487
|
}
|
|
1371
1488
|
}
|
|
1489
|
+
// Shutdown queue manager
|
|
1490
|
+
if (this.queueManager) {
|
|
1491
|
+
try {
|
|
1492
|
+
await this.queueManager.shutdown();
|
|
1493
|
+
this.logger.debug('Queue system shutdown complete');
|
|
1494
|
+
}
|
|
1495
|
+
catch (error) {
|
|
1496
|
+
this.logger.warn(`Error shutting down queue system: ${error}`);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
// Clear queue state on close
|
|
1500
|
+
this.queueConfigs.clear();
|
|
1501
|
+
this.queueInitialized = false;
|
|
1502
|
+
this.queueInitPromise = undefined;
|
|
1503
|
+
this.queueManager = undefined;
|
|
1504
|
+
// Shutdown mail system
|
|
1505
|
+
if (this.mailManager) {
|
|
1506
|
+
try {
|
|
1507
|
+
await this.mailManager.close();
|
|
1508
|
+
this.logger.debug('Mail system shutdown complete');
|
|
1509
|
+
}
|
|
1510
|
+
catch (error) {
|
|
1511
|
+
this.logger.warn(`Error shutting down mail system: ${error}`);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1372
1514
|
// Clean up event listeners
|
|
1373
1515
|
try {
|
|
1374
1516
|
this.eventBus.removeAllListeners();
|
|
1375
1517
|
this.removeAllListeners();
|
|
1376
1518
|
}
|
|
1377
|
-
catch
|
|
1519
|
+
catch {
|
|
1378
1520
|
// Ignore cleanup errors
|
|
1379
1521
|
}
|
|
1380
1522
|
this.logger.debug('Moro application closed successfully');
|
|
1381
1523
|
}
|
|
1524
|
+
// ========================================
|
|
1525
|
+
// Job Scheduling API
|
|
1526
|
+
// ========================================
|
|
1527
|
+
/**
|
|
1528
|
+
* Register a background job with cron or interval schedule
|
|
1529
|
+
* @param name - Job name (used for identification)
|
|
1530
|
+
* @param schedule - Cron expression, interval string ('5m', '1h'), or schedule object
|
|
1531
|
+
* @param handler - Job function to execute
|
|
1532
|
+
* @param options - Job configuration options
|
|
1533
|
+
* @returns Job ID for management
|
|
1534
|
+
*
|
|
1535
|
+
* @example
|
|
1536
|
+
* // Cron schedule
|
|
1537
|
+
* app.job('cleanup', '0 2 * * *', async () => {
|
|
1538
|
+
* await cleanupOldData();
|
|
1539
|
+
* });
|
|
1540
|
+
*
|
|
1541
|
+
* // Interval schedule
|
|
1542
|
+
* app.job('health-check', '5m', async (ctx) => {
|
|
1543
|
+
* console.log('Health check', ctx.executionId);
|
|
1544
|
+
* });
|
|
1545
|
+
*
|
|
1546
|
+
* // Advanced options
|
|
1547
|
+
* app.job('report', '@daily', generateReport, {
|
|
1548
|
+
* timeout: 60000,
|
|
1549
|
+
* maxRetries: 3,
|
|
1550
|
+
* onError: (ctx, error) => console.error('Job failed', error)
|
|
1551
|
+
* });
|
|
1552
|
+
*/
|
|
1553
|
+
job(name, schedule, handler, options = {}) {
|
|
1554
|
+
if (!this.jobScheduler) {
|
|
1555
|
+
throw new Error('Job scheduler is not enabled. Set config.jobs.enabled = true');
|
|
1556
|
+
}
|
|
1557
|
+
let jobSchedule;
|
|
1558
|
+
// Parse schedule input
|
|
1559
|
+
if (typeof schedule === 'string') {
|
|
1560
|
+
// Check if it's a cron expression or interval
|
|
1561
|
+
if (schedule.match(/^(\d+[smhd]|\d+\s*(seconds?|minutes?|hours?|days?))$/i)) {
|
|
1562
|
+
// Interval format
|
|
1563
|
+
jobSchedule = everyInterval(schedule);
|
|
1564
|
+
}
|
|
1565
|
+
else {
|
|
1566
|
+
// Cron format
|
|
1567
|
+
jobSchedule = cronSchedule(schedule, options.timezone);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
else {
|
|
1571
|
+
jobSchedule = schedule;
|
|
1572
|
+
}
|
|
1573
|
+
const jobOptions = {
|
|
1574
|
+
name: options.name || name,
|
|
1575
|
+
enabled: options.enabled,
|
|
1576
|
+
priority: options.priority,
|
|
1577
|
+
timezone: options.timezone,
|
|
1578
|
+
maxConcurrent: options.maxConcurrent,
|
|
1579
|
+
timeout: options.timeout,
|
|
1580
|
+
maxRetries: options.maxRetries,
|
|
1581
|
+
retryDelay: options.retryDelay,
|
|
1582
|
+
retryBackoff: options.retryBackoff,
|
|
1583
|
+
enableCircuitBreaker: options.enableCircuitBreaker,
|
|
1584
|
+
metadata: options.metadata,
|
|
1585
|
+
onStart: options.onStart,
|
|
1586
|
+
onComplete: options.onComplete,
|
|
1587
|
+
onError: options.onError,
|
|
1588
|
+
};
|
|
1589
|
+
const jobId = this.jobScheduler.registerJob(name, jobSchedule, handler, jobOptions);
|
|
1590
|
+
this.logger.info(`Job registered: ${name} (${jobId})`);
|
|
1591
|
+
return jobId;
|
|
1592
|
+
}
|
|
1593
|
+
/**
|
|
1594
|
+
* Enable or disable a registered job
|
|
1595
|
+
*/
|
|
1596
|
+
setJobEnabled(jobId, enabled) {
|
|
1597
|
+
if (!this.jobScheduler) {
|
|
1598
|
+
return false;
|
|
1599
|
+
}
|
|
1600
|
+
return this.jobScheduler.setJobEnabled(jobId, enabled);
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Manually trigger a job execution
|
|
1604
|
+
*/
|
|
1605
|
+
async triggerJob(jobId, metadata) {
|
|
1606
|
+
if (!this.jobScheduler) {
|
|
1607
|
+
throw new Error('Job scheduler is not enabled');
|
|
1608
|
+
}
|
|
1609
|
+
return this.jobScheduler.triggerJob(jobId, metadata);
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Unregister a job
|
|
1613
|
+
*/
|
|
1614
|
+
unregisterJob(jobId) {
|
|
1615
|
+
if (!this.jobScheduler) {
|
|
1616
|
+
throw new Error('Job scheduler is not enabled');
|
|
1617
|
+
}
|
|
1618
|
+
return this.jobScheduler.unregisterJob(jobId);
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Get job metrics
|
|
1622
|
+
*/
|
|
1623
|
+
getJobMetrics(jobId) {
|
|
1624
|
+
if (!this.jobScheduler) {
|
|
1625
|
+
return null;
|
|
1626
|
+
}
|
|
1627
|
+
return this.jobScheduler.getJobMetrics(jobId);
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* Get job health status
|
|
1631
|
+
*/
|
|
1632
|
+
getJobHealth(jobId) {
|
|
1633
|
+
if (!this.jobHealthChecker) {
|
|
1634
|
+
if (jobId) {
|
|
1635
|
+
return {
|
|
1636
|
+
jobId,
|
|
1637
|
+
name: 'Unknown',
|
|
1638
|
+
status: 'unknown',
|
|
1639
|
+
enabled: false,
|
|
1640
|
+
consecutiveFailures: 0,
|
|
1641
|
+
message: 'Job scheduler not enabled',
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
return [];
|
|
1645
|
+
}
|
|
1646
|
+
if (jobId) {
|
|
1647
|
+
return this.jobHealthChecker.checkJobHealth(jobId);
|
|
1648
|
+
}
|
|
1649
|
+
return this.jobHealthChecker.checkAllJobs();
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* Get scheduler statistics
|
|
1653
|
+
*/
|
|
1654
|
+
getJobStats() {
|
|
1655
|
+
if (!this.jobScheduler) {
|
|
1656
|
+
return null;
|
|
1657
|
+
}
|
|
1658
|
+
return this.jobScheduler.getStats();
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Get overall scheduler health
|
|
1662
|
+
*/
|
|
1663
|
+
getSchedulerHealth() {
|
|
1664
|
+
if (!this.jobHealthChecker) {
|
|
1665
|
+
return {
|
|
1666
|
+
status: 'unknown',
|
|
1667
|
+
message: 'Job scheduler not enabled',
|
|
1668
|
+
stats: null,
|
|
1669
|
+
jobs: [],
|
|
1670
|
+
unhealthyJobCount: 0,
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
return this.jobHealthChecker.getSchedulerHealth();
|
|
1674
|
+
}
|
|
1675
|
+
/**
|
|
1676
|
+
* Start the job scheduler (called automatically on listen)
|
|
1677
|
+
*/
|
|
1678
|
+
async startJobScheduler() {
|
|
1679
|
+
if (!this.jobScheduler || this.jobsStarted) {
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
try {
|
|
1683
|
+
await this.jobScheduler.start();
|
|
1684
|
+
this.jobsStarted = true;
|
|
1685
|
+
this.logger.info('Job scheduler started');
|
|
1686
|
+
}
|
|
1687
|
+
catch (err) {
|
|
1688
|
+
this.logger.error(`Failed to start job scheduler: ${String(err)}`);
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
// ========================================
|
|
1692
|
+
// GraphQL API
|
|
1693
|
+
// ========================================
|
|
1694
|
+
/**
|
|
1695
|
+
* Configure GraphQL endpoint (synchronous, lazy initialization)
|
|
1696
|
+
*
|
|
1697
|
+
* @param options - GraphQL configuration options
|
|
1698
|
+
*
|
|
1699
|
+
* @example
|
|
1700
|
+
* ```ts
|
|
1701
|
+
* // Using type definitions and resolvers
|
|
1702
|
+
* app.graphqlInit({
|
|
1703
|
+
* typeDefs: `
|
|
1704
|
+
* type Query {
|
|
1705
|
+
* hello: String
|
|
1706
|
+
* users: [User]
|
|
1707
|
+
* }
|
|
1708
|
+
* type User {
|
|
1709
|
+
* id: ID!
|
|
1710
|
+
* name: String!
|
|
1711
|
+
* }
|
|
1712
|
+
* `,
|
|
1713
|
+
* resolvers: {
|
|
1714
|
+
* Query: {
|
|
1715
|
+
* hello: () => 'Hello World!',
|
|
1716
|
+
* users: () => [{ id: '1', name: 'Alice' }]
|
|
1717
|
+
* }
|
|
1718
|
+
* }
|
|
1719
|
+
* });
|
|
1720
|
+
*
|
|
1721
|
+
* // Using Pothos schema builder
|
|
1722
|
+
* import SchemaBuilder from '@pothos/core';
|
|
1723
|
+
*
|
|
1724
|
+
* const builder = new SchemaBuilder();
|
|
1725
|
+
* builder.queryType({
|
|
1726
|
+
* fields: (t) => ({
|
|
1727
|
+
* hello: t.string({ resolve: () => 'Hello World!' })
|
|
1728
|
+
* })
|
|
1729
|
+
* });
|
|
1730
|
+
*
|
|
1731
|
+
* app.graphqlInit({
|
|
1732
|
+
* pothosSchema: builder
|
|
1733
|
+
* });
|
|
1734
|
+
* ```
|
|
1735
|
+
*/
|
|
1736
|
+
graphqlInit(options) {
|
|
1737
|
+
if (this.graphqlCore || this.graphqlInitPromise) {
|
|
1738
|
+
throw new Error('GraphQL has already been configured. Call graphqlInit() only once.');
|
|
1739
|
+
}
|
|
1740
|
+
// Check if graphql package is available
|
|
1741
|
+
if (!this.isGraphQLAvailable()) {
|
|
1742
|
+
throw new Error('GraphQL support requires the graphql package to be installed.\n' +
|
|
1743
|
+
'Install it with: npm install graphql\n' +
|
|
1744
|
+
'For TypeScript-first GraphQL, also consider: npm install @pothos/core\n' +
|
|
1745
|
+
'For performance boost: npm install graphql-jit');
|
|
1746
|
+
}
|
|
1747
|
+
this.logger.info('Configuring GraphQL (will initialize on server start)', 'GraphQL', {
|
|
1748
|
+
path: options.path || '/graphql',
|
|
1749
|
+
jit: options.enableJIT !== false,
|
|
1750
|
+
playground: options.enablePlayground !== false,
|
|
1751
|
+
});
|
|
1752
|
+
// Store config and trigger initialization immediately
|
|
1753
|
+
this.graphqlConfig = options;
|
|
1754
|
+
this.graphqlInitPromise = this.initializeGraphQL(options);
|
|
1755
|
+
this.eventBus.emit('graphql:configured', { options });
|
|
1756
|
+
return this;
|
|
1757
|
+
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Lazy initialize GraphQL system
|
|
1760
|
+
*/
|
|
1761
|
+
async ensureGraphQLInitialized() {
|
|
1762
|
+
if (this.graphqlCore || this.graphqlInitPromise) {
|
|
1763
|
+
await this.graphqlInitPromise;
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
if (!this.graphqlConfig) {
|
|
1767
|
+
return; // GraphQL not configured, skip
|
|
1768
|
+
}
|
|
1769
|
+
this.graphqlInitPromise = this.initializeGraphQL(this.graphqlConfig);
|
|
1770
|
+
await this.graphqlInitPromise;
|
|
1771
|
+
}
|
|
1772
|
+
/**
|
|
1773
|
+
* Initialize GraphQL system asynchronously
|
|
1774
|
+
*/
|
|
1775
|
+
async initializeGraphQL(options) {
|
|
1776
|
+
try {
|
|
1777
|
+
// Lazy load GraphQL modules
|
|
1778
|
+
await this.loadGraphQLModules();
|
|
1779
|
+
// Create GraphQL core
|
|
1780
|
+
this.graphqlCore = new GraphQLCore(options);
|
|
1781
|
+
// Initialize GraphQL
|
|
1782
|
+
await this.graphqlCore.initialize();
|
|
1783
|
+
this.logger.info('GraphQL initialized successfully', 'GraphQL');
|
|
1784
|
+
// Setup subscriptions if enabled
|
|
1785
|
+
if (options.enableSubscriptions !== false && this.config.websocket.enabled) {
|
|
1786
|
+
this.setupGraphQLSubscriptions(options);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
catch (error) {
|
|
1790
|
+
this.logger.error('Failed to initialize GraphQL', 'GraphQL', { error });
|
|
1791
|
+
throw error;
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Check if GraphQL package is available
|
|
1796
|
+
*/
|
|
1797
|
+
isGraphQLAvailable() {
|
|
1798
|
+
try {
|
|
1799
|
+
require.resolve('graphql');
|
|
1800
|
+
return true;
|
|
1801
|
+
}
|
|
1802
|
+
catch {
|
|
1803
|
+
return false;
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
/**
|
|
1807
|
+
* Lazy load GraphQL modules
|
|
1808
|
+
*/
|
|
1809
|
+
async loadGraphQLModules() {
|
|
1810
|
+
if (GraphQLCore) {
|
|
1811
|
+
return; // Already loaded
|
|
1812
|
+
}
|
|
1813
|
+
try {
|
|
1814
|
+
// GraphQL core now uses adapters with no static imports from 'graphql'
|
|
1815
|
+
const coreModule = await import('./core/graphql/core.js');
|
|
1816
|
+
GraphQLCore = coreModule.GraphQLCore;
|
|
1817
|
+
const subsModule = await import('./core/middleware/built-in/graphql/subscriptions.js');
|
|
1818
|
+
GraphQLSubscriptionManager = subsModule.GraphQLSubscriptionManager;
|
|
1819
|
+
setupGraphQLSubscriptions = subsModule.setupGraphQLSubscriptions;
|
|
1820
|
+
}
|
|
1821
|
+
catch (error) {
|
|
1822
|
+
this.logger.error('Failed to load GraphQL modules', 'GraphQL', { error });
|
|
1823
|
+
throw new Error('Failed to load GraphQL modules. Please ensure graphql is installed.');
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Setup GraphQL subscriptions
|
|
1828
|
+
*/
|
|
1829
|
+
setupGraphQLSubscriptions(options) {
|
|
1830
|
+
const websocketAdapter = this.coreFramework.getWebSocketAdapter();
|
|
1831
|
+
if (!websocketAdapter) {
|
|
1832
|
+
this.logger.warn('GraphQL subscriptions require WebSocket to be enabled', 'GraphQL');
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
if (!this.graphqlCore) {
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
const schema = this.graphqlCore.getSchema();
|
|
1839
|
+
this.graphqlSubscriptionManager = setupGraphQLSubscriptions(websocketAdapter, schema, {
|
|
1840
|
+
path: options.path ? `${options.path}/subscriptions` : '/graphql/subscriptions',
|
|
1841
|
+
contextFactory: options.context,
|
|
1842
|
+
});
|
|
1843
|
+
this.logger.info('GraphQL subscriptions enabled', 'GraphQL', {
|
|
1844
|
+
path: options.path ? `${options.path}/subscriptions` : '/graphql/subscriptions',
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
/**
|
|
1848
|
+
* Get GraphQL schema (if configured)
|
|
1849
|
+
*/
|
|
1850
|
+
async getGraphQLSchema() {
|
|
1851
|
+
await this.ensureGraphQLInitialized();
|
|
1852
|
+
return this.graphqlCore?.getSchema();
|
|
1853
|
+
}
|
|
1854
|
+
// ========================================
|
|
1855
|
+
// Worker Threads API
|
|
1856
|
+
// ========================================
|
|
1857
|
+
/**
|
|
1858
|
+
* Execute a task on worker threads (CPU-intensive operations)
|
|
1859
|
+
* @param task - Task to execute
|
|
1860
|
+
* @returns Promise resolving to task result
|
|
1861
|
+
*
|
|
1862
|
+
* @example
|
|
1863
|
+
* ```ts
|
|
1864
|
+
* // JWT verification (CPU-intensive)
|
|
1865
|
+
* const payload = await app.executeOnWorker({
|
|
1866
|
+
* id: 'jwt-verify-123',
|
|
1867
|
+
* type: 'jwt:verify',
|
|
1868
|
+
* data: { token, secret }
|
|
1869
|
+
* });
|
|
1870
|
+
*
|
|
1871
|
+
* // Heavy computation
|
|
1872
|
+
* const result = await app.executeOnWorker({
|
|
1873
|
+
* id: 'compute-456',
|
|
1874
|
+
* type: 'computation:heavy',
|
|
1875
|
+
* data: { iterations: 1000000 }
|
|
1876
|
+
* });
|
|
1877
|
+
* ```
|
|
1878
|
+
*/
|
|
1879
|
+
async executeOnWorker(task) {
|
|
1880
|
+
return this.workerFacade.executeTask(task);
|
|
1881
|
+
}
|
|
1882
|
+
/**
|
|
1883
|
+
* Get worker manager instance for advanced usage
|
|
1884
|
+
*/
|
|
1885
|
+
async getWorkerManager() {
|
|
1886
|
+
await this.workerFacade.ensureInitialized?.(); // Trigger initialization if needed
|
|
1887
|
+
return this.workerFacade;
|
|
1888
|
+
}
|
|
1889
|
+
/**
|
|
1890
|
+
* Get worker thread statistics
|
|
1891
|
+
*/
|
|
1892
|
+
async getWorkerStats() {
|
|
1893
|
+
return this.workerFacade.getStats();
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* JWT operations using worker threads (prevents event loop blocking)
|
|
1897
|
+
*/
|
|
1898
|
+
async getJwtWorker() {
|
|
1899
|
+
return this.workerFacade.getJwtWorker();
|
|
1900
|
+
}
|
|
1901
|
+
/**
|
|
1902
|
+
* Crypto operations using worker threads
|
|
1903
|
+
*/
|
|
1904
|
+
async getCryptoWorker() {
|
|
1905
|
+
return this.workerFacade.getCryptoWorker();
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Heavy computation operations using worker threads
|
|
1909
|
+
*/
|
|
1910
|
+
async getComputeWorker() {
|
|
1911
|
+
return this.workerFacade.getComputeWorker();
|
|
1912
|
+
}
|
|
1913
|
+
/**
|
|
1914
|
+
* Get GraphQL stats
|
|
1915
|
+
*/
|
|
1916
|
+
async getGraphQLStats() {
|
|
1917
|
+
await this.ensureGraphQLInitialized();
|
|
1918
|
+
if (!this.graphqlCore) {
|
|
1919
|
+
return null;
|
|
1920
|
+
}
|
|
1921
|
+
return {
|
|
1922
|
+
...this.graphqlCore.getStats(),
|
|
1923
|
+
subscriptions: this.graphqlSubscriptionManager?.getSubscriptionCount() || 0,
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
// =====================
|
|
1927
|
+
// gRPC Methods
|
|
1928
|
+
// =====================
|
|
1929
|
+
/**
|
|
1930
|
+
* Configure gRPC server (synchronous, lazy initialization)
|
|
1931
|
+
*
|
|
1932
|
+
* @example
|
|
1933
|
+
* ```typescript
|
|
1934
|
+
* app.grpcInit({
|
|
1935
|
+
* port: 50051,
|
|
1936
|
+
* host: '0.0.0.0',
|
|
1937
|
+
* adapter: 'grpc-js',
|
|
1938
|
+
* enableHealthCheck: true,
|
|
1939
|
+
* enableReflection: true
|
|
1940
|
+
* });
|
|
1941
|
+
* ```
|
|
1942
|
+
*/
|
|
1943
|
+
grpcInit(options = {}) {
|
|
1944
|
+
if (this.grpcInitPromise) {
|
|
1945
|
+
this.logger.warn('gRPC already configured', 'GRPC');
|
|
1946
|
+
return this;
|
|
1947
|
+
}
|
|
1948
|
+
this.logger.info('Configuring gRPC (will initialize on server start)', 'GRPC');
|
|
1949
|
+
// Store config for lazy initialization
|
|
1950
|
+
this.grpcConfig = {
|
|
1951
|
+
port: 50051,
|
|
1952
|
+
host: '0.0.0.0',
|
|
1953
|
+
adapter: 'grpc-js',
|
|
1954
|
+
enableHealthCheck: true,
|
|
1955
|
+
enableReflection: false,
|
|
1956
|
+
...options,
|
|
1957
|
+
};
|
|
1958
|
+
return this;
|
|
1959
|
+
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Lazy initialize gRPC system
|
|
1962
|
+
*/
|
|
1963
|
+
async ensureGrpcInitialized() {
|
|
1964
|
+
if (this.grpcManager || this.grpcInitPromise) {
|
|
1965
|
+
await this.grpcInitPromise;
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
if (!this.grpcConfig) {
|
|
1969
|
+
return; // gRPC not configured, skip
|
|
1970
|
+
}
|
|
1971
|
+
this.grpcInitPromise = (async () => {
|
|
1972
|
+
try {
|
|
1973
|
+
this.logger.info('Initializing gRPC system', 'GRPC');
|
|
1974
|
+
// Lazy load gRPC manager
|
|
1975
|
+
const { GrpcManager } = await import('./core/grpc/grpc-manager.js');
|
|
1976
|
+
// Create gRPC manager
|
|
1977
|
+
this.grpcManager = new GrpcManager(this.grpcConfig);
|
|
1978
|
+
// Initialize gRPC
|
|
1979
|
+
await this.grpcManager.initialize();
|
|
1980
|
+
this.logger.info('gRPC system initialized', 'GRPC');
|
|
1981
|
+
}
|
|
1982
|
+
catch (error) {
|
|
1983
|
+
this.logger.error(`Failed to initialize gRPC: ${error}`, 'GRPC');
|
|
1984
|
+
throw error;
|
|
1985
|
+
}
|
|
1986
|
+
})();
|
|
1987
|
+
await this.grpcInitPromise;
|
|
1988
|
+
}
|
|
1989
|
+
/**
|
|
1990
|
+
* Register a gRPC service from a proto file
|
|
1991
|
+
*
|
|
1992
|
+
* @example
|
|
1993
|
+
* ```typescript
|
|
1994
|
+
* app.grpcService('./proto/users.proto', 'UserService', {
|
|
1995
|
+
* getUser: async (call, callback) => {
|
|
1996
|
+
* const user = await db.users.findById(call.request.id);
|
|
1997
|
+
* callback(null, user);
|
|
1998
|
+
* },
|
|
1999
|
+
* listUsers: async (call) => {
|
|
2000
|
+
* for (const user of users) {
|
|
2001
|
+
* call.write(user);
|
|
2002
|
+
* }
|
|
2003
|
+
* call.end();
|
|
2004
|
+
* }
|
|
2005
|
+
* });
|
|
2006
|
+
* ```
|
|
2007
|
+
*/
|
|
2008
|
+
async grpcService(protoPath, serviceName, implementation, packageName) {
|
|
2009
|
+
if (!this.grpcConfig && !this.grpcManager) {
|
|
2010
|
+
throw new Error('gRPC not initialized. Call app.grpcInit() first.\n' +
|
|
2011
|
+
'Example: app.grpcInit({ port: 50051 });');
|
|
2012
|
+
}
|
|
2013
|
+
// Lazy initialize gRPC if not already done
|
|
2014
|
+
await this.ensureGrpcInitialized();
|
|
2015
|
+
if (!this.grpcManager) {
|
|
2016
|
+
throw new Error('gRPC not initialized. Call app.grpcInit() first.\n' +
|
|
2017
|
+
'Example: app.grpcInit({ port: 50051 });');
|
|
2018
|
+
}
|
|
2019
|
+
try {
|
|
2020
|
+
await this.grpcManager.registerService(protoPath, serviceName, implementation, packageName);
|
|
2021
|
+
this.logger.info(`gRPC service registered: ${serviceName}`, 'GRPC');
|
|
2022
|
+
}
|
|
2023
|
+
catch (error) {
|
|
2024
|
+
this.logger.error(`Failed to register gRPC service ${serviceName}: ${error}`, 'GRPC');
|
|
2025
|
+
throw error;
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
2029
|
+
* Start gRPC server
|
|
2030
|
+
* Called automatically by listen() if gRPC is configured
|
|
2031
|
+
*/
|
|
2032
|
+
async startGrpc() {
|
|
2033
|
+
// Lazy initialize if needed
|
|
2034
|
+
await this.ensureGrpcInitialized();
|
|
2035
|
+
if (!this.grpcManager) {
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
if (this.grpcStarted) {
|
|
2039
|
+
this.logger.warn('gRPC server already started', 'GRPC');
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
try {
|
|
2043
|
+
await this.grpcManager.start();
|
|
2044
|
+
this.grpcStarted = true;
|
|
2045
|
+
const stats = this.grpcManager.getStats();
|
|
2046
|
+
if (stats) {
|
|
2047
|
+
this.logger.info('gRPC server started successfully', 'GRPC');
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
catch (error) {
|
|
2051
|
+
this.logger.error(`Failed to start gRPC server: ${error}`, 'GRPC');
|
|
2052
|
+
throw error;
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Stop gRPC server gracefully
|
|
2057
|
+
*/
|
|
2058
|
+
async stopGrpc() {
|
|
2059
|
+
if (!this.grpcManager || !this.grpcStarted) {
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
try {
|
|
2063
|
+
await this.grpcManager.stop();
|
|
2064
|
+
this.grpcStarted = false;
|
|
2065
|
+
this.logger.info('gRPC server stopped', 'GRPC');
|
|
2066
|
+
}
|
|
2067
|
+
catch (error) {
|
|
2068
|
+
this.logger.error(`Error stopping gRPC server: ${error}`, 'GRPC');
|
|
2069
|
+
throw error;
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
/**
|
|
2073
|
+
* Create a gRPC client for calling remote services
|
|
2074
|
+
*
|
|
2075
|
+
* @example
|
|
2076
|
+
* ```typescript
|
|
2077
|
+
* const client = await app.createGrpcClient(
|
|
2078
|
+
* './proto/users.proto',
|
|
2079
|
+
* 'UserService',
|
|
2080
|
+
* 'localhost:50051'
|
|
2081
|
+
* );
|
|
2082
|
+
*
|
|
2083
|
+
* const user = await client.getUser({ id: '123' });
|
|
2084
|
+
* ```
|
|
2085
|
+
*/
|
|
2086
|
+
async createGrpcClient(protoPath, serviceName, address, options) {
|
|
2087
|
+
if (!this.grpcConfig && !this.grpcManager) {
|
|
2088
|
+
throw new Error('gRPC not initialized. Call app.grpcInit() first.\n' +
|
|
2089
|
+
'Example: app.grpcInit({ port: 50051 });');
|
|
2090
|
+
}
|
|
2091
|
+
// Lazy initialize gRPC if not already done
|
|
2092
|
+
await this.ensureGrpcInitialized();
|
|
2093
|
+
if (!this.grpcManager) {
|
|
2094
|
+
throw new Error('gRPC not initialized. Call app.grpcInit() first.\n' +
|
|
2095
|
+
'Example: app.grpcInit({ port: 50051 });');
|
|
2096
|
+
}
|
|
2097
|
+
try {
|
|
2098
|
+
const client = await this.grpcManager.createClient(protoPath, serviceName, address, options);
|
|
2099
|
+
this.logger.info(`gRPC client created for ${serviceName} at ${address}`, 'GRPC');
|
|
2100
|
+
return client;
|
|
2101
|
+
}
|
|
2102
|
+
catch (error) {
|
|
2103
|
+
this.logger.error(`Failed to create gRPC client: ${error}`, 'GRPC');
|
|
2104
|
+
throw error;
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Get gRPC statistics
|
|
2109
|
+
*/
|
|
2110
|
+
getGrpcStats() {
|
|
2111
|
+
if (!this.grpcManager) {
|
|
2112
|
+
return null;
|
|
2113
|
+
}
|
|
2114
|
+
return this.grpcManager.getStats();
|
|
2115
|
+
}
|
|
2116
|
+
/**
|
|
2117
|
+
* Get list of registered gRPC services
|
|
2118
|
+
*/
|
|
2119
|
+
getGrpcServices() {
|
|
2120
|
+
if (!this.grpcManager) {
|
|
2121
|
+
return [];
|
|
2122
|
+
}
|
|
2123
|
+
return this.grpcManager.getServices();
|
|
2124
|
+
}
|
|
2125
|
+
/**
|
|
2126
|
+
* Initialize queue system (synchronous for first queue, subsequent queues added immediately)
|
|
2127
|
+
*
|
|
2128
|
+
* @example
|
|
2129
|
+
* ```typescript
|
|
2130
|
+
* app.queueInit('emails', {
|
|
2131
|
+
* adapter: 'bull',
|
|
2132
|
+
* connection: {
|
|
2133
|
+
* host: 'localhost',
|
|
2134
|
+
* port: 6379
|
|
2135
|
+
* },
|
|
2136
|
+
* concurrency: 5
|
|
2137
|
+
* });
|
|
2138
|
+
* ```
|
|
2139
|
+
*/
|
|
2140
|
+
queueInit(name, options) {
|
|
2141
|
+
// Initialize queue manager on first call
|
|
2142
|
+
if (!this.queueInitialized && !this.queueInitPromise) {
|
|
2143
|
+
this.queueInitPromise = (async () => {
|
|
2144
|
+
try {
|
|
2145
|
+
this.logger.info('Initializing queue system', 'QUEUE');
|
|
2146
|
+
const { QueueManager } = await import('./core/queue/index.js');
|
|
2147
|
+
this.queueManager = new QueueManager(this.eventBus);
|
|
2148
|
+
this.queueInitialized = true;
|
|
2149
|
+
this.logger.info('Queue system initialized', 'QUEUE');
|
|
2150
|
+
}
|
|
2151
|
+
catch (error) {
|
|
2152
|
+
this.logger.error(`Failed to initialize queue system: ${error}`, 'QUEUE');
|
|
2153
|
+
throw error;
|
|
2154
|
+
}
|
|
2155
|
+
})();
|
|
2156
|
+
}
|
|
2157
|
+
// Store config to register after initialization
|
|
2158
|
+
this.queueConfigs.set(name, options);
|
|
2159
|
+
this.logger.debug(`Queue "${name}" configured`, 'QUEUE');
|
|
2160
|
+
// Register queue async (manager will be ready)
|
|
2161
|
+
if (this.queueInitPromise) {
|
|
2162
|
+
this.queueInitPromise
|
|
2163
|
+
.then(async () => {
|
|
2164
|
+
if (this.queueManager) {
|
|
2165
|
+
await this.queueManager.registerQueue(name, options);
|
|
2166
|
+
this.logger.info(`Queue "${name}" registered with ${options.adapter} adapter`, 'QUEUE');
|
|
2167
|
+
}
|
|
2168
|
+
})
|
|
2169
|
+
.catch(error => {
|
|
2170
|
+
this.logger.error(`Failed to register queue "${name}": ${error}`, 'QUEUE');
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
return this;
|
|
2174
|
+
}
|
|
2175
|
+
/**
|
|
2176
|
+
* Ensure queue system is initialized
|
|
2177
|
+
*/
|
|
2178
|
+
async ensureQueueInitialized() {
|
|
2179
|
+
if (this.queueInitPromise) {
|
|
2180
|
+
await this.queueInitPromise;
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* @deprecated Use queueInit() instead
|
|
2185
|
+
*/
|
|
2186
|
+
async queue(name, options) {
|
|
2187
|
+
if (!this.queueInitialized) {
|
|
2188
|
+
try {
|
|
2189
|
+
this.logger.info('Initializing queue system', 'QUEUE');
|
|
2190
|
+
const { QueueManager } = await import('./core/queue/index.js');
|
|
2191
|
+
this.queueManager = new QueueManager(this.eventBus);
|
|
2192
|
+
this.queueInitialized = true;
|
|
2193
|
+
this.logger.info('Queue system initialized', 'QUEUE');
|
|
2194
|
+
}
|
|
2195
|
+
catch (error) {
|
|
2196
|
+
this.logger.error(`Failed to initialize queue system: ${error}`, 'QUEUE');
|
|
2197
|
+
throw error;
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
try {
|
|
2201
|
+
await this.queueManager.registerQueue(name, options);
|
|
2202
|
+
this.logger.info(`Queue "${name}" registered with ${options.adapter} adapter`, 'QUEUE');
|
|
2203
|
+
}
|
|
2204
|
+
catch (error) {
|
|
2205
|
+
this.logger.error(`Failed to register queue "${name}": ${error}`, 'QUEUE');
|
|
2206
|
+
throw error;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Add a job to a queue
|
|
2211
|
+
*
|
|
2212
|
+
* @example
|
|
2213
|
+
* ```typescript
|
|
2214
|
+
* await app.addToQueue('emails', {
|
|
2215
|
+
* to: 'user@example.com',
|
|
2216
|
+
* subject: 'Welcome'
|
|
2217
|
+
* }, {
|
|
2218
|
+
* priority: 10,
|
|
2219
|
+
* delay: 5000
|
|
2220
|
+
* });
|
|
2221
|
+
* ```
|
|
2222
|
+
*/
|
|
2223
|
+
async addToQueue(queueName, data, options) {
|
|
2224
|
+
// Ensure queue system is initialized
|
|
2225
|
+
await this.ensureQueueInitialized();
|
|
2226
|
+
if (!this.queueManager) {
|
|
2227
|
+
throw new Error(`Queue "${queueName}" not initialized. Call app.queueInit('${queueName}', options) first.`);
|
|
2228
|
+
}
|
|
2229
|
+
return await this.queueManager.addToQueue(queueName, data, options);
|
|
2230
|
+
}
|
|
2231
|
+
/**
|
|
2232
|
+
* Add multiple jobs to a queue in bulk
|
|
2233
|
+
*
|
|
2234
|
+
* @example
|
|
2235
|
+
* ```typescript
|
|
2236
|
+
* await app.addBulkToQueue('emails', [
|
|
2237
|
+
* { data: { to: 'user1@example.com' }, options: { priority: 1 } },
|
|
2238
|
+
* { data: { to: 'user2@example.com' }, options: { priority: 2 } }
|
|
2239
|
+
* ]);
|
|
2240
|
+
* ```
|
|
2241
|
+
*/
|
|
2242
|
+
async addBulkToQueue(queueName, jobs) {
|
|
2243
|
+
// Ensure queue system is initialized
|
|
2244
|
+
await this.ensureQueueInitialized();
|
|
2245
|
+
if (!this.queueManager) {
|
|
2246
|
+
throw new Error(`Queue "${queueName}" not initialized. Call app.queue('${queueName}', options) first.`);
|
|
2247
|
+
}
|
|
2248
|
+
return await this.queueManager.addBulkToQueue(queueName, jobs);
|
|
2249
|
+
}
|
|
2250
|
+
/**
|
|
2251
|
+
* Register a processor for a queue
|
|
2252
|
+
*
|
|
2253
|
+
* @example
|
|
2254
|
+
* ```typescript
|
|
2255
|
+
* // Simple processor
|
|
2256
|
+
* app.processQueue('emails', async (job) => {
|
|
2257
|
+
* await sendEmail(job.data);
|
|
2258
|
+
* });
|
|
2259
|
+
*
|
|
2260
|
+
* // With concurrency
|
|
2261
|
+
* app.processQueue('images', 3, async (job) => {
|
|
2262
|
+
* await processImage(job.data);
|
|
2263
|
+
* });
|
|
2264
|
+
* ```
|
|
2265
|
+
*/
|
|
2266
|
+
async processQueue(queueName, concurrencyOrHandler, handler) {
|
|
2267
|
+
// Ensure queue system is initialized
|
|
2268
|
+
await this.ensureQueueInitialized();
|
|
2269
|
+
if (!this.queueManager) {
|
|
2270
|
+
throw new Error(`Queue "${queueName}" not initialized. Call app.queueInit('${queueName}', options) first.`);
|
|
2271
|
+
}
|
|
2272
|
+
return await this.queueManager.processQueue(queueName, concurrencyOrHandler, handler);
|
|
2273
|
+
}
|
|
2274
|
+
/**
|
|
2275
|
+
* Get queue status
|
|
2276
|
+
*/
|
|
2277
|
+
async getQueueStatus(queueName) {
|
|
2278
|
+
if (!this.queueManager) {
|
|
2279
|
+
throw new Error(`Queue system not initialized.`);
|
|
2280
|
+
}
|
|
2281
|
+
return await this.queueManager.getQueueStatus(queueName);
|
|
2282
|
+
}
|
|
2283
|
+
/**
|
|
2284
|
+
* Get a specific job from a queue
|
|
2285
|
+
*/
|
|
2286
|
+
async getJob(queueName, jobId) {
|
|
2287
|
+
if (!this.queueManager) {
|
|
2288
|
+
throw new Error(`Queue system not initialized.`);
|
|
2289
|
+
}
|
|
2290
|
+
return await this.queueManager.getJob(queueName, jobId);
|
|
2291
|
+
}
|
|
2292
|
+
/**
|
|
2293
|
+
* Get jobs from a queue by status
|
|
2294
|
+
*/
|
|
2295
|
+
async getJobs(queueName, status, start = 0, end = -1) {
|
|
2296
|
+
if (!this.queueManager) {
|
|
2297
|
+
throw new Error(`Queue system not initialized.`);
|
|
2298
|
+
}
|
|
2299
|
+
return await this.queueManager.getJobs(queueName, status, start, end);
|
|
2300
|
+
}
|
|
2301
|
+
/**
|
|
2302
|
+
* Remove a job from a queue
|
|
2303
|
+
*/
|
|
2304
|
+
async removeJob(queueName, jobId) {
|
|
2305
|
+
if (!this.queueManager) {
|
|
2306
|
+
throw new Error(`Queue system not initialized.`);
|
|
2307
|
+
}
|
|
2308
|
+
return await this.queueManager.removeJob(queueName, jobId);
|
|
2309
|
+
}
|
|
2310
|
+
/**
|
|
2311
|
+
* Retry a failed job
|
|
2312
|
+
*/
|
|
2313
|
+
async retryJob(queueName, jobId) {
|
|
2314
|
+
if (!this.queueManager) {
|
|
2315
|
+
throw new Error(`Queue system not initialized.`);
|
|
2316
|
+
}
|
|
2317
|
+
return await this.queueManager.retryJob(queueName, jobId);
|
|
2318
|
+
}
|
|
2319
|
+
/**
|
|
2320
|
+
* Pause a queue
|
|
2321
|
+
*/
|
|
2322
|
+
async pauseQueue(queueName) {
|
|
2323
|
+
if (!this.queueManager) {
|
|
2324
|
+
throw new Error(`Queue system not initialized.`);
|
|
2325
|
+
}
|
|
2326
|
+
return await this.queueManager.pauseQueue(queueName);
|
|
2327
|
+
}
|
|
2328
|
+
/**
|
|
2329
|
+
* Resume a paused queue
|
|
2330
|
+
*/
|
|
2331
|
+
async resumeQueue(queueName) {
|
|
2332
|
+
if (!this.queueManager) {
|
|
2333
|
+
throw new Error(`Queue system not initialized.`);
|
|
2334
|
+
}
|
|
2335
|
+
return await this.queueManager.resumeQueue(queueName);
|
|
2336
|
+
}
|
|
2337
|
+
/**
|
|
2338
|
+
* Clean old jobs from a queue
|
|
2339
|
+
*/
|
|
2340
|
+
async cleanQueue(queueName, gracePeriod, status) {
|
|
2341
|
+
if (!this.queueManager) {
|
|
2342
|
+
throw new Error(`Queue system not initialized.`);
|
|
2343
|
+
}
|
|
2344
|
+
return await this.queueManager.cleanQueue(queueName, gracePeriod, status);
|
|
2345
|
+
}
|
|
2346
|
+
/**
|
|
2347
|
+
* Get all registered queue names
|
|
2348
|
+
*/
|
|
2349
|
+
getQueueNames() {
|
|
2350
|
+
// Return configured queues (includes both initialized and pending)
|
|
2351
|
+
const configuredQueues = Array.from(this.queueConfigs.keys());
|
|
2352
|
+
if (!this.queueManager) {
|
|
2353
|
+
return configuredQueues;
|
|
2354
|
+
}
|
|
2355
|
+
// Merge with manager's queues (in case some were added directly)
|
|
2356
|
+
const managerQueues = this.queueManager.getQueueNames();
|
|
2357
|
+
const allQueues = new Set([...configuredQueues, ...managerQueues]);
|
|
2358
|
+
return Array.from(allQueues);
|
|
2359
|
+
}
|
|
2360
|
+
/**
|
|
2361
|
+
* Check if a queue is registered
|
|
2362
|
+
*/
|
|
2363
|
+
hasQueue(queueName) {
|
|
2364
|
+
// Check if it's configured (includes both initialized and pending)
|
|
2365
|
+
if (this.queueConfigs.has(queueName)) {
|
|
2366
|
+
return true;
|
|
2367
|
+
}
|
|
2368
|
+
if (!this.queueManager) {
|
|
2369
|
+
return false;
|
|
2370
|
+
}
|
|
2371
|
+
return this.queueManager.hasQueue(queueName);
|
|
2372
|
+
}
|
|
2373
|
+
// =====================
|
|
2374
|
+
// Mail System Methods
|
|
2375
|
+
// =====================
|
|
2376
|
+
/**
|
|
2377
|
+
* Configure mail system (synchronous, lazy initialization)
|
|
2378
|
+
*
|
|
2379
|
+
* @example
|
|
2380
|
+
* ```typescript
|
|
2381
|
+
* // Using Nodemailer (SMTP) - chainable, no await needed!
|
|
2382
|
+
* app.mailInit({
|
|
2383
|
+
* adapter: 'nodemailer',
|
|
2384
|
+
* from: { name: 'My App', email: 'noreply@myapp.com' },
|
|
2385
|
+
* connection: {
|
|
2386
|
+
* host: 'smtp.gmail.com',
|
|
2387
|
+
* port: 587,
|
|
2388
|
+
* secure: false,
|
|
2389
|
+
* auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASSWORD }
|
|
2390
|
+
* },
|
|
2391
|
+
* templates: { path: './emails', engine: 'moro', cache: true }
|
|
2392
|
+
* });
|
|
2393
|
+
*
|
|
2394
|
+
* // Using SendGrid
|
|
2395
|
+
* app.mailInit({
|
|
2396
|
+
* adapter: 'sendgrid',
|
|
2397
|
+
* from: 'noreply@myapp.com',
|
|
2398
|
+
* connection: { apiKey: process.env.SENDGRID_API_KEY }
|
|
2399
|
+
* });
|
|
2400
|
+
* ```
|
|
2401
|
+
*/
|
|
2402
|
+
mailInit(config) {
|
|
2403
|
+
if (this.mailInitialized) {
|
|
2404
|
+
this.logger.warn('Mail system already configured', 'Mail');
|
|
2405
|
+
return this;
|
|
2406
|
+
}
|
|
2407
|
+
// Store config for lazy initialization
|
|
2408
|
+
this.mailConfig = config;
|
|
2409
|
+
this.logger.debug('Mail system configured (will initialize on first use)', 'Mail');
|
|
2410
|
+
this.eventBus.emit('mail:configured', { config });
|
|
2411
|
+
return this;
|
|
2412
|
+
}
|
|
2413
|
+
/**
|
|
2414
|
+
* Lazy initialize mail system on first use
|
|
2415
|
+
*/
|
|
2416
|
+
async ensureMailInitialized() {
|
|
2417
|
+
if (this.mailInitialized) {
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
if (!this.mailConfig) {
|
|
2421
|
+
throw new Error('Mail system not configured. Call app.mailInit(config) first.\n' +
|
|
2422
|
+
"Example: app.mailInit({ adapter: 'console', from: 'noreply@myapp.com' });");
|
|
2423
|
+
}
|
|
2424
|
+
try {
|
|
2425
|
+
this.logger.info('Initializing mail system', 'Mail');
|
|
2426
|
+
const { MailManager } = await import('./core/mail/index.js');
|
|
2427
|
+
this.mailManager = new MailManager(this.mailConfig);
|
|
2428
|
+
await this.mailManager.initialize();
|
|
2429
|
+
if (this.mailConfig.queue?.enabled && this.queueManager) {
|
|
2430
|
+
this.mailManager.setQueueManager(this.queueManager);
|
|
2431
|
+
const queueName = this.mailConfig.queue.name || 'emails';
|
|
2432
|
+
if (!this.queueManager.hasQueue(queueName)) {
|
|
2433
|
+
this.queueConfigs.set(queueName, {
|
|
2434
|
+
adapter: 'memory',
|
|
2435
|
+
concurrency: this.mailConfig.queue.attempts || 3,
|
|
2436
|
+
});
|
|
2437
|
+
await this.ensureQueueInitialized();
|
|
2438
|
+
await this.processQueue(queueName, async (job) => {
|
|
2439
|
+
if (this.mailManager) {
|
|
2440
|
+
return await this.mailManager.send(job.data);
|
|
2441
|
+
}
|
|
2442
|
+
throw new Error('Mail manager not initialized');
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2445
|
+
this.logger.info(`Mail queue "${queueName}" configured`, 'Mail');
|
|
2446
|
+
}
|
|
2447
|
+
this.mailInitialized = true;
|
|
2448
|
+
this.logger.info('Mail system initialized successfully', 'Mail');
|
|
2449
|
+
}
|
|
2450
|
+
catch (error) {
|
|
2451
|
+
this.logger.error(`Failed to initialize mail system: ${error}`, 'Mail');
|
|
2452
|
+
throw error;
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
/**
|
|
2456
|
+
* Send an email
|
|
2457
|
+
*
|
|
2458
|
+
* @example
|
|
2459
|
+
* ```typescript
|
|
2460
|
+
* // Simple email
|
|
2461
|
+
* await app.sendMail({
|
|
2462
|
+
* to: 'user@example.com',
|
|
2463
|
+
* subject: 'Welcome',
|
|
2464
|
+
* text: 'Welcome to our app!'
|
|
2465
|
+
* });
|
|
2466
|
+
*
|
|
2467
|
+
* // With template
|
|
2468
|
+
* await app.sendMail({
|
|
2469
|
+
* to: 'user@example.com',
|
|
2470
|
+
* subject: 'Password Reset',
|
|
2471
|
+
* template: 'password-reset',
|
|
2472
|
+
* data: { name: 'John', resetUrl: 'https://myapp.com/reset/token123' }
|
|
2473
|
+
* });
|
|
2474
|
+
*
|
|
2475
|
+
* // With attachments
|
|
2476
|
+
* await app.sendMail({
|
|
2477
|
+
* to: 'user@example.com',
|
|
2478
|
+
* subject: 'Invoice',
|
|
2479
|
+
* template: 'invoice',
|
|
2480
|
+
* data: { invoice },
|
|
2481
|
+
* attachments: [{ filename: 'invoice.pdf', content: pdfBuffer }]
|
|
2482
|
+
* });
|
|
2483
|
+
* ```
|
|
2484
|
+
*/
|
|
2485
|
+
async sendMail(options) {
|
|
2486
|
+
// Lazy initialize on first use
|
|
2487
|
+
await this.ensureMailInitialized();
|
|
2488
|
+
this.eventBus.emit('mail:sending', { options });
|
|
2489
|
+
try {
|
|
2490
|
+
const result = await this.mailManager.send(options);
|
|
2491
|
+
if (result.success) {
|
|
2492
|
+
this.eventBus.emit('mail:sent', { result, options });
|
|
2493
|
+
}
|
|
2494
|
+
else {
|
|
2495
|
+
this.eventBus.emit('mail:failed', { error: new Error(result.error), options });
|
|
2496
|
+
}
|
|
2497
|
+
return result;
|
|
2498
|
+
}
|
|
2499
|
+
catch (error) {
|
|
2500
|
+
this.eventBus.emit('mail:failed', { error, options });
|
|
2501
|
+
throw error;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
/**
|
|
2505
|
+
* Send multiple emails in bulk
|
|
2506
|
+
*
|
|
2507
|
+
* @example
|
|
2508
|
+
* ```typescript
|
|
2509
|
+
* await app.sendBulkMail([
|
|
2510
|
+
* { to: 'user1@example.com', subject: 'Hello', text: 'Hi!' },
|
|
2511
|
+
* { to: 'user2@example.com', subject: 'Hello', text: 'Hi!' }
|
|
2512
|
+
* ]);
|
|
2513
|
+
* ```
|
|
2514
|
+
*/
|
|
2515
|
+
async sendBulkMail(options) {
|
|
2516
|
+
// Lazy initialize on first use
|
|
2517
|
+
await this.ensureMailInitialized();
|
|
2518
|
+
return await this.mailManager.sendBulk(options);
|
|
2519
|
+
}
|
|
2520
|
+
/**
|
|
2521
|
+
* Verify mail adapter connection
|
|
2522
|
+
*/
|
|
2523
|
+
async verifyMail() {
|
|
2524
|
+
// Lazy initialize on first use
|
|
2525
|
+
try {
|
|
2526
|
+
await this.ensureMailInitialized();
|
|
2527
|
+
return await this.mailManager.verify();
|
|
2528
|
+
}
|
|
2529
|
+
catch {
|
|
2530
|
+
return false;
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
/**
|
|
2534
|
+
* Get mail template engine for advanced usage
|
|
2535
|
+
*/
|
|
2536
|
+
getMailTemplateEngine() {
|
|
2537
|
+
if (!this.mailManager) {
|
|
2538
|
+
return undefined;
|
|
2539
|
+
}
|
|
2540
|
+
return this.mailManager.getTemplateEngine();
|
|
2541
|
+
}
|
|
2542
|
+
/**
|
|
2543
|
+
* Check if mail system is configured
|
|
2544
|
+
*/
|
|
2545
|
+
hasMailSystem() {
|
|
2546
|
+
return !!this.mailConfig || this.mailInitialized;
|
|
2547
|
+
}
|
|
1382
2548
|
}
|
|
1383
2549
|
// Export convenience function
|
|
1384
2550
|
export function createApp(options) {
|