@morojs/moro 1.6.6 → 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 (186) 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 +1 -0
  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.js +31 -9
  50. package/dist/core/http/http-server.js.map +1 -1
  51. package/dist/core/http/utils/uws-worker-clustering.d.ts +28 -0
  52. package/dist/core/http/utils/uws-worker-clustering.js +313 -0
  53. package/dist/core/http/utils/uws-worker-clustering.js.map +1 -0
  54. package/dist/core/http/uws-http-server.d.ts +3 -1
  55. package/dist/core/http/uws-http-server.js +37 -8
  56. package/dist/core/http/uws-http-server.js.map +1 -1
  57. package/dist/core/jobs/cron-parser.d.ts +62 -0
  58. package/dist/core/jobs/cron-parser.js +239 -0
  59. package/dist/core/jobs/cron-parser.js.map +1 -0
  60. package/dist/core/jobs/index.d.ts +12 -0
  61. package/dist/core/jobs/index.js +9 -0
  62. package/dist/core/jobs/index.js.map +1 -0
  63. package/dist/core/jobs/job-executor.d.ts +134 -0
  64. package/dist/core/jobs/job-executor.js +413 -0
  65. package/dist/core/jobs/job-executor.js.map +1 -0
  66. package/dist/core/jobs/job-scheduler.d.ts +214 -0
  67. package/dist/core/jobs/job-scheduler.js +551 -0
  68. package/dist/core/jobs/job-scheduler.js.map +1 -0
  69. package/dist/core/jobs/job-state-manager.d.ts +158 -0
  70. package/dist/core/jobs/job-state-manager.js +444 -0
  71. package/dist/core/jobs/job-state-manager.js.map +1 -0
  72. package/dist/core/jobs/leader-election.d.ts +124 -0
  73. package/dist/core/jobs/leader-election.js +481 -0
  74. package/dist/core/jobs/leader-election.js.map +1 -0
  75. package/dist/core/jobs/types.d.ts +151 -0
  76. package/dist/core/jobs/types.js +4 -0
  77. package/dist/core/jobs/types.js.map +1 -0
  78. package/dist/core/jobs/utils.d.ts +95 -0
  79. package/dist/core/jobs/utils.js +258 -0
  80. package/dist/core/jobs/utils.js.map +1 -0
  81. package/dist/core/logger/filters.js +2 -0
  82. package/dist/core/logger/filters.js.map +1 -1
  83. package/dist/core/logger/logger.js +7 -3
  84. package/dist/core/logger/logger.js.map +1 -1
  85. package/dist/core/logger/outputs.js +2 -0
  86. package/dist/core/logger/outputs.js.map +1 -1
  87. package/dist/core/middleware/built-in/auth/helpers.js +1 -1
  88. package/dist/core/middleware/built-in/auth/helpers.js.map +1 -1
  89. package/dist/core/middleware/built-in/auth/jwt-helpers.js +1 -1
  90. package/dist/core/middleware/built-in/auth/jwt-helpers.js.map +1 -1
  91. package/dist/core/middleware/built-in/auth/providers.js +1 -1
  92. package/dist/core/middleware/built-in/auth/providers.js.map +1 -1
  93. package/dist/core/middleware/built-in/cache/adapters/cache/file.js +3 -3
  94. package/dist/core/middleware/built-in/cache/adapters/cache/file.js.map +1 -1
  95. package/dist/core/middleware/built-in/cache/adapters/cache/memory.js +1 -0
  96. package/dist/core/middleware/built-in/cache/adapters/cache/memory.js.map +1 -1
  97. package/dist/core/middleware/built-in/cache/adapters/cache/redis.js +1 -1
  98. package/dist/core/middleware/built-in/cache/adapters/cache/redis.js.map +1 -1
  99. package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.d.ts +8 -0
  100. package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.js +100 -7
  101. package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.js.map +1 -1
  102. package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.d.ts +6 -0
  103. package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.js +97 -13
  104. package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.js.map +1 -1
  105. package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudfront.js +1 -1
  106. package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudfront.js.map +1 -1
  107. package/dist/core/middleware/built-in/cookie/hook.d.ts +1 -1
  108. package/dist/core/middleware/built-in/cookie/hook.js +2 -2
  109. package/dist/core/middleware/built-in/cookie/hook.js.map +1 -1
  110. package/dist/core/middleware/built-in/csrf/core.js +1 -0
  111. package/dist/core/middleware/built-in/csrf/core.js.map +1 -1
  112. package/dist/core/middleware/built-in/graphql/core.d.ts +11 -0
  113. package/dist/core/middleware/built-in/graphql/core.js +24 -0
  114. package/dist/core/middleware/built-in/graphql/core.js.map +1 -0
  115. package/dist/core/middleware/built-in/graphql/helpers.d.ts +69 -0
  116. package/dist/core/middleware/built-in/graphql/helpers.js +187 -0
  117. package/dist/core/middleware/built-in/graphql/helpers.js.map +1 -0
  118. package/dist/core/middleware/built-in/graphql/hook.d.ts +7 -0
  119. package/dist/core/middleware/built-in/graphql/hook.js +78 -0
  120. package/dist/core/middleware/built-in/graphql/hook.js.map +1 -0
  121. package/dist/core/middleware/built-in/graphql/index.d.ts +5 -0
  122. package/dist/core/middleware/built-in/graphql/index.js +5 -0
  123. package/dist/core/middleware/built-in/graphql/index.js.map +1 -0
  124. package/dist/core/middleware/built-in/graphql/middleware.d.ts +7 -0
  125. package/dist/core/middleware/built-in/graphql/middleware.js +54 -0
  126. package/dist/core/middleware/built-in/graphql/middleware.js.map +1 -0
  127. package/dist/core/middleware/built-in/graphql/subscriptions.d.ts +20 -0
  128. package/dist/core/middleware/built-in/graphql/subscriptions.js +37 -0
  129. package/dist/core/middleware/built-in/graphql/subscriptions.js.map +1 -0
  130. package/dist/core/middleware/built-in/index.d.ts +2 -1
  131. package/dist/core/middleware/built-in/index.js +3 -0
  132. package/dist/core/middleware/built-in/index.js.map +1 -1
  133. package/dist/core/middleware/built-in/validation/core.js +4 -2
  134. package/dist/core/middleware/built-in/validation/core.js.map +1 -1
  135. package/dist/core/middleware/index.js +1 -0
  136. package/dist/core/middleware/index.js.map +1 -1
  137. package/dist/core/modules/auto-discovery.js +5 -4
  138. package/dist/core/modules/auto-discovery.js.map +1 -1
  139. package/dist/core/modules/modules.js.map +1 -1
  140. package/dist/core/networking/adapters/socketio-adapter.js +1 -1
  141. package/dist/core/networking/adapters/socketio-adapter.js.map +1 -1
  142. package/dist/core/networking/adapters/uws-adapter.js +7 -2
  143. package/dist/core/networking/adapters/uws-adapter.js.map +1 -1
  144. package/dist/core/networking/adapters/ws-adapter.js +5 -2
  145. package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
  146. package/dist/core/networking/websocket-manager.js +2 -0
  147. package/dist/core/networking/websocket-manager.js.map +1 -1
  148. package/dist/core/pooling/object-pool-manager.js +3 -0
  149. package/dist/core/pooling/object-pool-manager.js.map +1 -1
  150. package/dist/core/routing/app-integration.d.ts +3 -3
  151. package/dist/core/routing/app-integration.js +1 -1
  152. package/dist/core/routing/app-integration.js.map +1 -1
  153. package/dist/core/routing/index.d.ts +1 -1
  154. package/dist/core/routing/index.js +1 -1
  155. package/dist/core/routing/index.js.map +1 -1
  156. package/dist/core/runtime/base-adapter.js +3 -3
  157. package/dist/core/runtime/base-adapter.js.map +1 -1
  158. package/dist/core/runtime/cloudflare-workers-adapter.js +1 -1
  159. package/dist/core/runtime/cloudflare-workers-adapter.js.map +1 -1
  160. package/dist/core/runtime/node-adapter.d.ts +1 -1
  161. package/dist/core/runtime/node-adapter.js +7 -4
  162. package/dist/core/runtime/node-adapter.js.map +1 -1
  163. package/dist/core/runtime/vercel-edge-adapter.js +1 -0
  164. package/dist/core/runtime/vercel-edge-adapter.js.map +1 -1
  165. package/dist/core/utilities/circuit-breaker.d.ts +9 -2
  166. package/dist/core/utilities/circuit-breaker.js +32 -3
  167. package/dist/core/utilities/circuit-breaker.js.map +1 -1
  168. package/dist/core/utilities/container.js +6 -0
  169. package/dist/core/utilities/container.js.map +1 -1
  170. package/dist/core/utilities/hooks.js +4 -0
  171. package/dist/core/utilities/hooks.js.map +1 -1
  172. package/dist/core/validation/index.js +6 -1
  173. package/dist/core/validation/index.js.map +1 -1
  174. package/dist/index.d.ts +6 -0
  175. package/dist/index.js +5 -0
  176. package/dist/index.js.map +1 -1
  177. package/dist/moro.d.ts +154 -1
  178. package/dist/moro.js +588 -11
  179. package/dist/moro.js.map +1 -1
  180. package/dist/types/config.d.ts +28 -0
  181. package/dist/types/core.d.ts +1 -0
  182. package/dist/types/events.d.ts +1 -1
  183. package/dist/types/events.js +1 -0
  184. package/dist/types/events.js.map +1 -1
  185. package/dist/types/module.d.ts +2 -2
  186. package/package.json +21 -1
