@morojs/moro 1.6.5 → 1.6.8

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.
Files changed (200) hide show
  1. package/README.md +20 -4
  2. package/dist/core/auth/morojs-adapter.js +17 -14
  3. package/dist/core/auth/morojs-adapter.js.map +1 -1
  4. package/dist/core/config/config-sources.js +44 -0
  5. package/dist/core/config/config-sources.js.map +1 -1
  6. package/dist/core/database/adapters/drizzle.js +5 -5
  7. package/dist/core/database/adapters/drizzle.js.map +1 -1
  8. package/dist/core/database/adapters/mongodb.js +5 -1
  9. package/dist/core/database/adapters/mongodb.js.map +1 -1
  10. package/dist/core/database/adapters/mysql.js +5 -1
  11. package/dist/core/database/adapters/mysql.js.map +1 -1
  12. package/dist/core/database/adapters/postgresql.js +1 -1
  13. package/dist/core/database/adapters/postgresql.js.map +1 -1
  14. package/dist/core/database/adapters/redis.js +2 -2
  15. package/dist/core/database/adapters/redis.js.map +1 -1
  16. package/dist/core/database/adapters/sqlite.js +5 -1
  17. package/dist/core/database/adapters/sqlite.js.map +1 -1
  18. package/dist/core/docs/index.js.map +1 -1
  19. package/dist/core/docs/simple-docs.js +2 -1
  20. package/dist/core/docs/simple-docs.js.map +1 -1
  21. package/dist/core/docs/swagger-ui.js +1 -0
  22. package/dist/core/docs/swagger-ui.js.map +1 -1
  23. package/dist/core/docs/zod-to-openapi.js +4 -0
  24. package/dist/core/docs/zod-to-openapi.js.map +1 -1
  25. package/dist/core/events/event-bus.d.ts +1 -1
  26. package/dist/core/events/event-bus.js +8 -4
  27. package/dist/core/events/event-bus.js.map +1 -1
  28. package/dist/core/framework.d.ts +1 -1
  29. package/dist/core/framework.js +3 -1
  30. package/dist/core/framework.js.map +1 -1
  31. package/dist/core/graphql/adapter.d.ts +73 -0
  32. package/dist/core/graphql/adapter.js +2 -0
  33. package/dist/core/graphql/adapter.js.map +1 -0
  34. package/dist/core/graphql/adapters/graphql-js-adapter.d.ts +26 -0
  35. package/dist/core/graphql/adapters/graphql-js-adapter.js +229 -0
  36. package/dist/core/graphql/adapters/graphql-js-adapter.js.map +1 -0
  37. package/dist/core/graphql/core.d.ts +60 -0
  38. package/dist/core/graphql/core.js +165 -0
  39. package/dist/core/graphql/core.js.map +1 -0
  40. package/dist/core/graphql/index.d.ts +4 -0
  41. package/dist/core/graphql/index.js +4 -0
  42. package/dist/core/graphql/index.js.map +1 -0
  43. package/dist/core/graphql/loader.d.ts +9 -0
  44. package/dist/core/graphql/loader.js +32 -0
  45. package/dist/core/graphql/loader.js.map +1 -0
  46. package/dist/core/graphql/types.d.ts +211 -0
  47. package/dist/core/graphql/types.js +2 -0
  48. package/dist/core/graphql/types.js.map +1 -0
  49. package/dist/core/http/http-server.d.ts +7 -0
  50. package/dist/core/http/http-server.js +267 -123
  51. package/dist/core/http/http-server.js.map +1 -1
  52. package/dist/core/http/utils/uws-worker-clustering.d.ts +28 -0
  53. package/dist/core/http/utils/uws-worker-clustering.js +313 -0
  54. package/dist/core/http/utils/uws-worker-clustering.js.map +1 -0
  55. package/dist/core/http/uws-http-server.d.ts +7 -1
  56. package/dist/core/http/uws-http-server.js +272 -189
  57. package/dist/core/http/uws-http-server.js.map +1 -1
  58. package/dist/core/jobs/cron-parser.d.ts +62 -0
  59. package/dist/core/jobs/cron-parser.js +239 -0
  60. package/dist/core/jobs/cron-parser.js.map +1 -0
  61. package/dist/core/jobs/index.d.ts +12 -0
  62. package/dist/core/jobs/index.js +9 -0
  63. package/dist/core/jobs/index.js.map +1 -0
  64. package/dist/core/jobs/job-executor.d.ts +134 -0
  65. package/dist/core/jobs/job-executor.js +413 -0
  66. package/dist/core/jobs/job-executor.js.map +1 -0
  67. package/dist/core/jobs/job-scheduler.d.ts +214 -0
  68. package/dist/core/jobs/job-scheduler.js +551 -0
  69. package/dist/core/jobs/job-scheduler.js.map +1 -0
  70. package/dist/core/jobs/job-state-manager.d.ts +158 -0
  71. package/dist/core/jobs/job-state-manager.js +444 -0
  72. package/dist/core/jobs/job-state-manager.js.map +1 -0
  73. package/dist/core/jobs/leader-election.d.ts +124 -0
  74. package/dist/core/jobs/leader-election.js +481 -0
  75. package/dist/core/jobs/leader-election.js.map +1 -0
  76. package/dist/core/jobs/types.d.ts +151 -0
  77. package/dist/core/jobs/types.js +4 -0
  78. package/dist/core/jobs/types.js.map +1 -0
  79. package/dist/core/jobs/utils.d.ts +95 -0
  80. package/dist/core/jobs/utils.js +258 -0
  81. package/dist/core/jobs/utils.js.map +1 -0
  82. package/dist/core/logger/filters.js +2 -0
  83. package/dist/core/logger/filters.js.map +1 -1
  84. package/dist/core/logger/logger.d.ts +7 -5
  85. package/dist/core/logger/logger.js +68 -27
  86. package/dist/core/logger/logger.js.map +1 -1
  87. package/dist/core/logger/outputs.js +2 -0
  88. package/dist/core/logger/outputs.js.map +1 -1
  89. package/dist/core/middleware/built-in/auth/helpers.js +1 -1
  90. package/dist/core/middleware/built-in/auth/helpers.js.map +1 -1
  91. package/dist/core/middleware/built-in/auth/jwt-helpers.js +1 -1
  92. package/dist/core/middleware/built-in/auth/jwt-helpers.js.map +1 -1
  93. package/dist/core/middleware/built-in/auth/providers.js +1 -1
  94. package/dist/core/middleware/built-in/auth/providers.js.map +1 -1
  95. package/dist/core/middleware/built-in/cache/adapters/cache/file.js +3 -3
  96. package/dist/core/middleware/built-in/cache/adapters/cache/file.js.map +1 -1
  97. package/dist/core/middleware/built-in/cache/adapters/cache/memory.js +1 -0
  98. package/dist/core/middleware/built-in/cache/adapters/cache/memory.js.map +1 -1
  99. package/dist/core/middleware/built-in/cache/adapters/cache/redis.js +1 -1
  100. package/dist/core/middleware/built-in/cache/adapters/cache/redis.js.map +1 -1
  101. package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.d.ts +8 -0
  102. package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.js +100 -7
  103. package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.js.map +1 -1
  104. package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.d.ts +6 -0
  105. package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.js +97 -13
  106. package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.js.map +1 -1
  107. package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudfront.js +1 -1
  108. package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudfront.js.map +1 -1
  109. package/dist/core/middleware/built-in/cookie/hook.d.ts +1 -1
  110. package/dist/core/middleware/built-in/cookie/hook.js +2 -2
  111. package/dist/core/middleware/built-in/cookie/hook.js.map +1 -1
  112. package/dist/core/middleware/built-in/csrf/core.js +1 -0
  113. package/dist/core/middleware/built-in/csrf/core.js.map +1 -1
  114. package/dist/core/middleware/built-in/graphql/core.d.ts +11 -0
  115. package/dist/core/middleware/built-in/graphql/core.js +24 -0
  116. package/dist/core/middleware/built-in/graphql/core.js.map +1 -0
  117. package/dist/core/middleware/built-in/graphql/helpers.d.ts +69 -0
  118. package/dist/core/middleware/built-in/graphql/helpers.js +187 -0
  119. package/dist/core/middleware/built-in/graphql/helpers.js.map +1 -0
  120. package/dist/core/middleware/built-in/graphql/hook.d.ts +7 -0
  121. package/dist/core/middleware/built-in/graphql/hook.js +78 -0
  122. package/dist/core/middleware/built-in/graphql/hook.js.map +1 -0
  123. package/dist/core/middleware/built-in/graphql/index.d.ts +5 -0
  124. package/dist/core/middleware/built-in/graphql/index.js +5 -0
  125. package/dist/core/middleware/built-in/graphql/index.js.map +1 -0
  126. package/dist/core/middleware/built-in/graphql/middleware.d.ts +7 -0
  127. package/dist/core/middleware/built-in/graphql/middleware.js +54 -0
  128. package/dist/core/middleware/built-in/graphql/middleware.js.map +1 -0
  129. package/dist/core/middleware/built-in/graphql/subscriptions.d.ts +20 -0
  130. package/dist/core/middleware/built-in/graphql/subscriptions.js +37 -0
  131. package/dist/core/middleware/built-in/graphql/subscriptions.js.map +1 -0
  132. package/dist/core/middleware/built-in/index.d.ts +2 -1
  133. package/dist/core/middleware/built-in/index.js +3 -0
  134. package/dist/core/middleware/built-in/index.js.map +1 -1
  135. package/dist/core/middleware/built-in/rate-limit/core.d.ts +5 -0
  136. package/dist/core/middleware/built-in/rate-limit/core.js +16 -8
  137. package/dist/core/middleware/built-in/rate-limit/core.js.map +1 -1
  138. package/dist/core/middleware/built-in/validation/core.js +42 -19
  139. package/dist/core/middleware/built-in/validation/core.js.map +1 -1
  140. package/dist/core/middleware/index.js +1 -0
  141. package/dist/core/middleware/index.js.map +1 -1
  142. package/dist/core/modules/auto-discovery.js +5 -4
  143. package/dist/core/modules/auto-discovery.js.map +1 -1
  144. package/dist/core/modules/modules.js.map +1 -1
  145. package/dist/core/networking/adapters/socketio-adapter.js +1 -1
  146. package/dist/core/networking/adapters/socketio-adapter.js.map +1 -1
  147. package/dist/core/networking/adapters/uws-adapter.js +7 -2
  148. package/dist/core/networking/adapters/uws-adapter.js.map +1 -1
  149. package/dist/core/networking/adapters/ws-adapter.js +5 -2
  150. package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
  151. package/dist/core/networking/websocket-manager.js +2 -0
  152. package/dist/core/networking/websocket-manager.js.map +1 -1
  153. package/dist/core/pooling/object-pool-manager.d.ts +8 -2
  154. package/dist/core/pooling/object-pool-manager.js +38 -18
  155. package/dist/core/pooling/object-pool-manager.js.map +1 -1
  156. package/dist/core/routing/app-integration.d.ts +3 -3
  157. package/dist/core/routing/app-integration.js +1 -1
  158. package/dist/core/routing/app-integration.js.map +1 -1
  159. package/dist/core/routing/index.d.ts +1 -1
  160. package/dist/core/routing/index.js +1 -1
  161. package/dist/core/routing/index.js.map +1 -1
  162. package/dist/core/routing/path-matcher.d.ts +6 -0
  163. package/dist/core/routing/path-matcher.js +46 -7
  164. package/dist/core/routing/path-matcher.js.map +1 -1
  165. package/dist/core/routing/unified-router.d.ts +4 -0
  166. package/dist/core/routing/unified-router.js +104 -43
  167. package/dist/core/routing/unified-router.js.map +1 -1
  168. package/dist/core/runtime/base-adapter.js +3 -3
  169. package/dist/core/runtime/base-adapter.js.map +1 -1
  170. package/dist/core/runtime/cloudflare-workers-adapter.js +1 -1
  171. package/dist/core/runtime/cloudflare-workers-adapter.js.map +1 -1
  172. package/dist/core/runtime/node-adapter.d.ts +1 -1
  173. package/dist/core/runtime/node-adapter.js +7 -4
  174. package/dist/core/runtime/node-adapter.js.map +1 -1
  175. package/dist/core/runtime/vercel-edge-adapter.js +1 -0
  176. package/dist/core/runtime/vercel-edge-adapter.js.map +1 -1
  177. package/dist/core/utilities/circuit-breaker.d.ts +9 -2
  178. package/dist/core/utilities/circuit-breaker.js +32 -3
  179. package/dist/core/utilities/circuit-breaker.js.map +1 -1
  180. package/dist/core/utilities/container.js +6 -0
  181. package/dist/core/utilities/container.js.map +1 -1
  182. package/dist/core/utilities/hooks.d.ts +4 -0
  183. package/dist/core/utilities/hooks.js +134 -22
  184. package/dist/core/utilities/hooks.js.map +1 -1
  185. package/dist/core/validation/index.js +6 -1
  186. package/dist/core/validation/index.js.map +1 -1
  187. package/dist/index.d.ts +6 -0
  188. package/dist/index.js +5 -0
  189. package/dist/index.js.map +1 -1
  190. package/dist/moro.d.ts +154 -1
  191. package/dist/moro.js +592 -16
  192. package/dist/moro.js.map +1 -1
  193. package/dist/types/config.d.ts +28 -0
  194. package/dist/types/core.d.ts +1 -0
  195. package/dist/types/events.d.ts +1 -1
  196. package/dist/types/events.js +1 -0
  197. package/dist/types/events.js.map +1 -1
  198. package/dist/types/logger.d.ts +1 -0
  199. package/dist/types/module.d.ts +2 -2
  200. package/package.json +21 -1
