alepha 0.13.7 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (512) hide show
  1. package/README.md +5 -2
  2. package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
  3. package/assets/swagger-ui/swagger-ui-standalone-preset.js +1 -1
  4. package/assets/swagger-ui/swagger-ui.css +1 -1
  5. package/dist/{api-audits → api/audits}/index.browser.js +4 -4
  6. package/dist/api/audits/index.browser.js.map +1 -0
  7. package/dist/{api-audits → api/audits}/index.d.ts +10 -9
  8. package/dist/api/audits/index.d.ts.map +1 -0
  9. package/dist/{api-audits → api/audits}/index.js +8 -8
  10. package/dist/api/audits/index.js.map +1 -0
  11. package/dist/{api-files → api/files}/index.browser.js +5 -5
  12. package/dist/api/files/index.browser.js.map +1 -0
  13. package/dist/{api-files → api/files}/index.d.ts +18 -10
  14. package/dist/api/files/index.d.ts.map +1 -0
  15. package/dist/{api-files → api/files}/index.js +10 -10
  16. package/dist/api/files/index.js.map +1 -0
  17. package/dist/{api-jobs → api/jobs}/index.browser.js +5 -5
  18. package/dist/api/jobs/index.browser.js.map +1 -0
  19. package/dist/{api-jobs → api/jobs}/index.d.ts +168 -167
  20. package/dist/api/jobs/index.d.ts.map +1 -0
  21. package/dist/{api-jobs → api/jobs}/index.js +9 -9
  22. package/dist/api/jobs/index.js.map +1 -0
  23. package/dist/{api-notifications → api/notifications}/index.browser.js +11 -11
  24. package/dist/api/notifications/index.browser.js.map +1 -0
  25. package/dist/api/notifications/index.d.ts +327 -0
  26. package/dist/api/notifications/index.d.ts.map +1 -0
  27. package/dist/{api-notifications → api/notifications}/index.js +11 -11
  28. package/dist/api/notifications/index.js.map +1 -0
  29. package/dist/{api-parameters → api/parameters}/index.browser.js +2 -2
  30. package/dist/api/parameters/index.browser.js.map +1 -0
  31. package/dist/{api-parameters → api/parameters}/index.d.ts +11 -11
  32. package/dist/api/parameters/index.d.ts.map +1 -0
  33. package/dist/{api-parameters → api/parameters}/index.js +7 -7
  34. package/dist/api/parameters/index.js.map +1 -0
  35. package/dist/{api-users → api/users}/index.browser.js +6 -6
  36. package/dist/api/users/index.browser.js.map +1 -0
  37. package/dist/{api-users → api/users}/index.d.ts +836 -836
  38. package/dist/api/users/index.d.ts.map +1 -0
  39. package/dist/{api-users → api/users}/index.js +99 -766
  40. package/dist/api/users/index.js.map +1 -0
  41. package/dist/{api-verifications → api/verifications}/index.browser.js +5 -5
  42. package/dist/api/verifications/index.browser.js.map +1 -0
  43. package/dist/api/verifications/index.d.ts +248 -0
  44. package/dist/api/verifications/index.d.ts.map +1 -0
  45. package/dist/{api-verifications → api/verifications}/index.js +11 -11
  46. package/dist/api/verifications/index.js.map +1 -0
  47. package/dist/batch/index.d.ts.map +1 -0
  48. package/dist/bucket/index.d.ts.map +1 -0
  49. package/dist/cache/{index.d.ts → core/index.d.ts} +4 -4
  50. package/dist/cache/core/index.d.ts.map +1 -0
  51. package/dist/cache/{index.js → core/index.js} +5 -5
  52. package/dist/cache/core/index.js.map +1 -0
  53. package/dist/{cache-redis → cache/redis}/index.d.ts +2 -2
  54. package/dist/cache/redis/index.d.ts.map +1 -0
  55. package/dist/{cache-redis → cache/redis}/index.js +2 -2
  56. package/dist/cache/redis/index.js.map +1 -0
  57. package/dist/cli/index.d.ts +78 -58
  58. package/dist/cli/index.d.ts.map +1 -0
  59. package/dist/cli/index.js +454 -154
  60. package/dist/cli/index.js.map +1 -1
  61. package/dist/command/index.d.ts +15 -5
  62. package/dist/command/index.d.ts.map +1 -0
  63. package/dist/command/index.js +45 -6
  64. package/dist/command/index.js.map +1 -1
  65. package/dist/core/index.browser.js +1334 -1318
  66. package/dist/core/index.browser.js.map +1 -1
  67. package/dist/core/index.d.ts +75 -71
  68. package/dist/core/index.d.ts.map +1 -0
  69. package/dist/core/index.js +1337 -1321
  70. package/dist/core/index.js.map +1 -1
  71. package/dist/core/index.native.js +1337 -1321
  72. package/dist/core/index.native.js.map +1 -1
  73. package/dist/datetime/index.d.ts.map +1 -0
  74. package/dist/email/index.d.ts.map +1 -0
  75. package/dist/fake/index.d.ts.map +1 -0
  76. package/dist/file/index.d.ts.map +1 -0
  77. package/dist/lock/{index.d.ts → core/index.d.ts} +5 -5
  78. package/dist/lock/core/index.d.ts.map +1 -0
  79. package/dist/lock/{index.js → core/index.js} +5 -5
  80. package/dist/lock/core/index.js.map +1 -0
  81. package/dist/{lock-redis → lock/redis}/index.d.ts +2 -2
  82. package/dist/lock/redis/index.d.ts.map +1 -0
  83. package/dist/{lock-redis → lock/redis}/index.js +2 -2
  84. package/dist/lock/redis/index.js.map +1 -0
  85. package/dist/logger/index.d.ts +1 -0
  86. package/dist/logger/index.d.ts.map +1 -0
  87. package/dist/mcp/index.d.ts +820 -0
  88. package/dist/mcp/index.d.ts.map +1 -0
  89. package/dist/mcp/index.js +978 -0
  90. package/dist/mcp/index.js.map +1 -0
  91. package/dist/orm/index.d.ts +180 -107
  92. package/dist/orm/index.d.ts.map +1 -0
  93. package/dist/orm/index.js +260 -174
  94. package/dist/orm/index.js.map +1 -1
  95. package/dist/queue/core/index.d.ts +548 -0
  96. package/dist/queue/core/index.d.ts.map +1 -0
  97. package/dist/queue/core/index.js +391 -0
  98. package/dist/queue/core/index.js.map +1 -0
  99. package/dist/queue/redis/index.d.ts +28 -0
  100. package/dist/queue/redis/index.d.ts.map +1 -0
  101. package/dist/queue/redis/index.js +43 -0
  102. package/dist/queue/redis/index.js.map +1 -0
  103. package/dist/redis/index.d.ts.map +1 -0
  104. package/dist/retry/index.d.ts.map +1 -0
  105. package/dist/router/index.d.ts.map +1 -0
  106. package/dist/scheduler/index.d.ts +1 -1
  107. package/dist/scheduler/index.d.ts.map +1 -0
  108. package/dist/scheduler/index.js +1 -393
  109. package/dist/scheduler/index.js.map +1 -1
  110. package/dist/security/index.d.ts +1 -1
  111. package/dist/security/index.d.ts.map +1 -0
  112. package/dist/security/index.js +2 -1413
  113. package/dist/security/index.js.map +1 -1
  114. package/dist/{server-auth → server/auth}/index.browser.js +6 -6
  115. package/dist/server/auth/index.browser.js.map +1 -0
  116. package/dist/{server-auth → server/auth}/index.d.ts +167 -167
  117. package/dist/server/auth/index.d.ts.map +1 -0
  118. package/dist/server/auth/index.js +742 -0
  119. package/dist/server/auth/index.js.map +1 -0
  120. package/dist/{server-cache → server/cache}/index.d.ts +2 -2
  121. package/dist/server/cache/index.d.ts.map +1 -0
  122. package/dist/{server-cache → server/cache}/index.js +2 -2
  123. package/dist/server/cache/index.js.map +1 -0
  124. package/dist/{server-compress → server/compress}/index.d.ts +2 -2
  125. package/dist/server/compress/index.d.ts.map +1 -0
  126. package/dist/{server-compress → server/compress}/index.js +2 -2
  127. package/dist/server/compress/index.js.map +1 -0
  128. package/dist/{server-cookies → server/cookies}/index.browser.js +3 -3
  129. package/dist/server/cookies/index.browser.js.map +1 -0
  130. package/dist/{server-cookies → server/cookies}/index.d.ts +4 -4
  131. package/dist/server/cookies/index.d.ts.map +1 -0
  132. package/dist/{server-cookies → server/cookies}/index.js +4 -4
  133. package/dist/server/cookies/index.js.map +1 -0
  134. package/dist/server/{index.browser.js → core/index.browser.js} +14 -14
  135. package/dist/server/core/index.browser.js.map +1 -0
  136. package/dist/server/{index.d.ts → core/index.d.ts} +36 -36
  137. package/dist/server/core/index.d.ts.map +1 -0
  138. package/dist/server/{index.js → core/index.js} +27 -27
  139. package/dist/server/core/index.js.map +1 -0
  140. package/dist/{server-cors → server/cors}/index.d.ts +3 -3
  141. package/dist/server/cors/index.d.ts.map +1 -0
  142. package/dist/{server-cors → server/cors}/index.js +3 -3
  143. package/dist/server/cors/index.js.map +1 -0
  144. package/dist/{server-health → server/health}/index.d.ts +3 -3
  145. package/dist/server/health/index.d.ts.map +1 -0
  146. package/dist/{server-health → server/health}/index.js +3 -3
  147. package/dist/server/health/index.js.map +1 -0
  148. package/dist/{server-helmet → server/helmet}/index.d.ts +2 -2
  149. package/dist/server/helmet/index.d.ts.map +1 -0
  150. package/dist/{server-helmet → server/helmet}/index.js +2 -2
  151. package/dist/server/helmet/index.js.map +1 -0
  152. package/dist/{server-links → server/links}/index.browser.js +5 -5
  153. package/dist/server/links/index.browser.js.map +1 -0
  154. package/dist/{server-links → server/links}/index.d.ts +40 -40
  155. package/dist/server/links/index.d.ts.map +1 -0
  156. package/dist/{server-links → server/links}/index.js +7 -7
  157. package/dist/server/links/index.js.map +1 -0
  158. package/dist/{server-metrics → server/metrics}/index.d.ts +2 -2
  159. package/dist/server/metrics/index.d.ts.map +1 -0
  160. package/dist/server/metrics/index.js +74 -0
  161. package/dist/server/metrics/index.js.map +1 -0
  162. package/dist/{server-multipart → server/multipart}/index.d.ts +2 -2
  163. package/dist/server/multipart/index.d.ts.map +1 -0
  164. package/dist/{server-multipart → server/multipart}/index.js +2 -2
  165. package/dist/server/multipart/index.js.map +1 -0
  166. package/dist/{server-proxy → server/proxy}/index.d.ts +3 -3
  167. package/dist/server/proxy/index.d.ts.map +1 -0
  168. package/dist/{server-proxy → server/proxy}/index.js +3 -3
  169. package/dist/server/proxy/index.js.map +1 -0
  170. package/dist/{server-rate-limit → server/rate-limit}/index.d.ts +4 -4
  171. package/dist/server/rate-limit/index.d.ts.map +1 -0
  172. package/dist/{server-rate-limit → server/rate-limit}/index.js +4 -4
  173. package/dist/server/rate-limit/index.js.map +1 -0
  174. package/dist/{server-security → server/security}/index.browser.js +1 -1
  175. package/dist/server/security/index.browser.js.map +1 -0
  176. package/dist/{server-security → server/security}/index.d.ts +4 -4
  177. package/dist/server/security/index.d.ts.map +1 -0
  178. package/dist/{server-security → server/security}/index.js +4 -4
  179. package/dist/server/security/index.js.map +1 -0
  180. package/dist/{server-static → server/static}/index.d.ts +3 -3
  181. package/dist/server/static/index.d.ts.map +1 -0
  182. package/dist/{server-static → server/static}/index.js +3 -3
  183. package/dist/server/static/index.js.map +1 -0
  184. package/dist/{server-swagger → server/swagger}/index.d.ts +3 -3
  185. package/dist/server/swagger/index.d.ts.map +1 -0
  186. package/dist/{server-swagger → server/swagger}/index.js +4 -4
  187. package/dist/server/swagger/index.js.map +1 -0
  188. package/dist/sms/index.d.ts.map +1 -0
  189. package/dist/thread/index.d.ts.map +1 -0
  190. package/dist/topic/{index.d.ts → core/index.d.ts} +6 -6
  191. package/dist/topic/core/index.d.ts.map +1 -0
  192. package/dist/topic/{index.js → core/index.js} +6 -6
  193. package/dist/topic/core/index.js.map +1 -0
  194. package/dist/{topic-redis → topic/redis}/index.d.ts +2 -2
  195. package/dist/topic/redis/index.d.ts.map +1 -0
  196. package/dist/{topic-redis → topic/redis}/index.js +2 -2
  197. package/dist/topic/redis/index.js.map +1 -0
  198. package/dist/vite/index.d.ts +21 -2
  199. package/dist/vite/index.d.ts.map +1 -0
  200. package/dist/vite/index.js +48 -19
  201. package/dist/vite/index.js.map +1 -1
  202. package/dist/websocket/index.d.ts.map +1 -0
  203. package/package.json +162 -158
  204. package/src/{api-files → api/files}/index.ts +1 -0
  205. package/src/{api-parameters → api/parameters}/index.ts +1 -1
  206. package/src/{api-users → api/users}/primitives/$userRealm.ts +1 -1
  207. package/src/{api-users → api/users}/providers/UserRealmProvider.ts +6 -7
  208. package/src/{api-verifications → api/verifications}/index.ts +2 -0
  209. package/src/cli/apps/AlephaCli.ts +2 -0
  210. package/src/cli/apps/AlephaPackageBuilderCli.ts +83 -54
  211. package/src/cli/assets/appRouterTs.ts +1 -1
  212. package/src/cli/assets/biomeJson.ts +1 -1
  213. package/src/cli/assets/indexHtml.ts +1 -1
  214. package/src/cli/assets/mainBrowserTs.ts +1 -1
  215. package/src/cli/assets/mainTs.ts +9 -10
  216. package/src/cli/assets/viteConfigTs.ts +1 -1
  217. package/src/cli/commands/ChangelogCommands.ts +389 -0
  218. package/src/cli/commands/CoreCommands.ts +10 -6
  219. package/src/cli/commands/DrizzleCommands.ts +204 -4
  220. package/src/cli/commands/VerifyCommands.ts +4 -1
  221. package/src/cli/commands/ViteCommands.ts +46 -25
  222. package/src/cli/services/AlephaCliUtils.ts +52 -164
  223. package/src/command/providers/CliProvider.ts +76 -5
  224. package/src/core/providers/SchemaValidator.ts +24 -2
  225. package/src/mcp/errors/McpError.ts +72 -0
  226. package/src/mcp/helpers/jsonrpc.ts +163 -0
  227. package/src/mcp/index.ts +132 -0
  228. package/src/mcp/interfaces/McpTypes.ts +248 -0
  229. package/src/mcp/primitives/$prompt.ts +188 -0
  230. package/src/mcp/primitives/$resource.ts +171 -0
  231. package/src/mcp/primitives/$tool.ts +285 -0
  232. package/src/mcp/providers/McpServerProvider.ts +382 -0
  233. package/src/mcp/transports/SseMcpTransport.ts +172 -0
  234. package/src/mcp/transports/StdioMcpTransport.ts +126 -0
  235. package/src/orm/index.ts +12 -0
  236. package/src/orm/providers/drivers/CloudflareD1Provider.ts +164 -0
  237. package/src/orm/providers/drivers/NodeSqliteProvider.ts +3 -1
  238. package/src/queue/{index.ts → core/index.ts} +2 -3
  239. package/src/queue/{primitives → core/primitives}/$queue.ts +17 -162
  240. package/src/queue/core/providers/MemoryQueueProvider.ts +19 -0
  241. package/src/queue/core/providers/QueueProvider.ts +23 -0
  242. package/src/queue/core/providers/WorkerProvider.ts +244 -0
  243. package/src/queue/redis/providers/RedisQueueProvider.ts +31 -0
  244. package/src/server/{index.ts → core/index.ts} +1 -0
  245. package/src/{server-rate-limit → server/rate-limit}/index.ts +1 -1
  246. package/src/{server-swagger → server/swagger}/providers/ServerSwaggerProvider.ts +1 -0
  247. package/src/vite/plugins/viteAlephaBuild.ts +8 -2
  248. package/src/vite/plugins/viteAlephaDev.ts +6 -2
  249. package/src/vite/tasks/buildServer.ts +1 -1
  250. package/src/vite/tasks/copyAssets.ts +32 -8
  251. package/src/vite/tasks/generateCloudflare.ts +43 -15
  252. package/src/vite/tasks/runAlepha.ts +1 -0
  253. package/dist/api-audits/index.browser.js.map +0 -1
  254. package/dist/api-audits/index.js.map +0 -1
  255. package/dist/api-files/index.browser.js.map +0 -1
  256. package/dist/api-files/index.js.map +0 -1
  257. package/dist/api-jobs/index.browser.js.map +0 -1
  258. package/dist/api-jobs/index.js.map +0 -1
  259. package/dist/api-notifications/index.browser.js.map +0 -1
  260. package/dist/api-notifications/index.d.ts +0 -327
  261. package/dist/api-notifications/index.js.map +0 -1
  262. package/dist/api-parameters/index.browser.js.map +0 -1
  263. package/dist/api-parameters/index.js.map +0 -1
  264. package/dist/api-users/index.browser.js.map +0 -1
  265. package/dist/api-users/index.js.map +0 -1
  266. package/dist/api-verifications/index.browser.js.map +0 -1
  267. package/dist/api-verifications/index.d.ts +0 -229
  268. package/dist/api-verifications/index.js.map +0 -1
  269. package/dist/cache/index.js.map +0 -1
  270. package/dist/cache-redis/index.js.map +0 -1
  271. package/dist/lock/index.js.map +0 -1
  272. package/dist/lock-redis/index.js.map +0 -1
  273. package/dist/queue/index.d.ts +0 -1265
  274. package/dist/queue/index.js +0 -1037
  275. package/dist/queue/index.js.map +0 -1
  276. package/dist/queue-redis/index.d.ts +0 -82
  277. package/dist/queue-redis/index.js +0 -872
  278. package/dist/queue-redis/index.js.map +0 -1
  279. package/dist/server/index.browser.js.map +0 -1
  280. package/dist/server/index.js.map +0 -1
  281. package/dist/server-auth/index.browser.js.map +0 -1
  282. package/dist/server-auth/index.js +0 -1973
  283. package/dist/server-auth/index.js.map +0 -1
  284. package/dist/server-cache/index.js.map +0 -1
  285. package/dist/server-compress/index.js.map +0 -1
  286. package/dist/server-cookies/index.browser.js.map +0 -1
  287. package/dist/server-cookies/index.js.map +0 -1
  288. package/dist/server-cors/index.js.map +0 -1
  289. package/dist/server-health/index.js.map +0 -1
  290. package/dist/server-helmet/index.js.map +0 -1
  291. package/dist/server-links/index.browser.js.map +0 -1
  292. package/dist/server-links/index.js.map +0 -1
  293. package/dist/server-metrics/index.js +0 -4532
  294. package/dist/server-metrics/index.js.map +0 -1
  295. package/dist/server-multipart/index.js.map +0 -1
  296. package/dist/server-proxy/index.js.map +0 -1
  297. package/dist/server-rate-limit/index.js.map +0 -1
  298. package/dist/server-security/index.browser.js.map +0 -1
  299. package/dist/server-security/index.js.map +0 -1
  300. package/dist/server-static/index.js.map +0 -1
  301. package/dist/server-swagger/index.js.map +0 -1
  302. package/dist/topic/index.js.map +0 -1
  303. package/dist/topic-redis/index.js.map +0 -1
  304. package/src/queue/interfaces/QueueJob.ts +0 -459
  305. package/src/queue/providers/MemoryQueueProvider.ts +0 -850
  306. package/src/queue/providers/QueueProvider.ts +0 -319
  307. package/src/queue/providers/WorkerProvider.ts +0 -344
  308. package/src/queue-redis/providers/RedisQueueProvider.ts +0 -1209
  309. /package/src/{api-audits → api/audits}/controllers/AuditController.ts +0 -0
  310. /package/src/{api-audits → api/audits}/entities/audits.ts +0 -0
  311. /package/src/{api-audits → api/audits}/index.browser.ts +0 -0
  312. /package/src/{api-audits → api/audits}/index.ts +0 -0
  313. /package/src/{api-audits → api/audits}/primitives/$audit.ts +0 -0
  314. /package/src/{api-audits → api/audits}/schemas/auditQuerySchema.ts +0 -0
  315. /package/src/{api-audits → api/audits}/schemas/auditResourceSchema.ts +0 -0
  316. /package/src/{api-audits → api/audits}/schemas/createAuditSchema.ts +0 -0
  317. /package/src/{api-audits → api/audits}/services/AuditService.ts +0 -0
  318. /package/src/{api-files → api/files}/controllers/FileController.ts +0 -0
  319. /package/src/{api-files → api/files}/controllers/StorageStatsController.ts +0 -0
  320. /package/src/{api-files → api/files}/entities/files.ts +0 -0
  321. /package/src/{api-files → api/files}/index.browser.ts +0 -0
  322. /package/src/{api-files → api/files}/jobs/FileJobs.ts +0 -0
  323. /package/src/{api-files → api/files}/schemas/fileQuerySchema.ts +0 -0
  324. /package/src/{api-files → api/files}/schemas/fileResourceSchema.ts +0 -0
  325. /package/src/{api-files → api/files}/schemas/storageStatsSchema.ts +0 -0
  326. /package/src/{api-files → api/files}/services/FileService.ts +0 -0
  327. /package/src/{api-jobs → api/jobs}/controllers/JobController.ts +0 -0
  328. /package/src/{api-jobs → api/jobs}/entities/jobExecutions.ts +0 -0
  329. /package/src/{api-jobs → api/jobs}/index.browser.ts +0 -0
  330. /package/src/{api-jobs → api/jobs}/index.ts +0 -0
  331. /package/src/{api-jobs → api/jobs}/primitives/$job.ts +0 -0
  332. /package/src/{api-jobs → api/jobs}/providers/JobProvider.ts +0 -0
  333. /package/src/{api-jobs → api/jobs}/schemas/jobExecutionQuerySchema.ts +0 -0
  334. /package/src/{api-jobs → api/jobs}/schemas/jobExecutionResourceSchema.ts +0 -0
  335. /package/src/{api-jobs → api/jobs}/schemas/triggerJobSchema.ts +0 -0
  336. /package/src/{api-jobs → api/jobs}/services/JobService.ts +0 -0
  337. /package/src/{api-notifications → api/notifications}/controllers/NotificationController.ts +0 -0
  338. /package/src/{api-notifications → api/notifications}/entities/notifications.ts +0 -0
  339. /package/src/{api-notifications → api/notifications}/index.browser.ts +0 -0
  340. /package/src/{api-notifications → api/notifications}/index.ts +0 -0
  341. /package/src/{api-notifications → api/notifications}/jobs/NotificationJobs.ts +0 -0
  342. /package/src/{api-notifications → api/notifications}/primitives/$notification.ts +0 -0
  343. /package/src/{api-notifications → api/notifications}/queues/NotificationQueues.ts +0 -0
  344. /package/src/{api-notifications → api/notifications}/schemas/notificationContactPreferencesSchema.ts +0 -0
  345. /package/src/{api-notifications → api/notifications}/schemas/notificationContactSchema.ts +0 -0
  346. /package/src/{api-notifications → api/notifications}/schemas/notificationCreateSchema.ts +0 -0
  347. /package/src/{api-notifications → api/notifications}/schemas/notificationQuerySchema.ts +0 -0
  348. /package/src/{api-notifications → api/notifications}/services/NotificationSenderService.ts +0 -0
  349. /package/src/{api-notifications → api/notifications}/services/NotificationService.ts +0 -0
  350. /package/src/{api-parameters → api/parameters}/controllers/ConfigController.ts +0 -0
  351. /package/src/{api-parameters → api/parameters}/entities/parameters.ts +0 -0
  352. /package/src/{api-parameters → api/parameters}/index.browser.ts +0 -0
  353. /package/src/{api-parameters → api/parameters}/primitives/$config.ts +0 -0
  354. /package/src/{api-parameters → api/parameters}/schedulers/ConfigActivationScheduler.ts +0 -0
  355. /package/src/{api-parameters → api/parameters}/services/ConfigStore.ts +0 -0
  356. /package/src/{api-users → api/users}/atoms/realmAuthSettingsAtom.ts +0 -0
  357. /package/src/{api-users → api/users}/controllers/IdentityController.ts +0 -0
  358. /package/src/{api-users → api/users}/controllers/SessionController.ts +0 -0
  359. /package/src/{api-users → api/users}/controllers/UserController.ts +0 -0
  360. /package/src/{api-users → api/users}/controllers/UserRealmController.ts +0 -0
  361. /package/src/{api-users → api/users}/entities/identities.ts +0 -0
  362. /package/src/{api-users → api/users}/entities/sessions.ts +0 -0
  363. /package/src/{api-users → api/users}/entities/users.ts +0 -0
  364. /package/src/{api-users → api/users}/index.browser.ts +0 -0
  365. /package/src/{api-users → api/users}/index.ts +0 -0
  366. /package/src/{api-users → api/users}/notifications/UserNotifications.ts +0 -0
  367. /package/src/{api-users → api/users}/schemas/completePasswordResetRequestSchema.ts +0 -0
  368. /package/src/{api-users → api/users}/schemas/completeRegistrationRequestSchema.ts +0 -0
  369. /package/src/{api-users → api/users}/schemas/createUserSchema.ts +0 -0
  370. /package/src/{api-users → api/users}/schemas/identityQuerySchema.ts +0 -0
  371. /package/src/{api-users → api/users}/schemas/identityResourceSchema.ts +0 -0
  372. /package/src/{api-users → api/users}/schemas/loginSchema.ts +0 -0
  373. /package/src/{api-users → api/users}/schemas/passwordResetIntentResponseSchema.ts +0 -0
  374. /package/src/{api-users → api/users}/schemas/registerQuerySchema.ts +0 -0
  375. /package/src/{api-users → api/users}/schemas/registerRequestSchema.ts +0 -0
  376. /package/src/{api-users → api/users}/schemas/registerResponseSchema.ts +0 -0
  377. /package/src/{api-users → api/users}/schemas/registerSchema.ts +0 -0
  378. /package/src/{api-users → api/users}/schemas/registrationIntentResponseSchema.ts +0 -0
  379. /package/src/{api-users → api/users}/schemas/resetPasswordSchema.ts +0 -0
  380. /package/src/{api-users → api/users}/schemas/sessionQuerySchema.ts +0 -0
  381. /package/src/{api-users → api/users}/schemas/sessionResourceSchema.ts +0 -0
  382. /package/src/{api-users → api/users}/schemas/updateUserSchema.ts +0 -0
  383. /package/src/{api-users → api/users}/schemas/userQuerySchema.ts +0 -0
  384. /package/src/{api-users → api/users}/schemas/userRealmConfigSchema.ts +0 -0
  385. /package/src/{api-users → api/users}/schemas/userResourceSchema.ts +0 -0
  386. /package/src/{api-users → api/users}/services/CredentialService.ts +0 -0
  387. /package/src/{api-users → api/users}/services/IdentityService.ts +0 -0
  388. /package/src/{api-users → api/users}/services/RegistrationService.ts +0 -0
  389. /package/src/{api-users → api/users}/services/SessionCrudService.ts +0 -0
  390. /package/src/{api-users → api/users}/services/SessionService.ts +0 -0
  391. /package/src/{api-users → api/users}/services/UserService.ts +0 -0
  392. /package/src/{api-verifications → api/verifications}/controllers/VerificationController.ts +0 -0
  393. /package/src/{api-verifications → api/verifications}/entities/verifications.ts +0 -0
  394. /package/src/{api-verifications → api/verifications}/index.browser.ts +0 -0
  395. /package/src/{api-verifications → api/verifications}/jobs/VerificationJobs.ts +0 -0
  396. /package/src/{api-verifications → api/verifications}/parameters/VerificationParameters.ts +0 -0
  397. /package/src/{api-verifications → api/verifications}/schemas/requestVerificationCodeResponseSchema.ts +0 -0
  398. /package/src/{api-verifications → api/verifications}/schemas/validateVerificationCodeResponseSchema.ts +0 -0
  399. /package/src/{api-verifications → api/verifications}/schemas/verificationSettingsSchema.ts +0 -0
  400. /package/src/{api-verifications → api/verifications}/schemas/verificationTypeEnumSchema.ts +0 -0
  401. /package/src/{api-verifications → api/verifications}/services/VerificationService.ts +0 -0
  402. /package/src/cache/{errors → core/errors}/CacheError.ts +0 -0
  403. /package/src/cache/{index.ts → core/index.ts} +0 -0
  404. /package/src/cache/{primitives → core/primitives}/$cache.ts +0 -0
  405. /package/src/cache/{providers → core/providers}/CacheProvider.ts +0 -0
  406. /package/src/cache/{providers → core/providers}/MemoryCacheProvider.ts +0 -0
  407. /package/src/{cache-redis → cache/redis}/index.ts +0 -0
  408. /package/src/{cache-redis → cache/redis}/providers/RedisCacheProvider.ts +0 -0
  409. /package/src/lock/{index.ts → core/index.ts} +0 -0
  410. /package/src/lock/{primitives → core/primitives}/$lock.ts +0 -0
  411. /package/src/lock/{providers → core/providers}/LockProvider.ts +0 -0
  412. /package/src/lock/{providers → core/providers}/LockTopicProvider.ts +0 -0
  413. /package/src/lock/{providers → core/providers}/MemoryLockProvider.ts +0 -0
  414. /package/src/{lock-redis → lock/redis}/index.ts +0 -0
  415. /package/src/{lock-redis → lock/redis}/providers/RedisLockProvider.ts +0 -0
  416. /package/src/queue/{primitives → core/primitives}/$consumer.ts +0 -0
  417. /package/src/{queue-redis → queue/redis}/index.ts +0 -0
  418. /package/src/{server-auth → server/auth}/constants/routes.ts +0 -0
  419. /package/src/{server-auth → server/auth}/index.browser.ts +0 -0
  420. /package/src/{server-auth → server/auth}/index.shared.ts +0 -0
  421. /package/src/{server-auth → server/auth}/index.ts +0 -0
  422. /package/src/{server-auth → server/auth}/primitives/$auth.ts +0 -0
  423. /package/src/{server-auth → server/auth}/primitives/$authApple.ts +0 -0
  424. /package/src/{server-auth → server/auth}/primitives/$authCredentials.ts +0 -0
  425. /package/src/{server-auth → server/auth}/primitives/$authGithub.ts +0 -0
  426. /package/src/{server-auth → server/auth}/primitives/$authGoogle.ts +0 -0
  427. /package/src/{server-auth → server/auth}/providers/ServerAuthProvider.ts +0 -0
  428. /package/src/{server-auth → server/auth}/schemas/authenticationProviderSchema.ts +0 -0
  429. /package/src/{server-auth → server/auth}/schemas/tokenResponseSchema.ts +0 -0
  430. /package/src/{server-auth → server/auth}/schemas/tokensSchema.ts +0 -0
  431. /package/src/{server-auth → server/auth}/schemas/userinfoResponseSchema.ts +0 -0
  432. /package/src/{server-cache → server/cache}/index.ts +0 -0
  433. /package/src/{server-cache → server/cache}/providers/ServerCacheProvider.ts +0 -0
  434. /package/src/{server-compress → server/compress}/index.ts +0 -0
  435. /package/src/{server-compress → server/compress}/providers/ServerCompressProvider.ts +0 -0
  436. /package/src/{server-cookies → server/cookies}/index.browser.ts +0 -0
  437. /package/src/{server-cookies → server/cookies}/index.ts +0 -0
  438. /package/src/{server-cookies → server/cookies}/primitives/$cookie.browser.ts +0 -0
  439. /package/src/{server-cookies → server/cookies}/primitives/$cookie.ts +0 -0
  440. /package/src/{server-cookies → server/cookies}/providers/ServerCookiesProvider.ts +0 -0
  441. /package/src/{server-cookies → server/cookies}/services/CookieParser.ts +0 -0
  442. /package/src/server/{constants → core/constants}/routeMethods.ts +0 -0
  443. /package/src/server/{errors → core/errors}/BadRequestError.ts +0 -0
  444. /package/src/server/{errors → core/errors}/ConflictError.ts +0 -0
  445. /package/src/server/{errors → core/errors}/ForbiddenError.ts +0 -0
  446. /package/src/server/{errors → core/errors}/HttpError.ts +0 -0
  447. /package/src/server/{errors → core/errors}/NotFoundError.ts +0 -0
  448. /package/src/server/{errors → core/errors}/UnauthorizedError.ts +0 -0
  449. /package/src/server/{errors → core/errors}/ValidationError.ts +0 -0
  450. /package/src/server/{helpers → core/helpers}/ServerReply.ts +0 -0
  451. /package/src/server/{helpers → core/helpers}/isMultipart.ts +0 -0
  452. /package/src/server/{index.browser.ts → core/index.browser.ts} +0 -0
  453. /package/src/server/{index.shared.ts → core/index.shared.ts} +0 -0
  454. /package/src/server/{interfaces → core/interfaces}/ServerRequest.ts +0 -0
  455. /package/src/server/{primitives → core/primitives}/$action.ts +0 -0
  456. /package/src/server/{primitives → core/primitives}/$route.ts +0 -0
  457. /package/src/server/{providers → core/providers}/BunHttpServerProvider.ts +0 -0
  458. /package/src/server/{providers → core/providers}/NodeHttpServerProvider.ts +0 -0
  459. /package/src/server/{providers → core/providers}/ServerBodyParserProvider.ts +0 -0
  460. /package/src/server/{providers → core/providers}/ServerLoggerProvider.ts +0 -0
  461. /package/src/server/{providers → core/providers}/ServerNotReadyProvider.ts +0 -0
  462. /package/src/server/{providers → core/providers}/ServerProvider.ts +0 -0
  463. /package/src/server/{providers → core/providers}/ServerRouterProvider.ts +0 -0
  464. /package/src/server/{providers → core/providers}/ServerTimingProvider.ts +0 -0
  465. /package/src/server/{schemas → core/schemas}/errorSchema.ts +0 -0
  466. /package/src/server/{schemas → core/schemas}/okSchema.ts +0 -0
  467. /package/src/server/{services → core/services}/HttpClient.ts +0 -0
  468. /package/src/server/{services → core/services}/ServerRequestParser.ts +0 -0
  469. /package/src/server/{services → core/services}/UserAgentParser.ts +0 -0
  470. /package/src/{server-cors → server/cors}/index.ts +0 -0
  471. /package/src/{server-cors → server/cors}/primitives/$cors.ts +0 -0
  472. /package/src/{server-cors → server/cors}/providers/ServerCorsProvider.ts +0 -0
  473. /package/src/{server-health → server/health}/index.ts +0 -0
  474. /package/src/{server-health → server/health}/providers/ServerHealthProvider.ts +0 -0
  475. /package/src/{server-health → server/health}/schemas/healthSchema.ts +0 -0
  476. /package/src/{server-helmet → server/helmet}/index.ts +0 -0
  477. /package/src/{server-helmet → server/helmet}/providers/ServerHelmetProvider.ts +0 -0
  478. /package/src/{server-links → server/links}/index.browser.ts +0 -0
  479. /package/src/{server-links → server/links}/index.ts +0 -0
  480. /package/src/{server-links → server/links}/primitives/$client.ts +0 -0
  481. /package/src/{server-links → server/links}/primitives/$remote.ts +0 -0
  482. /package/src/{server-links → server/links}/providers/LinkProvider.ts +0 -0
  483. /package/src/{server-links → server/links}/providers/RemotePrimitiveProvider.ts +0 -0
  484. /package/src/{server-links → server/links}/providers/ServerLinksProvider.ts +0 -0
  485. /package/src/{server-links → server/links}/schemas/apiLinksResponseSchema.ts +0 -0
  486. /package/src/{server-metrics → server/metrics}/index.ts +0 -0
  487. /package/src/{server-metrics → server/metrics}/providers/ServerMetricsProvider.ts +0 -0
  488. /package/src/{server-multipart → server/multipart}/index.ts +0 -0
  489. /package/src/{server-multipart → server/multipart}/providers/ServerMultipartProvider.ts +0 -0
  490. /package/src/{server-proxy → server/proxy}/index.ts +0 -0
  491. /package/src/{server-proxy → server/proxy}/primitives/$proxy.ts +0 -0
  492. /package/src/{server-proxy → server/proxy}/providers/ServerProxyProvider.ts +0 -0
  493. /package/src/{server-rate-limit → server/rate-limit}/primitives/$rateLimit.ts +0 -0
  494. /package/src/{server-rate-limit → server/rate-limit}/providers/ServerRateLimitProvider.ts +0 -0
  495. /package/src/{server-security → server/security}/index.browser.ts +0 -0
  496. /package/src/{server-security → server/security}/index.ts +0 -0
  497. /package/src/{server-security → server/security}/primitives/$basicAuth.ts +0 -0
  498. /package/src/{server-security → server/security}/providers/ServerBasicAuthProvider.ts +0 -0
  499. /package/src/{server-security → server/security}/providers/ServerSecurityProvider.ts +0 -0
  500. /package/src/{server-static → server/static}/index.ts +0 -0
  501. /package/src/{server-static → server/static}/primitives/$serve.ts +0 -0
  502. /package/src/{server-static → server/static}/providers/ServerStaticProvider.ts +0 -0
  503. /package/src/{server-swagger → server/swagger}/index.ts +0 -0
  504. /package/src/{server-swagger → server/swagger}/primitives/$swagger.ts +0 -0
  505. /package/src/topic/{errors → core/errors}/TopicTimeoutError.ts +0 -0
  506. /package/src/topic/{index.ts → core/index.ts} +0 -0
  507. /package/src/topic/{primitives → core/primitives}/$subscriber.ts +0 -0
  508. /package/src/topic/{primitives → core/primitives}/$topic.ts +0 -0
  509. /package/src/topic/{providers → core/providers}/MemoryTopicProvider.ts +0 -0
  510. /package/src/topic/{providers → core/providers}/TopicProvider.ts +0 -0
  511. /package/src/{topic-redis → topic/redis}/index.ts +0 -0
  512. /package/src/{topic-redis → topic/redis}/providers/RedisTopicProvider.ts +0 -0