package/dist/moro.js CHANGED
@@ -17,6 +17,16 @@ import { normalizeValidationError } from './core/validation/schema-interface.js'
17
17
  import { initializeConfig } from './core/config/index.js';
18
18
  // Runtime System Integration
19
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;
20
30
  export class Moro extends EventEmitter {
21
31
  coreFramework;
22
32
  routes = [];
@@ -49,6 +59,14 @@ export class Moro extends EventEmitter {
49
59
  middlewareManager;
50
60
  // Queued WebSocket registrations (for async adapter detection)
51
61
  queuedWebSocketRegistrations = [];
62
+ // Job scheduling system
63
+ jobScheduler;
64
+ jobHealthChecker;
65
+ jobsStarted = false;
66
+ // GraphQL system
67
+ graphqlCore; // Use any to avoid import dependency
68
+ graphqlSubscriptionManager;
69
+ graphqlInitPromise;
52
70
  constructor(options = {}) {
53
71
  super(); // Call EventEmitter constructor
54
72
  // Track if user explicitly set logger/logging options
@@ -110,6 +128,33 @@ export class Moro extends EventEmitter {
110
128
  }
111
129
  // Access enterprise event bus from core framework
112
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
+ }
113
158
  // Setup default middleware if enabled - use config defaults with options override
114
159
  this.setupDefaultMiddleware({
115
160
  ...this.getDefaultOptionsFromConfig(),
@@ -412,15 +457,16 @@ export class Moro extends EventEmitter {
412
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');
413
458
  }
414
459
  // Check if clustering is enabled for massive performance gains
415
- // NOTE: uWebSockets.js does NOT support Node.js clustering - it's single-threaded only
416
460
  const usingUWebSockets = this.config.server?.useUWebSockets || false;
417
461
  if (this.config.performance?.clustering?.enabled) {
418
462
  if (usingUWebSockets) {
419
- this.logger.warn('Clustering is not supported with uWebSockets.js - running in single-threaded mode. ' +
420
- 'uWebSockets is so fast that single-threaded performance often exceeds multi-threaded Node.js!', 'Cluster');
421
- // 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;
422
467
  }
423
468
  else {
469
+ // Use traditional Node.js cluster module for standard HTTP
424
470
  this.startWithClustering(port, host, callback);
425
471
  return;
426
472
  }
@@ -432,7 +478,7 @@ export class Moro extends EventEmitter {
432
478
  this.coreFramework.addMiddleware(docsMiddleware);
433
479
  this.logger.debug('Documentation middleware added', 'Documentation');
434
480
  }
435
- catch (error) {
481
+ catch {
436
482
  // Documentation not enabled, that's fine
437
483
  this.logger.debug('Documentation not enabled', 'Documentation');
438
484
  }
@@ -478,6 +524,10 @@ export class Moro extends EventEmitter {
478
524
  this.unifiedRouter.logPerformanceStats();
479
525
  }
480
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
+ });
481
531
  if (callback)
482
532
  callback();
483
533
  };
@@ -604,7 +654,7 @@ export class Moro extends EventEmitter {
604
654
  if (res.headersSent)
605
655
  return;
606
656
  }
607
- catch (error) {
657
+ catch {
608
658
  // Documentation not enabled, that's fine
609
659
  }
610
660
  // Try unified router first (handles all routes)
@@ -622,6 +672,7 @@ export class Moro extends EventEmitter {
622
672
  // Handle direct routes for runtime adapters
623
673
  async handleDirectRoutes(req, res) {
624
674
  // Find matching route
675
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
625
676
  const route = this.findMatchingRoute(req.method, req.path);
626
677
  if (!route) {
627
678
  res.status(404).json({ success: false, error: 'Not found' });
@@ -790,6 +841,7 @@ export class Moro extends EventEmitter {
790
841
  if (!this.dynamicRoutesBySegments.has(segmentCount)) {
791
842
  this.dynamicRoutesBySegments.set(segmentCount, []);
792
843
  }
844
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
793
845
  this.dynamicRoutesBySegments.get(segmentCount).push(route);
794
846
  }
795
847
  }
@@ -1146,6 +1198,7 @@ export class Moro extends EventEmitter {
1146
1198
  const gracefulShutdown = () => {
1147
1199
  this.logger.info('Gracefully shutting down cluster...', 'Cluster');
1148
1200
  // Clean up all workers
1201
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1149
1202
  for (const [pid, worker] of this.clusterWorkers) {
1150
1203
  worker.removeAllListeners();
1151
1204
  worker.kill('SIGTERM');
@@ -1160,6 +1213,7 @@ export class Moro extends EventEmitter {
1160
1213
  // Fork workers with basic tracking
1161
1214
  for (let i = 0; i < workerCount; i++) {
1162
1215
  const worker = cluster.fork();
1216
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1163
1217
  this.clusterWorkers.set(worker.process.pid, worker);
1164
1218
  this.logger.info(`Worker ${worker.process.pid} started`, 'Cluster');
1165
1219
  // Handle individual worker messages
@@ -1173,6 +1227,7 @@ export class Moro extends EventEmitter {
1173
1227
  this.logger.warn(`Worker ${pid} died unexpectedly (${signal || code}). Restarting...`, 'Cluster');
1174
1228
  // Simple restart
1175
1229
  const newWorker = cluster.fork();
1230
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1176
1231
  this.clusterWorkers.set(newWorker.process.pid, newWorker);
1177
1232
  this.logger.info(`Worker ${newWorker.process.pid} restarted`, 'Cluster');
1178
1233
  }
@@ -1254,7 +1309,7 @@ export class Moro extends EventEmitter {
1254
1309
  const docsMiddleware = this.documentation.getDocsMiddleware();
1255
1310
  this.coreFramework.addMiddleware(docsMiddleware);
1256
1311
  }
1257
- catch (error) {
1312
+ catch {
1258
1313
  // Documentation not enabled, that's fine
1259
1314
  }
1260
1315
  // Add unified routing middleware (handles both chainable and direct routes)
@@ -1329,18 +1384,209 @@ export class Moro extends EventEmitter {
1329
1384
  // Log other worker messages
1330
1385
  this.logger.debug(`Worker message: ${JSON.stringify(message)}`, 'Cluster');
1331
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
+ }
1332
1555
  /**
1333
1556
  * Gracefully close the application and clean up resources
1334
1557
  * This should be called in tests and during shutdown
1335
1558
  */
1336
1559
  async close() {
1337
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
+ }
1338
1584
  // Flush logger buffer before shutdown
1339
1585
  try {
1340
1586
  // Use flushBuffer for immediate synchronous flush
1341
1587
  this.logger.flushBuffer();
1342
1588
  }
1343
- catch (error) {
1589
+ catch {
1344
1590
  // Ignore flush errors during shutdown
1345
1591
  }
1346
1592
  // Close the core framework with timeout
@@ -1355,7 +1601,7 @@ export class Moro extends EventEmitter {
1355
1601
  new Promise(resolve => setTimeout(resolve, 2000)), // 2 second timeout
1356
1602
  ]);
1357
1603
  }
1358
- catch (error) {
1604
+ catch {
1359
1605
  // Force close if graceful close fails
1360
1606
  this.logger.warn('Force closing HTTP server due to timeout');
1361
1607
  }
@@ -1365,7 +1611,7 @@ export class Moro extends EventEmitter {
1365
1611
  try {
1366
1612
  this.moduleDiscovery.cleanup();
1367
1613
  }
1368
- catch (error) {
1614
+ catch {
1369
1615
  // Ignore cleanup errors
1370
1616
  }
1371
1617
  }
@@ -1374,11 +1620,342 @@ export class Moro extends EventEmitter {
1374
1620
  this.eventBus.removeAllListeners();
1375
1621
  this.removeAllListeners();
1376
1622
  }
1377
- catch (error) {
1623
+ catch {
1378
1624
  // Ignore cleanup errors
1379
1625
  }
1380
1626
  this.logger.debug('Moro application closed successfully');
1381
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
+ }
1382
1959
  }
1383
1960
  // Export convenience function
1384
1961
  export function createApp(options) {