@zintrust/core 0.4.28 → 0.4.30

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 (453) hide show
  1. package/bin/z.js +0 -0
  2. package/bin/zin.js +0 -0
  3. package/bin/zintrust.js +0 -0
  4. package/bin/zt.js +0 -0
  5. package/package.json +78 -23
  6. package/src/cli/commands/D1ProxyCommand.d.ts.map +1 -1
  7. package/src/cli/commands/D1ProxyCommand.js +69 -9
  8. package/src/cli/commands/KvProxyCommand.d.ts.map +1 -1
  9. package/src/cli/commands/KvProxyCommand.js +69 -9
  10. package/src/cli/commands/NewCommand.d.ts.map +1 -1
  11. package/src/cli/commands/NewCommand.js +8 -28
  12. package/src/cli/scaffolding/GovernanceScaffolder.d.ts.map +1 -1
  13. package/src/cli/scaffolding/GovernanceScaffolder.js +31 -4
  14. package/src/cli/scaffolding/ProjectScaffolder.d.ts.map +1 -1
  15. package/src/cli/scaffolding/ProjectScaffolder.js +31 -5
  16. package/src/cli/utils/DistPackager.d.ts +2 -2
  17. package/src/cli/utils/DistPackager.js +2 -2
  18. package/src/index.js +3 -3
  19. package/src/zintrust.plugins.js +1 -1
  20. package/app/Controllers/AuthController.d.ts +0 -10
  21. package/app/Controllers/AuthController.d.ts.map +0 -1
  22. package/app/Controllers/AuthController.js +0 -233
  23. package/app/Controllers/TestController.d.ts +0 -22
  24. package/app/Controllers/TestController.d.ts.map +0 -1
  25. package/app/Controllers/TestController.js +0 -188
  26. package/app/Controllers/UserController.d.ts +0 -9
  27. package/app/Controllers/UserController.d.ts.map +0 -1
  28. package/app/Controllers/UserController.js +0 -8
  29. package/app/Controllers/UserQueryBuilderController.d.ts +0 -16
  30. package/app/Controllers/UserQueryBuilderController.d.ts.map +0 -1
  31. package/app/Controllers/UserQueryBuilderController.js +0 -404
  32. package/app/Jobs/AdvancedEmailJobService.d.ts +0 -57
  33. package/app/Jobs/AdvancedEmailJobService.d.ts.map +0 -1
  34. package/app/Jobs/AdvancedEmailJobService.js +0 -185
  35. package/app/Jobs/EmailJobService.d.ts +0 -33
  36. package/app/Jobs/EmailJobService.d.ts.map +0 -1
  37. package/app/Jobs/EmailJobService.js +0 -113
  38. package/app/Middleware/AuthFailureResponder.d.ts +0 -3
  39. package/app/Middleware/AuthFailureResponder.d.ts.map +0 -1
  40. package/app/Middleware/AuthFailureResponder.js +0 -8
  41. package/app/Middleware/ProfilerMiddleware.d.ts +0 -12
  42. package/app/Middleware/ProfilerMiddleware.d.ts.map +0 -1
  43. package/app/Middleware/ProfilerMiddleware.js +0 -47
  44. package/app/Middleware/index.d.ts +0 -59
  45. package/app/Middleware/index.d.ts.map +0 -1
  46. package/app/Middleware/index.js +0 -216
  47. package/app/Models/Post.d.ts +0 -14
  48. package/app/Models/Post.d.ts.map +0 -1
  49. package/app/Models/Post.js +0 -27
  50. package/app/Models/User.d.ts +0 -14
  51. package/app/Models/User.d.ts.map +0 -1
  52. package/app/Models/User.js +0 -44
  53. package/app/Schedules/JobTracking.d.ts +0 -3
  54. package/app/Schedules/JobTracking.d.ts.map +0 -1
  55. package/app/Schedules/JobTracking.js +0 -13
  56. package/app/Schedules/index.d.ts +0 -2
  57. package/app/Schedules/index.d.ts.map +0 -1
  58. package/app/Schedules/index.js +0 -1
  59. package/app/Toolkit/Broadcast/sendBroadcast.d.ts +0 -6
  60. package/app/Toolkit/Broadcast/sendBroadcast.d.ts.map +0 -1
  61. package/app/Toolkit/Broadcast/sendBroadcast.js +0 -5
  62. package/app/Toolkit/Mail/sendWelcomeEmail.d.ts +0 -6
  63. package/app/Toolkit/Mail/sendWelcomeEmail.d.ts.map +0 -1
  64. package/app/Toolkit/Mail/sendWelcomeEmail.js +0 -20
  65. package/app/Toolkit/Notification/sendSlackNotification.d.ts +0 -8
  66. package/app/Toolkit/Notification/sendSlackNotification.d.ts.map +0 -1
  67. package/app/Toolkit/Notification/sendSlackNotification.js +0 -5
  68. package/app/Toolkit/Notification/sendSms.d.ts +0 -6
  69. package/app/Toolkit/Notification/sendSms.d.ts.map +0 -1
  70. package/app/Toolkit/Notification/sendSms.js +0 -5
  71. package/app/Types/controller.d.ts +0 -44
  72. package/app/Types/controller.d.ts.map +0 -1
  73. package/app/Types/controller.js +0 -1
  74. package/app/Workers/AdvancEmailWorker.d.ts +0 -44
  75. package/app/Workers/AdvancEmailWorker.d.ts.map +0 -1
  76. package/app/Workers/AdvancEmailWorker.js +0 -192
  77. package/app/Workers/EmailWorker.d.ts +0 -39
  78. package/app/Workers/EmailWorker.d.ts.map +0 -1
  79. package/app/Workers/EmailWorker.js +0 -199
  80. package/app/Workers/TestWorker.d.ts +0 -16
  81. package/app/Workers/TestWorker.d.ts.map +0 -1
  82. package/app/Workers/TestWorker.js +0 -153
  83. package/build-manifest.json +0 -6410
  84. package/config/broadcast.d.ts +0 -38
  85. package/config/broadcast.d.ts.map +0 -1
  86. package/config/broadcast.js +0 -37
  87. package/config/cache.d.ts +0 -40
  88. package/config/cache.d.ts.map +0 -1
  89. package/config/cache.js +0 -39
  90. package/config/database.d.ts +0 -58
  91. package/config/database.d.ts.map +0 -1
  92. package/config/database.js +0 -65
  93. package/config/mail.d.ts +0 -51
  94. package/config/mail.d.ts.map +0 -1
  95. package/config/mail.js +0 -69
  96. package/config/middleware.d.ts +0 -21
  97. package/config/middleware.d.ts.map +0 -1
  98. package/config/middleware.js +0 -61
  99. package/config/notification.d.ts +0 -33
  100. package/config/notification.d.ts.map +0 -1
  101. package/config/notification.js +0 -33
  102. package/config/queue.d.ts +0 -55
  103. package/config/queue.d.ts.map +0 -1
  104. package/config/queue.js +0 -87
  105. package/config/storage.d.ts +0 -59
  106. package/config/storage.d.ts.map +0 -1
  107. package/config/storage.js +0 -59
  108. package/config/workers.d.ts +0 -54
  109. package/config/workers.d.ts.map +0 -1
  110. package/config/workers.js +0 -83
  111. package/database/factories/UserFactory.d.ts +0 -40
  112. package/database/factories/UserFactory.d.ts.map +0 -1
  113. package/database/factories/UserFactory.js +0 -99
  114. package/database/migrations/20260109074544_create_users_table.d.ts +0 -7
  115. package/database/migrations/20260109074544_create_users_table.d.ts.map +0 -1
  116. package/database/migrations/20260109074544_create_users_table.js +0 -21
  117. package/database/migrations/20260109144731_create_tasks_table.d.ts +0 -7
  118. package/database/migrations/20260109144731_create_tasks_table.d.ts.map +0 -1
  119. package/database/migrations/20260109144731_create_tasks_table.js +0 -19
  120. package/database/migrations/20260109144735_add_done_tasks_table.d.ts +0 -11
  121. package/database/migrations/20260109144735_add_done_tasks_table.d.ts.map +0 -1
  122. package/database/migrations/20260109144735_add_done_tasks_table.js +0 -25
  123. package/database/migrations/20260109150837_add_start_tasks_table.d.ts +0 -11
  124. package/database/migrations/20260109150837_add_start_tasks_table.d.ts.map +0 -1
  125. package/database/migrations/20260109150837_add_start_tasks_table.js +0 -22
  126. package/database/migrations/20260109152114_add_way_tasks_table.d.ts +0 -11
  127. package/database/migrations/20260109152114_add_way_tasks_table.d.ts.map +0 -1
  128. package/database/migrations/20260109152114_add_way_tasks_table.js +0 -22
  129. package/database/migrations/20260218121500_create_jwt_revocations_table.d.ts +0 -7
  130. package/database/migrations/20260218121500_create_jwt_revocations_table.d.ts.map +0 -1
  131. package/database/migrations/20260218121500_create_jwt_revocations_table.js +0 -22
  132. package/database/migrations/20260222120000_add_kind_to_jwt_revocations_table.d.ts +0 -11
  133. package/database/migrations/20260222120000_add_kind_to_jwt_revocations_table.d.ts.map +0 -1
  134. package/database/migrations/20260222120000_add_kind_to_jwt_revocations_table.js +0 -20
  135. package/database/migrations/20260222123000_add_user_id_to_jwt_revocations_table.d.ts +0 -11
  136. package/database/migrations/20260222123000_add_user_id_to_jwt_revocations_table.d.ts.map +0 -1
  137. package/database/migrations/20260222123000_add_user_id_to_jwt_revocations_table.js +0 -20
  138. package/database/migrations/20260327112651_create_posts_table.d.ts +0 -11
  139. package/database/migrations/20260327112651_create_posts_table.d.ts.map +0 -1
  140. package/database/migrations/20260327112651_create_posts_table.js +0 -24
  141. package/database/seeders/DatabaseSeeder.d.ts +0 -5
  142. package/database/seeders/DatabaseSeeder.d.ts.map +0 -1
  143. package/database/seeders/DatabaseSeeder.js +0 -15
  144. package/database/seeders/UserSeeder.d.ts +0 -30
  145. package/database/seeders/UserSeeder.d.ts.map +0 -1
  146. package/database/seeders/UserSeeder.js +0 -97
  147. package/index.d.ts +0 -1
  148. package/index.js +0 -1
  149. package/packages/cache-redis/src/RedisProxyAdapter.d.ts +0 -6
  150. package/packages/cache-redis/src/RedisProxyAdapter.d.ts.map +0 -1
  151. package/packages/cache-redis/src/RedisProxyAdapter.js +0 -144
  152. package/packages/cache-redis/src/index.d.ts +0 -27
  153. package/packages/cache-redis/src/index.d.ts.map +0 -1
  154. package/packages/cache-redis/src/index.js +0 -153
  155. package/packages/db-d1/src/index.d.ts +0 -55
  156. package/packages/db-d1/src/index.d.ts.map +0 -1
  157. package/packages/db-d1/src/index.js +0 -118
  158. package/packages/db-d1/src/register.d.ts +0 -6
  159. package/packages/db-d1/src/register.d.ts.map +0 -1
  160. package/packages/db-d1/src/register.js +0 -8
  161. package/packages/db-mysql/src/common.d.ts +0 -7
  162. package/packages/db-mysql/src/common.d.ts.map +0 -1
  163. package/packages/db-mysql/src/common.js +0 -17
  164. package/packages/db-mysql/src/index.d.ts +0 -43
  165. package/packages/db-mysql/src/index.d.ts.map +0 -1
  166. package/packages/db-mysql/src/index.js +0 -265
  167. package/packages/db-mysql/src/register.d.ts +0 -6
  168. package/packages/db-mysql/src/register.d.ts.map +0 -1
  169. package/packages/db-mysql/src/register.js +0 -32
  170. package/packages/db-postgres/src/index.d.ts +0 -39
  171. package/packages/db-postgres/src/index.d.ts.map +0 -1
  172. package/packages/db-postgres/src/index.js +0 -179
  173. package/packages/db-postgres/src/register.d.ts +0 -6
  174. package/packages/db-postgres/src/register.d.ts.map +0 -1
  175. package/packages/db-postgres/src/register.js +0 -8
  176. package/packages/db-sqlite/src/index.d.ts +0 -9
  177. package/packages/db-sqlite/src/index.d.ts.map +0 -1
  178. package/packages/db-sqlite/src/index.js +0 -7
  179. package/packages/db-sqlite/src/register.d.ts +0 -6
  180. package/packages/db-sqlite/src/register.d.ts.map +0 -1
  181. package/packages/db-sqlite/src/register.js +0 -8
  182. package/packages/db-sqlserver/src/index.d.ts +0 -38
  183. package/packages/db-sqlserver/src/index.d.ts.map +0 -1
  184. package/packages/db-sqlserver/src/index.js +0 -73
  185. package/packages/db-sqlserver/src/register.d.ts +0 -6
  186. package/packages/db-sqlserver/src/register.d.ts.map +0 -1
  187. package/packages/db-sqlserver/src/register.js +0 -21
  188. package/packages/mail-sendgrid/src/register.d.ts +0 -6
  189. package/packages/mail-sendgrid/src/register.d.ts.map +0 -1
  190. package/packages/mail-sendgrid/src/register.js +0 -24
  191. package/packages/mail-smtp/src/register.d.ts +0 -6
  192. package/packages/mail-smtp/src/register.d.ts.map +0 -1
  193. package/packages/mail-smtp/src/register.js +0 -29
  194. package/packages/queue-monitor/src/QueueMonitoringService.d.ts +0 -35
  195. package/packages/queue-monitor/src/QueueMonitoringService.d.ts.map +0 -1
  196. package/packages/queue-monitor/src/QueueMonitoringService.js +0 -194
  197. package/packages/queue-monitor/src/connection.d.ts +0 -3
  198. package/packages/queue-monitor/src/connection.d.ts.map +0 -1
  199. package/packages/queue-monitor/src/connection.js +0 -1
  200. package/packages/queue-monitor/src/dashboard-ui.d.ts +0 -7
  201. package/packages/queue-monitor/src/dashboard-ui.d.ts.map +0 -1
  202. package/packages/queue-monitor/src/dashboard-ui.js +0 -997
  203. package/packages/queue-monitor/src/driver.d.ts +0 -15
  204. package/packages/queue-monitor/src/driver.d.ts.map +0 -1
  205. package/packages/queue-monitor/src/driver.js +0 -115
  206. package/packages/queue-monitor/src/index.d.ts +0 -71
  207. package/packages/queue-monitor/src/index.d.ts.map +0 -1
  208. package/packages/queue-monitor/src/index.js +0 -296
  209. package/packages/queue-monitor/src/metrics.d.ts +0 -27
  210. package/packages/queue-monitor/src/metrics.d.ts.map +0 -1
  211. package/packages/queue-monitor/src/metrics.js +0 -92
  212. package/packages/queue-monitor/src/worker.d.ts +0 -8
  213. package/packages/queue-monitor/src/worker.d.ts.map +0 -1
  214. package/packages/queue-monitor/src/worker.js +0 -35
  215. package/packages/queue-redis/src/BullMQRedisQueue.d.ts +0 -26
  216. package/packages/queue-redis/src/BullMQRedisQueue.d.ts.map +0 -1
  217. package/packages/queue-redis/src/BullMQRedisQueue.js +0 -465
  218. package/packages/queue-redis/src/HttpQueueDriver.d.ts +0 -18
  219. package/packages/queue-redis/src/HttpQueueDriver.d.ts.map +0 -1
  220. package/packages/queue-redis/src/HttpQueueDriver.js +0 -249
  221. package/packages/queue-redis/src/QueueHttpGateway.d.ts +0 -16
  222. package/packages/queue-redis/src/QueueHttpGateway.d.ts.map +0 -1
  223. package/packages/queue-redis/src/QueueHttpGateway.js +0 -217
  224. package/packages/queue-redis/src/RedisPublishClient.d.ts +0 -14
  225. package/packages/queue-redis/src/RedisPublishClient.d.ts.map +0 -1
  226. package/packages/queue-redis/src/RedisPublishClient.js +0 -251
  227. package/packages/queue-redis/src/index.d.ts +0 -12
  228. package/packages/queue-redis/src/index.d.ts.map +0 -1
  229. package/packages/queue-redis/src/index.js +0 -10
  230. package/packages/queue-redis/src/register.d.ts +0 -6
  231. package/packages/queue-redis/src/register.d.ts.map +0 -1
  232. package/packages/queue-redis/src/register.js +0 -21
  233. package/packages/workers/migrations/20260119100000_create_zintrust_workers_table.d.ts +0 -11
  234. package/packages/workers/migrations/20260119100000_create_zintrust_workers_table.d.ts.map +0 -1
  235. package/packages/workers/migrations/20260119100000_create_zintrust_workers_table.js +0 -32
  236. package/packages/workers/migrations/20260123180000_create_queue_jobs_table.d.ts +0 -11
  237. package/packages/workers/migrations/20260123180000_create_queue_jobs_table.d.ts.map +0 -1
  238. package/packages/workers/migrations/20260123180000_create_queue_jobs_table.js +0 -46
  239. package/packages/workers/migrations/20260213142000_create_zintrust_job_tracking_tables.d.ts +0 -7
  240. package/packages/workers/migrations/20260213142000_create_zintrust_job_tracking_tables.d.ts.map +0 -1
  241. package/packages/workers/migrations/20260213142000_create_zintrust_job_tracking_tables.js +0 -44
  242. package/packages/workers/migrations/20260213183000_expand_zintrust_job_tracking_reliability_tables.d.ts +0 -7
  243. package/packages/workers/migrations/20260213183000_expand_zintrust_job_tracking_reliability_tables.d.ts.map +0 -1
  244. package/packages/workers/migrations/20260213183000_expand_zintrust_job_tracking_reliability_tables.js +0 -104
  245. package/packages/workers/src/AnomalyDetection.d.ts +0 -107
  246. package/packages/workers/src/AnomalyDetection.d.ts.map +0 -1
  247. package/packages/workers/src/AnomalyDetection.js +0 -329
  248. package/packages/workers/src/AutoScaler.d.ts +0 -128
  249. package/packages/workers/src/AutoScaler.d.ts.map +0 -1
  250. package/packages/workers/src/AutoScaler.js +0 -425
  251. package/packages/workers/src/BroadcastWorker.d.ts +0 -29
  252. package/packages/workers/src/BroadcastWorker.d.ts.map +0 -1
  253. package/packages/workers/src/BroadcastWorker.js +0 -24
  254. package/packages/workers/src/CanaryController.d.ts +0 -104
  255. package/packages/workers/src/CanaryController.d.ts.map +0 -1
  256. package/packages/workers/src/CanaryController.js +0 -424
  257. package/packages/workers/src/ChaosEngineering.d.ts +0 -80
  258. package/packages/workers/src/ChaosEngineering.d.ts.map +0 -1
  259. package/packages/workers/src/ChaosEngineering.js +0 -229
  260. package/packages/workers/src/CircuitBreaker.d.ts +0 -107
  261. package/packages/workers/src/CircuitBreaker.d.ts.map +0 -1
  262. package/packages/workers/src/CircuitBreaker.js +0 -374
  263. package/packages/workers/src/ClusterLock.d.ts +0 -91
  264. package/packages/workers/src/ClusterLock.d.ts.map +0 -1
  265. package/packages/workers/src/ClusterLock.js +0 -397
  266. package/packages/workers/src/ComplianceManager.d.ts +0 -178
  267. package/packages/workers/src/ComplianceManager.d.ts.map +0 -1
  268. package/packages/workers/src/ComplianceManager.js +0 -556
  269. package/packages/workers/src/DatacenterOrchestrator.d.ts +0 -134
  270. package/packages/workers/src/DatacenterOrchestrator.d.ts.map +0 -1
  271. package/packages/workers/src/DatacenterOrchestrator.js +0 -404
  272. package/packages/workers/src/DeadLetterQueue.d.ts +0 -123
  273. package/packages/workers/src/DeadLetterQueue.d.ts.map +0 -1
  274. package/packages/workers/src/DeadLetterQueue.js +0 -544
  275. package/packages/workers/src/HealthMonitor.d.ts +0 -43
  276. package/packages/workers/src/HealthMonitor.d.ts.map +0 -1
  277. package/packages/workers/src/HealthMonitor.js +0 -312
  278. package/packages/workers/src/MultiQueueWorker.d.ts +0 -90
  279. package/packages/workers/src/MultiQueueWorker.d.ts.map +0 -1
  280. package/packages/workers/src/MultiQueueWorker.js +0 -282
  281. package/packages/workers/src/NotificationWorker.d.ts +0 -29
  282. package/packages/workers/src/NotificationWorker.d.ts.map +0 -1
  283. package/packages/workers/src/NotificationWorker.js +0 -23
  284. package/packages/workers/src/Observability.d.ts +0 -154
  285. package/packages/workers/src/Observability.d.ts.map +0 -1
  286. package/packages/workers/src/Observability.js +0 -538
  287. package/packages/workers/src/PluginManager.d.ts +0 -124
  288. package/packages/workers/src/PluginManager.d.ts.map +0 -1
  289. package/packages/workers/src/PluginManager.js +0 -392
  290. package/packages/workers/src/PriorityQueue.d.ts +0 -118
  291. package/packages/workers/src/PriorityQueue.d.ts.map +0 -1
  292. package/packages/workers/src/PriorityQueue.js +0 -276
  293. package/packages/workers/src/ResourceMonitor.d.ts +0 -165
  294. package/packages/workers/src/ResourceMonitor.d.ts.map +0 -1
  295. package/packages/workers/src/ResourceMonitor.js +0 -632
  296. package/packages/workers/src/SLAMonitor.d.ts +0 -111
  297. package/packages/workers/src/SLAMonitor.d.ts.map +0 -1
  298. package/packages/workers/src/SLAMonitor.js +0 -274
  299. package/packages/workers/src/WorkerFactory.d.ts +0 -226
  300. package/packages/workers/src/WorkerFactory.d.ts.map +0 -1
  301. package/packages/workers/src/WorkerFactory.js +0 -2551
  302. package/packages/workers/src/WorkerInit.d.ts +0 -103
  303. package/packages/workers/src/WorkerInit.d.ts.map +0 -1
  304. package/packages/workers/src/WorkerInit.js +0 -359
  305. package/packages/workers/src/WorkerMetrics.d.ts +0 -116
  306. package/packages/workers/src/WorkerMetrics.d.ts.map +0 -1
  307. package/packages/workers/src/WorkerMetrics.js +0 -570
  308. package/packages/workers/src/WorkerRegistry.d.ts +0 -152
  309. package/packages/workers/src/WorkerRegistry.d.ts.map +0 -1
  310. package/packages/workers/src/WorkerRegistry.js +0 -396
  311. package/packages/workers/src/WorkerShutdown.d.ts +0 -70
  312. package/packages/workers/src/WorkerShutdown.d.ts.map +0 -1
  313. package/packages/workers/src/WorkerShutdown.js +0 -185
  314. package/packages/workers/src/WorkerVersioning.d.ts +0 -108
  315. package/packages/workers/src/WorkerVersioning.d.ts.map +0 -1
  316. package/packages/workers/src/WorkerVersioning.js +0 -300
  317. package/packages/workers/src/config/workerConfig.d.ts +0 -5
  318. package/packages/workers/src/config/workerConfig.d.ts.map +0 -1
  319. package/packages/workers/src/config/workerConfig.js +0 -25
  320. package/packages/workers/src/createQueueWorker.d.ts +0 -31
  321. package/packages/workers/src/createQueueWorker.d.ts.map +0 -1
  322. package/packages/workers/src/createQueueWorker.js +0 -382
  323. package/packages/workers/src/dashboard/index.d.ts +0 -2
  324. package/packages/workers/src/dashboard/index.d.ts.map +0 -1
  325. package/packages/workers/src/dashboard/index.js +0 -1
  326. package/packages/workers/src/dashboard/types.d.ts +0 -123
  327. package/packages/workers/src/dashboard/types.d.ts.map +0 -1
  328. package/packages/workers/src/dashboard/types.js +0 -1
  329. package/packages/workers/src/dashboard/workers-api.d.ts +0 -5
  330. package/packages/workers/src/dashboard/workers-api.d.ts.map +0 -1
  331. package/packages/workers/src/dashboard/workers-api.js +0 -776
  332. package/packages/workers/src/helper/index.d.ts +0 -6
  333. package/packages/workers/src/helper/index.d.ts.map +0 -1
  334. package/packages/workers/src/helper/index.js +0 -10
  335. package/packages/workers/src/http/WorkerApiController.d.ts +0 -39
  336. package/packages/workers/src/http/WorkerApiController.d.ts.map +0 -1
  337. package/packages/workers/src/http/WorkerApiController.js +0 -313
  338. package/packages/workers/src/http/WorkerController.d.ts +0 -375
  339. package/packages/workers/src/http/WorkerController.d.ts.map +0 -1
  340. package/packages/workers/src/http/WorkerController.js +0 -1454
  341. package/packages/workers/src/http/WorkerMonitoringService.d.ts +0 -12
  342. package/packages/workers/src/http/WorkerMonitoringService.d.ts.map +0 -1
  343. package/packages/workers/src/http/WorkerMonitoringService.js +0 -89
  344. package/packages/workers/src/http/middleware/CustomValidation.d.ts +0 -93
  345. package/packages/workers/src/http/middleware/CustomValidation.d.ts.map +0 -1
  346. package/packages/workers/src/http/middleware/CustomValidation.js +0 -270
  347. package/packages/workers/src/http/middleware/DatacenterValidator.d.ts +0 -4
  348. package/packages/workers/src/http/middleware/DatacenterValidator.d.ts.map +0 -1
  349. package/packages/workers/src/http/middleware/DatacenterValidator.js +0 -94
  350. package/packages/workers/src/http/middleware/EditWorkerValidation.d.ts +0 -8
  351. package/packages/workers/src/http/middleware/EditWorkerValidation.d.ts.map +0 -1
  352. package/packages/workers/src/http/middleware/EditWorkerValidation.js +0 -56
  353. package/packages/workers/src/http/middleware/FeaturesValidator.d.ts +0 -4
  354. package/packages/workers/src/http/middleware/FeaturesValidator.d.ts.map +0 -1
  355. package/packages/workers/src/http/middleware/FeaturesValidator.js +0 -61
  356. package/packages/workers/src/http/middleware/InfrastructureValidator.d.ts +0 -32
  357. package/packages/workers/src/http/middleware/InfrastructureValidator.d.ts.map +0 -1
  358. package/packages/workers/src/http/middleware/InfrastructureValidator.js +0 -226
  359. package/packages/workers/src/http/middleware/OptionsValidator.d.ts +0 -4
  360. package/packages/workers/src/http/middleware/OptionsValidator.d.ts.map +0 -1
  361. package/packages/workers/src/http/middleware/OptionsValidator.js +0 -112
  362. package/packages/workers/src/http/middleware/PayloadSanitizer.d.ts +0 -8
  363. package/packages/workers/src/http/middleware/PayloadSanitizer.d.ts.map +0 -1
  364. package/packages/workers/src/http/middleware/PayloadSanitizer.js +0 -42
  365. package/packages/workers/src/http/middleware/ProcessorPathSanitizer.d.ts +0 -4
  366. package/packages/workers/src/http/middleware/ProcessorPathSanitizer.d.ts.map +0 -1
  367. package/packages/workers/src/http/middleware/ProcessorPathSanitizer.js +0 -140
  368. package/packages/workers/src/http/middleware/QueueNameSanitizer.d.ts +0 -4
  369. package/packages/workers/src/http/middleware/QueueNameSanitizer.d.ts.map +0 -1
  370. package/packages/workers/src/http/middleware/QueueNameSanitizer.js +0 -45
  371. package/packages/workers/src/http/middleware/ValidateDriver.d.ts +0 -8
  372. package/packages/workers/src/http/middleware/ValidateDriver.d.ts.map +0 -1
  373. package/packages/workers/src/http/middleware/ValidateDriver.js +0 -20
  374. package/packages/workers/src/http/middleware/VersionSanitizer.d.ts +0 -4
  375. package/packages/workers/src/http/middleware/VersionSanitizer.d.ts.map +0 -1
  376. package/packages/workers/src/http/middleware/VersionSanitizer.js +0 -25
  377. package/packages/workers/src/http/middleware/WorkerNameSanitizer.d.ts +0 -4
  378. package/packages/workers/src/http/middleware/WorkerNameSanitizer.d.ts.map +0 -1
  379. package/packages/workers/src/http/middleware/WorkerNameSanitizer.js +0 -46
  380. package/packages/workers/src/http/middleware/WorkerValidationChain.d.ts +0 -28
  381. package/packages/workers/src/http/middleware/WorkerValidationChain.d.ts.map +0 -1
  382. package/packages/workers/src/http/middleware/WorkerValidationChain.js +0 -186
  383. package/packages/workers/src/index.d.ts +0 -47
  384. package/packages/workers/src/index.d.ts.map +0 -1
  385. package/packages/workers/src/index.js +0 -48
  386. package/packages/workers/src/register.d.ts +0 -10
  387. package/packages/workers/src/register.d.ts.map +0 -1
  388. package/packages/workers/src/register.js +0 -43
  389. package/packages/workers/src/routes/workers.d.ts +0 -13
  390. package/packages/workers/src/routes/workers.d.ts.map +0 -1
  391. package/packages/workers/src/routes/workers.js +0 -126
  392. package/packages/workers/src/storage/WorkerStore.d.ts +0 -52
  393. package/packages/workers/src/storage/WorkerStore.d.ts.map +0 -1
  394. package/packages/workers/src/storage/WorkerStore.js +0 -259
  395. package/packages/workers/src/telemetry/api/TelemetryAPI.d.ts +0 -47
  396. package/packages/workers/src/telemetry/api/TelemetryAPI.d.ts.map +0 -1
  397. package/packages/workers/src/telemetry/api/TelemetryAPI.js +0 -219
  398. package/packages/workers/src/telemetry/api/TelemetryMonitoringService.d.ts +0 -18
  399. package/packages/workers/src/telemetry/api/TelemetryMonitoringService.d.ts.map +0 -1
  400. package/packages/workers/src/telemetry/api/TelemetryMonitoringService.js +0 -140
  401. package/packages/workers/src/telemetry/components/AlertPanel.d.ts +0 -2
  402. package/packages/workers/src/telemetry/components/AlertPanel.d.ts.map +0 -1
  403. package/packages/workers/src/telemetry/components/AlertPanel.js +0 -13
  404. package/packages/workers/src/telemetry/components/CostTracking.d.ts +0 -2
  405. package/packages/workers/src/telemetry/components/CostTracking.d.ts.map +0 -1
  406. package/packages/workers/src/telemetry/components/CostTracking.js +0 -14
  407. package/packages/workers/src/telemetry/components/ResourceUsageChart.d.ts +0 -2
  408. package/packages/workers/src/telemetry/components/ResourceUsageChart.d.ts.map +0 -1
  409. package/packages/workers/src/telemetry/components/ResourceUsageChart.js +0 -11
  410. package/packages/workers/src/telemetry/components/WorkerHealthChart.d.ts +0 -2
  411. package/packages/workers/src/telemetry/components/WorkerHealthChart.d.ts.map +0 -1
  412. package/packages/workers/src/telemetry/components/WorkerHealthChart.js +0 -11
  413. package/packages/workers/src/telemetry/index.d.ts +0 -16
  414. package/packages/workers/src/telemetry/index.d.ts.map +0 -1
  415. package/packages/workers/src/telemetry/index.js +0 -60
  416. package/packages/workers/src/telemetry/routes/dashboard.d.ts +0 -7
  417. package/packages/workers/src/telemetry/routes/dashboard.d.ts.map +0 -1
  418. package/packages/workers/src/telemetry/routes/dashboard.js +0 -608
  419. package/packages/workers/src/type.d.ts +0 -77
  420. package/packages/workers/src/type.d.ts.map +0 -1
  421. package/packages/workers/src/type.js +0 -1
  422. package/packages/workers/src/ui/router/EmbeddedAssets.d.ts +0 -5
  423. package/packages/workers/src/ui/router/EmbeddedAssets.d.ts.map +0 -1
  424. package/packages/workers/src/ui/router/EmbeddedAssets.js +0 -13
  425. package/packages/workers/src/ui/router/ui.d.ts +0 -4
  426. package/packages/workers/src/ui/router/ui.d.ts.map +0 -1
  427. package/packages/workers/src/ui/router/ui.js +0 -208
  428. package/packages/workers/src/ui/types/worker-ui.d.ts +0 -230
  429. package/packages/workers/src/ui/types/worker-ui.d.ts.map +0 -1
  430. package/packages/workers/src/ui/types/worker-ui.js +0 -5
  431. package/routes/DirectTestRoutes.d.ts +0 -25
  432. package/routes/DirectTestRoutes.d.ts.map +0 -1
  433. package/routes/DirectTestRoutes.js +0 -480
  434. package/routes/TestRoutes.d.ts +0 -11
  435. package/routes/TestRoutes.d.ts.map +0 -1
  436. package/routes/TestRoutes.js +0 -25
  437. package/routes/api.d.ts +0 -7
  438. package/routes/api.d.ts.map +0 -1
  439. package/routes/api.js +0 -136
  440. package/routes/apiDev.d.ts +0 -7
  441. package/routes/apiDev.d.ts.map +0 -1
  442. package/routes/apiDev.js +0 -92
  443. package/routes/broadcast.d.ts +0 -9
  444. package/routes/broadcast.d.ts.map +0 -1
  445. package/routes/broadcast.js +0 -27
  446. package/routes/mail.d.ts +0 -12
  447. package/routes/mail.d.ts.map +0 -1
  448. package/routes/mail.js +0 -138
  449. package/routes/storage.d.ts +0 -4
  450. package/routes/storage.d.ts.map +0 -1
  451. package/routes/storage.js +0 -35
  452. package/start.d.ts +0 -1
  453. package/start.js +0 -1