package/dist/moro.js CHANGED
@@ -7,6 +7,7 @@ import { createFrameworkLogger, applyLoggingConfiguration } from './core/logger/
7
7
  import { MiddlewareManager } from './core/middleware/index.js';
8
8
  import { IntelligentRoutingManager } from './core/routing/app-integration.js';
9
9
  import { UnifiedRouter, } from './core/routing/unified-router.js';
10
+ import { PathMatcher } from './core/routing/path-matcher.js';
10
11
  import { AppDocumentationManager } from './core/docs/index.js';
11
12
  import { EventEmitter } from 'events';
12
13
  import cluster from 'cluster';
@@ -16,6 +17,16 @@ import { normalizeValidationError } from './core/validation/schema-interface.js'
16
17
  import { initializeConfig } from './core/config/index.js';
17
18
  // Runtime System Integration
18
19
  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
+ // Job System Integration
24
+ import { JobScheduler, JobHealthChecker, everyInterval, cronSchedule, } from './core/jobs/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;
19
30
  export class Moro extends EventEmitter {
20
31
  coreFramework;
21
32
  routes = [];
@@ -48,6 +59,14 @@ export class Moro extends EventEmitter {
48
59
  middlewareManager;
49
60
  // Queued WebSocket registrations (for async adapter detection)
50
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;
51
70
  constructor(options = {}) {
52
71
  super(); // Call EventEmitter constructor
53
72
  // Track if user explicitly set logger/logging options
@@ -109,6 +128,33 @@ export class Moro extends EventEmitter {
109
128
  }
110
129
  // Access enterprise event bus from core framework
111
130
  this.eventBus = this.coreFramework.eventBus;
131
+ // Initialize job scheduler if enabled in config
132
+ // Default to enabled (true) unless explicitly disabled
133
+ const jobsEnabled = this.config.jobs?.enabled !== false && options.jobs?.enabled !== false;
134
+ if (jobsEnabled) {
135
+ const leaderElectionOptions = this.config.jobs?.leaderElection?.enabled !== false
136
+ ? {
137
+ strategy: this.config.jobs?.leaderElection?.strategy ?? 'file',
138
+ lockPath: this.config.jobs?.leaderElection?.lockPath,
139
+ lockTimeout: this.config.jobs?.leaderElection?.lockTimeout,
140
+ heartbeatInterval: this.config.jobs?.leaderElection?.heartbeatInterval,
141
+ }
142
+ : undefined;
143
+ const jobSchedulerOptions = {
144
+ maxConcurrentJobs: this.config.jobs?.maxConcurrentJobs,
145
+ enableLeaderElection: this.config.jobs?.leaderElection?.enabled !== false,
146
+ leaderElection: leaderElectionOptions,
147
+ executor: this.config.jobs?.executor,
148
+ stateManager: this.config.jobs?.stateManager,
149
+ gracefulShutdownTimeout: this.config.jobs?.gracefulShutdownTimeout,
150
+ };
151
+ this.jobScheduler = new JobScheduler(createFrameworkLogger('Jobs'), jobSchedulerOptions);
152
+ this.jobHealthChecker = new JobHealthChecker(this.jobScheduler, createFrameworkLogger('JobHealth'));
153
+ this.logger.info('Job scheduler initialized', 'Jobs', {
154
+ maxConcurrentJobs: jobSchedulerOptions.maxConcurrentJobs,
155
+ leaderElection: jobSchedulerOptions.enableLeaderElection,
156
+ });
157
+ }
112
158
  // Setup default middleware if enabled - use config defaults with options override
113
159
  this.setupDefaultMiddleware({
114
160
  ...this.getDefaultOptionsFromConfig(),
@@ -411,15 +457,16 @@ export class Moro extends EventEmitter {
411
457
  throw new Error('Port not specified and not found in configuration. Please provide a port number or configure it in moro.config.js/ts');
412
458
  }
413
459
  // Check if clustering is enabled for massive performance gains
414
- // NOTE: uWebSockets.js does NOT support Node.js clustering - it's single-threaded only
415
460
  const usingUWebSockets = this.config.server?.useUWebSockets || false;
416
461
  if (this.config.performance?.clustering?.enabled) {
417
462
  if (usingUWebSockets) {
418
- this.logger.warn('Clustering is not supported with uWebSockets.js - running in single-threaded mode. ' +
419
- 'uWebSockets is so fast that single-threaded performance often exceeds multi-threaded Node.js!', 'Cluster');
420
- // Continue without clustering
463
+ // Use worker thread clustering for uWebSockets
464
+ this.logger.info('uWebSockets clustering enabled - using worker threads with acceptor pattern', 'Cluster');
465
+ this.startWithUWSClustering(port, host, callback);
466
+ return;
421
467
  }
422
468
  else {
469
+ // Use traditional Node.js cluster module for standard HTTP
423
470
  this.startWithClustering(port, host, callback);
424
471
  return;
425
472
  }
@@ -431,7 +478,7 @@ export class Moro extends EventEmitter {
431
478
  this.coreFramework.addMiddleware(docsMiddleware);
432
479
  this.logger.debug('Documentation middleware added', 'Documentation');
433
480
  }
434
- catch (error) {
481
+ catch {
435
482
  // Documentation not enabled, that's fine
436
483
  this.logger.debug('Documentation not enabled', 'Documentation');
437
484
  }
@@ -477,6 +524,10 @@ export class Moro extends EventEmitter {
477
524
  this.unifiedRouter.logPerformanceStats();
478
525
  }
479
526
  this.eventBus.emit('server:started', { port, runtime: this.runtimeType });
527
+ // Start job scheduler after server starts
528
+ this.startJobScheduler().catch(err => {
529
+ this.logger.error(`Failed to start job scheduler: ${String(err)}`);
530
+ });
480
531
  if (callback)
481
532
  callback();
482
533
  };
@@ -603,7 +654,7 @@ export class Moro extends EventEmitter {
603
654
  if (res.headersSent)
604
655
  return;
605
656
  }
606
- catch (error) {
657
+ catch {
607
658
  // Documentation not enabled, that's fine
608
659
  }
609
660
  // Try unified router first (handles all routes)
@@ -621,6 +672,7 @@ export class Moro extends EventEmitter {
621
672
  // Handle direct routes for runtime adapters
622
673
  async handleDirectRoutes(req, res) {
623
674
  // Find matching route
675
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
624
676
  const route = this.findMatchingRoute(req.method, req.path);
625
677
  if (!route) {
626
678
  res.status(404).json({ success: false, error: 'Not found' });
@@ -707,8 +759,7 @@ export class Moro extends EventEmitter {
707
759
  };
708
760
  }
709
761
  // Phase 2: Optimized dynamic route matching by segment count
710
- const segments = path.split('/').filter(s => s.length > 0);
711
- const segmentCount = segments.length;
762
+ const segmentCount = PathMatcher.countSegments(path);
712
763
  const candidateRoutes = this.dynamicRoutesBySegments.get(segmentCount) || [];
713
764
  for (const route of candidateRoutes) {
714
765
  if (route.method === method) {
@@ -786,11 +837,11 @@ export class Moro extends EventEmitter {
786
837
  }
787
838
  else {
788
839
  // Dynamic route - organize by segment count
789
- const segments = route.path.split('/').filter((s) => s.length > 0);
790
- const segmentCount = segments.length;
840
+ const segmentCount = PathMatcher.countSegments(route.path);
791
841
  if (!this.dynamicRoutesBySegments.has(segmentCount)) {
792
842
  this.dynamicRoutesBySegments.set(segmentCount, []);
793
843
  }
844
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
794
845
  this.dynamicRoutesBySegments.get(segmentCount).push(route);
795
846
  }
796
847
  }
@@ -1147,6 +1198,7 @@ export class Moro extends EventEmitter {
1147
1198
  const gracefulShutdown = () => {
1148
1199
  this.logger.info('Gracefully shutting down cluster...', 'Cluster');
1149
1200
  // Clean up all workers
1201
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1150
1202
  for (const [pid, worker] of this.clusterWorkers) {
1151
1203
  worker.removeAllListeners();
1152
1204
  worker.kill('SIGTERM');
@@ -1161,6 +1213,7 @@ export class Moro extends EventEmitter {
1161
1213
  // Fork workers with basic tracking
1162
1214
  for (let i = 0; i < workerCount; i++) {
1163
1215
  const worker = cluster.fork();
1216
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1164
1217
  this.clusterWorkers.set(worker.process.pid, worker);
1165
1218
  this.logger.info(`Worker ${worker.process.pid} started`, 'Cluster');
1166
1219
  // Handle individual worker messages
@@ -1174,6 +1227,7 @@ export class Moro extends EventEmitter {
1174
1227
  this.logger.warn(`Worker ${pid} died unexpectedly (${signal || code}). Restarting...`, 'Cluster');
1175
1228
  // Simple restart
1176
1229
  const newWorker = cluster.fork();
1230
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1177
1231
  this.clusterWorkers.set(newWorker.process.pid, newWorker);
1178
1232
  this.logger.info(`Worker ${newWorker.process.pid} restarted`, 'Cluster');
1179
1233
  }
@@ -1212,7 +1266,7 @@ export class Moro extends EventEmitter {
1212
1266
  '--turbo-fast-api-calls', // Optimize API calls
1213
1267
  '--turbo-escape-analysis', // Escape analysis optimization
1214
1268
  '--turbo-inline-api-calls', // Inline API calls
1215
- '--max-old-space-size=1024', // Limit memory to prevent GC pressure
1269
+ `--max-old-space-size=${heapSizePerWorkerMB}`, // Limit memory to prevent GC pressure
1216
1270
  ];
1217
1271
  process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS || '') + ' ' + v8Flags.join(' ');
1218
1272
  }
@@ -1255,7 +1309,7 @@ export class Moro extends EventEmitter {
1255
1309
  const docsMiddleware = this.documentation.getDocsMiddleware();
1256
1310
  this.coreFramework.addMiddleware(docsMiddleware);
1257
1311
  }
1258
- catch (error) {
1312
+ catch {
1259
1313
  // Documentation not enabled, that's fine
1260
1314
  }
1261
1315
  // Add unified routing middleware (handles both chainable and direct routes)
@@ -1330,18 +1384,209 @@ export class Moro extends EventEmitter {
1330
1384
  // Log other worker messages
1331
1385
  this.logger.debug(`Worker message: ${JSON.stringify(message)}`, 'Cluster');
1332
1386
  }
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
+ }
1333
1555
  /**
1334
1556
  * Gracefully close the application and clean up resources
1335
1557
  * This should be called in tests and during shutdown
1336
1558
  */
1337
1559
  async close() {
1338
1560
  this.logger.debug('Closing Moro application...');
1561
+ // Shutdown job scheduler first
1562
+ if (this.jobScheduler) {
1563
+ try {
1564
+ this.logger.info('Shutting down job scheduler...');
1565
+ await this.jobScheduler.shutdown();
1566
+ this.jobsStarted = false;
1567
+ }
1568
+ catch (err) {
1569
+ this.logger.error(`Error shutting down job scheduler: ${String(err)}`);
1570
+ }
1571
+ }
1572
+ // Cleanup GraphQL executor timers
1573
+ if (this.graphqlCore) {
1574
+ try {
1575
+ const executor = this.graphqlCore.getExecutor();
1576
+ if (executor) {
1577
+ executor.cleanup();
1578
+ }
1579
+ }
1580
+ catch (err) {
1581
+ this.logger.error(`Error cleaning up GraphQL: ${String(err)}`);
1582
+ }
1583
+ }
1339
1584
  // Flush logger buffer before shutdown
1340
1585
  try {
1341
1586
  // Use flushBuffer for immediate synchronous flush
1342
1587
  this.logger.flushBuffer();
1343
1588
  }
1344
- catch (error) {
1589
+ catch {
1345
1590
  // Ignore flush errors during shutdown
1346
1591
  }
1347
1592
  // Close the core framework with timeout
@@ -1356,7 +1601,7 @@ export class Moro extends EventEmitter {
1356
1601
  new Promise(resolve => setTimeout(resolve, 2000)), // 2 second timeout
1357
1602
  ]);
1358
1603
  }
1359
- catch (error) {
1604
+ catch {
1360
1605
  // Force close if graceful close fails
1361
1606
  this.logger.warn('Force closing HTTP server due to timeout');
1362
1607
  }
@@ -1366,7 +1611,7 @@ export class Moro extends EventEmitter {
1366
1611
  try {
1367
1612
  this.moduleDiscovery.cleanup();
1368
1613
  }
1369
- catch (error) {
1614
+ catch {
1370
1615
  // Ignore cleanup errors
1371
1616
  }
1372
1617
  }
@@ -1375,11 +1620,342 @@ export class Moro extends EventEmitter {
1375
1620
  this.eventBus.removeAllListeners();
1376
1621
  this.removeAllListeners();
1377
1622
  }
1378
- catch (error) {
1623
+ catch {
1379
1624
  // Ignore cleanup errors
1380
1625
  }
1381
1626
  this.logger.debug('Moro application closed successfully');
1382
1627
  }
1628
+ // ========================================
1629
+ // Job Scheduling API
1630
+ // ========================================
1631
+ /**
1632
+ * Register a background job with cron or interval schedule
1633
+ * @param name - Job name (used for identification)
1634
+ * @param schedule - Cron expression, interval string ('5m', '1h'), or schedule object
1635
+ * @param handler - Job function to execute
1636
+ * @param options - Job configuration options
1637
+ * @returns Job ID for management
1638
+ *
1639
+ * @example
1640
+ * // Cron schedule
1641
+ * app.job('cleanup', '0 2 * * *', async () => {
1642
+ * await cleanupOldData();
1643
+ * });
1644
+ *
1645
+ * // Interval schedule
1646
+ * app.job('health-check', '5m', async (ctx) => {
1647
+ * console.log('Health check', ctx.executionId);
1648
+ * });
1649
+ *
1650
+ * // Advanced options
1651
+ * app.job('report', '@daily', generateReport, {
1652
+ * timeout: 60000,
1653
+ * maxRetries: 3,
1654
+ * onError: (ctx, error) => console.error('Job failed', error)
1655
+ * });
1656
+ */
1657
+ job(name, schedule, handler, options = {}) {
1658
+ if (!this.jobScheduler) {
1659
+ throw new Error('Job scheduler is not enabled. Set config.jobs.enabled = true');
1660
+ }
1661
+ let jobSchedule;
1662
+ // Parse schedule input
1663
+ if (typeof schedule === 'string') {
1664
+ // Check if it's a cron expression or interval
1665
+ if (schedule.match(/^(\d+[smhd]|\d+\s*(seconds?|minutes?|hours?|days?))$/i)) {
1666
+ // Interval format
1667
+ jobSchedule = everyInterval(schedule);
1668
+ }
1669
+ else {
1670
+ // Cron format
1671
+ jobSchedule = cronSchedule(schedule, options.timezone);
1672
+ }
1673
+ }
1674
+ else {
1675
+ jobSchedule = schedule;
1676
+ }
1677
+ const jobOptions = {
1678
+ name: options.name || name,
1679
+ enabled: options.enabled,
1680
+ priority: options.priority,
1681
+ timezone: options.timezone,
1682
+ maxConcurrent: options.maxConcurrent,
1683
+ timeout: options.timeout,
1684
+ maxRetries: options.maxRetries,
1685
+ retryDelay: options.retryDelay,
1686
+ retryBackoff: options.retryBackoff,
1687
+ enableCircuitBreaker: options.enableCircuitBreaker,
1688
+ metadata: options.metadata,
1689
+ onStart: options.onStart,
1690
+ onComplete: options.onComplete,
1691
+ onError: options.onError,
1692
+ };
1693
+ const jobId = this.jobScheduler.registerJob(name, jobSchedule, handler, jobOptions);
1694
+ this.logger.info(`Job registered: ${name} (${jobId})`);
1695
+ return jobId;
1696
+ }
1697
+ /**
1698
+ * Enable or disable a registered job
1699
+ */
1700
+ setJobEnabled(jobId, enabled) {
1701
+ if (!this.jobScheduler) {
1702
+ return false;
1703
+ }
1704
+ return this.jobScheduler.setJobEnabled(jobId, enabled);
1705
+ }
1706
+ /**
1707
+ * Manually trigger a job execution
1708
+ */
1709
+ async triggerJob(jobId, metadata) {
1710
+ if (!this.jobScheduler) {
1711
+ throw new Error('Job scheduler is not enabled');
1712
+ }
1713
+ return this.jobScheduler.triggerJob(jobId, metadata);
1714
+ }
1715
+ /**
1716
+ * Unregister a job
1717
+ */
1718
+ unregisterJob(jobId) {
1719
+ if (!this.jobScheduler) {
1720
+ throw new Error('Job scheduler is not enabled');
1721
+ }
1722
+ return this.jobScheduler.unregisterJob(jobId);
1723
+ }
1724
+ /**
1725
+ * Get job metrics
1726
+ */
1727
+ getJobMetrics(jobId) {
1728
+ if (!this.jobScheduler) {
1729
+ return null;
1730
+ }
1731
+ return this.jobScheduler.getJobMetrics(jobId);
1732
+ }
1733
+ /**
1734
+ * Get job health status
1735
+ */
1736
+ getJobHealth(jobId) {
1737
+ if (!this.jobHealthChecker) {
1738
+ if (jobId) {
1739
+ return {
1740
+ jobId,
1741
+ name: 'Unknown',
1742
+ status: 'unknown',
1743
+ enabled: false,
1744
+ consecutiveFailures: 0,
1745
+ message: 'Job scheduler not enabled',
1746
+ };
1747
+ }
1748
+ return [];
1749
+ }
1750
+ if (jobId) {
1751
+ return this.jobHealthChecker.checkJobHealth(jobId);
1752
+ }
1753
+ return this.jobHealthChecker.checkAllJobs();
1754
+ }
1755
+ /**
1756
+ * Get scheduler statistics
1757
+ */
1758
+ getJobStats() {
1759
+ if (!this.jobScheduler) {
1760
+ return null;
1761
+ }
1762
+ return this.jobScheduler.getStats();
1763
+ }
1764
+ /**
1765
+ * Get overall scheduler health
1766
+ */
1767
+ getSchedulerHealth() {
1768
+ if (!this.jobHealthChecker) {
1769
+ return {
1770
+ status: 'unknown',
1771
+ message: 'Job scheduler not enabled',
1772
+ stats: null,
1773
+ jobs: [],
1774
+ unhealthyJobCount: 0,
1775
+ };
1776
+ }
1777
+ return this.jobHealthChecker.getSchedulerHealth();
1778
+ }
1779
+ /**
1780
+ * Start the job scheduler (called automatically on listen)
1781
+ */
1782
+ async startJobScheduler() {
1783
+ if (!this.jobScheduler || this.jobsStarted) {
1784
+ return;
1785
+ }
1786
+ try {
1787
+ await this.jobScheduler.start();
1788
+ this.jobsStarted = true;
1789
+ this.logger.info('Job scheduler started');
1790
+ }
1791
+ catch (err) {
1792
+ this.logger.error(`Failed to start job scheduler: ${String(err)}`);
1793
+ }
1794
+ }
1795
+ // ========================================
1796
+ // GraphQL API
1797
+ // ========================================
1798
+ /**
1799
+ * Configure GraphQL endpoint with schema, resolvers, and options
1800
+ *
1801
+ * @param options - GraphQL configuration options
1802
+ *
1803
+ * @example
1804
+ * ```ts
1805
+ * // Using type definitions and resolvers
1806
+ * app.graphql({
1807
+ * typeDefs: `
1808
+ * type Query {
1809
+ * hello: String
1810
+ * users: [User]
1811
+ * }
1812
+ * type User {
1813
+ * id: ID!
1814
+ * name: String!
1815
+ * }
1816
+ * `,
1817
+ * resolvers: {
1818
+ * Query: {
1819
+ * hello: () => 'Hello World!',
1820
+ * users: () => [{ id: '1', name: 'Alice' }]
1821
+ * }
1822
+ * }
1823
+ * });
1824
+ *
1825
+ * // Using Pothos schema builder
1826
+ * import SchemaBuilder from '@pothos/core';
1827
+ *
1828
+ * const builder = new SchemaBuilder();
1829
+ * builder.queryType({
1830
+ * fields: (t) => ({
1831
+ * hello: t.string({ resolve: () => 'Hello World!' })
1832
+ * })
1833
+ * });
1834
+ *
1835
+ * app.graphql({
1836
+ * pothosSchema: builder
1837
+ * });
1838
+ * ```
1839
+ */
1840
+ graphql(options) {
1841
+ if (this.graphqlCore || this.graphqlInitPromise) {
1842
+ throw new Error('GraphQL has already been configured. Call graphql() only once.');
1843
+ }
1844
+ // Check if graphql package is available
1845
+ if (!this.isGraphQLAvailable()) {
1846
+ throw new Error('GraphQL support requires the graphql package to be installed.\n' +
1847
+ 'Install it with: npm install graphql\n' +
1848
+ 'For TypeScript-first GraphQL, also consider: npm install @pothos/core\n' +
1849
+ 'For performance boost: npm install graphql-jit');
1850
+ }
1851
+ this.logger.info('Configuring GraphQL', 'GraphQL', {
1852
+ path: options.path || '/graphql',
1853
+ jit: options.enableJIT !== false,
1854
+ playground: options.enablePlayground !== false,
1855
+ });
1856
+ // Initialize GraphQL asynchronously (like WebSocket registration pattern)
1857
+ this.graphqlInitPromise = this.initializeGraphQL(options);
1858
+ // Add to initialization promises
1859
+ const originalEnsure = this.ensureAutoDiscoveryComplete.bind(this);
1860
+ this.ensureAutoDiscoveryComplete = async () => {
1861
+ await originalEnsure();
1862
+ await this.graphqlInitPromise;
1863
+ };
1864
+ return this;
1865
+ }
1866
+ /**
1867
+ * Initialize GraphQL system asynchronously
1868
+ */
1869
+ async initializeGraphQL(options) {
1870
+ try {
1871
+ // Lazy load GraphQL modules
1872
+ await this.loadGraphQLModules();
1873
+ // Create GraphQL core
1874
+ this.graphqlCore = new GraphQLCore(options);
1875
+ // Initialize GraphQL
1876
+ await this.graphqlCore.initialize();
1877
+ this.logger.info('GraphQL initialized successfully', 'GraphQL');
1878
+ // Setup subscriptions if enabled
1879
+ if (options.enableSubscriptions !== false && this.config.websocket.enabled) {
1880
+ this.setupGraphQLSubscriptions(options);
1881
+ }
1882
+ }
1883
+ catch (error) {
1884
+ this.logger.error('Failed to initialize GraphQL', 'GraphQL', { error });
1885
+ throw error;
1886
+ }
1887
+ }
1888
+ /**
1889
+ * Check if GraphQL package is available
1890
+ */
1891
+ isGraphQLAvailable() {
1892
+ try {
1893
+ require.resolve('graphql');
1894
+ return true;
1895
+ }
1896
+ catch {
1897
+ return false;
1898
+ }
1899
+ }
1900
+ /**
1901
+ * Lazy load GraphQL modules
1902
+ */
1903
+ async loadGraphQLModules() {
1904
+ if (GraphQLCore) {
1905
+ return; // Already loaded
1906
+ }
1907
+ try {
1908
+ // GraphQL core now uses adapters with no static imports from 'graphql'
1909
+ const coreModule = await import('./core/graphql/core.js');
1910
+ GraphQLCore = coreModule.GraphQLCore;
1911
+ const subsModule = await import('./core/middleware/built-in/graphql/subscriptions.js');
1912
+ GraphQLSubscriptionManager = subsModule.GraphQLSubscriptionManager;
1913
+ setupGraphQLSubscriptions = subsModule.setupGraphQLSubscriptions;
1914
+ }
1915
+ catch (error) {
1916
+ this.logger.error('Failed to load GraphQL modules', 'GraphQL', { error });
1917
+ throw new Error('Failed to load GraphQL modules. Please ensure graphql is installed.');
1918
+ }
1919
+ }
1920
+ /**
1921
+ * Setup GraphQL subscriptions
1922
+ */
1923
+ setupGraphQLSubscriptions(options) {
1924
+ const websocketAdapter = this.coreFramework.getWebSocketAdapter();
1925
+ if (!websocketAdapter) {
1926
+ this.logger.warn('GraphQL subscriptions require WebSocket to be enabled', 'GraphQL');
1927
+ return;
1928
+ }
1929
+ if (!this.graphqlCore) {
1930
+ return;
1931
+ }
1932
+ const schema = this.graphqlCore.getSchema();
1933
+ this.graphqlSubscriptionManager = setupGraphQLSubscriptions(websocketAdapter, schema, {
1934
+ path: options.path ? `${options.path}/subscriptions` : '/graphql/subscriptions',
1935
+ contextFactory: options.context,
1936
+ });
1937
+ this.logger.info('GraphQL subscriptions enabled', 'GraphQL', {
1938
+ path: options.path ? `${options.path}/subscriptions` : '/graphql/subscriptions',
1939
+ });
1940
+ }
1941
+ /**
1942
+ * Get GraphQL schema (if configured)
1943
+ */
1944
+ getGraphQLSchema() {
1945
+ return this.graphqlCore?.getSchema();
1946
+ }
1947
+ /**
1948
+ * Get GraphQL stats
1949
+ */
1950
+ getGraphQLStats() {
1951
+ if (!this.graphqlCore) {
1952
+ return null;
1953
+ }
1954
+ return {
1955
+ ...this.graphqlCore.getStats(),
1956
+ subscriptions: this.graphqlSubscriptionManager?.getSubscriptionCount() || 0,
1957
+ };
1958
+ }
1383
1959
  }
1384
1960
  // Export convenience function
1385
1961
  export function createApp(options) {