@@ -1,1209 +0,0 @@
1
- import { $env, $hook, $inject, type Static, t } from "alepha";
2
- import { $logger } from "alepha/logger";
3
- import {
4
- type QueueAcquiredJob,
5
- type QueueCleanOptions,
6
- type QueueGetJobsOptions,
7
- type QueueJob,
8
- type QueueJobCounts,
9
- type QueueJobOptions,
10
- type QueueJobStatus,
11
- QueueProvider,
12
- } from "alepha/queue";
13
- import { type RedisClient, RedisProvider } from "alepha/redis";
14
-
15
- // Default job options
16
- const DEFAULT_MAX_ATTEMPTS = 1;
17
- const DEFAULT_LOCK_DURATION = 30000; // 30 seconds
18
- const DEFAULT_BACKOFF_DELAY = 1000; // 1 second
19
- const DEFAULT_BACKOFF_MAX_DELAY = 30000; // 30 seconds
20
-
21
- const envSchema = t.object({
22
- REDIS_QUEUE_PREFIX: t.text({
23
- default: "queue",
24
- }),
25
- });
26
-
27
- // Lua script for atomic job acquisition
28
- // This script atomically:
29
- // 1. Gets the highest priority job from waiting ZSET
30
- // 2. Removes it from waiting
31
- // 3. Adds it to active SET
32
- // 4. Updates job state in HASH
33
- // Returns: job data as JSON string, or nil if no job available
34
- const ACQUIRE_JOB_SCRIPT = `
35
- local waitingKey = KEYS[1]
36
- local activeKey = KEYS[2]
37
- local jobKeyPrefix = KEYS[3]
38
- local workerId = ARGV[1]
39
- local now = tonumber(ARGV[2])
40
- local lockDuration = tonumber(ARGV[3])
41
-
42
- -- Get highest priority job (lowest score)
43
- local jobs = redis.call('ZRANGE', waitingKey, 0, 0)
44
- if #jobs == 0 then
45
- return nil
46
- end
47
-
48
- local jobId = jobs[1]
49
- local jobKey = jobKeyPrefix .. ':' .. jobId
50
-
51
- -- Remove from waiting (atomic check)
52
- local removed = redis.call('ZREM', waitingKey, jobId)
53
- if removed == 0 then
54
- return nil
55
- end
56
-
57
- -- Get current job data
58
- local jobData = redis.call('HGETALL', jobKey)
59
- if #jobData == 0 then
60
- return nil
61
- end
62
-
63
- -- Parse job data into table
64
- local job = {}
65
- for i = 1, #jobData, 2 do
66
- job[jobData[i]] = jobData[i + 1]
67
- end
68
-
69
- -- Parse current state
70
- local state = cjson.decode(job['state'])
71
- local options = cjson.decode(job['options'])
72
-
73
- -- Update state
74
- state['status'] = 'active'
75
- state['attempts'] = state['attempts'] + 1
76
- state['lockedBy'] = workerId
77
- state['lockedUntil'] = now + (options['lockDuration'] or lockDuration)
78
- state['processedAt'] = now
79
-
80
- -- Save updated state
81
- redis.call('HSET', jobKey, 'state', cjson.encode(state))
82
-
83
- -- Add to active set
84
- redis.call('SADD', activeKey, jobId)
85
-
86
- -- Return job data
87
- return cjson.encode({
88
- id = job['id'],
89
- queue = job['queue'],
90
- payload = cjson.decode(job['payload']),
91
- options = options,
92
- state = state
93
- })
94
- `;
95
-
96
- // Lua script for completing a job with removeOnComplete support
97
- const COMPLETE_JOB_SCRIPT = `
98
- local jobKey = KEYS[1]
99
- local activeKey = KEYS[2]
100
- local completedKey = KEYS[3]
101
- local jobId = ARGV[1]
102
- local now = tonumber(ARGV[2])
103
- local result = ARGV[3]
104
-
105
- -- Get job data
106
- local jobData = redis.call('HGETALL', jobKey)
107
- if #jobData == 0 then
108
- return nil
109
- end
110
-
111
- -- Parse job data
112
- local job = {}
113
- for i = 1, #jobData, 2 do
114
- job[jobData[i]] = jobData[i + 1]
115
- end
116
-
117
- local state = cjson.decode(job['state'])
118
- local options = cjson.decode(job['options'])
119
- local processedAt = state['processedAt'] or now
120
-
121
- -- Remove from active
122
- redis.call('SREM', activeKey, jobId)
123
-
124
- -- Update state
125
- state['status'] = 'completed'
126
- state['completedAt'] = now
127
- state['result'] = result ~= '' and cjson.decode(result) or nil
128
- state['lockedBy'] = nil
129
- state['lockedUntil'] = nil
130
-
131
- local removeOnComplete = options['removeOnComplete']
132
-
133
- if removeOnComplete == true then
134
- -- Remove job immediately
135
- redis.call('DEL', jobKey)
136
- return cjson.encode({ removed = true, duration = now - processedAt })
137
- else
138
- -- Update job state
139
- redis.call('HSET', jobKey, 'state', cjson.encode(state))
140
-
141
- -- Add to completed list (newest first)
142
- redis.call('LPUSH', completedKey, jobId)
143
-
144
- -- If removeOnComplete is a number, trim the list (0 means keep none)
145
- if type(removeOnComplete) == 'number' and removeOnComplete >= 0 then
146
- -- Get jobs to remove
147
- local toRemove = redis.call('LRANGE', completedKey, removeOnComplete, -1)
148
- for _, oldJobId in ipairs(toRemove) do
149
- redis.call('DEL', jobKey:gsub(jobId, oldJobId))
150
- end
151
- redis.call('LTRIM', completedKey, 0, removeOnComplete - 1)
152
- end
153
-
154
- return cjson.encode({ removed = false, duration = now - processedAt })
155
- end
156
- `;
157
-
158
- // Lua script for failing a job with retry support
159
- const FAIL_JOB_SCRIPT = `
160
- local jobKey = KEYS[1]
161
- local activeKey = KEYS[2]
162
- local delayedKey = KEYS[3]
163
- local failedKey = KEYS[4]
164
- local jobId = ARGV[1]
165
- local now = tonumber(ARGV[2])
166
- local errorMsg = ARGV[3]
167
- local stackTrace = ARGV[4]
168
- local backoffDelay = tonumber(ARGV[5])
169
-
170
- -- Get job data
171
- local jobData = redis.call('HGETALL', jobKey)
172
- if #jobData == 0 then
173
- return nil
174
- end
175
-
176
- -- Parse job data
177
- local job = {}
178
- for i = 1, #jobData, 2 do
179
- job[jobData[i]] = jobData[i + 1]
180
- end
181
-
182
- local state = cjson.decode(job['state'])
183
- local options = cjson.decode(job['options'])
184
-
185
- -- Remove from active
186
- redis.call('SREM', activeKey, jobId)
187
-
188
- local maxAttempts = options['maxAttempts'] or 1
189
- local hasMoreAttempts = state['attempts'] < maxAttempts
190
-
191
- if hasMoreAttempts then
192
- -- Schedule for retry
193
- state['status'] = 'delayed'
194
- state['availableAt'] = now + backoffDelay
195
- state['error'] = errorMsg
196
- state['stackTrace'] = stackTrace ~= '' and stackTrace or nil
197
- state['lockedBy'] = nil
198
- state['lockedUntil'] = nil
199
-
200
- redis.call('HSET', jobKey, 'state', cjson.encode(state))
201
- redis.call('ZADD', delayedKey, now + backoffDelay, jobId)
202
-
203
- return cjson.encode({ status = 'retrying', delay = backoffDelay, attempt = state['attempts'] + 1 })
204
- else
205
- -- Permanently failed
206
- state['status'] = 'failed'
207
- state['failedAt'] = now
208
- state['error'] = errorMsg
209
- state['stackTrace'] = stackTrace ~= '' and stackTrace or nil
210
- state['lockedBy'] = nil
211
- state['lockedUntil'] = nil
212
-
213
- local removeOnFail = options['removeOnFail']
214
-
215
- if removeOnFail == true then
216
- redis.call('DEL', jobKey)
217
- return cjson.encode({ status = 'failed', removed = true, attempts = state['attempts'] })
218
- else
219
- redis.call('HSET', jobKey, 'state', cjson.encode(state))
220
- redis.call('LPUSH', failedKey, jobId)
221
-
222
- if type(removeOnFail) == 'number' and removeOnFail >= 0 then
223
- local toRemove = redis.call('LRANGE', failedKey, removeOnFail, -1)
224
- for _, oldJobId in ipairs(toRemove) do
225
- redis.call('DEL', jobKey:gsub(jobId, oldJobId))
226
- end
227
- redis.call('LTRIM', failedKey, 0, removeOnFail - 1)
228
- end
229
-
230
- return cjson.encode({ status = 'failed', removed = false, attempts = state['attempts'] })
231
- end
232
- end
233
- `;
234
-
235
- /**
236
- * Redis-based queue provider with full job support.
237
- *
238
- * Features:
239
- * - Atomic job acquisition using Lua scripts
240
- * - Blocking wait using Redis BZPOPMIN (no polling)
241
- * - Event emission for job lifecycle
242
- * - removeOnComplete/removeOnFail support
243
- *
244
- * Uses the following Redis data structures:
245
- * - HASH `{prefix}:job:{queue}:{id}` - Job data
246
- * - ZSET `{prefix}:waiting:{queue}` - Waiting jobs (score = priority)
247
- * - ZSET `{prefix}:delayed:{queue}` - Delayed jobs (score = availableAt timestamp)
248
- * - SET `{prefix}:active:{queue}` - Active jobs
249
- * - LIST `{prefix}:completed:{queue}` - Completed jobs (newest first)
250
- * - LIST `{prefix}:failed:{queue}` - Failed jobs (newest first)
251
- * - LIST `{prefix}:messages:{queue}` - Simple message queue (backward compat)
252
- * - LIST `{prefix}:notify:{queue}` - Notification list for blocking wait
253
- */
254
- export class RedisQueueProvider extends QueueProvider {
255
- protected readonly log = $logger();
256
- protected readonly env: Static<typeof envSchema> = $env(envSchema);
257
- protected readonly redisProvider: RedisProvider = $inject(RedisProvider);
258
-
259
- // Dedicated connection for blocking operations
260
- protected blockingClient: RedisClient | undefined;
261
- protected shouldStop = false;
262
-
263
- // Loaded Lua script SHAs
264
- protected acquireJobSha: string | undefined;
265
- protected completeJobSha: string | undefined;
266
- protected failJobSha: string | undefined;
267
-
268
- protected readonly start = $hook({
269
- on: "start",
270
- handler: async () => {
271
- this.shouldStop = false;
272
- this.blockingClient = this.redisProvider.duplicate();
273
- await this.blockingClient.connect();
274
-
275
- // Load Lua scripts
276
- const redis = this.redisProvider.publisher;
277
- const acquireSha = await redis.scriptLoad(ACQUIRE_JOB_SCRIPT);
278
- const completeSha = await redis.scriptLoad(COMPLETE_JOB_SCRIPT);
279
- const failSha = await redis.scriptLoad(FAIL_JOB_SCRIPT);
280
- this.acquireJobSha = acquireSha.toString();
281
- this.completeJobSha = completeSha.toString();
282
- this.failJobSha = failSha.toString();
283
- },
284
- });
285
-
286
- protected readonly stop = $hook({
287
- on: "stop",
288
- handler: async () => {
289
- this.shouldStop = true;
290
- if (this.blockingClient?.isOpen) {
291
- await this.blockingClient.close();
292
- }
293
- },
294
- });
295
-
296
- // ===========================================
297
- // Key helpers
298
- // ===========================================
299
-
300
- protected key(type: string, queue: string, id?: string): string {
301
- const base = `${this.env.REDIS_QUEUE_PREFIX}:${type}:${queue}`;
302
- return id ? `${base}:${id}` : base;
303
- }
304
-
305
- protected messageKey(queue: string): string {
306
- return `${this.env.REDIS_QUEUE_PREFIX}:${queue}`;
307
- }
308
-
309
- protected notifyKey(queue: string): string {
310
- return `${this.env.REDIS_QUEUE_PREFIX}:notify:${queue}`;
311
- }
312
-
313
- // ===========================================
314
- // Simple Message API (backward compatible)
315
- // ===========================================
316
-
317
- public async push(queue: string, message: string): Promise<void> {
318
- await this.redisProvider.publisher.LPUSH(this.messageKey(queue), message);
319
- }
320
-
321
- public async pop(queue: string): Promise<string | undefined> {
322
- const value = await this.redisProvider.publisher.RPOP(
323
- this.messageKey(queue),
324
- );
325
- if (value == null) return undefined;
326
- return String(value);
327
- }
328
-
329
- public async popBlocking(
330
- queues: string[],
331
- timeoutSeconds: number,
332
- ): Promise<{ queue: string; message: string } | undefined> {
333
- if (queues.length === 0 || !this.blockingClient) {
334
- return undefined;
335
- }
336
-
337
- const prefixedQueues = queues.map((q) => this.messageKey(q));
338
- const result = await this.blockingClient.BRPOP(
339
- prefixedQueues,
340
- timeoutSeconds,
341
- );
342
-
343
- if (result == null) return undefined;
344
-
345
- const key = result.key.toString();
346
- const prefixLength = this.env.REDIS_QUEUE_PREFIX.length + 1;
347
- const queue = key.substring(prefixLength);
348
-
349
- return { queue, message: result.element.toString() };
350
- }
351
-
352
- // ===========================================
353
- // Job API Implementation
354
- // ===========================================
355
-
356
- protected async generateJobId(): Promise<string> {
357
- const counter = await this.redisProvider.publisher.INCR(
358
- `${this.env.REDIS_QUEUE_PREFIX}:job_counter`,
359
- );
360
- return `job_${counter}_${Date.now()}`;
361
- }
362
-
363
- protected serializeJob(job: QueueJob): Record<string, string> {
364
- return {
365
- id: job.id,
366
- queue: job.queue,
367
- payload: JSON.stringify(job.payload),
368
- options: JSON.stringify(job.options),
369
- state: JSON.stringify(job.state),
370
- };
371
- }
372
-
373
- protected deserializeJob(data: Record<string, string>): QueueJob | undefined {
374
- if (!data.id) return undefined;
375
- return {
376
- id: data.id,
377
- queue: data.queue,
378
- payload: JSON.parse(data.payload),
379
- options: JSON.parse(data.options),
380
- state: JSON.parse(data.state),
381
- };
382
- }
383
-
384
- public async addJob<T>(
385
- queue: string,
386
- payload: T,
387
- options?: QueueJobOptions,
388
- ): Promise<QueueJob<T>> {
389
- const redis = this.redisProvider.publisher;
390
- const now = Date.now();
391
- const delay = options?.delay ?? 0;
392
- const isDelayed = delay > 0;
393
-
394
- const job: QueueJob<T> = {
395
- id: await this.generateJobId(),
396
- queue,
397
- payload,
398
- options: {
399
- priority: options?.priority ?? 0,
400
- delay: options?.delay ?? 0,
401
- maxAttempts: options?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,
402
- backoff: options?.backoff,
403
- lockDuration: options?.lockDuration ?? DEFAULT_LOCK_DURATION,
404
- removeOnComplete: options?.removeOnComplete,
405
- removeOnFail: options?.removeOnFail,
406
- },
407
- state: {
408
- status: isDelayed ? "delayed" : "waiting",
409
- attempts: 0,
410
- createdAt: now,
411
- availableAt: isDelayed ? now + delay : now,
412
- },
413
- };
414
-
415
- // Store job data
416
- await redis.HSET(this.key("job", queue, job.id), this.serializeJob(job));
417
-
418
- // Add to appropriate queue
419
- if (isDelayed) {
420
- await redis.ZADD(this.key("delayed", queue), {
421
- score: job.state.availableAt!,
422
- value: job.id,
423
- });
424
- } else {
425
- await redis.ZADD(this.key("waiting", queue), {
426
- score: job.options.priority ?? 0,
427
- value: job.id,
428
- });
429
-
430
- // Notify blocking waiters by pushing to notify list
431
- await redis.LPUSH(this.notifyKey(queue), job.id);
432
- }
433
-
434
- this.log.debug(`Added job ${job.id} to queue ${queue}`, {
435
- status: job.state.status,
436
- priority: job.options.priority,
437
- });
438
-
439
- // Emit waiting event
440
- if (!isDelayed) {
441
- await this.emit({
442
- type: "waiting",
443
- queue,
444
- jobId: job.id,
445
- timestamp: now,
446
- job,
447
- });
448
- }
449
-
450
- return job;
451
- }
452
-
453
- public async acquireJob(
454
- queues: string[],
455
- workerId: string,
456
- timeoutSeconds: number,
457
- ): Promise<QueueAcquiredJob | undefined> {
458
- if (!this.blockingClient || this.shouldStop) {
459
- return undefined;
460
- }
461
-
462
- const redis = this.redisProvider.publisher;
463
- const now = Date.now();
464
- const endTime = now + timeoutSeconds * 1000;
465
-
466
- while (Date.now() < endTime && !this.shouldStop) {
467
- // Try to acquire a job from each queue using Lua script
468
- for (const queue of queues) {
469
- try {
470
- const result = await redis.evalSha(this.acquireJobSha!, {
471
- keys: [
472
- this.key("waiting", queue),
473
- this.key("active", queue),
474
- this.key("job", queue),
475
- ],
476
- arguments: [
477
- workerId,
478
- String(Date.now()),
479
- String(DEFAULT_LOCK_DURATION),
480
- ],
481
- });
482
-
483
- if (result) {
484
- const job = JSON.parse(result as string) as QueueJob;
485
-
486
- this.log.debug(`Worker ${workerId} acquired job ${job.id}`, {
487
- queue,
488
- attempt: job.state.attempts,
489
- });
490
-
491
- // Emit active event
492
- await this.emit({
493
- type: "active",
494
- queue,
495
- jobId: job.id,
496
- timestamp: Date.now(),
497
- workerId,
498
- attempt: job.state.attempts,
499
- });
500
-
501
- return { queue, job };
502
- }
503
- } catch (error) {
504
- // Script might fail if job data is corrupted, log and continue
505
- this.log.warn(`Failed to acquire job from ${queue}`, error);
506
- }
507
- }
508
-
509
- // No job found, wait for notification using BRPOP
510
- // This blocks until a new job is added or timeout
511
- const notifyKeys = queues.map((q) => this.notifyKey(q));
512
- const remainingTimeout = Math.max(
513
- 1,
514
- Math.ceil((endTime - Date.now()) / 1000),
515
- );
516
-
517
- try {
518
- const notification = await this.blockingClient.BRPOP(
519
- notifyKeys,
520
- Math.min(remainingTimeout, 5), // Check every 5s max for shutdown
521
- );
522
-
523
- // If we got a notification, loop back to try acquiring
524
- // The notification just signals that a job was added
525
- if (notification) {
526
- }
527
- } catch {
528
- // Blocking client closed during shutdown
529
- if (this.shouldStop) {
530
- return undefined;
531
- }
532
- }
533
- }
534
-
535
- return undefined;
536
- }
537
-
538
- protected bufferRecordToString(
539
- record: Record<string, Buffer>,
540
- ): Record<string, string> {
541
- const result: Record<string, string> = {};
542
- for (const [key, value] of Object.entries(record)) {
543
- result[key] = value?.toString() ?? "";
544
- }
545
- return result;
546
- }
547
-
548
- public async completeJob(
549
- queue: string,
550
- jobId: string,
551
- result?: unknown,
552
- ): Promise<void> {
553
- const redis = this.redisProvider.publisher;
554
- const now = Date.now();
555
-
556
- try {
557
- const luaResult = await redis.evalSha(this.completeJobSha!, {
558
- keys: [
559
- this.key("job", queue, jobId),
560
- this.key("active", queue),
561
- this.key("completed", queue),
562
- ],
563
- arguments: [
564
- jobId,
565
- String(now),
566
- result !== undefined ? JSON.stringify(result) : "",
567
- ],
568
- });
569
-
570
- if (!luaResult) {
571
- this.log.warn(`Attempted to complete unknown job ${jobId}`);
572
- return;
573
- }
574
-
575
- const { removed, duration } = JSON.parse(luaResult as string);
576
- this.log.debug(`Job ${jobId} completed${removed ? " and removed" : ""}`, {
577
- queue,
578
- result,
579
- });
580
-
581
- // Emit completed event
582
- await this.emit({
583
- type: "completed",
584
- queue,
585
- jobId,
586
- timestamp: now,
587
- result,
588
- duration,
589
- });
590
- } catch (error) {
591
- // Fallback to non-atomic completion if Lua fails
592
- this.log.warn(`Lua completeJob failed, using fallback`, error);
593
- await this.completeJobFallback(queue, jobId, result);
594
- }
595
- }
596
-
597
- protected async completeJobFallback(
598
- queue: string,
599
- jobId: string,
600
- result?: unknown,
601
- ): Promise<void> {
602
- const redis = this.redisProvider.publisher;
603
- const now = Date.now();
604
-
605
- // Get job data
606
- const jobData = await redis.HGETALL(this.key("job", queue, jobId));
607
- const job = this.deserializeJob(this.bufferRecordToString(jobData));
608
- if (!job) {
609
- this.log.warn(`Attempted to complete unknown job ${jobId}`);
610
- return;
611
- }
612
-
613
- const duration = now - (job.state.processedAt ?? now);
614
-
615
- // Remove from active
616
- await redis.SREM(this.key("active", queue), jobId);
617
-
618
- // Update job state
619
- job.state.status = "completed";
620
- job.state.completedAt = now;
621
- job.state.result = result;
622
- job.state.lockedBy = undefined;
623
- job.state.lockedUntil = undefined;
624
-
625
- const removeOnComplete = job.options.removeOnComplete;
626
- if (removeOnComplete === true) {
627
- await redis.DEL(this.key("job", queue, jobId));
628
- } else {
629
- await redis.HSET(this.key("job", queue, jobId), {
630
- state: JSON.stringify(job.state),
631
- });
632
- await redis.LPUSH(this.key("completed", queue), jobId);
633
-
634
- if (typeof removeOnComplete === "number" && removeOnComplete >= 0) {
635
- await this.cleanJobs(queue, "completed", {
636
- maxCount: removeOnComplete,
637
- });
638
- }
639
- }
640
-
641
- this.log.debug(`Job ${jobId} completed`, { queue });
642
-
643
- // Emit completed event
644
- await this.emit({
645
- type: "completed",
646
- queue,
647
- jobId,
648
- timestamp: now,
649
- result,
650
- duration,
651
- });
652
- }
653
-
654
- public async failJob(
655
- queue: string,
656
- jobId: string,
657
- error: string,
658
- stackTrace?: string,
659
- ): Promise<void> {
660
- const redis = this.redisProvider.publisher;
661
- const now = Date.now();
662
-
663
- // Get job to calculate backoff
664
- const jobData = await redis.HGETALL(this.key("job", queue, jobId));
665
- const job = this.deserializeJob(this.bufferRecordToString(jobData));
666
- if (!job) {
667
- this.log.warn(`Attempted to fail unknown job ${jobId}`);
668
- return;
669
- }
670
-
671
- const backoffDelay = this.calculateBackoff(job);
672
-
673
- try {
674
- const luaResult = await redis.evalSha(this.failJobSha!, {
675
- keys: [
676
- this.key("job", queue, jobId),
677
- this.key("active", queue),
678
- this.key("delayed", queue),
679
- this.key("failed", queue),
680
- ],
681
- arguments: [
682
- jobId,
683
- String(now),
684
- error,
685
- stackTrace ?? "",
686
- String(backoffDelay),
687
- ],
688
- });
689
-
690
- if (!luaResult) {
691
- this.log.warn(`Attempted to fail unknown job ${jobId}`);
692
- return;
693
- }
694
-
695
- const result = JSON.parse(luaResult as string);
696
-
697
- if (result.status === "retrying") {
698
- this.log.debug(`Job ${jobId} failed, will retry in ${result.delay}ms`, {
699
- queue,
700
- attempt: job.state.attempts,
701
- error,
702
- });
703
-
704
- // Emit retrying event
705
- await this.emit({
706
- type: "retrying",
707
- queue,
708
- jobId,
709
- timestamp: now,
710
- error,
711
- attempt: result.attempt,
712
- delay: result.delay,
713
- });
714
- } else {
715
- this.log.debug(
716
- `Job ${jobId} permanently failed${result.removed ? " and removed" : ""}`,
717
- { queue, error },
718
- );
719
-
720
- // Emit failed event
721
- await this.emit({
722
- type: "failed",
723
- queue,
724
- jobId,
725
- timestamp: now,
726
- error,
727
- stackTrace,
728
- attempts: result.attempts,
729
- });
730
- }
731
- } catch (luaError) {
732
- // Fallback to non-atomic fail if Lua fails
733
- this.log.warn(`Lua failJob failed, using fallback`, luaError);
734
- await this.failJobFallback(queue, jobId, error, stackTrace);
735
- }
736
- }
737
-
738
- protected async failJobFallback(
739
- queue: string,
740
- jobId: string,
741
- error: string,
742
- stackTrace?: string,
743
- ): Promise<void> {
744
- const redis = this.redisProvider.publisher;
745
- const now = Date.now();
746
-
747
- const jobData = await redis.HGETALL(this.key("job", queue, jobId));
748
- const job = this.deserializeJob(this.bufferRecordToString(jobData));
749
- if (!job) {
750
- this.log.warn(`Attempted to fail unknown job ${jobId}`);
751
- return;
752
- }
753
-
754
- // Remove from active
755
- await redis.SREM(this.key("active", queue), jobId);
756
-
757
- const maxAttempts = job.options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
758
- const hasMoreAttempts = job.state.attempts < maxAttempts;
759
-
760
- if (hasMoreAttempts) {
761
- const backoffDelay = this.calculateBackoff(job);
762
-
763
- job.state.status = "delayed";
764
- job.state.availableAt = now + backoffDelay;
765
- job.state.error = error;
766
- job.state.stackTrace = stackTrace;
767
- job.state.lockedBy = undefined;
768
- job.state.lockedUntil = undefined;
769
-
770
- await redis.HSET(this.key("job", queue, jobId), {
771
- state: JSON.stringify(job.state),
772
- });
773
- await redis.ZADD(this.key("delayed", queue), {
774
- score: job.state.availableAt,
775
- value: jobId,
776
- });
777
-
778
- this.log.debug(`Job ${jobId} failed, will retry in ${backoffDelay}ms`, {
779
- queue,
780
- attempt: job.state.attempts,
781
- maxAttempts,
782
- });
783
-
784
- // Emit retrying event
785
- await this.emit({
786
- type: "retrying",
787
- queue,
788
- jobId,
789
- timestamp: now,
790
- error,
791
- attempt: job.state.attempts + 1,
792
- delay: backoffDelay,
793
- });
794
- } else {
795
- job.state.status = "failed";
796
- job.state.failedAt = now;
797
- job.state.error = error;
798
- job.state.stackTrace = stackTrace;
799
- job.state.lockedBy = undefined;
800
- job.state.lockedUntil = undefined;
801
-
802
- const removeOnFail = job.options.removeOnFail;
803
- if (removeOnFail === true) {
804
- await redis.DEL(this.key("job", queue, jobId));
805
- } else {
806
- await redis.HSET(this.key("job", queue, jobId), {
807
- state: JSON.stringify(job.state),
808
- });
809
- await redis.LPUSH(this.key("failed", queue), jobId);
810
-
811
- if (typeof removeOnFail === "number" && removeOnFail >= 0) {
812
- await this.cleanJobs(queue, "failed", { maxCount: removeOnFail });
813
- }
814
- }
815
-
816
- this.log.debug(`Job ${jobId} permanently failed`, { queue });
817
-
818
- // Emit failed event
819
- await this.emit({
820
- type: "failed",
821
- queue,
822
- jobId,
823
- timestamp: now,
824
- error,
825
- stackTrace,
826
- attempts: job.state.attempts,
827
- });
828
- }
829
- }
830
-
831
- protected calculateBackoff(job: QueueJob): number {
832
- const backoff = job.options.backoff;
833
- const attempt = job.state.attempts;
834
-
835
- if (!backoff) return DEFAULT_BACKOFF_DELAY;
836
-
837
- const baseDelay = backoff.delay ?? DEFAULT_BACKOFF_DELAY;
838
- const maxDelay = backoff.maxDelay ?? DEFAULT_BACKOFF_MAX_DELAY;
839
-
840
- if (backoff.type === "fixed") return baseDelay;
841
-
842
- const exponentialDelay = baseDelay * 2 ** (attempt - 1);
843
- return Math.min(exponentialDelay, maxDelay);
844
- }
845
-
846
- public async renewJobLock(
847
- queue: string,
848
- jobId: string,
849
- workerId: string,
850
- ): Promise<boolean> {
851
- const redis = this.redisProvider.publisher;
852
-
853
- const jobData = await redis.HGETALL(this.key("job", queue, jobId));
854
- const job = this.deserializeJob(this.bufferRecordToString(jobData));
855
- if (!job || job.state.lockedBy !== workerId) {
856
- return false;
857
- }
858
-
859
- job.state.lockedUntil =
860
- Date.now() + (job.options.lockDuration ?? DEFAULT_LOCK_DURATION);
861
- await redis.HSET(this.key("job", queue, jobId), {
862
- state: JSON.stringify(job.state),
863
- });
864
-
865
- return true;
866
- }
867
-
868
- public async getJob(
869
- queue: string,
870
- jobId: string,
871
- ): Promise<QueueJob | undefined> {
872
- const redis = this.redisProvider.publisher;
873
- const jobData = await redis.HGETALL(this.key("job", queue, jobId));
874
- return this.deserializeJob(this.bufferRecordToString(jobData));
875
- }
876
-
877
- public async getJobs(
878
- queue: string,
879
- status: QueueJobStatus,
880
- options?: QueueGetJobsOptions,
881
- ): Promise<QueueJob[]> {
882
- const redis = this.redisProvider.publisher;
883
- const limit = options?.limit ?? 100;
884
- const offset = options?.offset ?? 0;
885
-
886
- let jobIds: string[];
887
-
888
- switch (status) {
889
- case "waiting": {
890
- const results = await redis.ZRANGE(
891
- this.key("waiting", queue),
892
- offset,
893
- offset + limit - 1,
894
- );
895
- jobIds = results.map((r) => r.toString());
896
- break;
897
- }
898
- case "delayed": {
899
- const results = await redis.ZRANGE(
900
- this.key("delayed", queue),
901
- offset,
902
- offset + limit - 1,
903
- );
904
- jobIds = results.map((r) => r.toString());
905
- break;
906
- }
907
- case "active": {
908
- const results = await redis.SMEMBERS(this.key("active", queue));
909
- jobIds = results.map((r) => r.toString()).slice(offset, offset + limit);
910
- break;
911
- }
912
- case "completed": {
913
- const results = await redis.LRANGE(
914
- this.key("completed", queue),
915
- offset,
916
- offset + limit - 1,
917
- );
918
- jobIds = results.map((r) => r.toString());
919
- break;
920
- }
921
- case "failed": {
922
- const results = await redis.LRANGE(
923
- this.key("failed", queue),
924
- offset,
925
- offset + limit - 1,
926
- );
927
- jobIds = results.map((r) => r.toString());
928
- break;
929
- }
930
- default:
931
- jobIds = [];
932
- }
933
-
934
- const jobs: QueueJob[] = [];
935
- for (const jobId of jobIds) {
936
- const job = await this.getJob(queue, jobId);
937
- if (job) jobs.push(job);
938
- }
939
-
940
- return jobs;
941
- }
942
-
943
- public async getJobCounts(queue: string): Promise<QueueJobCounts> {
944
- const redis = this.redisProvider.publisher;
945
-
946
- const [waiting, delayed, active, completed, failed] = await Promise.all([
947
- redis.ZCARD(this.key("waiting", queue)),
948
- redis.ZCARD(this.key("delayed", queue)),
949
- redis.SCARD(this.key("active", queue)),
950
- redis.LLEN(this.key("completed", queue)),
951
- redis.LLEN(this.key("failed", queue)),
952
- ]);
953
-
954
- return { waiting, delayed, active, completed, failed };
955
- }
956
-
957
- public async promoteDelayedJobs(queue: string): Promise<number> {
958
- const redis = this.redisProvider.publisher;
959
- const now = Date.now();
960
-
961
- // Get jobs whose availableAt has passed
962
- const results = await redis.ZRANGEBYSCORE(
963
- this.key("delayed", queue),
964
- "-inf",
965
- now,
966
- );
967
-
968
- let promoted = 0;
969
- for (const result of results) {
970
- const jobId = result.toString();
971
-
972
- // Remove from delayed
973
- const removed = await redis.ZREM(this.key("delayed", queue), jobId);
974
- if (removed === 0) continue;
975
-
976
- // Get and update job
977
- const job = await this.getJob(queue, jobId);
978
- if (!job) continue;
979
-
980
- job.state.status = "waiting";
981
- await redis.HSET(this.key("job", queue, jobId), {
982
- state: JSON.stringify(job.state),
983
- });
984
-
985
- // Add to waiting with priority score
986
- await redis.ZADD(this.key("waiting", queue), {
987
- score: job.options.priority ?? 0,
988
- value: jobId,
989
- });
990
-
991
- // Notify waiting workers
992
- await redis.LPUSH(this.notifyKey(queue), jobId);
993
-
994
- promoted++;
995
- this.log.debug(`Promoted delayed job ${jobId}`, { queue });
996
-
997
- // Emit waiting event
998
- await this.emit({
999
- type: "waiting",
1000
- queue,
1001
- jobId,
1002
- timestamp: now,
1003
- job,
1004
- });
1005
- }
1006
-
1007
- return promoted;
1008
- }
1009
-
1010
- public async recoverStalledJobs(
1011
- queue: string,
1012
- stalledThresholdMs: number,
1013
- ): Promise<string[]> {
1014
- const redis = this.redisProvider.publisher;
1015
- const now = Date.now();
1016
-
1017
- const activeJobIds = await redis.SMEMBERS(this.key("active", queue));
1018
- const stalledJobIds: string[] = [];
1019
-
1020
- for (const result of activeJobIds) {
1021
- const jobId = result.toString();
1022
- const job = await this.getJob(queue, jobId);
1023
- if (!job) continue;
1024
-
1025
- const lockExpired =
1026
- (job.state.lockedUntil ?? 0) + stalledThresholdMs < now;
1027
- if (!lockExpired) continue;
1028
-
1029
- stalledJobIds.push(jobId);
1030
- const workerId = job.state.lockedBy;
1031
-
1032
- // Remove from active
1033
- await redis.SREM(this.key("active", queue), jobId);
1034
-
1035
- const maxAttempts = job.options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
1036
- const hasMoreAttempts = job.state.attempts < maxAttempts;
1037
-
1038
- // Emit stalled event
1039
- await this.emit({
1040
- type: "stalled",
1041
- queue,
1042
- jobId,
1043
- timestamp: now,
1044
- workerId,
1045
- willRetry: hasMoreAttempts,
1046
- });
1047
-
1048
- if (hasMoreAttempts) {
1049
- job.state.status = "waiting";
1050
- job.state.lockedBy = undefined;
1051
- job.state.lockedUntil = undefined;
1052
- job.state.error = "Job stalled (worker timeout)";
1053
-
1054
- await redis.HSET(this.key("job", queue, jobId), {
1055
- state: JSON.stringify(job.state),
1056
- });
1057
- await redis.ZADD(this.key("waiting", queue), {
1058
- score: job.options.priority ?? 0,
1059
- value: jobId,
1060
- });
1061
-
1062
- // Notify waiting workers
1063
- await redis.LPUSH(this.notifyKey(queue), jobId);
1064
-
1065
- this.log.warn(`Recovered stalled job ${jobId}`, { queue });
1066
-
1067
- // Emit waiting event
1068
- await this.emit({
1069
- type: "waiting",
1070
- queue,
1071
- jobId,
1072
- timestamp: now,
1073
- job,
1074
- });
1075
- } else {
1076
- job.state.status = "failed";
1077
- job.state.failedAt = now;
1078
- job.state.lockedBy = undefined;
1079
- job.state.lockedUntil = undefined;
1080
- job.state.error =
1081
- "Job stalled (worker timeout) - max attempts exceeded";
1082
-
1083
- const removeOnFail = job.options.removeOnFail;
1084
- if (removeOnFail === true) {
1085
- await redis.DEL(this.key("job", queue, jobId));
1086
- } else {
1087
- await redis.HSET(this.key("job", queue, jobId), {
1088
- state: JSON.stringify(job.state),
1089
- });
1090
- await redis.LPUSH(this.key("failed", queue), jobId);
1091
-
1092
- if (typeof removeOnFail === "number" && removeOnFail >= 0) {
1093
- await this.cleanJobs(queue, "failed", { maxCount: removeOnFail });
1094
- }
1095
- }
1096
-
1097
- this.log.warn(`Stalled job ${jobId} permanently failed`, { queue });
1098
-
1099
- // Emit failed event
1100
- await this.emit({
1101
- type: "failed",
1102
- queue,
1103
- jobId,
1104
- timestamp: now,
1105
- error: job.state.error,
1106
- attempts: job.state.attempts,
1107
- });
1108
- }
1109
- }
1110
-
1111
- return stalledJobIds;
1112
- }
1113
-
1114
- public async cleanJobs(
1115
- queue: string,
1116
- status: "completed" | "failed",
1117
- options?: QueueCleanOptions,
1118
- ): Promise<number> {
1119
- const redis = this.redisProvider.publisher;
1120
- const listKey = this.key(status, queue);
1121
- const maxAge = options?.maxAge;
1122
- const maxCount = options?.maxCount;
1123
-
1124
- let removed = 0;
1125
-
1126
- // Remove by age
1127
- if (maxAge !== undefined) {
1128
- const now = Date.now();
1129
- const cutoff = now - maxAge;
1130
-
1131
- const jobIds = await redis.LRANGE(listKey, 0, -1);
1132
- for (const result of jobIds) {
1133
- const jobId = result.toString();
1134
- const job = await this.getJob(queue, jobId);
1135
- if (!job) continue;
1136
-
1137
- const timestamp =
1138
- status === "completed" ? job.state.completedAt : job.state.failedAt;
1139
-
1140
- if (timestamp && timestamp < cutoff) {
1141
- await redis.LREM(listKey, 1, jobId);
1142
- await redis.DEL(this.key("job", queue, jobId));
1143
- removed++;
1144
- }
1145
- }
1146
- }
1147
-
1148
- // Remove by count
1149
- if (maxCount !== undefined) {
1150
- const currentLen = await redis.LLEN(listKey);
1151
- if (currentLen > maxCount) {
1152
- // Get jobs to remove (oldest ones)
1153
- const toRemove = await redis.LRANGE(listKey, maxCount, -1);
1154
- for (const result of toRemove) {
1155
- const jobId = result.toString();
1156
- await redis.DEL(this.key("job", queue, jobId));
1157
- removed++;
1158
- }
1159
- // Trim the list
1160
- await redis.LTRIM(listKey, 0, maxCount - 1);
1161
- }
1162
- }
1163
-
1164
- return removed;
1165
- }
1166
-
1167
- public async removeJob(queue: string, jobId: string): Promise<void> {
1168
- const redis = this.redisProvider.publisher;
1169
- const job = await this.getJob(queue, jobId);
1170
- if (!job) return;
1171
-
1172
- const previousStatus = job.state.status;
1173
-
1174
- // Remove from appropriate list
1175
- switch (job.state.status) {
1176
- case "waiting":
1177
- await redis.ZREM(this.key("waiting", queue), jobId);
1178
- break;
1179
- case "delayed":
1180
- await redis.ZREM(this.key("delayed", queue), jobId);
1181
- break;
1182
- case "active":
1183
- await redis.SREM(this.key("active", queue), jobId);
1184
- break;
1185
- case "completed":
1186
- await redis.LREM(this.key("completed", queue), 1, jobId);
1187
- break;
1188
- case "failed":
1189
- await redis.LREM(this.key("failed", queue), 1, jobId);
1190
- break;
1191
- }
1192
-
1193
- // Delete job data
1194
- await redis.DEL(this.key("job", queue, jobId));
1195
-
1196
- // Emit removed event
1197
- await this.emit({
1198
- type: "removed",
1199
- queue,
1200
- jobId,
1201
- timestamp: Date.now(),
1202
- previousStatus,
1203
- });
1204
- }
1205
-
1206
- public cancelWaiters(): void {
1207
- this.shouldStop = true;
1208
- }
1209
- }