alepha 0.12.1 → 0.13.1

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 (198) hide show
  1. package/dist/api-notifications/index.d.ts +111 -111
  2. package/dist/api-users/index.d.ts +1240 -1240
  3. package/dist/api-verifications/index.d.ts +94 -94
  4. package/dist/cli/{dist-Sz2EXvQX.cjs → dist-Dl9Vl7Ur.js} +17 -13
  5. package/dist/cli/{dist-BBPjuQ56.js.map → dist-Dl9Vl7Ur.js.map} +1 -1
  6. package/dist/cli/index.d.ts +3 -11
  7. package/dist/cli/index.js +106 -74
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/email/index.js +71 -73
  10. package/dist/email/index.js.map +1 -1
  11. package/dist/orm/index.d.ts +1 -1
  12. package/dist/orm/index.js.map +1 -1
  13. package/dist/queue/index.d.ts +4 -4
  14. package/dist/redis/index.d.ts +10 -10
  15. package/dist/retry/index.d.ts +1 -1
  16. package/dist/retry/index.js +2 -2
  17. package/dist/retry/index.js.map +1 -1
  18. package/dist/scheduler/index.d.ts +6 -6
  19. package/dist/server/index.js +1 -1
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server-auth/index.d.ts +193 -193
  22. package/dist/server-health/index.d.ts +17 -17
  23. package/dist/server-links/index.d.ts +34 -34
  24. package/dist/server-metrics/index.js +170 -174
  25. package/dist/server-metrics/index.js.map +1 -1
  26. package/dist/server-security/index.d.ts +9 -9
  27. package/dist/vite/index.js +4 -5
  28. package/dist/vite/index.js.map +1 -1
  29. package/dist/websocket/index.d.ts +7 -7
  30. package/package.json +52 -103
  31. package/src/cli/apps/AlephaPackageBuilderCli.ts +7 -2
  32. package/src/cli/assets/appRouterTs.ts +9 -0
  33. package/src/cli/assets/indexHtml.ts +2 -1
  34. package/src/cli/assets/mainBrowserTs.ts +10 -0
  35. package/src/cli/commands/CoreCommands.ts +6 -5
  36. package/src/cli/commands/DrizzleCommands.ts +65 -57
  37. package/src/cli/commands/VerifyCommands.ts +1 -1
  38. package/src/cli/services/ProjectUtils.ts +44 -38
  39. package/src/orm/providers/DrizzleKitProvider.ts +1 -1
  40. package/src/retry/descriptors/$retry.ts +5 -3
  41. package/src/server/providers/NodeHttpServerProvider.ts +1 -1
  42. package/src/vite/helpers/boot.ts +3 -3
  43. package/dist/api-files/index.cjs +0 -1293
  44. package/dist/api-files/index.cjs.map +0 -1
  45. package/dist/api-files/index.d.cts +0 -829
  46. package/dist/api-jobs/index.cjs +0 -274
  47. package/dist/api-jobs/index.cjs.map +0 -1
  48. package/dist/api-jobs/index.d.cts +0 -654
  49. package/dist/api-notifications/index.cjs +0 -380
  50. package/dist/api-notifications/index.cjs.map +0 -1
  51. package/dist/api-notifications/index.d.cts +0 -289
  52. package/dist/api-parameters/index.cjs +0 -66
  53. package/dist/api-parameters/index.cjs.map +0 -1
  54. package/dist/api-parameters/index.d.cts +0 -84
  55. package/dist/api-users/index.cjs +0 -6009
  56. package/dist/api-users/index.cjs.map +0 -1
  57. package/dist/api-users/index.d.cts +0 -4740
  58. package/dist/api-verifications/index.cjs +0 -407
  59. package/dist/api-verifications/index.cjs.map +0 -1
  60. package/dist/api-verifications/index.d.cts +0 -207
  61. package/dist/batch/index.cjs +0 -408
  62. package/dist/batch/index.cjs.map +0 -1
  63. package/dist/batch/index.d.cts +0 -330
  64. package/dist/bin/index.cjs +0 -17
  65. package/dist/bin/index.cjs.map +0 -1
  66. package/dist/bin/index.d.cts +0 -1
  67. package/dist/bucket/index.cjs +0 -303
  68. package/dist/bucket/index.cjs.map +0 -1
  69. package/dist/bucket/index.d.cts +0 -355
  70. package/dist/cache/index.cjs +0 -241
  71. package/dist/cache/index.cjs.map +0 -1
  72. package/dist/cache/index.d.cts +0 -202
  73. package/dist/cache-redis/index.cjs +0 -84
  74. package/dist/cache-redis/index.cjs.map +0 -1
  75. package/dist/cache-redis/index.d.cts +0 -40
  76. package/dist/cli/chunk-DSlc6foC.cjs +0 -43
  77. package/dist/cli/dist-BBPjuQ56.js +0 -2778
  78. package/dist/cli/dist-Sz2EXvQX.cjs.map +0 -1
  79. package/dist/cli/index.cjs +0 -1241
  80. package/dist/cli/index.cjs.map +0 -1
  81. package/dist/cli/index.d.cts +0 -422
  82. package/dist/command/index.cjs +0 -693
  83. package/dist/command/index.cjs.map +0 -1
  84. package/dist/command/index.d.cts +0 -340
  85. package/dist/core/index.cjs +0 -2264
  86. package/dist/core/index.cjs.map +0 -1
  87. package/dist/core/index.d.cts +0 -1927
  88. package/dist/datetime/index.cjs +0 -318
  89. package/dist/datetime/index.cjs.map +0 -1
  90. package/dist/datetime/index.d.cts +0 -145
  91. package/dist/email/index.cjs +0 -10874
  92. package/dist/email/index.cjs.map +0 -1
  93. package/dist/email/index.d.cts +0 -186
  94. package/dist/fake/index.cjs +0 -34641
  95. package/dist/fake/index.cjs.map +0 -1
  96. package/dist/fake/index.d.cts +0 -74
  97. package/dist/file/index.cjs +0 -1212
  98. package/dist/file/index.cjs.map +0 -1
  99. package/dist/file/index.d.cts +0 -698
  100. package/dist/lock/index.cjs +0 -226
  101. package/dist/lock/index.cjs.map +0 -1
  102. package/dist/lock/index.d.cts +0 -361
  103. package/dist/lock-redis/index.cjs +0 -113
  104. package/dist/lock-redis/index.cjs.map +0 -1
  105. package/dist/lock-redis/index.d.cts +0 -24
  106. package/dist/logger/index.cjs +0 -521
  107. package/dist/logger/index.cjs.map +0 -1
  108. package/dist/logger/index.d.cts +0 -281
  109. package/dist/orm/index.cjs +0 -2986
  110. package/dist/orm/index.cjs.map +0 -1
  111. package/dist/orm/index.d.cts +0 -2213
  112. package/dist/queue/index.cjs +0 -1044
  113. package/dist/queue/index.cjs.map +0 -1
  114. package/dist/queue/index.d.cts +0 -1265
  115. package/dist/queue-redis/index.cjs +0 -873
  116. package/dist/queue-redis/index.cjs.map +0 -1
  117. package/dist/queue-redis/index.d.cts +0 -82
  118. package/dist/redis/index.cjs +0 -153
  119. package/dist/redis/index.cjs.map +0 -1
  120. package/dist/redis/index.d.cts +0 -82
  121. package/dist/retry/index.cjs +0 -146
  122. package/dist/retry/index.cjs.map +0 -1
  123. package/dist/retry/index.d.cts +0 -172
  124. package/dist/router/index.cjs +0 -111
  125. package/dist/router/index.cjs.map +0 -1
  126. package/dist/router/index.d.cts +0 -46
  127. package/dist/scheduler/index.cjs +0 -576
  128. package/dist/scheduler/index.cjs.map +0 -1
  129. package/dist/scheduler/index.d.cts +0 -145
  130. package/dist/security/index.cjs +0 -2402
  131. package/dist/security/index.cjs.map +0 -1
  132. package/dist/security/index.d.cts +0 -598
  133. package/dist/server/index.cjs +0 -1680
  134. package/dist/server/index.cjs.map +0 -1
  135. package/dist/server/index.d.cts +0 -810
  136. package/dist/server-auth/index.cjs +0 -3146
  137. package/dist/server-auth/index.cjs.map +0 -1
  138. package/dist/server-auth/index.d.cts +0 -1164
  139. package/dist/server-cache/index.cjs +0 -252
  140. package/dist/server-cache/index.cjs.map +0 -1
  141. package/dist/server-cache/index.d.cts +0 -164
  142. package/dist/server-compress/index.cjs +0 -141
  143. package/dist/server-compress/index.cjs.map +0 -1
  144. package/dist/server-compress/index.d.cts +0 -38
  145. package/dist/server-cookies/index.cjs +0 -234
  146. package/dist/server-cookies/index.cjs.map +0 -1
  147. package/dist/server-cookies/index.d.cts +0 -144
  148. package/dist/server-cors/index.cjs +0 -201
  149. package/dist/server-cors/index.cjs.map +0 -1
  150. package/dist/server-cors/index.d.cts +0 -140
  151. package/dist/server-health/index.cjs +0 -62
  152. package/dist/server-health/index.cjs.map +0 -1
  153. package/dist/server-health/index.d.cts +0 -58
  154. package/dist/server-helmet/index.cjs +0 -131
  155. package/dist/server-helmet/index.cjs.map +0 -1
  156. package/dist/server-helmet/index.d.cts +0 -97
  157. package/dist/server-links/index.cjs +0 -992
  158. package/dist/server-links/index.cjs.map +0 -1
  159. package/dist/server-links/index.d.cts +0 -513
  160. package/dist/server-metrics/index.cjs +0 -4535
  161. package/dist/server-metrics/index.cjs.map +0 -1
  162. package/dist/server-metrics/index.d.cts +0 -35
  163. package/dist/server-multipart/index.cjs +0 -237
  164. package/dist/server-multipart/index.cjs.map +0 -1
  165. package/dist/server-multipart/index.d.cts +0 -50
  166. package/dist/server-proxy/index.cjs +0 -186
  167. package/dist/server-proxy/index.cjs.map +0 -1
  168. package/dist/server-proxy/index.d.cts +0 -234
  169. package/dist/server-rate-limit/index.cjs +0 -241
  170. package/dist/server-rate-limit/index.cjs.map +0 -1
  171. package/dist/server-rate-limit/index.d.cts +0 -183
  172. package/dist/server-security/index.cjs +0 -316
  173. package/dist/server-security/index.cjs.map +0 -1
  174. package/dist/server-security/index.d.cts +0 -173
  175. package/dist/server-static/index.cjs +0 -170
  176. package/dist/server-static/index.cjs.map +0 -1
  177. package/dist/server-static/index.d.cts +0 -121
  178. package/dist/server-swagger/index.cjs +0 -1021
  179. package/dist/server-swagger/index.cjs.map +0 -1
  180. package/dist/server-swagger/index.d.cts +0 -382
  181. package/dist/sms/index.cjs +0 -221
  182. package/dist/sms/index.cjs.map +0 -1
  183. package/dist/sms/index.d.cts +0 -130
  184. package/dist/thread/index.cjs +0 -350
  185. package/dist/thread/index.cjs.map +0 -1
  186. package/dist/thread/index.d.cts +0 -260
  187. package/dist/topic/index.cjs +0 -282
  188. package/dist/topic/index.cjs.map +0 -1
  189. package/dist/topic/index.d.cts +0 -523
  190. package/dist/topic-redis/index.cjs +0 -71
  191. package/dist/topic-redis/index.cjs.map +0 -1
  192. package/dist/topic-redis/index.d.cts +0 -42
  193. package/dist/vite/index.cjs +0 -1077
  194. package/dist/vite/index.cjs.map +0 -1
  195. package/dist/vite/index.d.cts +0 -542
  196. package/dist/websocket/index.cjs +0 -1117
  197. package/dist/websocket/index.cjs.map +0 -1
  198. package/dist/websocket/index.d.cts +0 -861
