@morojs/moro 1.6.8 → 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 +3 -1
- package/dist/core/auth/morojs-adapter.js +16 -6
- package/dist/core/auth/morojs-adapter.js.map +1 -1
- package/dist/core/config/config-sources.js +27 -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/docs/openapi-generator.js +8 -9
- package/dist/core/docs/openapi-generator.js.map +1 -1
- package/dist/core/events/event-bus.js +1 -1
- package/dist/core/events/event-bus.js.map +1 -1
- package/dist/core/framework.d.ts +4 -2
- package/dist/core/framework.js +25 -24
- package/dist/core/framework.js.map +1 -1
- package/dist/core/graphql/core.js +34 -8
- package/dist/core/graphql/core.js.map +1 -1
- 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 +205 -792
- 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/uws-http-server.js +21 -26
- package/dist/core/http/uws-http-server.js.map +1 -1
- package/dist/core/jobs/job-executor.js +6 -1
- package/dist/core/jobs/job-executor.js.map +1 -1
- package/dist/core/jobs/job-scheduler.js +4 -1
- package/dist/core/jobs/job-scheduler.js.map +1 -1
- package/dist/core/jobs/leader-election.js +2 -1
- package/dist/core/jobs/leader-election.js.map +1 -1
- package/dist/core/logger/logger.js +41 -18
- package/dist/core/logger/logger.js.map +1 -1
- package/dist/core/logger/outputs.js +9 -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/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/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/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/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 +18 -0
- package/dist/core/middleware/built-in/index.js +28 -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/middleware.js +2 -1
- package/dist/core/middleware/built-in/validation/middleware.js.map +1 -1
- package/dist/core/networking/adapters/uws-adapter.js +54 -6
- package/dist/core/networking/adapters/uws-adapter.js.map +1 -1
- package/dist/core/networking/adapters/ws-adapter.js +56 -17
- package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
- package/dist/core/pooling/object-pool-manager.js +9 -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/index.js +41 -10
- 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 +15 -5
- package/dist/core/runtime/base-adapter.js.map +1 -1
- package/dist/core/runtime/cloudflare-workers-adapter.js +35 -12
- package/dist/core/runtime/cloudflare-workers-adapter.js.map +1 -1
- package/dist/core/runtime/vercel-edge-adapter.js +16 -6
- package/dist/core/runtime/vercel-edge-adapter.js.map +1 -1
- package/dist/core/utilities/container.js +3 -1
- package/dist/core/utilities/container.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 +8 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/moro.d.ts +345 -13
- package/dist/moro.js +803 -214
- package/dist/moro.js.map +1 -1
- package/dist/types/cache.d.ts +4 -0
- package/dist/types/config.d.ts +42 -0
- package/dist/types/core.d.ts +18 -1
- package/package.json +94 -20
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,11 +16,12 @@ 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';
|
|
20
|
-
// uWebSockets Worker Thread Clustering
|
|
21
|
-
import { UWSWorkerClusterManager } from './core/http/utils/uws-worker-clustering.js';
|
|
22
|
-
import { isMainThread } from 'worker_threads';
|
|
23
19
|
// Job System Integration
|
|
24
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
25
|
// Lazy imports for GraphQL to avoid crashes when not installed
|
|
26
26
|
let GraphQLCore;
|
|
27
27
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -67,6 +67,23 @@ export class Moro extends EventEmitter {
|
|
|
67
67
|
graphqlCore; // Use any to avoid import dependency
|
|
68
68
|
graphqlSubscriptionManager;
|
|
69
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
|
|
70
87
|
constructor(options = {}) {
|
|
71
88
|
super(); // Call EventEmitter constructor
|
|
72
89
|
// Track if user explicitly set logger/logging options
|
|
@@ -155,6 +172,8 @@ export class Moro extends EventEmitter {
|
|
|
155
172
|
leaderElection: jobSchedulerOptions.enableLeaderElection,
|
|
156
173
|
});
|
|
157
174
|
}
|
|
175
|
+
// Initialize worker threads facade (optional - lazy loaded)
|
|
176
|
+
this.workerFacade = new WorkerThreadsFacade();
|
|
158
177
|
// Setup default middleware if enabled - use config defaults with options override
|
|
159
178
|
this.setupDefaultMiddleware({
|
|
160
179
|
...this.getDefaultOptionsFromConfig(),
|
|
@@ -457,19 +476,10 @@ export class Moro extends EventEmitter {
|
|
|
457
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');
|
|
458
477
|
}
|
|
459
478
|
// Check if clustering is enabled for massive performance gains
|
|
460
|
-
const usingUWebSockets = this.config.server?.useUWebSockets || false;
|
|
461
479
|
if (this.config.performance?.clustering?.enabled) {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
this.startWithUWSClustering(port, host, callback);
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
else {
|
|
469
|
-
// Use traditional Node.js cluster module for standard HTTP
|
|
470
|
-
this.startWithClustering(port, host, callback);
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
480
|
+
this.logger.info('Clustering enabled - using Node.js cluster', 'Cluster');
|
|
481
|
+
this.startWithClustering(port, host, callback);
|
|
482
|
+
return;
|
|
473
483
|
}
|
|
474
484
|
this.eventBus.emit('server:starting', { port, runtime: this.runtimeType });
|
|
475
485
|
// Add documentation middleware first (if enabled)
|
|
@@ -483,7 +493,7 @@ export class Moro extends EventEmitter {
|
|
|
483
493
|
this.logger.debug('Documentation not enabled', 'Documentation');
|
|
484
494
|
}
|
|
485
495
|
// Add unified routing middleware (handles both chainable and direct routes)
|
|
486
|
-
//
|
|
496
|
+
// Call router without extra async wrapper when possible
|
|
487
497
|
this.coreFramework.addMiddleware(async (req, res, next) => {
|
|
488
498
|
// Try unified router first (handles all route types)
|
|
489
499
|
const handled = this.unifiedRouter.handleRequest(req, res);
|
|
@@ -528,6 +538,16 @@ export class Moro extends EventEmitter {
|
|
|
528
538
|
this.startJobScheduler().catch(err => {
|
|
529
539
|
this.logger.error(`Failed to start job scheduler: ${String(err)}`);
|
|
530
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
|
+
}
|
|
531
551
|
if (callback)
|
|
532
552
|
callback();
|
|
533
553
|
};
|
|
@@ -748,7 +768,7 @@ export class Moro extends EventEmitter {
|
|
|
748
768
|
staticRouteMap = new Map();
|
|
749
769
|
dynamicRoutesBySegments = new Map();
|
|
750
770
|
findMatchingRoute(method, path) {
|
|
751
|
-
//
|
|
771
|
+
// O(1) static route lookup
|
|
752
772
|
const staticKey = `${method}:${path}`;
|
|
753
773
|
const staticRoute = this.staticRouteMap.get(staticKey);
|
|
754
774
|
if (staticRoute) {
|
|
@@ -758,7 +778,7 @@ export class Moro extends EventEmitter {
|
|
|
758
778
|
paramNames: [],
|
|
759
779
|
};
|
|
760
780
|
}
|
|
761
|
-
//
|
|
781
|
+
// Dynamic route matching by segment count
|
|
762
782
|
const segmentCount = PathMatcher.countSegments(path);
|
|
763
783
|
const candidateRoutes = this.dynamicRoutesBySegments.get(segmentCount) || [];
|
|
764
784
|
for (const route of candidateRoutes) {
|
|
@@ -941,11 +961,11 @@ export class Moro extends EventEmitter {
|
|
|
941
961
|
: this.config.security.cors
|
|
942
962
|
? this.config.security.cors
|
|
943
963
|
: {};
|
|
944
|
-
this.use(
|
|
964
|
+
this.use(cors(corsOptions));
|
|
945
965
|
}
|
|
946
966
|
// Helmet - check config enabled property OR options.security.helmet.enabled === true
|
|
947
967
|
if (this.config.security.helmet.enabled || options.security?.helmet?.enabled === true) {
|
|
948
|
-
this.use(
|
|
968
|
+
this.use(helmet());
|
|
949
969
|
}
|
|
950
970
|
// Compression - check config enabled property OR options.performance.compression.enabled === true
|
|
951
971
|
if (this.config.performance.compression.enabled ||
|
|
@@ -955,10 +975,10 @@ export class Moro extends EventEmitter {
|
|
|
955
975
|
: this.config.performance.compression
|
|
956
976
|
? this.config.performance.compression
|
|
957
977
|
: {};
|
|
958
|
-
this.use(
|
|
978
|
+
this.use(compression(compressionOptions));
|
|
959
979
|
}
|
|
960
980
|
// Body size limiting
|
|
961
|
-
this.use(
|
|
981
|
+
this.use(bodySize({ limit: '10mb' }));
|
|
962
982
|
}
|
|
963
983
|
// Enhanced auto-discovery initialization
|
|
964
984
|
async initializeAutoDiscovery(options) {
|
|
@@ -1313,7 +1333,7 @@ export class Moro extends EventEmitter {
|
|
|
1313
1333
|
// Documentation not enabled, that's fine
|
|
1314
1334
|
}
|
|
1315
1335
|
// Add unified routing middleware (handles both chainable and direct routes)
|
|
1316
|
-
//
|
|
1336
|
+
// Call router without extra async wrapper when possible
|
|
1317
1337
|
this.coreFramework.addMiddleware(async (req, res, next) => {
|
|
1318
1338
|
// Try unified router first (handles all route types)
|
|
1319
1339
|
const handled = this.unifiedRouter.handleRequest(req, res);
|
|
@@ -1384,181 +1404,22 @@ export class Moro extends EventEmitter {
|
|
|
1384
1404
|
// Log other worker messages
|
|
1385
1405
|
this.logger.debug(`Worker message: ${JSON.stringify(message)}`, 'Cluster');
|
|
1386
1406
|
}
|
|
1387
|
-
/**
|
|
1388
|
-
* uWebSockets Worker Thread Clustering Implementation
|
|
1389
|
-
* Uses worker threads with acceptor pattern for maximum performance
|
|
1390
|
-
*/
|
|
1391
|
-
async startWithUWSClustering(port, host, callback) {
|
|
1392
|
-
// Check if we're in a worker thread spawned by UWSWorkerClusterManager
|
|
1393
|
-
if (UWSWorkerClusterManager.isUWSWorker()) {
|
|
1394
|
-
// Worker thread mode - setup the app and send descriptor to acceptor
|
|
1395
|
-
await this.startUWSWorker(port, host, callback);
|
|
1396
|
-
return;
|
|
1397
|
-
}
|
|
1398
|
-
// Main thread mode - create acceptor and spawn workers
|
|
1399
|
-
if (isMainThread) {
|
|
1400
|
-
await this.startUWSAcceptor(port, host, callback);
|
|
1401
|
-
return;
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
async startUWSAcceptor(port, host, callback) {
|
|
1405
|
-
const clusterManager = new UWSWorkerClusterManager({
|
|
1406
|
-
workers: this.config.performance?.clustering?.workers,
|
|
1407
|
-
memoryPerWorkerGB: this.config.performance?.clustering?.memoryPerWorkerGB,
|
|
1408
|
-
port,
|
|
1409
|
-
host,
|
|
1410
|
-
ssl: this.config.server?.ssl,
|
|
1411
|
-
});
|
|
1412
|
-
try {
|
|
1413
|
-
await clusterManager.startAcceptorAndWorkers(() => {
|
|
1414
|
-
// This factory function is not used in our implementation
|
|
1415
|
-
// as we're using process.argv[1] to spawn workers
|
|
1416
|
-
return null;
|
|
1417
|
-
});
|
|
1418
|
-
if (callback) {
|
|
1419
|
-
callback();
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
catch (error) {
|
|
1423
|
-
this.logger.error('Failed to start uWebSockets cluster', 'UWSCluster', {
|
|
1424
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1425
|
-
});
|
|
1426
|
-
throw error;
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
async startUWSWorker(port, host, callback) {
|
|
1430
|
-
this.logger.info(`UWS Worker thread ${process.pid} initializing`, 'UWSWorker');
|
|
1431
|
-
// Reduce logging contention in workers
|
|
1432
|
-
if (!this.userSetLogger) {
|
|
1433
|
-
applyLoggingConfiguration(undefined, { level: 'warn' });
|
|
1434
|
-
}
|
|
1435
|
-
// Worker-specific optimizations
|
|
1436
|
-
process.env.UV_THREADPOOL_SIZE = '64';
|
|
1437
|
-
// Setup graceful shutdown for worker
|
|
1438
|
-
let isShuttingDown = false;
|
|
1439
|
-
const shutdownWorker = async () => {
|
|
1440
|
-
if (isShuttingDown)
|
|
1441
|
-
return;
|
|
1442
|
-
isShuttingDown = true;
|
|
1443
|
-
this.logger.info(`UWS Worker thread ${process.pid} shutting down...`, 'UWSWorker');
|
|
1444
|
-
// Close the server to clean up handles
|
|
1445
|
-
const httpServer = this.coreFramework.httpServer;
|
|
1446
|
-
if (httpServer && typeof httpServer.close === 'function') {
|
|
1447
|
-
// Convert callback-based close to Promise
|
|
1448
|
-
await new Promise(resolve => {
|
|
1449
|
-
httpServer.close(() => {
|
|
1450
|
-
resolve();
|
|
1451
|
-
});
|
|
1452
|
-
// Timeout after 1.5 seconds
|
|
1453
|
-
setTimeout(() => {
|
|
1454
|
-
this.logger.warn('HTTP server close timeout', 'UWSWorker');
|
|
1455
|
-
resolve();
|
|
1456
|
-
}, 1500);
|
|
1457
|
-
});
|
|
1458
|
-
}
|
|
1459
|
-
// Clean up ALL event listeners
|
|
1460
|
-
try {
|
|
1461
|
-
this.eventBus.removeAllListeners();
|
|
1462
|
-
}
|
|
1463
|
-
catch {
|
|
1464
|
-
// Ignore cleanup errors
|
|
1465
|
-
}
|
|
1466
|
-
try {
|
|
1467
|
-
this.removeAllListeners();
|
|
1468
|
-
}
|
|
1469
|
-
catch {
|
|
1470
|
-
// Ignore cleanup errors
|
|
1471
|
-
}
|
|
1472
|
-
// Remove all signal handlers
|
|
1473
|
-
process.removeAllListeners('SIGINT');
|
|
1474
|
-
process.removeAllListeners('SIGTERM');
|
|
1475
|
-
this.logger.info(`UWS Worker thread ${process.pid} cleanup complete`, 'UWSWorker');
|
|
1476
|
-
};
|
|
1477
|
-
// Handle shutdown messages from parent
|
|
1478
|
-
UWSWorkerClusterManager.setupWorkerShutdownHandler(shutdownWorker);
|
|
1479
|
-
// Also handle direct signals
|
|
1480
|
-
process.once('SIGINT', shutdownWorker);
|
|
1481
|
-
process.once('SIGTERM', shutdownWorker);
|
|
1482
|
-
// Emit worker starting event
|
|
1483
|
-
this.eventBus.emit('server:starting', {
|
|
1484
|
-
port,
|
|
1485
|
-
runtime: this.runtimeType,
|
|
1486
|
-
worker: process.pid,
|
|
1487
|
-
workerType: 'thread',
|
|
1488
|
-
});
|
|
1489
|
-
// Add documentation middleware first (if enabled)
|
|
1490
|
-
try {
|
|
1491
|
-
const docsMiddleware = this.documentation.getDocsMiddleware();
|
|
1492
|
-
this.coreFramework.addMiddleware(docsMiddleware);
|
|
1493
|
-
}
|
|
1494
|
-
catch {
|
|
1495
|
-
// Documentation not enabled, that's fine
|
|
1496
|
-
}
|
|
1497
|
-
// Add unified routing middleware
|
|
1498
|
-
this.coreFramework.addMiddleware(async (req, res, next) => {
|
|
1499
|
-
const handled = this.unifiedRouter.handleRequest(req, res);
|
|
1500
|
-
if (handled && typeof handled.then === 'function') {
|
|
1501
|
-
const isHandled = await handled;
|
|
1502
|
-
if (!isHandled) {
|
|
1503
|
-
next();
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
else {
|
|
1507
|
-
if (!handled) {
|
|
1508
|
-
next();
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
});
|
|
1512
|
-
// Register legacy direct routes with the HTTP server
|
|
1513
|
-
if (this.routes.length > 0) {
|
|
1514
|
-
this.registerDirectRoutes();
|
|
1515
|
-
}
|
|
1516
|
-
const workerCallback = () => {
|
|
1517
|
-
const displayHost = host || 'localhost';
|
|
1518
|
-
this.logger.info(`UWS Worker thread ${process.pid} ready on ${displayHost}:${port}`, 'UWSWorker');
|
|
1519
|
-
this.eventBus.emit('server:started', {
|
|
1520
|
-
port,
|
|
1521
|
-
runtime: this.runtimeType,
|
|
1522
|
-
worker: process.pid,
|
|
1523
|
-
workerType: 'thread',
|
|
1524
|
-
});
|
|
1525
|
-
// Send descriptor to acceptor
|
|
1526
|
-
const httpServer = this.coreFramework.httpServer;
|
|
1527
|
-
if (httpServer && typeof httpServer.getDescriptor === 'function') {
|
|
1528
|
-
UWSWorkerClusterManager.sendDescriptorToAcceptor(httpServer.getApp())
|
|
1529
|
-
.then(() => {
|
|
1530
|
-
if (callback) {
|
|
1531
|
-
callback();
|
|
1532
|
-
}
|
|
1533
|
-
})
|
|
1534
|
-
.catch((error) => {
|
|
1535
|
-
this.logger.error('Failed to send descriptor to acceptor', 'UWSWorker', {
|
|
1536
|
-
error: error.message,
|
|
1537
|
-
});
|
|
1538
|
-
});
|
|
1539
|
-
}
|
|
1540
|
-
else {
|
|
1541
|
-
this.logger.error('HTTP server does not support getDescriptor()', 'UWSWorker');
|
|
1542
|
-
}
|
|
1543
|
-
};
|
|
1544
|
-
// Ensure WebSocket setup is complete before starting worker
|
|
1545
|
-
await this.processQueuedWebSocketRegistrations();
|
|
1546
|
-
// Start listening on worker-specific port (4000 range)
|
|
1547
|
-
const workerPort = 4000 + parseInt(process.env.UWS_WORKER_INDEX || '0', 10);
|
|
1548
|
-
if (host) {
|
|
1549
|
-
this.coreFramework.listen(workerPort, host, workerCallback);
|
|
1550
|
-
}
|
|
1551
|
-
else {
|
|
1552
|
-
this.coreFramework.listen(workerPort, workerCallback);
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
1407
|
/**
|
|
1556
1408
|
* Gracefully close the application and clean up resources
|
|
1557
1409
|
* This should be called in tests and during shutdown
|
|
1558
1410
|
*/
|
|
1559
1411
|
async close() {
|
|
1560
1412
|
this.logger.debug('Closing Moro application...');
|
|
1561
|
-
// Shutdown
|
|
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
|
|
1562
1423
|
if (this.jobScheduler) {
|
|
1563
1424
|
try {
|
|
1564
1425
|
this.logger.info('Shutting down job scheduler...');
|
|
@@ -1569,13 +1430,20 @@ export class Moro extends EventEmitter {
|
|
|
1569
1430
|
this.logger.error(`Error shutting down job scheduler: ${String(err)}`);
|
|
1570
1431
|
}
|
|
1571
1432
|
}
|
|
1572
|
-
//
|
|
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
|
|
1573
1444
|
if (this.graphqlCore) {
|
|
1574
1445
|
try {
|
|
1575
|
-
|
|
1576
|
-
if (executor) {
|
|
1577
|
-
executor.cleanup();
|
|
1578
|
-
}
|
|
1446
|
+
await this.graphqlCore.cleanup();
|
|
1579
1447
|
}
|
|
1580
1448
|
catch (err) {
|
|
1581
1449
|
this.logger.error(`Error cleaning up GraphQL: ${String(err)}`);
|
|
@@ -1598,7 +1466,10 @@ export class Moro extends EventEmitter {
|
|
|
1598
1466
|
resolve();
|
|
1599
1467
|
});
|
|
1600
1468
|
}),
|
|
1601
|
-
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
|
+
}),
|
|
1602
1473
|
]);
|
|
1603
1474
|
}
|
|
1604
1475
|
catch {
|
|
@@ -1615,6 +1486,31 @@ export class Moro extends EventEmitter {
|
|
|
1615
1486
|
// Ignore cleanup errors
|
|
1616
1487
|
}
|
|
1617
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
|
+
}
|
|
1618
1514
|
// Clean up event listeners
|
|
1619
1515
|
try {
|
|
1620
1516
|
this.eventBus.removeAllListeners();
|
|
@@ -1796,14 +1692,14 @@ export class Moro extends EventEmitter {
|
|
|
1796
1692
|
// GraphQL API
|
|
1797
1693
|
// ========================================
|
|
1798
1694
|
/**
|
|
1799
|
-
* Configure GraphQL endpoint
|
|
1695
|
+
* Configure GraphQL endpoint (synchronous, lazy initialization)
|
|
1800
1696
|
*
|
|
1801
1697
|
* @param options - GraphQL configuration options
|
|
1802
1698
|
*
|
|
1803
1699
|
* @example
|
|
1804
1700
|
* ```ts
|
|
1805
1701
|
* // Using type definitions and resolvers
|
|
1806
|
-
* app.
|
|
1702
|
+
* app.graphqlInit({
|
|
1807
1703
|
* typeDefs: `
|
|
1808
1704
|
* type Query {
|
|
1809
1705
|
* hello: String
|
|
@@ -1832,14 +1728,14 @@ export class Moro extends EventEmitter {
|
|
|
1832
1728
|
* })
|
|
1833
1729
|
* });
|
|
1834
1730
|
*
|
|
1835
|
-
* app.
|
|
1731
|
+
* app.graphqlInit({
|
|
1836
1732
|
* pothosSchema: builder
|
|
1837
1733
|
* });
|
|
1838
1734
|
* ```
|
|
1839
1735
|
*/
|
|
1840
|
-
|
|
1736
|
+
graphqlInit(options) {
|
|
1841
1737
|
if (this.graphqlCore || this.graphqlInitPromise) {
|
|
1842
|
-
throw new Error('GraphQL has already been configured. Call
|
|
1738
|
+
throw new Error('GraphQL has already been configured. Call graphqlInit() only once.');
|
|
1843
1739
|
}
|
|
1844
1740
|
// Check if graphql package is available
|
|
1845
1741
|
if (!this.isGraphQLAvailable()) {
|
|
@@ -1848,21 +1744,31 @@ export class Moro extends EventEmitter {
|
|
|
1848
1744
|
'For TypeScript-first GraphQL, also consider: npm install @pothos/core\n' +
|
|
1849
1745
|
'For performance boost: npm install graphql-jit');
|
|
1850
1746
|
}
|
|
1851
|
-
this.logger.info('Configuring GraphQL', 'GraphQL', {
|
|
1747
|
+
this.logger.info('Configuring GraphQL (will initialize on server start)', 'GraphQL', {
|
|
1852
1748
|
path: options.path || '/graphql',
|
|
1853
1749
|
jit: options.enableJIT !== false,
|
|
1854
1750
|
playground: options.enablePlayground !== false,
|
|
1855
1751
|
});
|
|
1856
|
-
//
|
|
1752
|
+
// Store config and trigger initialization immediately
|
|
1753
|
+
this.graphqlConfig = options;
|
|
1857
1754
|
this.graphqlInitPromise = this.initializeGraphQL(options);
|
|
1858
|
-
|
|
1859
|
-
const originalEnsure = this.ensureAutoDiscoveryComplete.bind(this);
|
|
1860
|
-
this.ensureAutoDiscoveryComplete = async () => {
|
|
1861
|
-
await originalEnsure();
|
|
1862
|
-
await this.graphqlInitPromise;
|
|
1863
|
-
};
|
|
1755
|
+
this.eventBus.emit('graphql:configured', { options });
|
|
1864
1756
|
return this;
|
|
1865
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
|
+
}
|
|
1866
1772
|
/**
|
|
1867
1773
|
* Initialize GraphQL system asynchronously
|
|
1868
1774
|
*/
|
|
@@ -1941,13 +1847,74 @@ export class Moro extends EventEmitter {
|
|
|
1941
1847
|
/**
|
|
1942
1848
|
* Get GraphQL schema (if configured)
|
|
1943
1849
|
*/
|
|
1944
|
-
getGraphQLSchema() {
|
|
1850
|
+
async getGraphQLSchema() {
|
|
1851
|
+
await this.ensureGraphQLInitialized();
|
|
1945
1852
|
return this.graphqlCore?.getSchema();
|
|
1946
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
|
+
}
|
|
1947
1913
|
/**
|
|
1948
1914
|
* Get GraphQL stats
|
|
1949
1915
|
*/
|
|
1950
|
-
getGraphQLStats() {
|
|
1916
|
+
async getGraphQLStats() {
|
|
1917
|
+
await this.ensureGraphQLInitialized();
|
|
1951
1918
|
if (!this.graphqlCore) {
|
|
1952
1919
|
return null;
|
|
1953
1920
|
}
|
|
@@ -1956,6 +1923,628 @@ export class Moro extends EventEmitter {
|
|
|
1956
1923
|
subscriptions: this.graphqlSubscriptionManager?.getSubscriptionCount() || 0,
|
|
1957
1924
|
};
|
|
1958
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
|
+
}
|
|
1959
2548
|
}
|
|
1960
2549
|
// Export convenience function
|
|
1961
2550
|
export function createApp(options) {
|