@@ -1,2551 +0,0 @@
1
- /**
2
- * Worker Factory
3
- * Central factory for creating workers with all advanced features
4
- * Sealed namespace for immutability
5
- */
6
- import { Cloudflare, createRedisConnection, databaseConfig, Env, ErrorFactory, generateUuid, getBullMQSafeQueueName, isFunction, isNonEmptyString, isObject, JobStateTracker, Logger, NodeSingletons, queueConfig, registerDatabasesFromRuntimeConfig, useEnsureDbConnected, workersConfig, ZintrustLang, } from '../../../src/index.js';
7
- import { Worker } from 'bullmq';
8
- import { AutoScaler } from './AutoScaler.js';
9
- import { CanaryController } from './CanaryController.js';
10
- import { CircuitBreaker } from './CircuitBreaker.js';
11
- import { ClusterLock } from './ClusterLock.js';
12
- import { ComplianceManager } from './ComplianceManager.js';
13
- import { DatacenterOrchestrator } from './DatacenterOrchestrator.js';
14
- import { DeadLetterQueue } from './DeadLetterQueue.js';
15
- import { HealthMonitor } from './HealthMonitor.js';
16
- import { MultiQueueWorker } from './MultiQueueWorker.js';
17
- import { Observability } from './Observability.js';
18
- import { PluginManager } from './PluginManager.js';
19
- import { PriorityQueue } from './PriorityQueue.js';
20
- import { ResourceMonitor } from './ResourceMonitor.js';
21
- import { WorkerMetrics } from './WorkerMetrics.js';
22
- import { WorkerRegistry } from './WorkerRegistry.js';
23
- import { WorkerVersioning } from './WorkerVersioning.js';
24
- import { keyPrefix } from './config/workerConfig.js';
25
- import { DbWorkerStore, InMemoryWorkerStore, RedisWorkerStore, } from './storage/WorkerStore.js';
26
- const path = NodeSingletons.path;
27
- const isNodeRuntime = () => typeof process !== 'undefined' && Boolean(process.versions?.node);
28
- const resolveProjectRoot = () => {
29
- const envRoot = Env.get('ZINTRUST_PROJECT_ROOT', '').trim();
30
- return envRoot.length > 0 ? envRoot : process.cwd();
31
- };
32
- const canUseProjectFileImports = () => typeof NodeSingletons?.fs?.writeFileSync === 'function' &&
33
- typeof NodeSingletons?.fs?.mkdirSync === 'function' &&
34
- typeof NodeSingletons?.fs?.existsSync === 'function' &&
35
- typeof NodeSingletons?.url?.pathToFileURL === 'function' &&
36
- typeof NodeSingletons?.path?.join === 'function';
37
- const buildCandidatesForSpecifier = (specifier, root) => {
38
- if (specifier === '@zintrust/core') {
39
- return [
40
- path.join(root, 'dist', 'src', 'index.js'),
41
- path.join(root, 'dist', 'index.js'),
42
- path.join(root, 'node_modules', '@zintrust', 'core', 'dist', 'src', 'index.js'),
43
- path.join(root, 'node_modules', '@zintrust', 'core', 'dist', 'index.js'),
44
- path.join(root, 'node_modules', '@zintrust', 'core', 'src', 'index.js'),
45
- path.join(root, 'node_modules', '@zintrust', 'core', 'index.js'),
46
- path.join(root, 'src', 'index.ts'),
47
- ];
48
- }
49
- if (specifier === '@zintrust/workers') {
50
- return [
51
- path.join(root, 'dist', 'packages', 'workers', 'src', 'index.js'),
52
- path.join(root, 'node_modules', '@zintrust', 'workers', 'dist', 'src', 'index.js'),
53
- path.join(root, 'node_modules', '@zintrust', 'workers', 'dist', 'index.js'),
54
- path.join(root, 'node_modules', '@zintrust', 'workers', 'src', 'index.js'),
55
- path.join(root, 'node_modules', '@zintrust', 'workers', 'index.js'),
56
- path.join(root, 'packages', 'workers', 'src', 'index.ts'),
57
- ];
58
- }
59
- return [];
60
- };
61
- const getProjectFileCandidates = (paths) => {
62
- if (!canUseProjectFileImports())
63
- return null;
64
- for (const candidate of paths) {
65
- if (NodeSingletons.fs.existsSync(candidate))
66
- return candidate;
67
- }
68
- return null;
69
- };
70
- const resolveLocalPackageFallback = (specifier) => {
71
- if (!canUseProjectFileImports())
72
- return null;
73
- const root = resolveProjectRoot();
74
- const candidates = buildCandidatesForSpecifier(specifier, root);
75
- const resolved = getProjectFileCandidates(candidates);
76
- if (!resolved)
77
- return null;
78
- return NodeSingletons.url.pathToFileURL(resolved).href;
79
- };
80
- const resolvePackageSpecifierUrl = (specifier) => {
81
- if (!isNodeRuntime() || !canUseProjectFileImports())
82
- return null;
83
- if (typeof NodeSingletons?.module?.createRequire !== 'function') {
84
- return resolveLocalPackageFallback(specifier);
85
- }
86
- try {
87
- const require = NodeSingletons.module.createRequire(import.meta.url);
88
- const resolved = require.resolve(specifier);
89
- if ((specifier === '@zintrust/workers' &&
90
- resolved.includes(`${path.sep}node_modules${path.sep}@zintrust${path.sep}workers${path.sep}`)) ||
91
- (specifier === '@zintrust/core' &&
92
- resolved.includes(`${path.sep}node_modules${path.sep}@zintrust${path.sep}core${path.sep}`))) {
93
- const local = resolveLocalPackageFallback(specifier);
94
- if (local)
95
- return local;
96
- }
97
- return NodeSingletons.url.pathToFileURL(resolved).href;
98
- }
99
- catch {
100
- return resolveLocalPackageFallback(specifier);
101
- }
102
- };
103
- const rewriteProcessorImports = (code) => {
104
- const replacements = [];
105
- const coreUrl = resolvePackageSpecifierUrl('@zintrust/core');
106
- if (coreUrl)
107
- replacements.push({ from: '@zintrust/core', to: coreUrl });
108
- const workersUrl = resolvePackageSpecifierUrl('@zintrust/workers');
109
- if (workersUrl)
110
- replacements.push({ from: '@zintrust/workers', to: workersUrl });
111
- if (replacements.length === 0)
112
- return code;
113
- let updated = code;
114
- for (const { from, to } of replacements) {
115
- updated = updated.replaceAll(`'${from}'`, `'${to}'`);
116
- updated = updated.replaceAll(`"${from}"`, `"${to}"`);
117
- }
118
- return updated;
119
- };
120
- const ensureProcessorSpecDir = () => {
121
- if (!isNodeRuntime() || !canUseProjectFileImports())
122
- return null;
123
- const dir = path.join(resolveProjectRoot(), '.zintrust', 'processor-specs');
124
- try {
125
- if (!NodeSingletons.fs.existsSync(dir)) {
126
- NodeSingletons.fs.mkdirSync(dir, { recursive: true });
127
- }
128
- return dir;
129
- }
130
- catch (error) {
131
- Logger.debug('Failed to prepare processor spec cache directory', error);
132
- return null;
133
- }
134
- };
135
- const shouldFallbackToFileImport = (error) => {
136
- if (!isNodeRuntime())
137
- return false;
138
- const message = error instanceof Error ? error.message : String(error);
139
- const code = error?.code ?? '';
140
- if (code === 'ERR_INVALID_URL' || code === 'ERR_UNSUPPORTED_ESM_URL_SCHEME')
141
- return true;
142
- return (message.includes('Invalid relative URL') ||
143
- message.includes('base scheme is not hierarchical') ||
144
- message.includes('Failed to resolve module specifier'));
145
- };
146
- const isOptionalD1ProxyModuleMissing = (error) => {
147
- const message = error instanceof Error ? error.message : String(error);
148
- const code = error?.code ?? '';
149
- if (code !== 'ERR_MODULE_NOT_FOUND')
150
- return false;
151
- return (message.includes('cloudflare-d1-proxy') ||
152
- message.includes('/packages/cloudflare-d1-proxy/src/index.js'));
153
- };
154
- const importModuleFromCode = async (params) => {
155
- const { code, normalized, cacheKey } = params;
156
- const dataUrl = `data:text/javascript;base64,${toBase64(code)}`;
157
- try {
158
- return (await import(dataUrl));
159
- }
160
- catch (error) {
161
- if (!shouldFallbackToFileImport(error))
162
- throw error;
163
- const dir = ensureProcessorSpecDir();
164
- if (!dir)
165
- throw error;
166
- try {
167
- const codeHash = await computeSha256(code);
168
- const filePath = path.join(dir, `${codeHash || cacheKey}.mjs`);
169
- NodeSingletons.fs.writeFileSync(filePath, code, 'utf8');
170
- const fileUrl = NodeSingletons.url.pathToFileURL(filePath).href;
171
- return (await import(fileUrl));
172
- }
173
- catch (fileError) {
174
- if (isOptionalD1ProxyModuleMissing(fileError)) {
175
- throw fileError;
176
- }
177
- Logger.debug(`Processor URL file fallback failed for ${normalized}`, fileError);
178
- throw error;
179
- }
180
- }
181
- };
182
- const isCloudflareRuntime = () => Cloudflare.getWorkersEnv() !== null;
183
- const getDefaultStoreForRuntime = async () => {
184
- if (!isCloudflareRuntime()) {
185
- await ensureWorkerStoreConfigured();
186
- return workerStore;
187
- }
188
- const bootstrapConfig = buildPersistenceBootstrapConfig();
189
- const persistence = resolvePersistenceConfig(bootstrapConfig);
190
- if (!persistence) {
191
- return InMemoryWorkerStore.create();
192
- }
193
- return resolveWorkerStoreForPersistence(persistence);
194
- };
195
- const getStoreForWorker = async (config, persistenceOverride) => {
196
- if (persistenceOverride) {
197
- return resolveWorkerStoreForPersistence(persistenceOverride);
198
- }
199
- // If worker has specific configuration, use it
200
- if (config) {
201
- const persistence = resolvePersistenceConfig(config);
202
- if (persistence) {
203
- return resolveWorkerStoreForPersistence(persistence);
204
- }
205
- }
206
- // Fallback to default/global store
207
- return getDefaultStoreForRuntime();
208
- };
209
- const validateAndGetStore = async (name, config, persistenceOverride) => {
210
- const store = await getStoreForWorker(config, persistenceOverride);
211
- const record = await store.get(name);
212
- if (!record) {
213
- throw ErrorFactory.createNotFoundError(`Worker "${name}" not found in the specified driver. Ensure you are addressing the correct storage backend.`);
214
- }
215
- return store;
216
- };
217
- // Worker creation status enum for proper lifecycle management
218
- export const WorkerCreationStatus = {
219
- CREATING: 'creating', // Initial state - worker is being created
220
- CONNECTING: 'connecting', // Connecting to Redis/Queue
221
- STARTING: 'starting', // Starting BullMQ worker
222
- RUNNING: 'running', // Actually processing jobs
223
- FAILED: 'failed', // Connection/startup failed
224
- STOPPED: 'stopped', // Intentionally stopped
225
- };
226
- // Internal initialization state to prevent memory leaks and redundant calls
227
- let clusteringInitialized = false;
228
- let metricsInitialized = false;
229
- let autoScalingInitialized = false;
230
- let deadLetterQueueInitialized = false;
231
- let resourceMonitoringInitialized = false;
232
- let complianceInitialized = false;
233
- let observabilityInitialized = false;
234
- // Internal state
235
- const workers = new Map();
236
- let workerStore = InMemoryWorkerStore.create();
237
- let workerStoreConfigured = false;
238
- let workerStoreConfig = null;
239
- const processorRegistry = new Map();
240
- const processorPathRegistry = new Map();
241
- const processorResolvers = [];
242
- const processorSpecRegistry = new Map();
243
- const queueWorkerMetaKey = '__zintrustQueueWorkerMeta';
244
- const fileWorkerDefinitionExportKeys = Object.freeze([
245
- 'workerDefinition',
246
- 'workerConfig',
247
- 'zintrustWorker',
248
- 'ZinTrustWorker',
249
- 'worker',
250
- 'defaultWorkerDefinition',
251
- ]);
252
- const workerDiscoveryDirectories = Object.freeze([
253
- ['dist', 'app', 'Workers'],
254
- ['dist', 'src', 'workers'],
255
- ['dist', 'src', 'Workers'],
256
- ['app', 'Workers'],
257
- ['src', 'workers'],
258
- ['src', 'Workers'],
259
- ]);
260
- const workerDiscoveryExtensions = new Set(['.js', '.mjs', '.cjs', '.ts']);
261
- const processorCache = new Map();
262
- let processorCacheSize = 0;
263
- const buildPersistenceBootstrapConfig = () => {
264
- const driver = Env.get('WORKER_PERSISTENCE_DRIVER', 'memory');
265
- const config = {
266
- name: '__zintrust_persistence_bootstrap__',
267
- queueName: '__zintrust_bootstrap__',
268
- processor: async () => undefined,
269
- infrastructure: {
270
- persistence: {
271
- driver,
272
- },
273
- },
274
- };
275
- // Add Redis config if using Redis persistence
276
- if (driver === 'redis') {
277
- config.infrastructure = {
278
- ...config.infrastructure,
279
- redis: queueConfig.drivers.redis,
280
- };
281
- }
282
- return config;
283
- };
284
- const registerProcessor = (name, processor) => {
285
- processorRegistry.set(name, processor);
286
- };
287
- const registerProcessors = (processors) => {
288
- Object.entries(processors).forEach(([name, processor]) => {
289
- if (typeof processor === 'function') {
290
- processorRegistry.set(name, processor);
291
- }
292
- });
293
- };
294
- const registerProcessorPaths = (paths) => {
295
- Object.entries(paths).forEach(([name, modulePath]) => {
296
- if (typeof modulePath === 'string' && modulePath.trim().length > 0) {
297
- processorPathRegistry.set(name, modulePath);
298
- }
299
- });
300
- };
301
- const registerProcessorResolver = (resolver) => {
302
- processorResolvers.push(resolver);
303
- };
304
- const registerProcessorSpec = (spec, processor) => {
305
- if (!spec || typeof processor !== 'function')
306
- return;
307
- processorSpecRegistry.set(normalizeProcessorSpec(spec), processor);
308
- };
309
- const decodeProcessorPathEntities = (value) => value
310
- .replaceAll(///gi, '/')
311
- .replaceAll('/', '/')
312
- .replaceAll(///gi, '/');
313
- const isUrlSpec = (spec) => {
314
- if (spec.startsWith('url:'))
315
- return true;
316
- return spec.includes('://');
317
- };
318
- const normalizeProcessorSpec = (spec) => spec.startsWith('url:') ? spec.slice(4) : spec;
319
- const parseCacheControl = (value) => {
320
- if (!value)
321
- return {};
322
- const parts = value.split(',').map((part) => part.trim().toLowerCase());
323
- const maxAge = parts.find((part) => part.startsWith('max-age='));
324
- if (!maxAge)
325
- return {};
326
- const raw = maxAge.split('=')[1];
327
- const parsed = Number.parseInt(raw ?? '', 10);
328
- return Number.isFinite(parsed) ? { maxAge: parsed } : {};
329
- };
330
- const getProcessorSpecConfig = () => workersConfig.processorSpec;
331
- const toPosixPath = (value) => value.split(path.sep).join('/');
332
- const normalizeWorkerFileName = (fileName) => {
333
- const baseName = fileName.replaceAll(/\.[^.]+$/, '');
334
- return baseName
335
- .replaceAll(/([a-z\d])([A-Z])/g, '$1-$2')
336
- .replaceAll(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1-$2')
337
- .replaceAll(/[\s_]+/g, '-')
338
- .replaceAll(/-+/g, '-')
339
- .toLowerCase();
340
- };
341
- const supportsWorkerFileDiscovery = () => {
342
- return (isNodeRuntime() &&
343
- canUseProjectFileImports() &&
344
- typeof NodeSingletons.fs.readdirSync === 'function' &&
345
- typeof NodeSingletons.fs.statSync === 'function');
346
- };
347
- const isSupportedWorkerModuleFile = (fileName) => {
348
- if (!isNonEmptyString(fileName))
349
- return false;
350
- if (fileName.endsWith('.d.ts'))
351
- return false;
352
- if (fileName.includes('.test.') || fileName.includes('.spec.'))
353
- return false;
354
- return workerDiscoveryExtensions.has(path.extname(fileName));
355
- };
356
- const getWorkerDiscoveryDirectories = () => {
357
- if (!supportsWorkerFileDiscovery())
358
- return [];
359
- const root = resolveProjectRoot();
360
- return workerDiscoveryDirectories.map((segments) => path.join(root, ...segments));
361
- };
362
- const listWorkerDefinitionFiles = () => {
363
- if (!supportsWorkerFileDiscovery())
364
- return [];
365
- const discovered = new Set();
366
- for (const directory of getWorkerDiscoveryDirectories()) {
367
- try {
368
- if (!NodeSingletons.fs.existsSync(directory))
369
- continue;
370
- const stats = NodeSingletons.fs.statSync(directory);
371
- if (!stats.isDirectory())
372
- continue;
373
- const entries = NodeSingletons.fs.readdirSync(directory, { withFileTypes: true });
374
- for (const entry of entries) {
375
- if (!entry.isFile() || !isSupportedWorkerModuleFile(entry.name))
376
- continue;
377
- discovered.add(path.join(directory, entry.name));
378
- }
379
- }
380
- catch (error) {
381
- Logger.debug(`Worker file discovery failed for directory: ${directory}`, error);
382
- }
383
- }
384
- return Array.from(discovered);
385
- };
386
- const getProjectRelativeWorkerSpec = (sourcePath) => {
387
- const relativePath = path.relative(resolveProjectRoot(), sourcePath);
388
- return isNonEmptyString(relativePath) ? toPosixPath(relativePath) : toPosixPath(sourcePath);
389
- };
390
- const isQueueWorkerMeta = (value) => {
391
- return (isObject(value) &&
392
- isNonEmptyString(value['kindLabel']) &&
393
- isNonEmptyString(value['defaultQueueName']) &&
394
- typeof value['maxAttempts'] === 'number');
395
- };
396
- const getExportedQueueWorkerMeta = (mod) => {
397
- for (const value of Object.values(mod)) {
398
- if (!isObject(value))
399
- continue;
400
- const meta = value[queueWorkerMetaKey];
401
- if (isQueueWorkerMeta(meta))
402
- return meta;
403
- }
404
- return undefined;
405
- };
406
- const assignStringDefinitionField = (definition, key, value) => {
407
- if (isNonEmptyString(value)) {
408
- definition[key] = value;
409
- }
410
- };
411
- const assignBooleanDefinitionField = (definition, key, value) => {
412
- if (typeof value === 'boolean') {
413
- definition[key] = value;
414
- }
415
- };
416
- const assignObjectDefinitionField = (definition, key, value) => {
417
- if (isObject(value)) {
418
- definition[key] = value;
419
- }
420
- };
421
- const assignConcurrencyDefinitionField = (definition, value) => {
422
- if (typeof value === 'number' && Number.isFinite(value)) {
423
- definition.concurrency = Math.max(1, Math.floor(value));
424
- }
425
- };
426
- const assignProcessorDefinitionField = (definition, value) => {
427
- if (isFunction(value)) {
428
- definition.processor = value;
429
- }
430
- };
431
- const normalizeFileWorkerDefinition = (value) => {
432
- if (!isObject(value))
433
- return undefined;
434
- const definition = {};
435
- assignStringDefinitionField(definition, 'name', value['name']);
436
- assignStringDefinitionField(definition, 'queueName', value['queueName']);
437
- assignStringDefinitionField(definition, 'version', value['version']);
438
- assignStringDefinitionField(definition, 'status', value['status']);
439
- assignStringDefinitionField(definition, 'region', value['region']);
440
- assignStringDefinitionField(definition, 'processorSpec', value['processorSpec']);
441
- assignBooleanDefinitionField(definition, 'autoStart', value['autoStart']);
442
- assignBooleanDefinitionField(definition, 'activeStatus', value['activeStatus']);
443
- assignObjectDefinitionField(definition, 'features', value['features']);
444
- assignObjectDefinitionField(definition, 'infrastructure', value['infrastructure']);
445
- assignObjectDefinitionField(definition, 'datacenter', value['datacenter']);
446
- assignConcurrencyDefinitionField(definition, value['concurrency']);
447
- assignProcessorDefinitionField(definition, value['processor']);
448
- return definition;
449
- };
450
- const getExportedFileWorkerDefinition = (mod) => {
451
- for (const key of fileWorkerDefinitionExportKeys) {
452
- const normalized = normalizeFileWorkerDefinition(mod[key]);
453
- if (normalized)
454
- return normalized;
455
- }
456
- const defaultExport = mod['default'];
457
- if (!isFunction(defaultExport)) {
458
- const normalized = normalizeFileWorkerDefinition(defaultExport);
459
- if (normalized)
460
- return normalized;
461
- }
462
- return undefined;
463
- };
464
- const importWorkerDefinitionModule = async (sourcePath) => {
465
- if (!supportsWorkerFileDiscovery())
466
- return undefined;
467
- try {
468
- return (await import(NodeSingletons.url.pathToFileURL(sourcePath).href));
469
- }
470
- catch (error) {
471
- Logger.debug(`Failed to import worker definition module: ${sourcePath}`, error);
472
- return undefined;
473
- }
474
- };
475
- const resolveDiscoveredWorkerProcessor = (definition, mod, sourcePath) => {
476
- if (definition?.processor)
477
- return definition.processor;
478
- return extractZinTrustProcessor(mod, sourcePath) ?? pickProcessorFromModule(mod, sourcePath);
479
- };
480
- const resolveDiscoveredWorkerName = (definition, sourcePath) => {
481
- return (definition?.name ?? normalizeWorkerFileName(path.basename(sourcePath, path.extname(sourcePath))));
482
- };
483
- const resolveDiscoveredQueueName = (definition, queueWorkerMeta, recordName) => {
484
- if (definition?.queueName)
485
- return definition.queueName;
486
- if (queueWorkerMeta?.defaultQueueName)
487
- return queueWorkerMeta.defaultQueueName;
488
- return `${recordName}-queue`;
489
- };
490
- const resolveDiscoveredProcessorSpec = (definition, sourcePath) => {
491
- return definition?.processorSpec ?? getProjectRelativeWorkerSpec(sourcePath);
492
- };
493
- const resolveDiscoveredVersion = (definition) => {
494
- return definition?.version ?? '1.0.0';
495
- };
496
- const resolveDiscoveredStatus = (definition) => {
497
- return definition?.status ?? WorkerCreationStatus.STOPPED;
498
- };
499
- const resolveDiscoveredAutoStart = (definition) => {
500
- return definition?.autoStart ?? false;
501
- };
502
- const resolveDiscoveredConcurrency = (definition) => {
503
- return definition?.concurrency ?? 1;
504
- };
505
- const resolveDiscoveredActiveStatus = (definition) => {
506
- return definition?.activeStatus ?? true;
507
- };
508
- const buildDiscoveredWorkerRecord = (definition, queueWorkerMeta, sourcePath) => {
509
- const recordName = resolveDiscoveredWorkerName(definition, sourcePath);
510
- if (!isNonEmptyString(recordName))
511
- return undefined;
512
- return {
513
- name: recordName,
514
- queueName: resolveDiscoveredQueueName(definition, queueWorkerMeta, recordName),
515
- version: resolveDiscoveredVersion(definition),
516
- status: resolveDiscoveredStatus(definition),
517
- autoStart: resolveDiscoveredAutoStart(definition),
518
- concurrency: resolveDiscoveredConcurrency(definition),
519
- region: definition?.region ?? null,
520
- processorSpec: resolveDiscoveredProcessorSpec(definition, sourcePath),
521
- activeStatus: resolveDiscoveredActiveStatus(definition),
522
- features: definition?.features ?? null,
523
- infrastructure: definition?.infrastructure ?? null,
524
- datacenter: definition?.datacenter ?? null,
525
- createdAt: new Date(),
526
- updatedAt: new Date(),
527
- };
528
- };
529
- const buildDiscoveredWorker = (mod, sourcePath) => {
530
- const definition = getExportedFileWorkerDefinition(mod);
531
- const queueWorkerMeta = getExportedQueueWorkerMeta(mod);
532
- const processor = resolveDiscoveredWorkerProcessor(definition, mod, sourcePath);
533
- const record = buildDiscoveredWorkerRecord(definition, queueWorkerMeta, sourcePath);
534
- if (!record)
535
- return undefined;
536
- return {
537
- record,
538
- processor,
539
- sourcePath,
540
- };
541
- };
542
- const resolveStartFromPersistedRecord = async (name, persistenceOverride) => {
543
- const persistedRecord = await getPersistedRecord(name, persistenceOverride);
544
- const discovered = persistedRecord ? null : await getDiscoveredFileWorker(name);
545
- const effectiveRecord = persistedRecord ?? discovered?.record ?? null;
546
- if (!effectiveRecord) {
547
- throw ErrorFactory.createNotFoundError(`Worker "${name}" not found in persistence store`);
548
- }
549
- return { record: effectiveRecord, discovered };
550
- };
551
- const resolveStartFromPersistedProcessor = async (name, record, discovered) => {
552
- let processor = await resolveProcessor(name);
553
- if (!processor && discovered?.processor) {
554
- processor = discovered.processor;
555
- }
556
- const spec = record.processorSpec ?? undefined;
557
- if (!processor && spec) {
558
- try {
559
- processor = await resolveProcessorSpec(spec);
560
- }
561
- catch (error) {
562
- Logger.error(`Failed to resolve processor module for "${name}"`, error);
563
- }
564
- }
565
- if (!processor) {
566
- throw ErrorFactory.createConfigError(`Worker "${name}" processor is not registered or resolvable. Register the processor at startup or persist a processorSpec.`);
567
- }
568
- return processor;
569
- };
570
- const discoverFileBackedWorkers = async () => {
571
- const files = listWorkerDefinitionFiles();
572
- if (files.length === 0)
573
- return [];
574
- const discovered = new Map();
575
- for (const filePath of files) {
576
- // eslint-disable-next-line no-await-in-loop
577
- const mod = await importWorkerDefinitionModule(filePath);
578
- if (!mod)
579
- continue;
580
- const discoveredWorker = buildDiscoveredWorker(mod, filePath);
581
- if (!discoveredWorker)
582
- continue;
583
- if (discovered.has(discoveredWorker.record.name)) {
584
- Logger.warn(`Duplicate file-backed worker definition detected for "${discoveredWorker.record.name}". Keeping the first discovered module.`);
585
- continue;
586
- }
587
- discovered.set(discoveredWorker.record.name, discoveredWorker);
588
- }
589
- return Array.from(discovered.values());
590
- };
591
- const getDiscoveredFileWorker = async (name) => {
592
- const discovered = await discoverFileBackedWorkers();
593
- return discovered.find((entry) => entry.record.name === name) ?? null;
594
- };
595
- const computeSha256 = async (value) => {
596
- if (typeof globalThis !== 'undefined' && globalThis.crypto?.subtle) {
597
- const data = new TextEncoder().encode(value);
598
- const digest = await globalThis.crypto.subtle.digest('SHA-256', data);
599
- return Array.from(new Uint8Array(digest))
600
- .map((b) => b.toString(16).padStart(2, '0'))
601
- .join('');
602
- }
603
- if (typeof NodeSingletons.createHash === 'function') {
604
- return NodeSingletons.createHash('sha256').update(value).digest('hex');
605
- }
606
- return String(generateUuid()).slice(2);
607
- };
608
- const toBase64 = (value) => {
609
- if (typeof Buffer !== 'undefined') {
610
- return Buffer.from(value, 'utf-8').toString('base64');
611
- }
612
- if (typeof globalThis !== 'undefined' && typeof globalThis.btoa === 'function') {
613
- const bytes = new TextEncoder().encode(value);
614
- let binary = '';
615
- bytes.forEach((byte) => {
616
- binary += String.fromCodePoint(byte);
617
- });
618
- return globalThis.btoa(binary);
619
- }
620
- return value;
621
- };
622
- const getCachedProcessor = (key) => {
623
- const entry = processorCache.get(key);
624
- if (!entry)
625
- return null;
626
- const now = Date.now();
627
- if (entry.expiresAt <= now) {
628
- processorCache.delete(key);
629
- processorCacheSize -= entry.size;
630
- return null;
631
- }
632
- entry.lastAccess = now;
633
- return entry;
634
- };
635
- const evictCacheIfNeeded = (maxSize) => {
636
- if (processorCacheSize <= maxSize)
637
- return;
638
- const entries = Array.from(processorCache.entries());
639
- entries.sort((a, b) => a[1].lastAccess - b[1].lastAccess);
640
- for (const [key, entry] of entries) {
641
- if (processorCacheSize <= maxSize)
642
- break;
643
- processorCache.delete(key);
644
- processorCacheSize -= entry.size;
645
- }
646
- };
647
- const setCachedProcessor = (key, entry, maxSize) => {
648
- const existing = processorCache.get(key);
649
- if (existing) {
650
- processorCacheSize -= existing.size;
651
- }
652
- processorCache.set(key, entry);
653
- processorCacheSize += entry.size;
654
- evictCacheIfNeeded(maxSize);
655
- };
656
- const isAllowedRemoteHost = (host) => {
657
- const allowlist = getProcessorSpecConfig().remoteAllowlist.map((value) => value.toLowerCase());
658
- return allowlist.includes(host.toLowerCase());
659
- };
660
- const waitForWorkerConnection = async (worker, name, _queueName, timeoutMs) => {
661
- const startTime = Date.now();
662
- const checkInterval = 100; // 100ms between checks
663
- let timeoutId = null;
664
- return new Promise((resolve, reject) => {
665
- const checkConnection = async () => {
666
- try {
667
- // Check if worker is actually running
668
- const isRunning = await worker.isRunning(); // NOSONAR - BullMQ's isRunning method
669
- if (!isRunning) {
670
- throw ErrorFactory.createWorkerError('Worker not running');
671
- }
672
- // Check Redis connection
673
- const client = await worker.client;
674
- const pingResult = await client.ping();
675
- if (pingResult !== 'PONG') {
676
- throw ErrorFactory.createWorkerError('Redis ping failed');
677
- }
678
- // Removed heavy Queue instantiation loop - relying on Redis ping for connectivity check
679
- // The queue instance creation was causing memory pressure and potential connection leaks in this retry loop
680
- Logger.debug(`Worker health verification passed for ${name}`, {
681
- isRunning,
682
- pingResult,
683
- });
684
- if (timeoutId)
685
- clearTimeout(timeoutId);
686
- resolve();
687
- return;
688
- }
689
- catch (error) {
690
- Logger.debug(`Worker health verification failed for ${name}, retrying...`, error);
691
- // Check timeout
692
- if (Date.now() - startTime >= timeoutMs) {
693
- if (timeoutId)
694
- clearTimeout(timeoutId);
695
- reject(ErrorFactory.createWorkerError('Worker failed health verification within timeout period'));
696
- return;
697
- }
698
- // Schedule next check
699
- timeoutId = globalThis.setTimeout(checkConnection, checkInterval);
700
- }
701
- };
702
- // Start checking
703
- checkConnection();
704
- });
705
- };
706
- const startHealthMonitoring = (name, worker, queueName) => {
707
- HealthMonitor.register(name, worker, queueName);
708
- };
709
- const sanitizeProcessorPath = (value) => {
710
- const decoded = decodeProcessorPathEntities(value);
711
- const base = decoded.split(/[?#&]/)[0]?.trim() ?? '';
712
- if (!base)
713
- return '';
714
- const isAbsolutePath = base.startsWith('/') || /^[A-Za-z]:[\\/]/.test(base);
715
- const relativePath = base.startsWith('.') ? base : `./${base}`;
716
- return isAbsolutePath ? base : path.resolve(process.cwd(), relativePath);
717
- };
718
- const stripProcessorExtension = (value) => value.replace(/\.(ts|js)$/i, '');
719
- const normalizeModulePath = (value) => value.replaceAll('\\', '/');
720
- const filterExistingFileCandidates = (candidates) => {
721
- if (!NodeSingletons?.fs?.existsSync)
722
- return candidates;
723
- return candidates.filter((candidate) => {
724
- try {
725
- return NodeSingletons.fs.existsSync(candidate);
726
- }
727
- catch {
728
- return false;
729
- }
730
- });
731
- };
732
- const buildProcessorModuleCandidates = (modulePath, resolvedPath) => {
733
- const candidates = [];
734
- const normalized = normalizeModulePath(modulePath.trim());
735
- const normalizedResolved = normalizeModulePath(resolvedPath);
736
- if (normalized.startsWith('/app/')) {
737
- candidates.push(`@app/${stripProcessorExtension(normalized.slice(5))}`);
738
- }
739
- else if (normalized.startsWith('app/')) {
740
- candidates.push(`@app/${stripProcessorExtension(normalized.slice(4))}`);
741
- }
742
- const appIndex = normalizedResolved.lastIndexOf('/app/');
743
- if (appIndex !== -1) {
744
- const relative = normalizedResolved.slice(appIndex + 5);
745
- if (relative) {
746
- candidates.push(`@app/${stripProcessorExtension(relative)}`);
747
- }
748
- }
749
- return Array.from(new Set(candidates));
750
- };
751
- const buildProcessorFilePathCandidates = (_modulePath, resolvedPath) => {
752
- const candidates = [];
753
- const normalizedResolved = normalizeModulePath(resolvedPath);
754
- const projectRoot = normalizeModulePath(resolveProjectRoot());
755
- const strippedResolved = stripProcessorExtension(resolvedPath);
756
- candidates.push(`${strippedResolved}.js`, `${strippedResolved}.mjs`);
757
- const appIndex = normalizedResolved.lastIndexOf('/app/');
758
- if (appIndex !== -1) {
759
- const relative = normalizedResolved.slice(appIndex + 5);
760
- if (relative) {
761
- const strippedRelative = stripProcessorExtension(relative);
762
- candidates.push(path.join(projectRoot, 'dist', 'app', `${strippedRelative}.js`), path.join(projectRoot, 'app', relative), path.join(projectRoot, 'app', `${strippedRelative}.js`), path.join('/app', 'dist', 'app', `${strippedRelative}.js`));
763
- }
764
- }
765
- return filterExistingFileCandidates(Array.from(new Set(candidates)));
766
- };
767
- const pickProcessorFromModule = (mod, source) => {
768
- const candidate = mod?.['default'] ?? mod?.['processor'] ?? mod?.['handler'] ?? mod?.['handle'];
769
- if (typeof candidate !== 'function') {
770
- const keys = mod ? Object.keys(mod) : [];
771
- Logger.warn(`Module imported from ${source} but no valid processor function found (exported: ${keys.join(', ')})`);
772
- return undefined;
773
- }
774
- return candidate;
775
- };
776
- const extractZinTrustProcessor = (mod, source) => {
777
- const candidate = mod?.['ZinTrustProcessor'];
778
- if (typeof candidate !== 'function') {
779
- const keys = mod ? Object.keys(mod) : [];
780
- Logger.warn(`Module imported from ${source} but missing ZinTrustProcessor export (exported: ${keys.join(', ')})`);
781
- return undefined;
782
- }
783
- return candidate;
784
- };
785
- const readResponseBody = async (response, maxSize) => {
786
- const contentLength = response.headers.get('content-length');
787
- if (contentLength) {
788
- const size = Number.parseInt(contentLength, 10);
789
- if (Number.isFinite(size) && size > maxSize) {
790
- throw ErrorFactory.createConfigError('PROCESSOR_FETCH_SIZE_EXCEEDED');
791
- }
792
- }
793
- const buffer = await response.arrayBuffer();
794
- if (buffer.byteLength > maxSize) {
795
- throw ErrorFactory.createConfigError('PROCESSOR_FETCH_SIZE_EXCEEDED');
796
- }
797
- return new TextDecoder().decode(buffer);
798
- };
799
- const computeCacheTtlSeconds = (config, cacheControl) => Math.min(config.cacheMaxTtlSeconds, cacheControl.maxAge ?? config.cacheDefaultTtlSeconds);
800
- const refreshCachedProcessor = (existing, config, cacheControl) => {
801
- const ttl = computeCacheTtlSeconds(config, cacheControl);
802
- const now = Date.now();
803
- existing.expiresAt = now + ttl * 1000;
804
- existing.lastAccess = now;
805
- return existing.processor;
806
- };
807
- const cacheProcessorFromResponse = async (params) => {
808
- const { response, normalized, config, cacheKey } = params;
809
- const rawCode = await readResponseBody(response, config.fetchMaxSizeBytes);
810
- const code = rewriteProcessorImports(rawCode);
811
- const mod = await importModuleFromCode({ code, normalized, cacheKey });
812
- const processor = extractZinTrustProcessor(mod, normalized);
813
- if (!processor) {
814
- throw ErrorFactory.createConfigError('INVALID_PROCESSOR_URL_EXPORT');
815
- }
816
- const cacheControl = parseCacheControl(response.headers.get('cache-control'));
817
- const ttl = computeCacheTtlSeconds(config, cacheControl);
818
- const size = new TextEncoder().encode(code).byteLength;
819
- const now = Date.now();
820
- setCachedProcessor(cacheKey, {
821
- code,
822
- processor,
823
- etag: response.headers.get('etag') ?? undefined,
824
- cachedAt: now,
825
- expiresAt: now + ttl * 1000,
826
- size,
827
- lastAccess: now,
828
- }, config.cacheMaxSizeBytes);
829
- return processor;
830
- };
831
- const delay = (ms) => new Promise((resolve) => {
832
- globalThis.setTimeout(resolve, ms);
833
- });
834
- const fetchProcessorAttempt = async (params) => {
835
- const { normalized, config, cacheKey, existing, attempt, maxAttempts } = params;
836
- const controller = new AbortController();
837
- const timeoutId = globalThis.setTimeout(() => controller.abort(), config.fetchTimeoutMs);
838
- try {
839
- const headers = {};
840
- if (existing?.etag)
841
- headers['If-None-Match'] = existing.etag;
842
- const response = await fetch(normalized, {
843
- method: 'GET',
844
- headers,
845
- signal: controller.signal,
846
- });
847
- if (response.status === 304 && existing) {
848
- const cacheControl = parseCacheControl(response.headers.get('cache-control'));
849
- return refreshCachedProcessor(existing, config, cacheControl);
850
- }
851
- if (!response.ok) {
852
- throw ErrorFactory.createConfigError(`PROCESSOR_FETCH_FAILED:${response.status}`);
853
- }
854
- return await cacheProcessorFromResponse({ response, normalized, config, cacheKey });
855
- }
856
- catch (error) {
857
- if (isOptionalD1ProxyModuleMissing(error)) {
858
- Logger.warn('Processor URL skipped: optional cloudflare-d1-proxy module is unavailable in this runtime.');
859
- return undefined;
860
- }
861
- if (controller.signal.aborted) {
862
- Logger.error('Processor URL fetch timeout', error);
863
- }
864
- else {
865
- Logger.error('Processor URL fetch failed', error);
866
- }
867
- if (attempt >= maxAttempts) {
868
- return undefined;
869
- }
870
- await delay(config.retryBackoffMs * attempt);
871
- return fetchProcessorAttempt({
872
- normalized,
873
- config,
874
- cacheKey,
875
- existing,
876
- attempt: attempt + 1,
877
- maxAttempts,
878
- });
879
- }
880
- finally {
881
- clearTimeout(timeoutId);
882
- }
883
- };
884
- const resolveProcessorFromUrl = async (spec) => {
885
- const normalized = normalizeProcessorSpec(spec);
886
- let parsed;
887
- try {
888
- parsed = new URL(normalized);
889
- }
890
- catch (error) {
891
- Logger.error('Invalid processor URL spec', error);
892
- return undefined;
893
- }
894
- if (parsed.protocol === 'file:') {
895
- const filePath = decodeURIComponent(parsed.pathname);
896
- return resolveProcessorFromPath(filePath);
897
- }
898
- if (parsed.protocol !== 'https:' && parsed.protocol !== 'file:') {
899
- Logger.warn(`Invalid processor URL protocol: ${parsed.protocol}. Only https:// and file:// are supported.`);
900
- return undefined;
901
- }
902
- if (!isAllowedRemoteHost(parsed.host) && parsed.protocol !== 'file:') {
903
- Logger.warn(`Invalid processor URL host: ${parsed.host}. Host is not in the allowlist.`);
904
- return undefined;
905
- }
906
- const config = getProcessorSpecConfig();
907
- const cacheKey = await computeSha256(normalized);
908
- const cached = getCachedProcessor(cacheKey);
909
- if (cached)
910
- return cached.processor;
911
- return fetchProcessorAttempt({
912
- normalized,
913
- config,
914
- cacheKey,
915
- existing: processorCache.get(cacheKey),
916
- attempt: 1,
917
- maxAttempts: Math.max(1, config.retryAttempts),
918
- });
919
- };
920
- const resolveProcessorSpec = async (spec) => {
921
- if (!spec)
922
- return undefined;
923
- const normalized = normalizeProcessorSpec(spec);
924
- const prebuilt = processorSpecRegistry.get(normalized) ?? processorSpecRegistry.get(spec);
925
- if (prebuilt)
926
- return prebuilt;
927
- if (isUrlSpec(spec))
928
- return resolveProcessorFromUrl(spec);
929
- return resolveProcessorFromPath(spec);
930
- };
931
- const resolveProcessorFromPath = async (modulePath) => {
932
- const trimmed = modulePath.trim();
933
- if (!trimmed)
934
- return undefined;
935
- const resolved = sanitizeProcessorPath(trimmed);
936
- if (!resolved)
937
- return undefined;
938
- const importProcessorFromCandidates = async (candidates) => {
939
- if (candidates.length === 0)
940
- return undefined;
941
- const [candidatePath, ...rest] = candidates;
942
- try {
943
- const importPath = candidatePath.startsWith('/') && !candidatePath.startsWith('//')
944
- ? NodeSingletons.url.pathToFileURL(candidatePath).href
945
- : candidatePath;
946
- const mod = await import(importPath);
947
- const candidate = pickProcessorFromModule(mod, importPath);
948
- if (candidate)
949
- return candidate;
950
- }
951
- catch (candidateError) {
952
- Logger.debug(`Processor module candidate import failed: ${candidatePath}`, candidateError);
953
- }
954
- return importProcessorFromCandidates(rest);
955
- };
956
- try {
957
- const mod = await import(resolved);
958
- const candidate = pickProcessorFromModule(mod, resolved);
959
- if (candidate)
960
- return candidate;
961
- }
962
- catch (err) {
963
- const fileCandidates = buildProcessorFilePathCandidates(trimmed, resolved);
964
- const resolvedFileCandidate = await importProcessorFromCandidates(fileCandidates);
965
- if (resolvedFileCandidate)
966
- return resolvedFileCandidate;
967
- const moduleCandidates = buildProcessorModuleCandidates(trimmed, resolved);
968
- const resolvedModuleCandidate = await importProcessorFromCandidates(moduleCandidates);
969
- if (resolvedModuleCandidate)
970
- return resolvedModuleCandidate;
971
- Logger.error(`Failed to import processor from path: ${resolved}`, err);
972
- }
973
- return undefined;
974
- };
975
- const resolveProcessor = async (name) => {
976
- const direct = processorRegistry.get(name);
977
- if (direct)
978
- return direct;
979
- const pathHint = processorPathRegistry.get(name);
980
- if (pathHint) {
981
- try {
982
- const resolved = await resolveProcessorSpec(pathHint);
983
- if (resolved)
984
- return resolved;
985
- }
986
- catch (error) {
987
- Logger.error(`Failed to resolve processor module for "${name}"`, error);
988
- }
989
- }
990
- const resolverResults = await Promise.all(processorResolvers.map(async (resolver) => {
991
- try {
992
- return await resolver(name);
993
- }
994
- catch (error) {
995
- Logger.error(`Processor resolver failed for "${name}"`, error);
996
- return undefined;
997
- }
998
- }));
999
- const resolvedFromResolvers = resolverResults.find((result) => result !== undefined);
1000
- if (resolvedFromResolvers)
1001
- return resolvedFromResolvers;
1002
- return undefined;
1003
- };
1004
- const resolveProcessorPath = async (modulePath) => resolveProcessorFromPath(modulePath);
1005
- const recordMetricSafely = (workerName, metricType, value, metadata) => {
1006
- WorkerMetrics.record(workerName, metricType, value, metadata).catch((error) => {
1007
- Logger.error(`Failed to record worker metric: ${workerName}/${metricType}`, error);
1008
- });
1009
- };
1010
- const ensureCircuitAllowsExecution = (workerName, version, jobId, features) => {
1011
- if (!(features?.circuitBreaker ?? false))
1012
- return;
1013
- const canExecute = CircuitBreaker.canExecute(workerName, version);
1014
- if (canExecute)
1015
- return;
1016
- const state = CircuitBreaker.getState(workerName, version);
1017
- Logger.warn('Circuit breaker is open, rejecting job', {
1018
- workerName,
1019
- version,
1020
- jobId,
1021
- circuitState: state?.state,
1022
- });
1023
- CircuitBreaker.recordRejection(workerName, version);
1024
- throw ErrorFactory.createGeneralError(`Circuit breaker is open for ${workerName}@${version}`);
1025
- };
1026
- const runBeforeProcessHooks = async (workerName, job, features) => {
1027
- if (!(features?.plugins ?? false)) {
1028
- return { skip: false, jobData: job.data };
1029
- }
1030
- const hookResult = await PluginManager.executeHook('beforeProcess', {
1031
- workerName,
1032
- jobId: job.id ?? '',
1033
- jobData: job.data,
1034
- timestamp: new Date(),
1035
- });
1036
- if (hookResult.stopped) {
1037
- const errorMessage = hookResult.errors[0]?.error?.message ?? 'Stopped by plugin';
1038
- Logger.info('Job processing stopped by plugin', {
1039
- workerName,
1040
- jobId: job.id,
1041
- reason: errorMessage,
1042
- });
1043
- return { skip: true, reason: errorMessage };
1044
- }
1045
- if (hookResult.modified) {
1046
- return { skip: false, jobData: hookResult.context.jobData };
1047
- }
1048
- return { skip: false, jobData: job.data };
1049
- };
1050
- const startProcessingSpan = (workerName, version, job, queueName, features) => {
1051
- if (!(features?.observability ?? false))
1052
- return null;
1053
- return Observability.startSpan(`worker.${workerName}.process`, {
1054
- attributes: {
1055
- worker_name: workerName,
1056
- worker_version: version,
1057
- job_id: job.id ?? '',
1058
- queue_name: queueName,
1059
- },
1060
- });
1061
- };
1062
- const usePluginManager = async (workerName, job, result) => {
1063
- await PluginManager.executeHook('afterProcess', {
1064
- workerName,
1065
- jobId: job.id ?? '',
1066
- jobData: job.data,
1067
- metadata: { result },
1068
- timestamp: new Date(),
1069
- });
1070
- await PluginManager.executeHook('onComplete', {
1071
- workerName,
1072
- jobId: job.id ?? '',
1073
- jobData: job.data,
1074
- metadata: { result },
1075
- timestamp: new Date(),
1076
- });
1077
- };
1078
- const handleSuccess = async (params) => {
1079
- const { workerName, jobVersion, job, result, duration, spanId, features } = params;
1080
- if (features?.metrics ?? false) {
1081
- recordMetricSafely(workerName, 'processed', 1);
1082
- recordMetricSafely(workerName, 'duration', duration);
1083
- }
1084
- if (features?.circuitBreaker ?? false) {
1085
- CircuitBreaker.recordSuccess(workerName, jobVersion);
1086
- }
1087
- if (features?.observability ?? false) {
1088
- Observability.recordJobMetrics(workerName, job.name, {
1089
- processed: 1,
1090
- failed: 0,
1091
- durationMs: duration,
1092
- });
1093
- if (spanId !== null) {
1094
- Observability.endSpan(spanId, { success: true });
1095
- }
1096
- }
1097
- if (features?.plugins ?? false) {
1098
- await usePluginManager(workerName, { id: job.id ?? '', data: job.data }, result);
1099
- }
1100
- };
1101
- const recordFailureMetrics = (workerName, _jobVersion, duration, features) => {
1102
- if (features?.metrics === true) {
1103
- recordMetricSafely(workerName, 'errors', 1);
1104
- recordMetricSafely(workerName, 'duration', duration);
1105
- }
1106
- };
1107
- const recordFailureObservability = (workerName, jobName, duration, spanId, features) => {
1108
- if (features?.observability === true) {
1109
- Observability.recordJobMetrics(workerName, jobName, {
1110
- processed: 0,
1111
- failed: 1,
1112
- durationMs: duration,
1113
- });
1114
- if (spanId !== null) {
1115
- Observability.recordSpanError(spanId, ErrorFactory.createGeneralError('Job processing failed'));
1116
- Observability.endSpan(spanId, { success: false });
1117
- }
1118
- }
1119
- };
1120
- const addFailedJobToDeadLetterQueue = async (workerName, job, error, duration, jobVersion, queueName, features) => {
1121
- if (features?.deadLetterQueue === true) {
1122
- await DeadLetterQueue.addFailedJob({
1123
- id: job.id ?? '',
1124
- queueName,
1125
- workerName,
1126
- jobName: job.name,
1127
- data: job.data,
1128
- error: {
1129
- name: error.name,
1130
- message: error.message,
1131
- stack: error.stack,
1132
- },
1133
- attemptsMade: job.attemptsMade ?? 0,
1134
- maxAttempts: job.opts.attempts ?? 0,
1135
- failedAt: new Date(),
1136
- firstAttemptAt: new Date(job.timestamp ?? Date.now()),
1137
- lastAttemptAt: new Date(),
1138
- processingTime: duration,
1139
- metadata: {
1140
- version: jobVersion,
1141
- },
1142
- complianceFlags: {
1143
- containsPII: false,
1144
- containsPHI: false,
1145
- dataClassification: 'public',
1146
- },
1147
- });
1148
- }
1149
- };
1150
- const executeFailurePlugins = async (workerName, job, error, features) => {
1151
- if (features?.plugins === true) {
1152
- await PluginManager.executeHook('onError', {
1153
- workerName,
1154
- jobId: job.id ?? '',
1155
- jobData: job.data,
1156
- error,
1157
- timestamp: new Date(),
1158
- });
1159
- }
1160
- };
1161
- const recordCircuitBreakerFailure = (workerName, jobVersion, error, features) => {
1162
- if (features?.circuitBreaker === true) {
1163
- CircuitBreaker.recordFailure(workerName, jobVersion, error);
1164
- }
1165
- };
1166
- const logAndRecordFailure = (workerName, jobVersion, job, error, features) => {
1167
- Logger.error(`Worker job failed: ${workerName}`, { error, jobId: job.id, version: jobVersion }, 'workers');
1168
- recordCircuitBreakerFailure(workerName, jobVersion, error, features);
1169
- };
1170
- const recordFailureObservabilityAndMetrics = (params) => {
1171
- const { workerName, jobVersion, jobName, duration, spanId, features } = params;
1172
- recordFailureMetrics(workerName, jobVersion, duration, features);
1173
- recordFailureObservability(workerName, jobName, duration, spanId, features);
1174
- };
1175
- const executeAllFailureHandlers = async (params) => {
1176
- const { workerName, jobVersion, job, error, duration, spanId, features, queueName } = params;
1177
- recordFailureObservabilityAndMetrics({
1178
- workerName,
1179
- jobVersion,
1180
- jobName: job.name,
1181
- duration,
1182
- spanId,
1183
- features,
1184
- });
1185
- if (features?.deadLetterQueue === true) {
1186
- await addFailedJobToDeadLetterQueue(workerName, job, error, duration, jobVersion, queueName, features);
1187
- }
1188
- };
1189
- const handleFailure = async (params) => {
1190
- const { workerName, jobVersion, job, error, features } = params;
1191
- logAndRecordFailure(workerName, jobVersion, job, error, features);
1192
- await executeAllFailureHandlers(params);
1193
- await executeFailurePlugins(workerName, job, error, features);
1194
- };
1195
- const toBackoffDelayMs = (backoff) => {
1196
- if (typeof backoff === 'number' && Number.isFinite(backoff)) {
1197
- return Math.max(0, Math.floor(backoff));
1198
- }
1199
- if (backoff !== null && backoff !== undefined && typeof backoff === 'object') {
1200
- const raw = backoff.delay;
1201
- if (typeof raw === 'number' && Number.isFinite(raw)) {
1202
- return Math.max(0, Math.floor(raw));
1203
- }
1204
- }
1205
- return 0;
1206
- };
1207
- const trackJobStarted = async (input) => {
1208
- if (!input.job.id)
1209
- return;
1210
- await JobStateTracker.started({
1211
- queueName: input.queueName,
1212
- jobId: input.job.id,
1213
- attempts: input.attempts,
1214
- timeoutMs: Math.max(1000, Env.getInt('QUEUE_JOB_TIMEOUT', 60) * 1000),
1215
- workerName: input.workerName,
1216
- workerVersion: input.workerVersion,
1217
- });
1218
- };
1219
- const trackJobCompleted = async (input) => {
1220
- if (!input.job.id)
1221
- return;
1222
- await JobStateTracker.completed({
1223
- queueName: input.queueName,
1224
- jobId: input.job.id,
1225
- processingTimeMs: input.duration,
1226
- result: input.result,
1227
- });
1228
- };
1229
- const trackJobFailed = async (input) => {
1230
- if (!input.job.id)
1231
- return;
1232
- const isFinal = input.maxAttempts === undefined ? true : input.attempts >= input.maxAttempts;
1233
- const backoffDelayMs = toBackoffDelayMs(input.job.opts?.backoff);
1234
- await JobStateTracker.failed({
1235
- queueName: input.queueName,
1236
- jobId: input.job.id,
1237
- attempts: input.attempts,
1238
- isFinal,
1239
- retryAt: !isFinal && backoffDelayMs > 0
1240
- ? new Date(Date.now() + backoffDelayMs).toISOString()
1241
- : undefined,
1242
- error: input.error,
1243
- });
1244
- };
1245
- /**
1246
- * Helper: Create enhanced processor with all features
1247
- */
1248
- const createEnhancedProcessor = (config) => {
1249
- return async (job) => {
1250
- const { name, version, processor, features } = config;
1251
- const jobVersion = version ?? '1.0.0';
1252
- ensureCircuitAllowsExecution(name, jobVersion, job.id, features);
1253
- const beforeOutcome = await runBeforeProcessHooks(name, job, features);
1254
- if (beforeOutcome.skip) {
1255
- return { skipped: true, reason: beforeOutcome.reason };
1256
- }
1257
- if (beforeOutcome.jobData !== undefined) {
1258
- job.data = beforeOutcome.jobData;
1259
- }
1260
- const startTime = Date.now();
1261
- let result;
1262
- let spanId = null;
1263
- const maxAttempts = typeof job.opts?.attempts === 'number' && Number.isFinite(job.opts.attempts)
1264
- ? Math.max(1, Math.floor(job.opts.attempts))
1265
- : undefined;
1266
- const attempts = Math.max(1, Math.floor((job.attemptsMade ?? 0) + 1));
1267
- try {
1268
- spanId = startProcessingSpan(name, jobVersion, job, config.queueName, features);
1269
- await trackJobStarted({
1270
- queueName: config.queueName,
1271
- job,
1272
- attempts,
1273
- workerName: name,
1274
- workerVersion: jobVersion,
1275
- });
1276
- // Process the job
1277
- result = await processor(job);
1278
- const duration = Date.now() - startTime;
1279
- await handleSuccess({
1280
- workerName: name,
1281
- jobVersion,
1282
- job,
1283
- result,
1284
- duration,
1285
- spanId,
1286
- features,
1287
- });
1288
- await trackJobCompleted({ queueName: config.queueName, job, duration, result });
1289
- return result;
1290
- }
1291
- catch (err) {
1292
- const error = err;
1293
- const duration = Date.now() - startTime;
1294
- await trackJobFailed({
1295
- queueName: config.queueName,
1296
- job,
1297
- attempts,
1298
- maxAttempts,
1299
- error,
1300
- });
1301
- await handleFailure({
1302
- workerName: name,
1303
- jobVersion,
1304
- job,
1305
- error,
1306
- duration,
1307
- spanId,
1308
- features,
1309
- queueName: config.queueName,
1310
- });
1311
- throw error;
1312
- }
1313
- };
1314
- };
1315
- const requireInfrastructure = (value, message) => {
1316
- if (value === null || value === undefined) {
1317
- throw ErrorFactory.createConfigError(message);
1318
- }
1319
- return value;
1320
- };
1321
- const resolveEnvString = (envKey, fallback) => {
1322
- if (!envKey)
1323
- return fallback;
1324
- return Env.get(envKey, fallback);
1325
- };
1326
- const resolveEnvInt = (envKey, fallback) => {
1327
- if (!envKey)
1328
- return fallback;
1329
- return Env.getInt(envKey, fallback);
1330
- };
1331
- const isRedisEnvConfig = (config) => config.env === true;
1332
- const requireRedisHost = (host, context) => {
1333
- if (!host) {
1334
- throw ErrorFactory.createConfigError(`${context}.host is required`);
1335
- }
1336
- return host;
1337
- };
1338
- const resolveRedisFallbacks = () => {
1339
- const queueRedis = queueConfig.drivers.redis;
1340
- return {
1341
- host: queueRedis?.driver === 'redis' ? queueRedis.host : Env.get('REDIS_HOST', '127.0.0.1'),
1342
- port: queueRedis?.driver === 'redis'
1343
- ? queueRedis.port
1344
- : Env.getInt('REDIS_PORT', ZintrustLang.REDIS_DEFAULT_PORT),
1345
- db: queueRedis?.driver === 'redis'
1346
- ? queueRedis.database
1347
- : Env.getInt('REDIS_QUEUE_DB', ZintrustLang.REDIS_DEFAULT_DB),
1348
- password: queueRedis?.driver === 'redis' ? (queueRedis.password ?? '') : Env.get('REDIS_PASSWORD', ''),
1349
- };
1350
- };
1351
- const resolveRedisConfigFromEnv = (config, context) => {
1352
- const fallback = resolveRedisFallbacks();
1353
- const host = requireRedisHost(resolveEnvString(config.host ?? 'REDIS_HOST', fallback.host), context);
1354
- const port = resolveEnvInt(String(config.port ?? 'REDIS_PORT'), fallback.port);
1355
- const db = resolveEnvInt(config.db ?? 'REDIS_QUEUE_DB', fallback.db);
1356
- const password = resolveEnvString(config.password ?? 'REDIS_PASSWORD', fallback.password);
1357
- return {
1358
- host,
1359
- port,
1360
- db,
1361
- password: password || undefined,
1362
- };
1363
- };
1364
- const resolveRedisConfigFromDirect = (config, context) => {
1365
- const fallbackDb = Env.getInt('REDIS_QUEUE_DB', ZintrustLang.REDIS_DEFAULT_DB);
1366
- let normalizedDb = fallbackDb;
1367
- if (typeof config.db === 'number') {
1368
- normalizedDb = config.db;
1369
- }
1370
- return {
1371
- host: requireRedisHost(config.host, context),
1372
- port: config.port,
1373
- db: normalizedDb,
1374
- password: config.password ?? Env.get('REDIS_PASSWORD'),
1375
- };
1376
- };
1377
- const resolveRedisConfig = (config, context) => isRedisEnvConfig(config)
1378
- ? resolveRedisConfigFromEnv(config, context)
1379
- : resolveRedisConfigFromDirect(config, context);
1380
- const resolveRedisConfigWithFallback = (primary, fallback, errorMessage, context) => {
1381
- const selected = primary ?? fallback;
1382
- if (!selected) {
1383
- throw ErrorFactory.createConfigError(errorMessage);
1384
- }
1385
- return resolveRedisConfig(selected, context);
1386
- };
1387
- const logRedisPersistenceConfig = (redisConfig, key_prefix, source) => {
1388
- Logger.debug('Worker persistence redis config', {
1389
- source,
1390
- host: redisConfig.host,
1391
- port: redisConfig.port,
1392
- db: redisConfig.db,
1393
- key_prefix,
1394
- });
1395
- };
1396
- const normalizeEnvValue = (value) => {
1397
- if (!value)
1398
- return undefined;
1399
- const trimmed = value.trim();
1400
- return trimmed.length > 0 ? trimmed : undefined;
1401
- };
1402
- const resolveDefaultPersistenceTable = () => normalizeEnvValue(Env.get('WORKER_PERSISTENCE_TABLE', 'zintrust_workers')) ?? 'zintrust_workers';
1403
- const resolveDefaultPersistenceConnection = () => normalizeEnvValue(Env.get('WORKER_PERSISTENCE_DB_CONNECTION', 'default')) ?? 'default';
1404
- const resolveAutoStart = (config) => {
1405
- // If explicitly set in config (not null/undefined), use that
1406
- if (config.autoStart !== undefined && config.autoStart !== null) {
1407
- return config.autoStart;
1408
- }
1409
- // Otherwise, use environment variable
1410
- return Env.getBool('WORKER_AUTO_START', false);
1411
- };
1412
- const normalizeExplicitPersistence = (persistence) => {
1413
- if (persistence.driver === 'memory')
1414
- return { driver: 'memory' };
1415
- if (persistence.driver === 'redis') {
1416
- return {
1417
- driver: 'redis',
1418
- redis: persistence.redis,
1419
- keyPrefix: keyPrefix(),
1420
- };
1421
- }
1422
- const clientIsConnection = typeof persistence.client === 'string';
1423
- const clientConnection = clientIsConnection ? persistence.client : undefined;
1424
- const resolvedConnection = persistence.connection ??
1425
- clientConnection ??
1426
- normalizeEnvValue(Env.get('WORKER_PERSISTENCE_DB_CONNECTION', 'default')) ??
1427
- resolveDefaultPersistenceConnection();
1428
- return {
1429
- driver: 'database',
1430
- client: clientIsConnection ? undefined : persistence.client,
1431
- connection: resolvedConnection,
1432
- table: persistence.table ??
1433
- normalizeEnvValue(Env.get('WORKER_PERSISTENCE_TABLE', 'zintrust_workers')) ??
1434
- resolveDefaultPersistenceTable(),
1435
- };
1436
- };
1437
- const resolvePersistenceConfig = (config) => {
1438
- const explicit = config.infrastructure?.persistence;
1439
- if (explicit)
1440
- return normalizeExplicitPersistence(explicit);
1441
- const driver = normalizeEnvValue(Env.get('WORKER_PERSISTENCE_DRIVER', ''))?.toLowerCase();
1442
- if (!driver)
1443
- return undefined;
1444
- if (driver === 'memory')
1445
- return { driver: 'memory' };
1446
- if (driver === 'redis') {
1447
- const persistenceDbOverride = normalizeEnvValue(Env.get('WORKER_PERSISTENCE_REDIS_DB', ''));
1448
- return {
1449
- driver: 'redis',
1450
- // Optional override; otherwise defaults to REDIS_QUEUE_DB.
1451
- redis: {
1452
- env: true,
1453
- db: persistenceDbOverride ? 'WORKER_PERSISTENCE_REDIS_DB' : 'REDIS_QUEUE_DB',
1454
- },
1455
- keyPrefix: keyPrefix(),
1456
- };
1457
- }
1458
- if (driver === 'db' || driver === 'database') {
1459
- return {
1460
- driver: 'database',
1461
- connection: resolveDefaultPersistenceConnection(),
1462
- table: resolveDefaultPersistenceTable(),
1463
- };
1464
- }
1465
- throw ErrorFactory.createConfigError('WORKER_PERSISTENCE_DRIVER must be one of memory, redis, or database');
1466
- };
1467
- const resolveDbClientFromEnv = async (connectionName = 'default') => {
1468
- const connect = async () => await useEnsureDbConnected(undefined, connectionName);
1469
- try {
1470
- return await connect();
1471
- }
1472
- catch (error) {
1473
- Logger.error('Worker persistence failed to resolve database connection', error);
1474
- }
1475
- try {
1476
- registerDatabasesFromRuntimeConfig(databaseConfig);
1477
- return await connect();
1478
- }
1479
- catch (error) {
1480
- Logger.error('Worker persistence failed after registering runtime databases', error);
1481
- throw ErrorFactory.createConfigError(`Worker persistence requires a database client. Register connection '${connectionName}' or pass infrastructure.persistence.client.`);
1482
- }
1483
- };
1484
- const resolveWorkerStore = async (config) => {
1485
- const persistence = resolvePersistenceConfig(config);
1486
- if (!persistence)
1487
- return workerStore;
1488
- let next;
1489
- if (persistence.driver === 'memory') {
1490
- next = InMemoryWorkerStore.create();
1491
- }
1492
- else if (persistence.driver === 'redis') {
1493
- const redisConfig = resolveRedisConfigWithFallback(persistence.redis, config.infrastructure?.redis, 'Worker persistence requires redis config (persistence.redis or infrastructure.redis)', 'infrastructure.persistence.redis');
1494
- const key_prefix = persistence.keyPrefix ?? keyPrefix();
1495
- logRedisPersistenceConfig(redisConfig, key_prefix, 'resolveWorkerStore');
1496
- const client = createRedisConnection(redisConfig);
1497
- next = RedisWorkerStore.create(client, key_prefix);
1498
- }
1499
- else if (persistence.driver === 'database') {
1500
- const explicitConnection = typeof persistence.client === 'string' ? persistence.client : persistence.connection;
1501
- const client = typeof persistence.client === 'string'
1502
- ? await resolveDbClientFromEnv(explicitConnection)
1503
- : (persistence.client ?? (await resolveDbClientFromEnv(explicitConnection)));
1504
- next = DbWorkerStore.create(client, persistence.table);
1505
- }
1506
- else {
1507
- next = InMemoryWorkerStore.create();
1508
- }
1509
- await next.init();
1510
- return next;
1511
- };
1512
- // Store instance cache to reuse connections
1513
- const storeInstanceCache = new Map();
1514
- /**
1515
- * Generate cache key for persistence configuration
1516
- */
1517
- const generateCacheKey = (persistence) => {
1518
- return JSON.stringify({
1519
- driver: persistence.driver,
1520
- redis: 'redis' in persistence ? persistence.redis : undefined,
1521
- keyPrefix: 'keyPrefix' in persistence ? persistence.keyPrefix : undefined,
1522
- connection: 'connection' in persistence ? persistence.connection : undefined,
1523
- table: 'table' in persistence ? persistence.table : undefined,
1524
- });
1525
- };
1526
- /**
1527
- * Create new store instance based on persistence configuration
1528
- */
1529
- const createWorkerStore = async (persistence) => {
1530
- if (persistence.driver === 'memory') {
1531
- if (workerStoreConfigured && workerStoreConfig?.driver === 'memory') {
1532
- return workerStore;
1533
- }
1534
- return InMemoryWorkerStore.create();
1535
- }
1536
- if (persistence.driver === 'redis') {
1537
- const redisConfig = resolveRedisConfigWithFallback(persistence.redis ?? { env: true }, undefined, 'Worker persistence requires redis config (persistence.redis or REDIS_* env values)', 'persistence.redis');
1538
- const key_prefix = persistence.keyPrefix ?? keyPrefix();
1539
- logRedisPersistenceConfig(redisConfig, key_prefix, 'createWorkerStore');
1540
- const client = createRedisConnection(redisConfig);
1541
- return RedisWorkerStore.create(client, key_prefix);
1542
- }
1543
- // Database driver
1544
- const explicitConnection = typeof persistence.client === 'string' ? persistence.client : persistence.connection;
1545
- const client = typeof persistence.client === 'string'
1546
- ? await resolveDbClientFromEnv(explicitConnection)
1547
- : (persistence.client ?? (await resolveDbClientFromEnv(explicitConnection)));
1548
- return DbWorkerStore.create(client, persistence.table);
1549
- };
1550
- const resolveWorkerStoreForPersistence = async (persistence) => {
1551
- const cacheKey = generateCacheKey(persistence);
1552
- const isCloudflare = Cloudflare.getWorkersEnv() !== null;
1553
- // Return cached instance if available (disable cache for Cloudflare to assume cleanup)
1554
- // Or handle cleanup differently. For now, disable cache for Cloudflare to allow per-request connections.
1555
- const cached = storeInstanceCache.get(cacheKey);
1556
- if (cached && !isCloudflare) {
1557
- return cached;
1558
- }
1559
- // Create new store instance
1560
- const store = await createWorkerStore(persistence);
1561
- await store.init();
1562
- // Cache the store instance for reuse only if not Cloudflare
1563
- if (!isCloudflare) {
1564
- storeInstanceCache.set(cacheKey, store);
1565
- }
1566
- return store;
1567
- };
1568
- const getPersistedRecord = async (name, persistenceOverride) => {
1569
- if (!persistenceOverride) {
1570
- if (!isCloudflareRuntime()) {
1571
- await ensureWorkerStoreConfigured();
1572
- return workerStore.get(name);
1573
- }
1574
- const store = await getDefaultStoreForRuntime();
1575
- try {
1576
- return await store.get(name);
1577
- }
1578
- finally {
1579
- if (store.close) {
1580
- await store.close();
1581
- }
1582
- }
1583
- }
1584
- const store = await resolveWorkerStoreForPersistence(persistenceOverride);
1585
- return store.get(name);
1586
- };
1587
- const ensureWorkerStoreConfigured = async () => {
1588
- if (workerStoreConfigured)
1589
- return;
1590
- const bootstrapConfig = buildPersistenceBootstrapConfig();
1591
- const persistence = resolvePersistenceConfig(bootstrapConfig);
1592
- if (!persistence)
1593
- return;
1594
- workerStore = await resolveWorkerStore(bootstrapConfig);
1595
- workerStoreConfigured = true;
1596
- workerStoreConfig = persistence;
1597
- };
1598
- const buildWorkerRecord = (config, status) => {
1599
- const now = new Date();
1600
- const normalizedProcessorSpec = config.processorSpec
1601
- ? normalizeProcessorSpec(config.processorSpec)
1602
- : null;
1603
- return {
1604
- name: config.name,
1605
- queueName: config.queueName,
1606
- version: config.version ?? '1.0.0',
1607
- status,
1608
- autoStart: resolveAutoStart(config),
1609
- concurrency: config.options?.concurrency ?? 1,
1610
- region: config.datacenter?.primaryRegion ?? null,
1611
- processorSpec: normalizedProcessorSpec ?? null,
1612
- activeStatus: config.activeStatus ?? true,
1613
- features: config.features ? { ...config.features } : null,
1614
- infrastructure: config.infrastructure ? { ...config.infrastructure } : null,
1615
- datacenter: config.datacenter ? { ...config.datacenter } : null,
1616
- createdAt: now,
1617
- updatedAt: now,
1618
- lastHealthCheck: undefined,
1619
- lastError: undefined,
1620
- connectionState: undefined,
1621
- };
1622
- };
1623
- const buildDefaultAutoScalerConfig = () => ({
1624
- enabled: workersConfig.autoScaling.enabled,
1625
- checkInterval: workersConfig.autoScaling.interval,
1626
- scalingPolicies: new Map(),
1627
- costOptimization: {
1628
- enabled: workersConfig.costOptimization.enabled,
1629
- maxCostPerHour: 0,
1630
- preferSpotInstances: workersConfig.costOptimization.spotInstances,
1631
- offPeakSchedule: {
1632
- start: workersConfig.autoScaling.offPeakSchedule.split('-')[0] ?? '22:00',
1633
- end: workersConfig.autoScaling.offPeakSchedule.split('-')[1] ?? '06:00',
1634
- timezone: 'UTC',
1635
- reductionPercentage: Math.round(workersConfig.autoScaling.offPeakReduction * 100),
1636
- },
1637
- budgetAlerts: {
1638
- dailyLimit: 0,
1639
- weeklyLimit: 0,
1640
- monthlyLimit: 0,
1641
- },
1642
- },
1643
- });
1644
- const resolveOffPeakSchedule = (input, defaults) => {
1645
- const fallback = defaults.costOptimization.offPeakSchedule ?? {
1646
- start: '22:00',
1647
- end: '06:00',
1648
- timezone: 'UTC',
1649
- reductionPercentage: 0,
1650
- };
1651
- const override = input?.costOptimization?.offPeakSchedule;
1652
- const schedule = { ...fallback };
1653
- if (override) {
1654
- Object.assign(schedule, override);
1655
- }
1656
- return schedule;
1657
- };
1658
- const resolveCostOptimization = (input, defaults) => ({
1659
- ...defaults.costOptimization,
1660
- ...input?.costOptimization,
1661
- offPeakSchedule: resolveOffPeakSchedule(input, defaults),
1662
- budgetAlerts: {
1663
- ...defaults.costOptimization.budgetAlerts,
1664
- ...input?.costOptimization?.budgetAlerts,
1665
- },
1666
- });
1667
- const resolveAutoScalerConfig = (input) => {
1668
- const defaults = buildDefaultAutoScalerConfig();
1669
- if (!input)
1670
- return defaults;
1671
- return {
1672
- ...defaults,
1673
- ...input,
1674
- costOptimization: resolveCostOptimization(input, defaults),
1675
- };
1676
- };
1677
- const resolveWorkerOptions = (config, autoStart) => {
1678
- const options = config.options ? { ...config.options } : {};
1679
- options.prefix ??= getBullMQSafeQueueName();
1680
- options.autorun ??= autoStart;
1681
- if (options.connection)
1682
- return options;
1683
- const redisConfig = resolveRedisConfigWithFallback(config.infrastructure?.redis, undefined, 'Worker requires a connection. Provide options.connection or infrastructure.redis config', 'infrastructure.redis');
1684
- return {
1685
- ...options,
1686
- connection: {
1687
- host: redisConfig.host,
1688
- port: redisConfig.port,
1689
- db: redisConfig.db,
1690
- password: redisConfig.password,
1691
- },
1692
- };
1693
- };
1694
- const buildDefaultObservabilityConfig = () => ({
1695
- prometheus: {
1696
- enabled: workersConfig.observability.prometheus.enabled,
1697
- port: workersConfig.observability.prometheus.port,
1698
- },
1699
- openTelemetry: {
1700
- enabled: workersConfig.observability.opentelemetry.enabled,
1701
- serviceName: 'zintrust-workers',
1702
- exporterUrl: workersConfig.observability.opentelemetry.endpoint,
1703
- },
1704
- datadog: {
1705
- enabled: workersConfig.observability.datadog.enabled,
1706
- tags: workersConfig.observability.datadog.apiKey
1707
- ? [`apiKey:${workersConfig.observability.datadog.apiKey}`]
1708
- : undefined,
1709
- },
1710
- });
1711
- const resolveObservabilityConfig = (input) => {
1712
- const defaults = buildDefaultObservabilityConfig();
1713
- if (!input)
1714
- return defaults;
1715
- const enabledOverride = 'enabled' in input ? input.enabled : undefined;
1716
- const prometheus = { ...defaults.prometheus };
1717
- if (input.prometheus) {
1718
- Object.assign(prometheus, input.prometheus);
1719
- }
1720
- const openTelemetry = { ...defaults.openTelemetry };
1721
- if (input.openTelemetry) {
1722
- Object.assign(openTelemetry, input.openTelemetry);
1723
- }
1724
- const datadog = { ...defaults.datadog };
1725
- if (input.datadog) {
1726
- Object.assign(datadog, input.datadog);
1727
- }
1728
- if (enabledOverride === false) {
1729
- prometheus.enabled = false;
1730
- openTelemetry.enabled = false;
1731
- datadog.enabled = false;
1732
- }
1733
- else if (enabledOverride === true) {
1734
- prometheus.enabled = true;
1735
- openTelemetry.enabled = true;
1736
- datadog.enabled = true;
1737
- }
1738
- if (!openTelemetry.serviceName) {
1739
- openTelemetry.serviceName = defaults.openTelemetry.serviceName;
1740
- }
1741
- return { prometheus, openTelemetry, datadog };
1742
- };
1743
- const initializeClustering = (config) => {
1744
- if (clusteringInitialized || !(config.features?.clustering ?? false))
1745
- return;
1746
- const redisConfig = resolveRedisConfigWithFallback(config.infrastructure?.redis, undefined, 'ClusterLock requires infrastructure.redis config', 'infrastructure.redis');
1747
- ClusterLock.initialize(redisConfig);
1748
- clusteringInitialized = true;
1749
- };
1750
- const initializeMetrics = (config) => {
1751
- if (metricsInitialized || !(config.features?.metrics ?? false))
1752
- return;
1753
- const redisConfig = resolveRedisConfigWithFallback(config.infrastructure?.redis, undefined, 'WorkerMetrics requires infrastructure.redis config', 'infrastructure.redis');
1754
- WorkerMetrics.initialize(redisConfig);
1755
- metricsInitialized = true;
1756
- };
1757
- const initializeAutoScaling = (config) => {
1758
- if (autoScalingInitialized || !(config.features?.autoScaling ?? false))
1759
- return;
1760
- const autoScalerConfig = resolveAutoScalerConfig(config.infrastructure?.autoScaler);
1761
- AutoScaler.initialize(autoScalerConfig);
1762
- autoScalingInitialized = true;
1763
- };
1764
- const initializeCircuitBreaker = (config, version) => {
1765
- if (!(config.features?.circuitBreaker ?? false))
1766
- return;
1767
- CircuitBreaker.initialize(config.name, version);
1768
- };
1769
- const initializeDeadLetterQueue = (config) => {
1770
- if (deadLetterQueueInitialized || !(config.features?.deadLetterQueue ?? false))
1771
- return;
1772
- const dlqConfig = requireInfrastructure(config.infrastructure?.deadLetterQueue, 'DeadLetterQueue requires infrastructure.deadLetterQueue config');
1773
- const dlqRedisConfig = resolveRedisConfigWithFallback(dlqConfig.redis, config.infrastructure?.redis, 'DeadLetterQueue requires infrastructure.deadLetterQueue.redis or infrastructure.redis config', 'infrastructure.deadLetterQueue.redis');
1774
- DeadLetterQueue.initialize(dlqRedisConfig, dlqConfig.policy);
1775
- deadLetterQueueInitialized = true;
1776
- };
1777
- const initializeResourceMonitoring = (config) => {
1778
- if (resourceMonitoringInitialized || !(config.features?.resourceMonitoring ?? false))
1779
- return;
1780
- ResourceMonitor.initialize();
1781
- ResourceMonitor.start();
1782
- resourceMonitoringInitialized = true;
1783
- };
1784
- const initializeCompliance = (config) => {
1785
- if (complianceInitialized || !(config.features?.compliance ?? false))
1786
- return;
1787
- const complianceConfig = requireInfrastructure(config.infrastructure?.compliance, 'ComplianceManager requires infrastructure.compliance config');
1788
- const complianceRedisConfig = resolveRedisConfigWithFallback(complianceConfig.redis, config.infrastructure?.redis, 'ComplianceManager requires infrastructure.compliance.redis or infrastructure.redis config', 'infrastructure.compliance.redis');
1789
- ComplianceManager.initialize(complianceRedisConfig, complianceConfig.config);
1790
- complianceInitialized = true;
1791
- };
1792
- const initializeObservability = async (config) => {
1793
- if (observabilityInitialized || !(config.features?.observability ?? false))
1794
- return;
1795
- const observabilityConfig = resolveObservabilityConfig(config.infrastructure?.observability);
1796
- await Observability.initialize(observabilityConfig);
1797
- observabilityInitialized = true;
1798
- };
1799
- const initializeVersioning = (config, version) => {
1800
- if (!(config.features?.versioning ?? false))
1801
- return;
1802
- WorkerVersioning.register({
1803
- workerName: config.name,
1804
- version: WorkerVersioning.parse(version),
1805
- changelog: 'Initial version',
1806
- });
1807
- };
1808
- const initializeDatacenter = (config) => {
1809
- if (!(config.features?.datacenterOrchestration ?? false) || !config.datacenter)
1810
- return;
1811
- DatacenterOrchestrator.placeWorker({
1812
- workerName: config.name,
1813
- primaryRegion: config.datacenter.primaryRegion,
1814
- secondaryRegions: config.datacenter.secondaryRegions ?? [],
1815
- replicationStrategy: 'active-passive',
1816
- affinityRules: {
1817
- preferLocal: config.datacenter.affinityRules?.preferLocal ?? true,
1818
- maxLatency: config.datacenter.affinityRules?.maxLatency,
1819
- avoidRegions: config.datacenter.affinityRules?.avoidRegions,
1820
- },
1821
- });
1822
- };
1823
- const setupWorkerEventListeners = (worker, workerName, workerVersion, features) => {
1824
- worker.on('completed', (job) => {
1825
- try {
1826
- Logger.debug(`Job completed: ${workerName}`, { jobId: job.id });
1827
- if (features?.observability === true) {
1828
- Observability.incrementCounter('worker.jobs.completed', 1, {
1829
- worker: workerName,
1830
- version: workerVersion,
1831
- });
1832
- }
1833
- }
1834
- catch (error) {
1835
- // Isolate error - don't let it bubble up
1836
- Logger.error(`Error in worker completed event handler: ${workerName}`, error, 'workers');
1837
- }
1838
- });
1839
- worker.on('failed', (job, error) => {
1840
- try {
1841
- Logger.error(`Job failed: ${workerName}`, { error, jobId: job?.id }, 'workers');
1842
- if (features?.observability === true) {
1843
- Observability.incrementCounter('worker.jobs.failed', 1, {
1844
- worker: workerName,
1845
- version: workerVersion,
1846
- });
1847
- }
1848
- }
1849
- catch (handlerError) {
1850
- // Isolate error - don't let it bubble up
1851
- Logger.error(`Error in worker failed event handler: ${workerName}`, handlerError, 'workers');
1852
- }
1853
- });
1854
- worker.on('error', (error) => {
1855
- try {
1856
- Logger.error(`Worker error: ${workerName}`, error);
1857
- // Check if this is a Redis connection error that should be handled gracefully
1858
- if (error.message.includes('ERR value is not an integer') ||
1859
- error.message.includes('NOAUTH') ||
1860
- error.message.includes('ECONNREFUSED')) {
1861
- Logger.warn(`Worker ${workerName} encountered Redis configuration error - worker will remain failed but server will continue running`);
1862
- }
1863
- }
1864
- catch (handlerError) {
1865
- // Isolate error - don't let it bubble up
1866
- Logger.error(`Error in worker error event handler: ${workerName}`, handlerError, 'workers');
1867
- }
1868
- });
1869
- };
1870
- const registerWorkerInstance = (params) => {
1871
- const { worker, config, workerVersion, queueName, options, autoStart } = params;
1872
- WorkerRegistry.register({
1873
- name: config.name,
1874
- config: {},
1875
- activeStatus: config.activeStatus ?? true,
1876
- version: workerVersion,
1877
- region: config.datacenter?.primaryRegion,
1878
- queues: [queueName],
1879
- factory: async () => {
1880
- await Promise.resolve();
1881
- return {
1882
- metadata: {
1883
- name: config.name,
1884
- status: autoStart ? 'running' : 'stopped',
1885
- version: workerVersion,
1886
- region: config.datacenter?.primaryRegion ?? 'unknown',
1887
- queueName,
1888
- concurrency: options?.concurrency ?? 1,
1889
- activeStatus: config.activeStatus ?? true,
1890
- startedAt: new Date(),
1891
- stoppedAt: null,
1892
- lastProcessedAt: null,
1893
- restartCount: 0,
1894
- processedCount: 0,
1895
- errorCount: 0,
1896
- lockKey: null,
1897
- priority: 0,
1898
- memoryUsage: 0,
1899
- cpuUsage: 0,
1900
- circuitState: 'closed',
1901
- queues: [queueName],
1902
- plugins: [],
1903
- datacenter: config.datacenter?.primaryRegion ?? 'unknown',
1904
- canaryPercentage: 0,
1905
- config: {},
1906
- },
1907
- instance: worker,
1908
- start: () => {
1909
- if (!autoStart) {
1910
- worker.run().catch((error) => {
1911
- Logger.error(`Failed to start worker "${config.name}"`, error);
1912
- });
1913
- }
1914
- },
1915
- stop: async () => worker.close(),
1916
- drain: async () => worker.close(),
1917
- sleep: async () => worker.pause(),
1918
- wakeup: () => {
1919
- worker.resume();
1920
- },
1921
- getStatus: () => 'running',
1922
- getHealth: () => 'green',
1923
- };
1924
- },
1925
- });
1926
- };
1927
- const initializeWorkerFeatures = async (config, workerVersion) => {
1928
- initializeClustering(config);
1929
- initializeMetrics(config);
1930
- initializeAutoScaling(config);
1931
- initializeCircuitBreaker(config, workerVersion);
1932
- initializeDeadLetterQueue(config);
1933
- initializeResourceMonitoring(config);
1934
- initializeCompliance(config);
1935
- await initializeObservability(config);
1936
- initializeVersioning(config, workerVersion);
1937
- initializeDatacenter(config);
1938
- };
1939
- /**
1940
- * Worker Factory - Sealed namespace
1941
- */
1942
- export const WorkerFactory = Object.freeze({
1943
- registerProcessor,
1944
- registerProcessors,
1945
- registerProcessorPaths,
1946
- registerProcessorResolver,
1947
- registerProcessorSpec,
1948
- resolveProcessorPath,
1949
- resolveProcessorSpec,
1950
- /**
1951
- * Register a new worker configuration without starting it.
1952
- */
1953
- async register(config) {
1954
- const { name } = config;
1955
- // Check in-memory first (though unlikely if we are just registering)
1956
- if (workers.has(name)) {
1957
- throw ErrorFactory.createWorkerError(`Worker "${name}" is already running locally`);
1958
- }
1959
- const store = await getStoreForWorker(config);
1960
- try {
1961
- const existing = await store.get(name);
1962
- if (existing) {
1963
- throw ErrorFactory.createWorkerError(`Worker "${name}" already exists in persistence`);
1964
- }
1965
- // Init features to validate config, but mainly we just want to save it.
1966
- // initializeWorkerFeatures might rely on being active or having resources, so we might skip it or do partial.
1967
- // For now, just save definition.
1968
- // Status should be STOPPED or CREATED.
1969
- await store.save(buildWorkerRecord(config, WorkerCreationStatus.STOPPED));
1970
- Logger.info(`Worker registered (persistence only): ${name}`);
1971
- }
1972
- finally {
1973
- // If Cloudflare environment, try to close store connection to avoid zombie connections
1974
- if (Cloudflare.getWorkersEnv() !== null && store.close) {
1975
- await store.close();
1976
- }
1977
- }
1978
- },
1979
- /**
1980
- * Create new worker with full setup
1981
- */
1982
- async create(config) {
1983
- const { name, version, queueName, features } = config;
1984
- const workerVersion = version ?? '1.0.0';
1985
- const autoStart = resolveAutoStart(config);
1986
- if (workers.has(name)) {
1987
- throw ErrorFactory.createWorkerError(`Worker "${name}" already exists`);
1988
- }
1989
- // Resolve the correct store for this worker configuration
1990
- const store = await getStoreForWorker(config);
1991
- // Save initial status as "creating"
1992
- await store.save(buildWorkerRecord(config, WorkerCreationStatus.CREATING));
1993
- try {
1994
- await initializeWorkerFeatures(config, workerVersion);
1995
- // Update status to "connecting"
1996
- await store.update(name, {
1997
- status: WorkerCreationStatus.CONNECTING,
1998
- updatedAt: new Date(),
1999
- });
2000
- // Create enhanced processor
2001
- const enhancedProcessor = createEnhancedProcessor(config);
2002
- // Create BullMQ worker
2003
- const resolvedOptions = resolveWorkerOptions(config, autoStart);
2004
- const worker = new Worker(queueName, enhancedProcessor, resolvedOptions);
2005
- setupWorkerEventListeners(worker, name, workerVersion, features);
2006
- // Update status to "starting"
2007
- await store.update(name, {
2008
- status: WorkerCreationStatus.STARTING,
2009
- updatedAt: new Date(),
2010
- });
2011
- const timeoutMs = Env.getInt('WORKER_CONNECTION_TIMEOUT', 5000);
2012
- // Wait for actual connection and health verification
2013
- await waitForWorkerConnection(worker, name, queueName, timeoutMs);
2014
- // Update status to "running" only after successful connection
2015
- await store.update(name, {
2016
- status: WorkerCreationStatus.RUNNING,
2017
- updatedAt: new Date(),
2018
- });
2019
- // Store worker instance
2020
- const instance = {
2021
- worker,
2022
- config,
2023
- startedAt: new Date(),
2024
- status: WorkerCreationStatus.RUNNING,
2025
- connectionState: 'connected',
2026
- };
2027
- workers.set(name, instance);
2028
- registerWorkerInstance({
2029
- worker,
2030
- config,
2031
- workerVersion,
2032
- queueName,
2033
- options: resolvedOptions,
2034
- autoStart,
2035
- });
2036
- if (autoStart) {
2037
- await WorkerRegistry.start(name, workerVersion);
2038
- }
2039
- // Execute afterStart hooks
2040
- if (features?.plugins === true) {
2041
- await PluginManager.executeHook('afterStart', {
2042
- workerName: name,
2043
- timestamp: new Date(),
2044
- });
2045
- }
2046
- // Start health monitoring for the worker
2047
- startHealthMonitoring(name, worker, queueName);
2048
- return worker;
2049
- }
2050
- catch (error) {
2051
- // Handle failure - update status to "failed"
2052
- // Re-resolve store in case of error to be safe
2053
- const failStore = await getStoreForWorker(config);
2054
- await failStore.update(name, {
2055
- status: WorkerCreationStatus.FAILED,
2056
- updatedAt: new Date(),
2057
- lastError: error.message,
2058
- });
2059
- Logger.error(`Worker creation failed: ${name}`, error);
2060
- throw error;
2061
- }
2062
- },
2063
- /**
2064
- * Get worker instance
2065
- */
2066
- get(name) {
2067
- const instance = workers.get(name);
2068
- return instance ? { ...instance } : null;
2069
- },
2070
- /**
2071
- * Update worker status directly (used by HealthMonitor)
2072
- */
2073
- async updateStatus(name, status, error) {
2074
- const instance = workers.get(name);
2075
- if (instance) {
2076
- instance.status = status;
2077
- }
2078
- try {
2079
- const store = await getStoreForWorker(instance?.config ?? {
2080
- name,
2081
- queueName: 'unknown',
2082
- processor: async () => {
2083
- return Promise.resolve(); //NOSONAR
2084
- },
2085
- });
2086
- const errorMessage = typeof error === 'string' ? error : error?.message;
2087
- await store.update(name, {
2088
- status: status,
2089
- updatedAt: new Date(),
2090
- lastError: errorMessage,
2091
- });
2092
- }
2093
- catch (err) {
2094
- Logger.warn(`Failed to update status for ${name} to ${status}`, err);
2095
- }
2096
- },
2097
- /**
2098
- * Stop worker
2099
- */
2100
- async stop(name, persistenceOverride, options) {
2101
- const skipPersistedUpdate = options?.skipPersistedUpdate === true;
2102
- const instance = workers.get(name);
2103
- const store = await validateAndGetStore(name, instance?.config, persistenceOverride);
2104
- if (!instance) {
2105
- if (!skipPersistedUpdate) {
2106
- await store.update(name, { status: 'stopped', updatedAt: new Date() });
2107
- Logger.info(`Worker marked stopped (not running): ${name}`);
2108
- }
2109
- return;
2110
- }
2111
- // Execute beforeStop hooks
2112
- if (instance.config.features?.plugins === true) {
2113
- await PluginManager.executeHook('beforeStop', {
2114
- workerName: name,
2115
- timestamp: new Date(),
2116
- });
2117
- }
2118
- // Close worker with timeout to prevent hanging
2119
- const workerClosePromise = instance.worker.close();
2120
- let timeoutId;
2121
- const timeoutPromise = new Promise((_, reject) => {
2122
- // eslint-disable-next-line no-restricted-syntax
2123
- timeoutId = setTimeout(() => {
2124
- reject(new Error('Worker close timeout'));
2125
- }, 5000);
2126
- });
2127
- try {
2128
- await Promise.race([workerClosePromise, timeoutPromise]);
2129
- }
2130
- catch (error) {
2131
- Logger.warn(`Worker "${name}" close failed or timed out, continuing...`, error);
2132
- }
2133
- finally {
2134
- // Always clean up timeout to prevent memory leak
2135
- if (timeoutId) {
2136
- clearTimeout(timeoutId);
2137
- timeoutId = undefined;
2138
- }
2139
- }
2140
- instance.status = WorkerCreationStatus.STOPPED;
2141
- // Stop health monitoring for this worker
2142
- HealthMonitor.unregister(name);
2143
- if (!skipPersistedUpdate) {
2144
- try {
2145
- await store.update(name, {
2146
- status: WorkerCreationStatus.STOPPED,
2147
- updatedAt: new Date(),
2148
- });
2149
- Logger.info(`Worker "${name}" status updated to stopped`);
2150
- }
2151
- catch (error) {
2152
- Logger.error(`Failed to update worker "${name}" status`, error);
2153
- }
2154
- }
2155
- await WorkerRegistry.stop(name);
2156
- // Execute afterStop hooks
2157
- if (instance.config.features?.plugins === true) {
2158
- await PluginManager.executeHook('afterStop', {
2159
- workerName: name,
2160
- timestamp: new Date(),
2161
- });
2162
- }
2163
- Logger.info(`Worker stopped: ${name}`);
2164
- },
2165
- /**
2166
- * Restart worker
2167
- */
2168
- async restart(name, persistenceOverride) {
2169
- const instance = workers.get(name);
2170
- if (!instance) {
2171
- await WorkerFactory.startFromPersisted(name, persistenceOverride);
2172
- Logger.info(`Worker started from persistence: ${name}`);
2173
- return;
2174
- }
2175
- await WorkerFactory.stop(name, persistenceOverride);
2176
- const refreshed = workers.get(name);
2177
- if (!refreshed) {
2178
- throw ErrorFactory.createNotFoundError(`Worker "${name}" not found`);
2179
- }
2180
- workers.delete(name);
2181
- const newWorker = await WorkerFactory.create(refreshed.config);
2182
- refreshed.worker = newWorker;
2183
- refreshed.status = WorkerCreationStatus.RUNNING;
2184
- refreshed.startedAt = new Date();
2185
- Logger.info(`Worker restarted: ${name}`);
2186
- },
2187
- /**
2188
- * Pause worker
2189
- */
2190
- async pause(name, persistenceOverride) {
2191
- const instance = workers.get(name);
2192
- const store = await validateAndGetStore(name, instance?.config, persistenceOverride);
2193
- if (instance) {
2194
- await instance.worker.pause();
2195
- instance.status = WorkerCreationStatus.STARTING; // Using STARTING as equivalent to sleeping/paused
2196
- }
2197
- await store.update(name, {
2198
- status: WorkerCreationStatus.STARTING,
2199
- updatedAt: new Date(),
2200
- });
2201
- Logger.info(`Worker paused: ${name}`);
2202
- },
2203
- /**
2204
- * Resume worker
2205
- */
2206
- async resume(name, persistenceOverride) {
2207
- const instance = workers.get(name);
2208
- const store = await validateAndGetStore(name, instance?.config, persistenceOverride);
2209
- if (instance) {
2210
- instance.worker.resume();
2211
- instance.status = WorkerCreationStatus.RUNNING;
2212
- }
2213
- try {
2214
- await store.update(name, { status: WorkerCreationStatus.RUNNING, updatedAt: new Date() });
2215
- }
2216
- catch (error) {
2217
- Logger.error('Failed to persist worker resume', error);
2218
- }
2219
- Logger.info(`Worker resumed: ${name}`);
2220
- },
2221
- /**
2222
- * Update auto-start for persisted worker
2223
- */
2224
- async setAutoStart(name, autoStart, persistenceOverride) {
2225
- const instance = workers.get(name);
2226
- const store = await validateAndGetStore(name, instance?.config, persistenceOverride);
2227
- if (instance) {
2228
- instance.config.autoStart = autoStart;
2229
- }
2230
- await store.update(name, { autoStart, updatedAt: new Date() });
2231
- if (!autoStart)
2232
- return;
2233
- const refreshed = workers.get(name);
2234
- if (refreshed) {
2235
- if (refreshed.status !== 'running') {
2236
- await WorkerFactory.start(name, persistenceOverride);
2237
- }
2238
- return;
2239
- }
2240
- await WorkerFactory.startFromPersisted(name, persistenceOverride);
2241
- },
2242
- /**
2243
- * Update active status for a worker
2244
- */
2245
- async setWorkerActiveStatus(name, activeStatus, persistenceOverride) {
2246
- const instance = workers.get(name);
2247
- const store = await validateAndGetStore(name, instance?.config, persistenceOverride);
2248
- if (instance) {
2249
- instance.config.activeStatus = activeStatus;
2250
- }
2251
- await store.update(name, { activeStatus, updatedAt: new Date() });
2252
- WorkerRegistry.setActiveStatus(name, activeStatus);
2253
- if (activeStatus === false && instance) {
2254
- await WorkerFactory.stop(name, persistenceOverride);
2255
- }
2256
- },
2257
- /**
2258
- * Get active status for a worker
2259
- */
2260
- async getWorkerActiveStatus(name, persistenceOverride) {
2261
- const instance = workers.get(name);
2262
- if (instance?.config.activeStatus !== undefined) {
2263
- return instance.config.activeStatus;
2264
- }
2265
- const store = await getStoreForWorker(instance?.config, persistenceOverride);
2266
- const record = await store.get(name);
2267
- if (!record)
2268
- return null;
2269
- return record.activeStatus ?? true;
2270
- },
2271
- /**
2272
- * Update persisted worker record and in-memory config if running.
2273
- */
2274
- async update(name, patch, persistenceOverride) {
2275
- const instance = workers.get(name);
2276
- const store = await getStoreForWorker(instance?.config, persistenceOverride);
2277
- const current = await store.get(name);
2278
- if (!current) {
2279
- throw ErrorFactory.createNotFoundError(`Worker "${name}" not found in persistence store`);
2280
- }
2281
- const merged = {
2282
- ...current,
2283
- ...patch,
2284
- updatedAt: patch.updatedAt ?? new Date(),
2285
- };
2286
- // Use save() which will insert or update appropriately for each store
2287
- await store.save(merged);
2288
- // If the worker is running in memory, update its runtime config so restarts use the new config
2289
- if (instance) {
2290
- const cfg = instance.config;
2291
- instance.config = {
2292
- ...cfg,
2293
- version: merged.version ?? cfg.version,
2294
- queueName: merged.queueName ?? cfg.queueName,
2295
- options: {
2296
- ...cfg.options,
2297
- concurrency: merged.concurrency ?? cfg.options?.concurrency,
2298
- },
2299
- processorSpec: merged.processorSpec ?? cfg.processorSpec,
2300
- activeStatus: merged.activeStatus ?? cfg.activeStatus,
2301
- infrastructure: merged.infrastructure ?? cfg.infrastructure,
2302
- features: merged.features ?? cfg.features,
2303
- datacenter: merged.datacenter ?? cfg.datacenter,
2304
- };
2305
- }
2306
- },
2307
- /**
2308
- * Start worker
2309
- */
2310
- async start(name, persistenceOverride) {
2311
- const instance = workers.get(name);
2312
- // Even if instance exists, we must validate against the requested driver
2313
- const store = await validateAndGetStore(name, instance?.config, persistenceOverride);
2314
- if (!instance) {
2315
- throw ErrorFactory.createNotFoundError(`Worker "${name}" not found`);
2316
- }
2317
- if (instance.config.activeStatus === false) {
2318
- throw ErrorFactory.createConfigError(`Worker "${name}" is inactive`);
2319
- }
2320
- const persisted = await store.get(name);
2321
- if (persisted?.activeStatus === false) {
2322
- throw ErrorFactory.createConfigError(`Worker "${name}" is inactive`);
2323
- }
2324
- const version = instance.config.version ?? '1.0.0';
2325
- await WorkerRegistry.start(name, version);
2326
- instance.status = WorkerCreationStatus.RUNNING;
2327
- instance.startedAt = new Date();
2328
- await store.update(name, { status: WorkerCreationStatus.RUNNING, updatedAt: new Date() });
2329
- Logger.info(`Worker started: ${name}`);
2330
- },
2331
- /**
2332
- * List all workers
2333
- */
2334
- list() {
2335
- return Array.from(workers.keys());
2336
- },
2337
- /**
2338
- * List all persisted workers
2339
- */
2340
- async listPersisted(persistenceOverride, options) {
2341
- const records = await WorkerFactory.listPersistedRecords(persistenceOverride, options);
2342
- return records.map((record) => record.name);
2343
- },
2344
- async listPersistedRecords(persistenceOverride, options) {
2345
- const includeInactive = options?.includeInactive === true;
2346
- if (!persistenceOverride) {
2347
- if (!isCloudflareRuntime()) {
2348
- await ensureWorkerStoreConfigured();
2349
- const records = await workerStore.list(options);
2350
- return includeInactive
2351
- ? records
2352
- : records.filter((record) => record.activeStatus !== false);
2353
- }
2354
- const store = await getDefaultStoreForRuntime();
2355
- try {
2356
- const records = await store.list(options);
2357
- return includeInactive
2358
- ? records
2359
- : records.filter((record) => record.activeStatus !== false);
2360
- }
2361
- finally {
2362
- if (store.close) {
2363
- await store.close();
2364
- }
2365
- }
2366
- }
2367
- const store = await resolveWorkerStoreForPersistence(persistenceOverride);
2368
- const records = await store.list(options);
2369
- return includeInactive ? records : records.filter((record) => record.activeStatus !== false);
2370
- },
2371
- /**
2372
- * Start a worker from persisted storage when it is not registered.
2373
- */
2374
- async startFromPersisted(name, persistenceOverride) {
2375
- const { record, discovered } = await resolveStartFromPersistedRecord(name, persistenceOverride);
2376
- if (record.activeStatus === false) {
2377
- throw ErrorFactory.createConfigError(`Worker "${name}" is inactive`);
2378
- }
2379
- const processor = await resolveStartFromPersistedProcessor(name, record, discovered);
2380
- await WorkerFactory.create({
2381
- name: record.name,
2382
- queueName: record.queueName,
2383
- version: record.version ?? undefined,
2384
- processor,
2385
- processorSpec: record.processorSpec ?? undefined,
2386
- activeStatus: record.activeStatus ?? true,
2387
- autoStart: true, // Override to true when manually starting
2388
- options: { concurrency: record.concurrency },
2389
- infrastructure: record.infrastructure,
2390
- features: record.features,
2391
- datacenter: record.datacenter,
2392
- });
2393
- },
2394
- /**
2395
- * List worker definitions discovered from project files.
2396
- */
2397
- async listFileBackedRecords() {
2398
- const discovered = await discoverFileBackedWorkers();
2399
- return discovered.map((entry) => entry.record);
2400
- },
2401
- /**
2402
- * Get a file-backed worker definition by name.
2403
- */
2404
- async getFileBackedRecord(name) {
2405
- const discovered = await getDiscoveredFileWorker(name);
2406
- return discovered?.record ?? null;
2407
- },
2408
- /**
2409
- * Get persisted worker record
2410
- */
2411
- async getPersisted(name, persistenceOverride) {
2412
- const instance = workers.get(name);
2413
- const store = await getStoreForWorker(instance?.config, persistenceOverride);
2414
- try {
2415
- const result = await store.get(name);
2416
- return result;
2417
- }
2418
- finally {
2419
- if (Cloudflare.getWorkersEnv() !== null && store.close) {
2420
- await store.close();
2421
- }
2422
- }
2423
- },
2424
- /**
2425
- * Remove worker
2426
- */
2427
- async remove(name, persistenceOverride) {
2428
- const instance = workers.get(name);
2429
- // Validate that worker exists in the store we are trying to remove from
2430
- const store = await validateAndGetStore(name, instance?.config, persistenceOverride);
2431
- if (instance) {
2432
- await WorkerFactory.stop(name, persistenceOverride);
2433
- const registry = WorkerRegistry;
2434
- registry.unregister?.(name);
2435
- AutoScaler.clearHistory(name);
2436
- ResourceMonitor.clearHistory(name);
2437
- CircuitBreaker.deleteWorker(name);
2438
- CanaryController.purge(name);
2439
- WorkerVersioning.clear(name);
2440
- DatacenterOrchestrator.removeWorker(name);
2441
- await Observability.clearWorkerMetrics(name);
2442
- // Stop health monitoring for this worker
2443
- HealthMonitor.unregister(name);
2444
- workers.delete(name);
2445
- }
2446
- await store.remove(name);
2447
- Logger.info(`Worker removed: ${name}`);
2448
- },
2449
- /**
2450
- * Get worker metrics
2451
- */
2452
- async getMetrics(name) {
2453
- const instance = workers.get(name);
2454
- if (!instance) {
2455
- throw ErrorFactory.createNotFoundError(`Worker "${name}" not found`);
2456
- }
2457
- if (instance.config.features?.metrics === undefined || !instance.config.features?.metrics) {
2458
- return null;
2459
- }
2460
- const now = Date.now();
2461
- const oneHourAgo = now - 3600 * 1000;
2462
- const metrics = await WorkerMetrics.aggregate({
2463
- workerName: name,
2464
- metricType: 'processed',
2465
- granularity: 'hourly',
2466
- startDate: new Date(oneHourAgo),
2467
- endDate: new Date(now),
2468
- });
2469
- return metrics;
2470
- },
2471
- /**
2472
- * Get worker health
2473
- */
2474
- async getHealth(name) {
2475
- const instance = workers.get(name);
2476
- if (!instance) {
2477
- throw ErrorFactory.createNotFoundError(`Worker "${name}" not found`);
2478
- }
2479
- if (!(instance.config.features?.metrics ?? false)) {
2480
- return { status: 'unknown' };
2481
- }
2482
- const health = await WorkerMetrics.getLatestHealth(name);
2483
- return health;
2484
- },
2485
- /**
2486
- * Shutdown all workers
2487
- */
2488
- async shutdown() {
2489
- Logger.info('WorkerFactory shutting down...');
2490
- const workerEntries = Array.from(workers.entries());
2491
- const workerNames = workerEntries.map(([name]) => name);
2492
- // Bulk-update persisted statuses before stopping workers to avoid per-worker DB updates
2493
- // during shutdown (which can fail if DB connections are closing).
2494
- const storeGroups = new Map();
2495
- // Parallel get stores for all workers
2496
- const storePromises = workerEntries.map(async ([name, instance]) => {
2497
- const store = await getStoreForWorker(instance.config);
2498
- return { name, store };
2499
- });
2500
- const storeMappings = await Promise.all(storePromises);
2501
- for (const { name, store } of storeMappings) {
2502
- const existing = storeGroups.get(store);
2503
- if (existing) {
2504
- existing.push(name);
2505
- }
2506
- else {
2507
- storeGroups.set(store, [name]);
2508
- }
2509
- }
2510
- // Parallel bulk updates for all store groups
2511
- const updatePromises = Array.from(storeGroups.entries()).map(async ([store, names]) => {
2512
- if (typeof store.updateMany === 'function') {
2513
- await store.updateMany(names, {
2514
- status: WorkerCreationStatus.STOPPED,
2515
- updatedAt: new Date(),
2516
- });
2517
- }
2518
- });
2519
- await Promise.all(updatePromises);
2520
- await Promise.all(workerNames.map(async (name) => WorkerFactory.stop(name, undefined, { skipPersistedUpdate: true })));
2521
- // Shutdown all modules
2522
- ResourceMonitor.stop();
2523
- await WorkerMetrics.shutdown();
2524
- await MultiQueueWorker.shutdown();
2525
- await ComplianceManager.shutdown();
2526
- await PriorityQueue.shutdown();
2527
- HealthMonitor.shutdown();
2528
- AutoScaler.stop();
2529
- ClusterLock.shutdown();
2530
- WorkerVersioning.shutdown();
2531
- CanaryController.shutdown();
2532
- DatacenterOrchestrator.shutdown();
2533
- PluginManager.shutdown();
2534
- Observability.shutdown();
2535
- await DeadLetterQueue.shutdown();
2536
- CircuitBreaker.shutdown();
2537
- workers.clear();
2538
- Logger.info('WorkerFactory shutdown complete');
2539
- },
2540
- /**
2541
- * Reset persistence connection state.
2542
- * Useful when connections become stale in long-running processes or serverless environments.
2543
- */
2544
- async resetPersistence() {
2545
- workerStoreConfigured = false;
2546
- workerStore = InMemoryWorkerStore.create();
2547
- storeInstanceCache.clear();
2548
- Logger.info('Worker persistence configuration reset');
2549
- },
2550
- });
2551
- // Graceful shutdown handled by WorkerShutdown