@@ -1,873 +0,0 @@
1
- let alepha = require("alepha");
2
- let alepha_queue = require("alepha/queue");
3
- let alepha_logger = require("alepha/logger");
4
- let alepha_redis = require("alepha/redis");
5
-
6
- //#region src/queue-redis/providers/RedisQueueProvider.ts
7
- const DEFAULT_MAX_ATTEMPTS = 1;
8
- const DEFAULT_LOCK_DURATION = 3e4;
9
- const DEFAULT_BACKOFF_DELAY = 1e3;
10
- const DEFAULT_BACKOFF_MAX_DELAY = 3e4;
11
- const envSchema = alepha.t.object({ REDIS_QUEUE_PREFIX: alepha.t.text({ default: "queue" }) });
12
- const ACQUIRE_JOB_SCRIPT = `
13
- local waitingKey = KEYS[1]
14
- local activeKey = KEYS[2]
15
- local jobKeyPrefix = KEYS[3]
16
- local workerId = ARGV[1]
17
- local now = tonumber(ARGV[2])
18
- local lockDuration = tonumber(ARGV[3])
19
-
20
- -- Get highest priority job (lowest score)
21
- local jobs = redis.call('ZRANGE', waitingKey, 0, 0)
22
- if #jobs == 0 then
23
- return nil
24
- end
25
-
26
- local jobId = jobs[1]
27
- local jobKey = jobKeyPrefix .. ':' .. jobId
28
-
29
- -- Remove from waiting (atomic check)
30
- local removed = redis.call('ZREM', waitingKey, jobId)
31
- if removed == 0 then
32
- return nil
33
- end
34
-
35
- -- Get current job data
36
- local jobData = redis.call('HGETALL', jobKey)
37
- if #jobData == 0 then
38
- return nil
39
- end
40
-
41
- -- Parse job data into table
42
- local job = {}
43
- for i = 1, #jobData, 2 do
44
- job[jobData[i]] = jobData[i + 1]
45
- end
46
-
47
- -- Parse current state
48
- local state = cjson.decode(job['state'])
49
- local options = cjson.decode(job['options'])
50
-
51
- -- Update state
52
- state['status'] = 'active'
53
- state['attempts'] = state['attempts'] + 1
54
- state['lockedBy'] = workerId
55
- state['lockedUntil'] = now + (options['lockDuration'] or lockDuration)
56
- state['processedAt'] = now
57
-
58
- -- Save updated state
59
- redis.call('HSET', jobKey, 'state', cjson.encode(state))
60
-
61
- -- Add to active set
62
- redis.call('SADD', activeKey, jobId)
63
-
64
- -- Return job data
65
- return cjson.encode({
66
- id = job['id'],
67
- queue = job['queue'],
68
- payload = cjson.decode(job['payload']),
69
- options = options,
70
- state = state
71
- })
72
- `;
73
- const COMPLETE_JOB_SCRIPT = `
74
- local jobKey = KEYS[1]
75
- local activeKey = KEYS[2]
76
- local completedKey = KEYS[3]
77
- local jobId = ARGV[1]
78
- local now = tonumber(ARGV[2])
79
- local result = ARGV[3]
80
-
81
- -- Get job data
82
- local jobData = redis.call('HGETALL', jobKey)
83
- if #jobData == 0 then
84
- return nil
85
- end
86
-
87
- -- Parse job data
88
- local job = {}
89
- for i = 1, #jobData, 2 do
90
- job[jobData[i]] = jobData[i + 1]
91
- end
92
-
93
- local state = cjson.decode(job['state'])
94
- local options = cjson.decode(job['options'])
95
- local processedAt = state['processedAt'] or now
96
-
97
- -- Remove from active
98
- redis.call('SREM', activeKey, jobId)
99
-
100
- -- Update state
101
- state['status'] = 'completed'
102
- state['completedAt'] = now
103
- state['result'] = result ~= '' and cjson.decode(result) or nil
104
- state['lockedBy'] = nil
105
- state['lockedUntil'] = nil
106
-
107
- local removeOnComplete = options['removeOnComplete']
108
-
109
- if removeOnComplete == true then
110
- -- Remove job immediately
111
- redis.call('DEL', jobKey)
112
- return cjson.encode({ removed = true, duration = now - processedAt })
113
- else
114
- -- Update job state
115
- redis.call('HSET', jobKey, 'state', cjson.encode(state))
116
-
117
- -- Add to completed list (newest first)
118
- redis.call('LPUSH', completedKey, jobId)
119
-
120
- -- If removeOnComplete is a number, trim the list (0 means keep none)
121
- if type(removeOnComplete) == 'number' and removeOnComplete >= 0 then
122
- -- Get jobs to remove
123
- local toRemove = redis.call('LRANGE', completedKey, removeOnComplete, -1)
124
- for _, oldJobId in ipairs(toRemove) do
125
- redis.call('DEL', jobKey:gsub(jobId, oldJobId))
126
- end
127
- redis.call('LTRIM', completedKey, 0, removeOnComplete - 1)
128
- end
129
-
130
- return cjson.encode({ removed = false, duration = now - processedAt })
131
- end
132
- `;
133
- const FAIL_JOB_SCRIPT = `
134
- local jobKey = KEYS[1]
135
- local activeKey = KEYS[2]
136
- local delayedKey = KEYS[3]
137
- local failedKey = KEYS[4]
138
- local jobId = ARGV[1]
139
- local now = tonumber(ARGV[2])
140
- local errorMsg = ARGV[3]
141
- local stackTrace = ARGV[4]
142
- local backoffDelay = tonumber(ARGV[5])
143
-
144
- -- Get job data
145
- local jobData = redis.call('HGETALL', jobKey)
146
- if #jobData == 0 then
147
- return nil
148
- end
149
-
150
- -- Parse job data
151
- local job = {}
152
- for i = 1, #jobData, 2 do
153
- job[jobData[i]] = jobData[i + 1]
154
- end
155
-
156
- local state = cjson.decode(job['state'])
157
- local options = cjson.decode(job['options'])
158
-
159
- -- Remove from active
160
- redis.call('SREM', activeKey, jobId)
161
-
162
- local maxAttempts = options['maxAttempts'] or 1
163
- local hasMoreAttempts = state['attempts'] < maxAttempts
164
-
165
- if hasMoreAttempts then
166
- -- Schedule for retry
167
- state['status'] = 'delayed'
168
- state['availableAt'] = now + backoffDelay
169
- state['error'] = errorMsg
170
- state['stackTrace'] = stackTrace ~= '' and stackTrace or nil
171
- state['lockedBy'] = nil
172
- state['lockedUntil'] = nil
173
-
174
- redis.call('HSET', jobKey, 'state', cjson.encode(state))
175
- redis.call('ZADD', delayedKey, now + backoffDelay, jobId)
176
-
177
- return cjson.encode({ status = 'retrying', delay = backoffDelay, attempt = state['attempts'] + 1 })
178
- else
179
- -- Permanently failed
180
- state['status'] = 'failed'
181
- state['failedAt'] = now
182
- state['error'] = errorMsg
183
- state['stackTrace'] = stackTrace ~= '' and stackTrace or nil
184
- state['lockedBy'] = nil
185
- state['lockedUntil'] = nil
186
-
187
- local removeOnFail = options['removeOnFail']
188
-
189
- if removeOnFail == true then
190
- redis.call('DEL', jobKey)
191
- return cjson.encode({ status = 'failed', removed = true, attempts = state['attempts'] })
192
- else
193
- redis.call('HSET', jobKey, 'state', cjson.encode(state))
194
- redis.call('LPUSH', failedKey, jobId)
195
-
196
- if type(removeOnFail) == 'number' and removeOnFail >= 0 then
197
- local toRemove = redis.call('LRANGE', failedKey, removeOnFail, -1)
198
- for _, oldJobId in ipairs(toRemove) do
199
- redis.call('DEL', jobKey:gsub(jobId, oldJobId))
200
- end
201
- redis.call('LTRIM', failedKey, 0, removeOnFail - 1)
202
- end
203
-
204
- return cjson.encode({ status = 'failed', removed = false, attempts = state['attempts'] })
205
- end
206
- end
207
- `;
208
- /**
209
- * Redis-based queue provider with full job support.
210
- *
211
- * Features:
212
- * - Atomic job acquisition using Lua scripts
213
- * - Blocking wait using Redis BZPOPMIN (no polling)
214
- * - Event emission for job lifecycle
215
- * - removeOnComplete/removeOnFail support
216
- *
217
- * Uses the following Redis data structures:
218
- * - HASH `{prefix}:job:{queue}:{id}` - Job data
219
- * - ZSET `{prefix}:waiting:{queue}` - Waiting jobs (score = priority)
220
- * - ZSET `{prefix}:delayed:{queue}` - Delayed jobs (score = availableAt timestamp)
221
- * - SET `{prefix}:active:{queue}` - Active jobs
222
- * - LIST `{prefix}:completed:{queue}` - Completed jobs (newest first)
223
- * - LIST `{prefix}:failed:{queue}` - Failed jobs (newest first)
224
- * - LIST `{prefix}:messages:{queue}` - Simple message queue (backward compat)
225
- * - LIST `{prefix}:notify:{queue}` - Notification list for blocking wait
226
- */
227
- var RedisQueueProvider = class extends alepha_queue.QueueProvider {
228
- log = (0, alepha_logger.$logger)();
229
- env = (0, alepha.$env)(envSchema);
230
- redisProvider = (0, alepha.$inject)(alepha_redis.RedisProvider);
231
- blockingClient;
232
- shouldStop = false;
233
- acquireJobSha;
234
- completeJobSha;
235
- failJobSha;
236
- start = (0, alepha.$hook)({
237
- on: "start",
238
- handler: async () => {
239
- this.shouldStop = false;
240
- this.blockingClient = this.redisProvider.duplicate();
241
- await this.blockingClient.connect();
242
- const redis = this.redisProvider.publisher;
243
- const acquireSha = await redis.scriptLoad(ACQUIRE_JOB_SCRIPT);
244
- const completeSha = await redis.scriptLoad(COMPLETE_JOB_SCRIPT);
245
- const failSha = await redis.scriptLoad(FAIL_JOB_SCRIPT);
246
- this.acquireJobSha = acquireSha.toString();
247
- this.completeJobSha = completeSha.toString();
248
- this.failJobSha = failSha.toString();
249
- }
250
- });
251
- stop = (0, alepha.$hook)({
252
- on: "stop",
253
- handler: async () => {
254
- this.shouldStop = true;
255
- if (this.blockingClient?.isOpen) await this.blockingClient.close();
256
- }
257
- });
258
- key(type, queue, id) {
259
- const base = `${this.env.REDIS_QUEUE_PREFIX}:${type}:${queue}`;
260
- return id ? `${base}:${id}` : base;
261
- }
262
- messageKey(queue) {
263
- return `${this.env.REDIS_QUEUE_PREFIX}:${queue}`;
264
- }
265
- notifyKey(queue) {
266
- return `${this.env.REDIS_QUEUE_PREFIX}:notify:${queue}`;
267
- }
268
- async push(queue, message) {
269
- await this.redisProvider.publisher.LPUSH(this.messageKey(queue), message);
270
- }
271
- async pop(queue) {
272
- const value = await this.redisProvider.publisher.RPOP(this.messageKey(queue));
273
- if (value == null) return void 0;
274
- return String(value);
275
- }
276
- async popBlocking(queues, timeoutSeconds) {
277
- if (queues.length === 0 || !this.blockingClient) return;
278
- const prefixedQueues = queues.map((q) => this.messageKey(q));
279
- const result = await this.blockingClient.BRPOP(prefixedQueues, timeoutSeconds);
280
- if (result == null) return void 0;
281
- const key = result.key.toString();
282
- const prefixLength = this.env.REDIS_QUEUE_PREFIX.length + 1;
283
- return {
284
- queue: key.substring(prefixLength),
285
- message: result.element.toString()
286
- };
287
- }
288
- async generateJobId() {
289
- return `job_${await this.redisProvider.publisher.INCR(`${this.env.REDIS_QUEUE_PREFIX}:job_counter`)}_${Date.now()}`;
290
- }
291
- serializeJob(job) {
292
- return {
293
- id: job.id,
294
- queue: job.queue,
295
- payload: JSON.stringify(job.payload),
296
- options: JSON.stringify(job.options),
297
- state: JSON.stringify(job.state)
298
- };
299
- }
300
- deserializeJob(data) {
301
- if (!data.id) return void 0;
302
- return {
303
- id: data.id,
304
- queue: data.queue,
305
- payload: JSON.parse(data.payload),
306
- options: JSON.parse(data.options),
307
- state: JSON.parse(data.state)
308
- };
309
- }
310
- async addJob(queue, payload, options) {
311
- const redis = this.redisProvider.publisher;
312
- const now = Date.now();
313
- const delay = options?.delay ?? 0;
314
- const isDelayed = delay > 0;
315
- const job = {
316
- id: await this.generateJobId(),
317
- queue,
318
- payload,
319
- options: {
320
- priority: options?.priority ?? 0,
321
- delay: options?.delay ?? 0,
322
- maxAttempts: options?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,
323
- backoff: options?.backoff,
324
- lockDuration: options?.lockDuration ?? DEFAULT_LOCK_DURATION,
325
- removeOnComplete: options?.removeOnComplete,
326
- removeOnFail: options?.removeOnFail
327
- },
328
- state: {
329
- status: isDelayed ? "delayed" : "waiting",
330
- attempts: 0,
331
- createdAt: now,
332
- availableAt: isDelayed ? now + delay : now
333
- }
334
- };
335
- await redis.HSET(this.key("job", queue, job.id), this.serializeJob(job));
336
- if (isDelayed) await redis.ZADD(this.key("delayed", queue), {
337
- score: job.state.availableAt,
338
- value: job.id
339
- });
340
- else {
341
- await redis.ZADD(this.key("waiting", queue), {
342
- score: job.options.priority ?? 0,
343
- value: job.id
344
- });
345
- await redis.LPUSH(this.notifyKey(queue), job.id);
346
- }
347
- this.log.debug(`Added job ${job.id} to queue ${queue}`, {
348
- status: job.state.status,
349
- priority: job.options.priority
350
- });
351
- if (!isDelayed) await this.emit({
352
- type: "waiting",
353
- queue,
354
- jobId: job.id,
355
- timestamp: now,
356
- job
357
- });
358
- return job;
359
- }
360
- async acquireJob(queues, workerId, timeoutSeconds) {
361
- if (!this.blockingClient || this.shouldStop) return;
362
- const redis = this.redisProvider.publisher;
363
- const endTime = Date.now() + timeoutSeconds * 1e3;
364
- while (Date.now() < endTime && !this.shouldStop) {
365
- for (const queue of queues) try {
366
- const result = await redis.evalSha(this.acquireJobSha, {
367
- keys: [
368
- this.key("waiting", queue),
369
- this.key("active", queue),
370
- this.key("job", queue)
371
- ],
372
- arguments: [
373
- workerId,
374
- String(Date.now()),
375
- String(DEFAULT_LOCK_DURATION)
376
- ]
377
- });
378
- if (result) {
379
- const job = JSON.parse(result);
380
- this.log.debug(`Worker ${workerId} acquired job ${job.id}`, {
381
- queue,
382
- attempt: job.state.attempts
383
- });
384
- await this.emit({
385
- type: "active",
386
- queue,
387
- jobId: job.id,
388
- timestamp: Date.now(),
389
- workerId,
390
- attempt: job.state.attempts
391
- });
392
- return {
393
- queue,
394
- job
395
- };
396
- }
397
- } catch (error) {
398
- this.log.warn(`Failed to acquire job from ${queue}`, error);
399
- }
400
- const notifyKeys = queues.map((q) => this.notifyKey(q));
401
- const remainingTimeout = Math.max(1, Math.ceil((endTime - Date.now()) / 1e3));
402
- try {
403
- if (await this.blockingClient.BRPOP(notifyKeys, Math.min(remainingTimeout, 5))) {}
404
- } catch {
405
- if (this.shouldStop) return;
406
- }
407
- }
408
- }
409
- bufferRecordToString(record) {
410
- const result = {};
411
- for (const [key, value] of Object.entries(record)) result[key] = value?.toString() ?? "";
412
- return result;
413
- }
414
- async completeJob(queue, jobId, result) {
415
- const redis = this.redisProvider.publisher;
416
- const now = Date.now();
417
- try {
418
- const luaResult = await redis.evalSha(this.completeJobSha, {
419
- keys: [
420
- this.key("job", queue, jobId),
421
- this.key("active", queue),
422
- this.key("completed", queue)
423
- ],
424
- arguments: [
425
- jobId,
426
- String(now),
427
- result !== void 0 ? JSON.stringify(result) : ""
428
- ]
429
- });
430
- if (!luaResult) {
431
- this.log.warn(`Attempted to complete unknown job ${jobId}`);
432
- return;
433
- }
434
- const { removed, duration } = JSON.parse(luaResult);
435
- this.log.debug(`Job ${jobId} completed${removed ? " and removed" : ""}`, {
436
- queue,
437
- result
438
- });
439
- await this.emit({
440
- type: "completed",
441
- queue,
442
- jobId,
443
- timestamp: now,
444
- result,
445
- duration
446
- });
447
- } catch (error) {
448
- this.log.warn(`Lua completeJob failed, using fallback`, error);
449
- await this.completeJobFallback(queue, jobId, result);
450
- }
451
- }
452
- async completeJobFallback(queue, jobId, result) {
453
- const redis = this.redisProvider.publisher;
454
- const now = Date.now();
455
- const jobData = await redis.HGETALL(this.key("job", queue, jobId));
456
- const job = this.deserializeJob(this.bufferRecordToString(jobData));
457
- if (!job) {
458
- this.log.warn(`Attempted to complete unknown job ${jobId}`);
459
- return;
460
- }
461
- const duration = now - (job.state.processedAt ?? now);
462
- await redis.SREM(this.key("active", queue), jobId);
463
- job.state.status = "completed";
464
- job.state.completedAt = now;
465
- job.state.result = result;
466
- job.state.lockedBy = void 0;
467
- job.state.lockedUntil = void 0;
468
- const removeOnComplete = job.options.removeOnComplete;
469
- if (removeOnComplete === true) await redis.DEL(this.key("job", queue, jobId));
470
- else {
471
- await redis.HSET(this.key("job", queue, jobId), { state: JSON.stringify(job.state) });
472
- await redis.LPUSH(this.key("completed", queue), jobId);
473
- if (typeof removeOnComplete === "number" && removeOnComplete >= 0) await this.cleanJobs(queue, "completed", { maxCount: removeOnComplete });
474
- }
475
- this.log.debug(`Job ${jobId} completed`, { queue });
476
- await this.emit({
477
- type: "completed",
478
- queue,
479
- jobId,
480
- timestamp: now,
481
- result,
482
- duration
483
- });
484
- }
485
- async failJob(queue, jobId, error, stackTrace) {
486
- const redis = this.redisProvider.publisher;
487
- const now = Date.now();
488
- const jobData = await redis.HGETALL(this.key("job", queue, jobId));
489
- const job = this.deserializeJob(this.bufferRecordToString(jobData));
490
- if (!job) {
491
- this.log.warn(`Attempted to fail unknown job ${jobId}`);
492
- return;
493
- }
494
- const backoffDelay = this.calculateBackoff(job);
495
- try {
496
- const luaResult = await redis.evalSha(this.failJobSha, {
497
- keys: [
498
- this.key("job", queue, jobId),
499
- this.key("active", queue),
500
- this.key("delayed", queue),
501
- this.key("failed", queue)
502
- ],
503
- arguments: [
504
- jobId,
505
- String(now),
506
- error,
507
- stackTrace ?? "",
508
- String(backoffDelay)
509
- ]
510
- });
511
- if (!luaResult) {
512
- this.log.warn(`Attempted to fail unknown job ${jobId}`);
513
- return;
514
- }
515
- const result = JSON.parse(luaResult);
516
- if (result.status === "retrying") {
517
- this.log.debug(`Job ${jobId} failed, will retry in ${result.delay}ms`, {
518
- queue,
519
- attempt: job.state.attempts,
520
- error
521
- });
522
- await this.emit({
523
- type: "retrying",
524
- queue,
525
- jobId,
526
- timestamp: now,
527
- error,
528
- attempt: result.attempt,
529
- delay: result.delay
530
- });
531
- } else {
532
- this.log.debug(`Job ${jobId} permanently failed${result.removed ? " and removed" : ""}`, {
533
- queue,
534
- error
535
- });
536
- await this.emit({
537
- type: "failed",
538
- queue,
539
- jobId,
540
- timestamp: now,
541
- error,
542
- stackTrace,
543
- attempts: result.attempts
544
- });
545
- }
546
- } catch (luaError) {
547
- this.log.warn(`Lua failJob failed, using fallback`, luaError);
548
- await this.failJobFallback(queue, jobId, error, stackTrace);
549
- }
550
- }
551
- async failJobFallback(queue, jobId, error, stackTrace) {
552
- const redis = this.redisProvider.publisher;
553
- const now = Date.now();
554
- const jobData = await redis.HGETALL(this.key("job", queue, jobId));
555
- const job = this.deserializeJob(this.bufferRecordToString(jobData));
556
- if (!job) {
557
- this.log.warn(`Attempted to fail unknown job ${jobId}`);
558
- return;
559
- }
560
- await redis.SREM(this.key("active", queue), jobId);
561
- const maxAttempts = job.options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
562
- if (job.state.attempts < maxAttempts) {
563
- const backoffDelay = this.calculateBackoff(job);
564
- job.state.status = "delayed";
565
- job.state.availableAt = now + backoffDelay;
566
- job.state.error = error;
567
- job.state.stackTrace = stackTrace;
568
- job.state.lockedBy = void 0;
569
- job.state.lockedUntil = void 0;
570
- await redis.HSET(this.key("job", queue, jobId), { state: JSON.stringify(job.state) });
571
- await redis.ZADD(this.key("delayed", queue), {
572
- score: job.state.availableAt,
573
- value: jobId
574
- });
575
- this.log.debug(`Job ${jobId} failed, will retry in ${backoffDelay}ms`, {
576
- queue,
577
- attempt: job.state.attempts,
578
- maxAttempts
579
- });
580
- await this.emit({
581
- type: "retrying",
582
- queue,
583
- jobId,
584
- timestamp: now,
585
- error,
586
- attempt: job.state.attempts + 1,
587
- delay: backoffDelay
588
- });
589
- } else {
590
- job.state.status = "failed";
591
- job.state.failedAt = now;
592
- job.state.error = error;
593
- job.state.stackTrace = stackTrace;
594
- job.state.lockedBy = void 0;
595
- job.state.lockedUntil = void 0;
596
- const removeOnFail = job.options.removeOnFail;
597
- if (removeOnFail === true) await redis.DEL(this.key("job", queue, jobId));
598
- else {
599
- await redis.HSET(this.key("job", queue, jobId), { state: JSON.stringify(job.state) });
600
- await redis.LPUSH(this.key("failed", queue), jobId);
601
- if (typeof removeOnFail === "number" && removeOnFail >= 0) await this.cleanJobs(queue, "failed", { maxCount: removeOnFail });
602
- }
603
- this.log.debug(`Job ${jobId} permanently failed`, { queue });
604
- await this.emit({
605
- type: "failed",
606
- queue,
607
- jobId,
608
- timestamp: now,
609
- error,
610
- stackTrace,
611
- attempts: job.state.attempts
612
- });
613
- }
614
- }
615
- calculateBackoff(job) {
616
- const backoff = job.options.backoff;
617
- const attempt = job.state.attempts;
618
- if (!backoff) return DEFAULT_BACKOFF_DELAY;
619
- const baseDelay = backoff.delay ?? DEFAULT_BACKOFF_DELAY;
620
- const maxDelay = backoff.maxDelay ?? DEFAULT_BACKOFF_MAX_DELAY;
621
- if (backoff.type === "fixed") return baseDelay;
622
- const exponentialDelay = baseDelay * 2 ** (attempt - 1);
623
- return Math.min(exponentialDelay, maxDelay);
624
- }
625
- async renewJobLock(queue, jobId, workerId) {
626
- const redis = this.redisProvider.publisher;
627
- const jobData = await redis.HGETALL(this.key("job", queue, jobId));
628
- const job = this.deserializeJob(this.bufferRecordToString(jobData));
629
- if (!job || job.state.lockedBy !== workerId) return false;
630
- job.state.lockedUntil = Date.now() + (job.options.lockDuration ?? DEFAULT_LOCK_DURATION);
631
- await redis.HSET(this.key("job", queue, jobId), { state: JSON.stringify(job.state) });
632
- return true;
633
- }
634
- async getJob(queue, jobId) {
635
- const jobData = await this.redisProvider.publisher.HGETALL(this.key("job", queue, jobId));
636
- return this.deserializeJob(this.bufferRecordToString(jobData));
637
- }
638
- async getJobs(queue, status, options) {
639
- const redis = this.redisProvider.publisher;
640
- const limit = options?.limit ?? 100;
641
- const offset = options?.offset ?? 0;
642
- let jobIds;
643
- switch (status) {
644
- case "waiting":
645
- jobIds = (await redis.ZRANGE(this.key("waiting", queue), offset, offset + limit - 1)).map((r) => r.toString());
646
- break;
647
- case "delayed":
648
- jobIds = (await redis.ZRANGE(this.key("delayed", queue), offset, offset + limit - 1)).map((r) => r.toString());
649
- break;
650
- case "active":
651
- jobIds = (await redis.SMEMBERS(this.key("active", queue))).map((r) => r.toString()).slice(offset, offset + limit);
652
- break;
653
- case "completed":
654
- jobIds = (await redis.LRANGE(this.key("completed", queue), offset, offset + limit - 1)).map((r) => r.toString());
655
- break;
656
- case "failed":
657
- jobIds = (await redis.LRANGE(this.key("failed", queue), offset, offset + limit - 1)).map((r) => r.toString());
658
- break;
659
- default: jobIds = [];
660
- }
661
- const jobs = [];
662
- for (const jobId of jobIds) {
663
- const job = await this.getJob(queue, jobId);
664
- if (job) jobs.push(job);
665
- }
666
- return jobs;
667
- }
668
- async getJobCounts(queue) {
669
- const redis = this.redisProvider.publisher;
670
- const [waiting, delayed, active, completed, failed] = await Promise.all([
671
- redis.ZCARD(this.key("waiting", queue)),
672
- redis.ZCARD(this.key("delayed", queue)),
673
- redis.SCARD(this.key("active", queue)),
674
- redis.LLEN(this.key("completed", queue)),
675
- redis.LLEN(this.key("failed", queue))
676
- ]);
677
- return {
678
- waiting,
679
- delayed,
680
- active,
681
- completed,
682
- failed
683
- };
684
- }
685
- async promoteDelayedJobs(queue) {
686
- const redis = this.redisProvider.publisher;
687
- const now = Date.now();
688
- const results = await redis.ZRANGEBYSCORE(this.key("delayed", queue), "-inf", now);
689
- let promoted = 0;
690
- for (const result of results) {
691
- const jobId = result.toString();
692
- if (await redis.ZREM(this.key("delayed", queue), jobId) === 0) continue;
693
- const job = await this.getJob(queue, jobId);
694
- if (!job) continue;
695
- job.state.status = "waiting";
696
- await redis.HSET(this.key("job", queue, jobId), { state: JSON.stringify(job.state) });
697
- await redis.ZADD(this.key("waiting", queue), {
698
- score: job.options.priority ?? 0,
699
- value: jobId
700
- });
701
- await redis.LPUSH(this.notifyKey(queue), jobId);
702
- promoted++;
703
- this.log.debug(`Promoted delayed job ${jobId}`, { queue });
704
- await this.emit({
705
- type: "waiting",
706
- queue,
707
- jobId,
708
- timestamp: now,
709
- job
710
- });
711
- }
712
- return promoted;
713
- }
714
- async recoverStalledJobs(queue, stalledThresholdMs) {
715
- const redis = this.redisProvider.publisher;
716
- const now = Date.now();
717
- const activeJobIds = await redis.SMEMBERS(this.key("active", queue));
718
- const stalledJobIds = [];
719
- for (const result of activeJobIds) {
720
- const jobId = result.toString();
721
- const job = await this.getJob(queue, jobId);
722
- if (!job) continue;
723
- if (!((job.state.lockedUntil ?? 0) + stalledThresholdMs < now)) continue;
724
- stalledJobIds.push(jobId);
725
- const workerId = job.state.lockedBy;
726
- await redis.SREM(this.key("active", queue), jobId);
727
- const maxAttempts = job.options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
728
- const hasMoreAttempts = job.state.attempts < maxAttempts;
729
- await this.emit({
730
- type: "stalled",
731
- queue,
732
- jobId,
733
- timestamp: now,
734
- workerId,
735
- willRetry: hasMoreAttempts
736
- });
737
- if (hasMoreAttempts) {
738
- job.state.status = "waiting";
739
- job.state.lockedBy = void 0;
740
- job.state.lockedUntil = void 0;
741
- job.state.error = "Job stalled (worker timeout)";
742
- await redis.HSET(this.key("job", queue, jobId), { state: JSON.stringify(job.state) });
743
- await redis.ZADD(this.key("waiting", queue), {
744
- score: job.options.priority ?? 0,
745
- value: jobId
746
- });
747
- await redis.LPUSH(this.notifyKey(queue), jobId);
748
- this.log.warn(`Recovered stalled job ${jobId}`, { queue });
749
- await this.emit({
750
- type: "waiting",
751
- queue,
752
- jobId,
753
- timestamp: now,
754
- job
755
- });
756
- } else {
757
- job.state.status = "failed";
758
- job.state.failedAt = now;
759
- job.state.lockedBy = void 0;
760
- job.state.lockedUntil = void 0;
761
- job.state.error = "Job stalled (worker timeout) - max attempts exceeded";
762
- const removeOnFail = job.options.removeOnFail;
763
- if (removeOnFail === true) await redis.DEL(this.key("job", queue, jobId));
764
- else {
765
- await redis.HSET(this.key("job", queue, jobId), { state: JSON.stringify(job.state) });
766
- await redis.LPUSH(this.key("failed", queue), jobId);
767
- if (typeof removeOnFail === "number" && removeOnFail >= 0) await this.cleanJobs(queue, "failed", { maxCount: removeOnFail });
768
- }
769
- this.log.warn(`Stalled job ${jobId} permanently failed`, { queue });
770
- await this.emit({
771
- type: "failed",
772
- queue,
773
- jobId,
774
- timestamp: now,
775
- error: job.state.error,
776
- attempts: job.state.attempts
777
- });
778
- }
779
- }
780
- return stalledJobIds;
781
- }
782
- async cleanJobs(queue, status, options) {
783
- const redis = this.redisProvider.publisher;
784
- const listKey = this.key(status, queue);
785
- const maxAge = options?.maxAge;
786
- const maxCount = options?.maxCount;
787
- let removed = 0;
788
- if (maxAge !== void 0) {
789
- const cutoff = Date.now() - maxAge;
790
- const jobIds = await redis.LRANGE(listKey, 0, -1);
791
- for (const result of jobIds) {
792
- const jobId = result.toString();
793
- const job = await this.getJob(queue, jobId);
794
- if (!job) continue;
795
- const timestamp = status === "completed" ? job.state.completedAt : job.state.failedAt;
796
- if (timestamp && timestamp < cutoff) {
797
- await redis.LREM(listKey, 1, jobId);
798
- await redis.DEL(this.key("job", queue, jobId));
799
- removed++;
800
- }
801
- }
802
- }
803
- if (maxCount !== void 0) {
804
- if (await redis.LLEN(listKey) > maxCount) {
805
- const toRemove = await redis.LRANGE(listKey, maxCount, -1);
806
- for (const result of toRemove) {
807
- const jobId = result.toString();
808
- await redis.DEL(this.key("job", queue, jobId));
809
- removed++;
810
- }
811
- await redis.LTRIM(listKey, 0, maxCount - 1);
812
- }
813
- }
814
- return removed;
815
- }
816
- async removeJob(queue, jobId) {
817
- const redis = this.redisProvider.publisher;
818
- const job = await this.getJob(queue, jobId);
819
- if (!job) return;
820
- const previousStatus = job.state.status;
821
- switch (job.state.status) {
822
- case "waiting":
823
- await redis.ZREM(this.key("waiting", queue), jobId);
824
- break;
825
- case "delayed":
826
- await redis.ZREM(this.key("delayed", queue), jobId);
827
- break;
828
- case "active":
829
- await redis.SREM(this.key("active", queue), jobId);
830
- break;
831
- case "completed":
832
- await redis.LREM(this.key("completed", queue), 1, jobId);
833
- break;
834
- case "failed":
835
- await redis.LREM(this.key("failed", queue), 1, jobId);
836
- break;
837
- }
838
- await redis.DEL(this.key("job", queue, jobId));
839
- await this.emit({
840
- type: "removed",
841
- queue,
842
- jobId,
843
- timestamp: Date.now(),
844
- previousStatus
845
- });
846
- }
847
- cancelWaiters() {
848
- this.shouldStop = true;
849
- }
850
- };
851
-
852
- //#endregion
853
- //#region src/queue-redis/index.ts
854
- /**
855
- * Plugin for Alepha Queue that provides Redis queue capabilities.
856
- *
857
- * @see {@link RedisQueueProvider}
858
- * @module alepha.queue.redis
859
- */
860
- const AlephaQueueRedis = (0, alepha.$module)({
861
- name: "alepha.queue.redis",
862
- services: [RedisQueueProvider],
863
- register: (alepha$1) => alepha$1.with({
864
- optional: true,
865
- provide: alepha_queue.QueueProvider,
866
- use: RedisQueueProvider
867
- }).with(alepha_queue.AlephaQueue)
868
- });
869
-
870
- //#endregion
871
- exports.AlephaQueueRedis = AlephaQueueRedis;
872
- exports.RedisQueueProvider = RedisQueueProvider;
873
- //# sourceMappingURL=index.cjs.map