alepha 0.13.0 → 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 (195) hide show
  1. package/dist/api-jobs/index.d.ts +26 -26
  2. package/dist/api-users/index.d.ts +1 -1
  3. package/dist/cli/{dist-Sz2EXvQX.cjs → dist-Dl9Vl7Ur.js} +17 -13
  4. package/dist/cli/{dist-BBPjuQ56.js.map → dist-Dl9Vl7Ur.js.map} +1 -1
  5. package/dist/cli/index.d.ts +3 -11
  6. package/dist/cli/index.js +106 -74
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/email/index.js +71 -73
  9. package/dist/email/index.js.map +1 -1
  10. package/dist/orm/index.d.ts +1 -1
  11. package/dist/orm/index.js.map +1 -1
  12. package/dist/queue/index.d.ts +4 -4
  13. package/dist/retry/index.d.ts +1 -1
  14. package/dist/retry/index.js +2 -2
  15. package/dist/retry/index.js.map +1 -1
  16. package/dist/scheduler/index.d.ts +6 -6
  17. package/dist/security/index.d.ts +28 -28
  18. package/dist/server/index.js +1 -1
  19. package/dist/server/index.js.map +1 -1
  20. package/dist/server-health/index.d.ts +17 -17
  21. package/dist/server-metrics/index.js +170 -174
  22. package/dist/server-metrics/index.js.map +1 -1
  23. package/dist/server-security/index.d.ts +9 -9
  24. package/dist/vite/index.js +4 -5
  25. package/dist/vite/index.js.map +1 -1
  26. package/dist/websocket/index.d.ts +7 -7
  27. package/package.json +52 -103
  28. package/src/cli/apps/AlephaPackageBuilderCli.ts +7 -2
  29. package/src/cli/assets/appRouterTs.ts +9 -0
  30. package/src/cli/assets/indexHtml.ts +2 -1
  31. package/src/cli/assets/mainBrowserTs.ts +10 -0
  32. package/src/cli/commands/CoreCommands.ts +6 -5
  33. package/src/cli/commands/DrizzleCommands.ts +65 -57
  34. package/src/cli/commands/VerifyCommands.ts +1 -1
  35. package/src/cli/services/ProjectUtils.ts +44 -38
  36. package/src/orm/providers/DrizzleKitProvider.ts +1 -1
  37. package/src/retry/descriptors/$retry.ts +5 -3
  38. package/src/server/providers/NodeHttpServerProvider.ts +1 -1
  39. package/src/vite/helpers/boot.ts +3 -3
  40. package/dist/api-files/index.cjs +0 -1293
  41. package/dist/api-files/index.cjs.map +0 -1
  42. package/dist/api-files/index.d.cts +0 -829
  43. package/dist/api-jobs/index.cjs +0 -274
  44. package/dist/api-jobs/index.cjs.map +0 -1
  45. package/dist/api-jobs/index.d.cts +0 -654
  46. package/dist/api-notifications/index.cjs +0 -380
  47. package/dist/api-notifications/index.cjs.map +0 -1
  48. package/dist/api-notifications/index.d.cts +0 -289
  49. package/dist/api-parameters/index.cjs +0 -66
  50. package/dist/api-parameters/index.cjs.map +0 -1
  51. package/dist/api-parameters/index.d.cts +0 -84
  52. package/dist/api-users/index.cjs +0 -6009
  53. package/dist/api-users/index.cjs.map +0 -1
  54. package/dist/api-users/index.d.cts +0 -4740
  55. package/dist/api-verifications/index.cjs +0 -407
  56. package/dist/api-verifications/index.cjs.map +0 -1
  57. package/dist/api-verifications/index.d.cts +0 -207
  58. package/dist/batch/index.cjs +0 -408
  59. package/dist/batch/index.cjs.map +0 -1
  60. package/dist/batch/index.d.cts +0 -330
  61. package/dist/bin/index.cjs +0 -17
  62. package/dist/bin/index.cjs.map +0 -1
  63. package/dist/bin/index.d.cts +0 -1
  64. package/dist/bucket/index.cjs +0 -303
  65. package/dist/bucket/index.cjs.map +0 -1
  66. package/dist/bucket/index.d.cts +0 -355
  67. package/dist/cache/index.cjs +0 -241
  68. package/dist/cache/index.cjs.map +0 -1
  69. package/dist/cache/index.d.cts +0 -202
  70. package/dist/cache-redis/index.cjs +0 -84
  71. package/dist/cache-redis/index.cjs.map +0 -1
  72. package/dist/cache-redis/index.d.cts +0 -40
  73. package/dist/cli/chunk-DSlc6foC.cjs +0 -43
  74. package/dist/cli/dist-BBPjuQ56.js +0 -2778
  75. package/dist/cli/dist-Sz2EXvQX.cjs.map +0 -1
  76. package/dist/cli/index.cjs +0 -1241
  77. package/dist/cli/index.cjs.map +0 -1
  78. package/dist/cli/index.d.cts +0 -422
  79. package/dist/command/index.cjs +0 -693
  80. package/dist/command/index.cjs.map +0 -1
  81. package/dist/command/index.d.cts +0 -340
  82. package/dist/core/index.cjs +0 -2264
  83. package/dist/core/index.cjs.map +0 -1
  84. package/dist/core/index.d.cts +0 -1927
  85. package/dist/datetime/index.cjs +0 -318
  86. package/dist/datetime/index.cjs.map +0 -1
  87. package/dist/datetime/index.d.cts +0 -145
  88. package/dist/email/index.cjs +0 -10874
  89. package/dist/email/index.cjs.map +0 -1
  90. package/dist/email/index.d.cts +0 -186
  91. package/dist/fake/index.cjs +0 -34641
  92. package/dist/fake/index.cjs.map +0 -1
  93. package/dist/fake/index.d.cts +0 -74
  94. package/dist/file/index.cjs +0 -1212
  95. package/dist/file/index.cjs.map +0 -1
  96. package/dist/file/index.d.cts +0 -698
  97. package/dist/lock/index.cjs +0 -226
  98. package/dist/lock/index.cjs.map +0 -1
  99. package/dist/lock/index.d.cts +0 -361
  100. package/dist/lock-redis/index.cjs +0 -113
  101. package/dist/lock-redis/index.cjs.map +0 -1
  102. package/dist/lock-redis/index.d.cts +0 -24
  103. package/dist/logger/index.cjs +0 -521
  104. package/dist/logger/index.cjs.map +0 -1
  105. package/dist/logger/index.d.cts +0 -281
  106. package/dist/orm/index.cjs +0 -2986
  107. package/dist/orm/index.cjs.map +0 -1
  108. package/dist/orm/index.d.cts +0 -2213
  109. package/dist/queue/index.cjs +0 -1044
  110. package/dist/queue/index.cjs.map +0 -1
  111. package/dist/queue/index.d.cts +0 -1265
  112. package/dist/queue-redis/index.cjs +0 -873
  113. package/dist/queue-redis/index.cjs.map +0 -1
  114. package/dist/queue-redis/index.d.cts +0 -82
  115. package/dist/redis/index.cjs +0 -153
  116. package/dist/redis/index.cjs.map +0 -1
  117. package/dist/redis/index.d.cts +0 -82
  118. package/dist/retry/index.cjs +0 -146
  119. package/dist/retry/index.cjs.map +0 -1
  120. package/dist/retry/index.d.cts +0 -172
  121. package/dist/router/index.cjs +0 -111
  122. package/dist/router/index.cjs.map +0 -1
  123. package/dist/router/index.d.cts +0 -46
  124. package/dist/scheduler/index.cjs +0 -576
  125. package/dist/scheduler/index.cjs.map +0 -1
  126. package/dist/scheduler/index.d.cts +0 -145
  127. package/dist/security/index.cjs +0 -2402
  128. package/dist/security/index.cjs.map +0 -1
  129. package/dist/security/index.d.cts +0 -598
  130. package/dist/server/index.cjs +0 -1680
  131. package/dist/server/index.cjs.map +0 -1
  132. package/dist/server/index.d.cts +0 -810
  133. package/dist/server-auth/index.cjs +0 -3146
  134. package/dist/server-auth/index.cjs.map +0 -1
  135. package/dist/server-auth/index.d.cts +0 -1164
  136. package/dist/server-cache/index.cjs +0 -252
  137. package/dist/server-cache/index.cjs.map +0 -1
  138. package/dist/server-cache/index.d.cts +0 -164
  139. package/dist/server-compress/index.cjs +0 -141
  140. package/dist/server-compress/index.cjs.map +0 -1
  141. package/dist/server-compress/index.d.cts +0 -38
  142. package/dist/server-cookies/index.cjs +0 -234
  143. package/dist/server-cookies/index.cjs.map +0 -1
  144. package/dist/server-cookies/index.d.cts +0 -144
  145. package/dist/server-cors/index.cjs +0 -201
  146. package/dist/server-cors/index.cjs.map +0 -1
  147. package/dist/server-cors/index.d.cts +0 -140
  148. package/dist/server-health/index.cjs +0 -62
  149. package/dist/server-health/index.cjs.map +0 -1
  150. package/dist/server-health/index.d.cts +0 -58
  151. package/dist/server-helmet/index.cjs +0 -131
  152. package/dist/server-helmet/index.cjs.map +0 -1
  153. package/dist/server-helmet/index.d.cts +0 -97
  154. package/dist/server-links/index.cjs +0 -992
  155. package/dist/server-links/index.cjs.map +0 -1
  156. package/dist/server-links/index.d.cts +0 -513
  157. package/dist/server-metrics/index.cjs +0 -4535
  158. package/dist/server-metrics/index.cjs.map +0 -1
  159. package/dist/server-metrics/index.d.cts +0 -35
  160. package/dist/server-multipart/index.cjs +0 -237
  161. package/dist/server-multipart/index.cjs.map +0 -1
  162. package/dist/server-multipart/index.d.cts +0 -50
  163. package/dist/server-proxy/index.cjs +0 -186
  164. package/dist/server-proxy/index.cjs.map +0 -1
  165. package/dist/server-proxy/index.d.cts +0 -234
  166. package/dist/server-rate-limit/index.cjs +0 -241
  167. package/dist/server-rate-limit/index.cjs.map +0 -1
  168. package/dist/server-rate-limit/index.d.cts +0 -183
  169. package/dist/server-security/index.cjs +0 -316
  170. package/dist/server-security/index.cjs.map +0 -1
  171. package/dist/server-security/index.d.cts +0 -173
  172. package/dist/server-static/index.cjs +0 -170
  173. package/dist/server-static/index.cjs.map +0 -1
  174. package/dist/server-static/index.d.cts +0 -121
  175. package/dist/server-swagger/index.cjs +0 -1021
  176. package/dist/server-swagger/index.cjs.map +0 -1
  177. package/dist/server-swagger/index.d.cts +0 -382
  178. package/dist/sms/index.cjs +0 -221
  179. package/dist/sms/index.cjs.map +0 -1
  180. package/dist/sms/index.d.cts +0 -130
  181. package/dist/thread/index.cjs +0 -350
  182. package/dist/thread/index.cjs.map +0 -1
  183. package/dist/thread/index.d.cts +0 -260
  184. package/dist/topic/index.cjs +0 -282
  185. package/dist/topic/index.cjs.map +0 -1
  186. package/dist/topic/index.d.cts +0 -523
  187. package/dist/topic-redis/index.cjs +0 -71
  188. package/dist/topic-redis/index.cjs.map +0 -1
  189. package/dist/topic-redis/index.d.cts +0 -42
  190. package/dist/vite/index.cjs +0 -1077
  191. package/dist/vite/index.cjs.map +0 -1
  192. package/dist/vite/index.d.cts +0 -542
  193. package/dist/websocket/index.cjs +0 -1117
  194. package/dist/websocket/index.cjs.map +0 -1
  195. package/dist/websocket/index.d.cts +0 -861
@@ -1,1044 +0,0 @@
1
- let alepha = require("alepha");
2
- let alepha_logger = require("alepha/logger");
3
- let alepha_datetime = require("alepha/datetime");
4
-
5
- //#region src/queue/descriptors/$consumer.ts
6
- /**
7
- * Creates a consumer descriptor to process messages from a specific queue.
8
- *
9
- * Provides a dedicated message consumer that connects to a queue and processes messages
10
- * with custom handler logic, enabling scalable architectures where multiple consumers
11
- * can process messages from the same queue.
12
- *
13
- * **Key Features**
14
- * - Seamless integration with any $queue descriptor
15
- * - Full type safety inherited from queue schema
16
- * - Automatic worker management for background processing
17
- * - Built-in error handling and retry mechanisms
18
- * - Support for multiple consumers per queue for horizontal scaling
19
- *
20
- * **Common Use Cases**
21
- * - Email sending and notification services
22
- * - Image and media processing workers
23
- * - Data synchronization and background jobs
24
- *
25
- * @example
26
- * ```ts
27
- * class EmailService {
28
- * emailQueue = $queue({
29
- * name: "emails",
30
- * schema: t.object({
31
- * to: t.text(),
32
- * subject: t.text(),
33
- * body: t.text()
34
- * })
35
- * });
36
- *
37
- * emailConsumer = $consumer({
38
- * queue: this.emailQueue,
39
- * handler: async (message) => {
40
- * const { to, subject, body } = message.payload;
41
- * await this.sendEmail(to, subject, body);
42
- * }
43
- * });
44
- *
45
- * async sendWelcomeEmail(userEmail: string) {
46
- * await this.emailQueue.push({
47
- * to: userEmail,
48
- * subject: "Welcome!",
49
- * body: "Thanks for joining."
50
- * });
51
- * }
52
- * }
53
- * ```
54
- */
55
- const $consumer = (options) => {
56
- return (0, alepha.createDescriptor)(ConsumerDescriptor, options);
57
- };
58
- var ConsumerDescriptor = class extends alepha.Descriptor {};
59
- $consumer[alepha.KIND] = ConsumerDescriptor;
60
-
61
- //#endregion
62
- //#region src/queue/providers/QueueProvider.ts
63
- /**
64
- * Queue provider interface supporting both simple message-based and advanced job-based operations.
65
- *
66
- * The simple API (push/pop/popBlocking) is for basic fire-and-forget messaging.
67
- * The job API provides crash recovery, retries, delayed jobs, priorities, and job history.
68
- */
69
- var QueueProvider = class {
70
- eventHandlers = /* @__PURE__ */ new Map();
71
- on(event, handler) {
72
- if (!this.eventHandlers.has(event)) this.eventHandlers.set(event, /* @__PURE__ */ new Set());
73
- this.eventHandlers.get(event).add(handler);
74
- return () => {
75
- this.eventHandlers.get(event)?.delete(handler);
76
- };
77
- }
78
- /**
79
- * Emit a queue event to all registered handlers.
80
- *
81
- * @param event The event to emit.
82
- */
83
- async emit(event) {
84
- const handlers = [...this.eventHandlers.get(event.type) ?? [], ...this.eventHandlers.get("*") ?? []];
85
- await Promise.all(handlers.map((handler) => handler(event)));
86
- }
87
- };
88
-
89
- //#endregion
90
- //#region src/queue/providers/MemoryQueueProvider.ts
91
- const DEFAULT_MAX_ATTEMPTS = 1;
92
- const DEFAULT_LOCK_DURATION = 3e4;
93
- const DEFAULT_BACKOFF_DELAY = 1e3;
94
- const DEFAULT_BACKOFF_MAX_DELAY = 3e4;
95
- /**
96
- * In-memory queue provider with full job support.
97
- *
98
- * This provider stores all data in memory and is suitable for:
99
- * - Development and testing
100
- * - Single-instance applications
101
- * - Scenarios where job persistence across restarts is not required
102
- */
103
- var MemoryQueueProvider = class extends QueueProvider {
104
- log = (0, alepha_logger.$logger)();
105
- messageQueues = {};
106
- messageWaiters = /* @__PURE__ */ new Set();
107
- jobs = /* @__PURE__ */ new Map();
108
- waiting = /* @__PURE__ */ new Map();
109
- delayed = /* @__PURE__ */ new Map();
110
- active = /* @__PURE__ */ new Map();
111
- completed = /* @__PURE__ */ new Map();
112
- failed = /* @__PURE__ */ new Map();
113
- jobWaiters = /* @__PURE__ */ new Set();
114
- jobIdCounter = 0;
115
- async push(queue, ...messages) {
116
- if (this.messageQueues[queue] == null) this.messageQueues[queue] = [];
117
- for (const message of messages) {
118
- const waiter = this.findMessageWaiter(queue);
119
- if (waiter) {
120
- this.removeMessageWaiter(waiter);
121
- waiter.resolve({
122
- queue,
123
- message
124
- });
125
- } else this.messageQueues[queue].push(message);
126
- }
127
- }
128
- async pop(queue) {
129
- return this.messageQueues[queue]?.shift();
130
- }
131
- async popBlocking(queues, timeoutSeconds) {
132
- for (const queue of queues) {
133
- const message = this.messageQueues[queue]?.shift();
134
- if (message) return {
135
- queue,
136
- message
137
- };
138
- }
139
- return new Promise((resolve) => {
140
- const timer = setTimeout(() => {
141
- this.removeMessageWaiter(waiter);
142
- resolve(void 0);
143
- }, timeoutSeconds * 1e3);
144
- const waiter = {
145
- queues: new Set(queues),
146
- resolve: (result) => {
147
- clearTimeout(timer);
148
- resolve(result);
149
- },
150
- timer
151
- };
152
- this.messageWaiters.add(waiter);
153
- });
154
- }
155
- findMessageWaiter(queue) {
156
- for (const waiter of this.messageWaiters) if (waiter.queues.has(queue)) return waiter;
157
- }
158
- removeMessageWaiter(waiter) {
159
- clearTimeout(waiter.timer);
160
- this.messageWaiters.delete(waiter);
161
- }
162
- generateJobId() {
163
- return `job_${++this.jobIdCounter}_${Date.now()}`;
164
- }
165
- ensureQueueStructures(queue) {
166
- if (!this.jobs.has(queue)) {
167
- this.jobs.set(queue, /* @__PURE__ */ new Map());
168
- this.waiting.set(queue, []);
169
- this.delayed.set(queue, []);
170
- this.active.set(queue, /* @__PURE__ */ new Set());
171
- this.completed.set(queue, []);
172
- this.failed.set(queue, []);
173
- }
174
- }
175
- async addJob(queue, payload, options) {
176
- this.ensureQueueStructures(queue);
177
- const now = Date.now();
178
- const delay = options?.delay ?? 0;
179
- const isDelayed = delay > 0;
180
- const job = {
181
- id: this.generateJobId(),
182
- queue,
183
- payload,
184
- options: {
185
- priority: options?.priority ?? 0,
186
- delay: options?.delay ?? 0,
187
- maxAttempts: options?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,
188
- backoff: options?.backoff,
189
- lockDuration: options?.lockDuration ?? DEFAULT_LOCK_DURATION,
190
- removeOnComplete: options?.removeOnComplete,
191
- removeOnFail: options?.removeOnFail
192
- },
193
- state: {
194
- status: isDelayed ? "delayed" : "waiting",
195
- attempts: 0,
196
- createdAt: now,
197
- availableAt: isDelayed ? now + delay : now
198
- }
199
- };
200
- this.jobs.get(queue).set(job.id, job);
201
- if (isDelayed) this.insertDelayed(queue, job);
202
- else {
203
- this.insertWaiting(queue, job);
204
- this.notifyJobWaiters(queue);
205
- }
206
- this.log.debug(`Added job ${job.id} to queue ${queue}`, {
207
- status: job.state.status,
208
- priority: job.options.priority
209
- });
210
- if (!isDelayed) await this.emit({
211
- type: "waiting",
212
- queue,
213
- jobId: job.id,
214
- timestamp: now,
215
- job
216
- });
217
- return job;
218
- }
219
- insertWaiting(queue, job) {
220
- const waitingList = this.waiting.get(queue);
221
- const priority = job.options.priority ?? 0;
222
- let insertIndex = waitingList.length;
223
- for (let i = 0; i < waitingList.length; i++) {
224
- const existingJob = this.jobs.get(queue).get(waitingList[i]);
225
- if (existingJob && (existingJob.options.priority ?? 0) > priority) {
226
- insertIndex = i;
227
- break;
228
- }
229
- }
230
- waitingList.splice(insertIndex, 0, job.id);
231
- }
232
- insertDelayed(queue, job) {
233
- const delayedList = this.delayed.get(queue);
234
- const availableAt = job.state.availableAt ?? 0;
235
- let insertIndex = delayedList.length;
236
- for (let i = 0; i < delayedList.length; i++) {
237
- const existingJob = this.jobs.get(queue).get(delayedList[i]);
238
- if (existingJob && (existingJob.state.availableAt ?? 0) > availableAt) {
239
- insertIndex = i;
240
- break;
241
- }
242
- }
243
- delayedList.splice(insertIndex, 0, job.id);
244
- }
245
- notifyJobWaiters(queue) {
246
- for (const waiter of this.jobWaiters) if (waiter.queues.has(queue)) {
247
- const result = this.tryAcquireJob(Array.from(waiter.queues), waiter.workerId);
248
- if (result) {
249
- this.removeJobWaiter(waiter);
250
- waiter.resolve(result);
251
- return;
252
- }
253
- }
254
- }
255
- removeJobWaiter(waiter) {
256
- clearTimeout(waiter.timer);
257
- this.jobWaiters.delete(waiter);
258
- }
259
- tryAcquireJob(queues, workerId) {
260
- const now = Date.now();
261
- for (const queue of queues) {
262
- const waitingList = this.waiting.get(queue);
263
- if (!waitingList || waitingList.length === 0) continue;
264
- const jobId = waitingList.shift();
265
- const job = this.jobs.get(queue).get(jobId);
266
- if (!job) continue;
267
- job.state.status = "active";
268
- job.state.attempts += 1;
269
- job.state.lockedBy = workerId;
270
- job.state.lockedUntil = now + (job.options.lockDuration ?? DEFAULT_LOCK_DURATION);
271
- job.state.processedAt = now;
272
- this.active.get(queue).add(jobId);
273
- this.log.debug(`Worker ${workerId} acquired job ${jobId}`, {
274
- queue,
275
- attempt: job.state.attempts
276
- });
277
- this.emit({
278
- type: "active",
279
- queue,
280
- jobId,
281
- timestamp: now,
282
- workerId,
283
- attempt: job.state.attempts
284
- });
285
- return {
286
- queue,
287
- job
288
- };
289
- }
290
- }
291
- async acquireJob(queues, workerId, timeoutSeconds) {
292
- for (const queue of queues) this.ensureQueueStructures(queue);
293
- const result = this.tryAcquireJob(queues, workerId);
294
- if (result) return result;
295
- return new Promise((resolve) => {
296
- const timer = setTimeout(() => {
297
- this.removeJobWaiter(waiter);
298
- resolve(void 0);
299
- }, timeoutSeconds * 1e3);
300
- const waiter = {
301
- queues: new Set(queues),
302
- workerId,
303
- resolve: (result$1) => {
304
- clearTimeout(timer);
305
- resolve(result$1);
306
- },
307
- timer
308
- };
309
- this.jobWaiters.add(waiter);
310
- });
311
- }
312
- async completeJob(queue, jobId, result) {
313
- const job = this.jobs.get(queue)?.get(jobId);
314
- if (!job) {
315
- this.log.warn(`Attempted to complete unknown job ${jobId}`);
316
- return;
317
- }
318
- const now = Date.now();
319
- const duration = now - (job.state.processedAt ?? now);
320
- this.active.get(queue)?.delete(jobId);
321
- job.state.status = "completed";
322
- job.state.completedAt = now;
323
- job.state.result = result;
324
- job.state.lockedBy = void 0;
325
- job.state.lockedUntil = void 0;
326
- const removeOnComplete = job.options.removeOnComplete;
327
- if (removeOnComplete === true) {
328
- this.jobs.get(queue)?.delete(jobId);
329
- this.log.debug(`Job ${jobId} completed and removed`, {
330
- queue,
331
- result
332
- });
333
- } else {
334
- this.completed.get(queue).unshift(jobId);
335
- if (typeof removeOnComplete === "number" && removeOnComplete >= 0) await this.cleanJobs(queue, "completed", { maxCount: removeOnComplete });
336
- this.log.debug(`Job ${jobId} completed`, {
337
- queue,
338
- result
339
- });
340
- }
341
- await this.emit({
342
- type: "completed",
343
- queue,
344
- jobId,
345
- timestamp: now,
346
- result,
347
- duration
348
- });
349
- }
350
- async failJob(queue, jobId, error, stackTrace) {
351
- const job = this.jobs.get(queue)?.get(jobId);
352
- if (!job) {
353
- this.log.warn(`Attempted to fail unknown job ${jobId}`);
354
- return;
355
- }
356
- const now = Date.now();
357
- this.active.get(queue)?.delete(jobId);
358
- const maxAttempts = job.options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
359
- if (job.state.attempts < maxAttempts) {
360
- const backoffDelay = this.calculateBackoff(job);
361
- job.state.status = "delayed";
362
- job.state.availableAt = now + backoffDelay;
363
- job.state.error = error;
364
- job.state.stackTrace = stackTrace;
365
- job.state.lockedBy = void 0;
366
- job.state.lockedUntil = void 0;
367
- this.insertDelayed(queue, job);
368
- this.log.debug(`Job ${jobId} failed, will retry in ${backoffDelay}ms`, {
369
- queue,
370
- attempt: job.state.attempts,
371
- maxAttempts,
372
- error
373
- });
374
- await this.emit({
375
- type: "retrying",
376
- queue,
377
- jobId,
378
- timestamp: now,
379
- error,
380
- attempt: job.state.attempts + 1,
381
- delay: backoffDelay
382
- });
383
- } else {
384
- job.state.status = "failed";
385
- job.state.failedAt = now;
386
- job.state.error = error;
387
- job.state.stackTrace = stackTrace;
388
- job.state.lockedBy = void 0;
389
- job.state.lockedUntil = void 0;
390
- const removeOnFail = job.options.removeOnFail;
391
- if (removeOnFail === true) {
392
- this.jobs.get(queue)?.delete(jobId);
393
- this.log.debug(`Job ${jobId} permanently failed and removed after ${job.state.attempts} attempts`, {
394
- queue,
395
- error
396
- });
397
- } else {
398
- this.failed.get(queue).unshift(jobId);
399
- if (typeof removeOnFail === "number" && removeOnFail >= 0) await this.cleanJobs(queue, "failed", { maxCount: removeOnFail });
400
- this.log.debug(`Job ${jobId} permanently failed after ${job.state.attempts} attempts`, {
401
- queue,
402
- error
403
- });
404
- }
405
- await this.emit({
406
- type: "failed",
407
- queue,
408
- jobId,
409
- timestamp: now,
410
- error,
411
- stackTrace,
412
- attempts: job.state.attempts
413
- });
414
- }
415
- }
416
- calculateBackoff(job) {
417
- const backoff = job.options.backoff;
418
- const attempt = job.state.attempts;
419
- if (!backoff) return DEFAULT_BACKOFF_DELAY;
420
- const baseDelay = backoff.delay ?? DEFAULT_BACKOFF_DELAY;
421
- const maxDelay = backoff.maxDelay ?? DEFAULT_BACKOFF_MAX_DELAY;
422
- if (backoff.type === "fixed") return baseDelay;
423
- const exponentialDelay = baseDelay * 2 ** (attempt - 1);
424
- return Math.min(exponentialDelay, maxDelay);
425
- }
426
- async renewJobLock(queue, jobId, workerId) {
427
- const job = this.jobs.get(queue)?.get(jobId);
428
- if (!job || job.state.lockedBy !== workerId) return false;
429
- job.state.lockedUntil = Date.now() + (job.options.lockDuration ?? DEFAULT_LOCK_DURATION);
430
- return true;
431
- }
432
- async getJob(queue, jobId) {
433
- return this.jobs.get(queue)?.get(jobId);
434
- }
435
- async getJobs(queue, status, options) {
436
- const limit = options?.limit ?? 100;
437
- const offset = options?.offset ?? 0;
438
- let jobIds;
439
- switch (status) {
440
- case "waiting":
441
- jobIds = this.waiting.get(queue) ?? [];
442
- break;
443
- case "delayed":
444
- jobIds = this.delayed.get(queue) ?? [];
445
- break;
446
- case "active":
447
- jobIds = Array.from(this.active.get(queue) ?? []);
448
- break;
449
- case "completed":
450
- jobIds = this.completed.get(queue) ?? [];
451
- break;
452
- case "failed":
453
- jobIds = this.failed.get(queue) ?? [];
454
- break;
455
- default: jobIds = [];
456
- }
457
- const jobsMap = this.jobs.get(queue);
458
- if (!jobsMap) return [];
459
- return jobIds.slice(offset, offset + limit).map((id) => jobsMap.get(id)).filter((job) => job !== void 0);
460
- }
461
- async getJobCounts(queue) {
462
- return {
463
- waiting: this.waiting.get(queue)?.length ?? 0,
464
- delayed: this.delayed.get(queue)?.length ?? 0,
465
- active: this.active.get(queue)?.size ?? 0,
466
- completed: this.completed.get(queue)?.length ?? 0,
467
- failed: this.failed.get(queue)?.length ?? 0
468
- };
469
- }
470
- async promoteDelayedJobs(queue) {
471
- const delayedList = this.delayed.get(queue);
472
- if (!delayedList || delayedList.length === 0) return 0;
473
- const now = Date.now();
474
- let promoted = 0;
475
- while (delayedList.length > 0) {
476
- const jobId = delayedList[0];
477
- const job = this.jobs.get(queue)?.get(jobId);
478
- if (!job || (job.state.availableAt ?? 0) > now) break;
479
- delayedList.shift();
480
- job.state.status = "waiting";
481
- this.insertWaiting(queue, job);
482
- promoted++;
483
- this.log.debug(`Promoted delayed job ${jobId}`, { queue });
484
- this.emit({
485
- type: "waiting",
486
- queue,
487
- jobId,
488
- timestamp: now,
489
- job
490
- });
491
- }
492
- if (promoted > 0) this.notifyJobWaiters(queue);
493
- return promoted;
494
- }
495
- async recoverStalledJobs(queue, stalledThresholdMs) {
496
- const activeSet = this.active.get(queue);
497
- if (!activeSet || activeSet.size === 0) return [];
498
- const now = Date.now();
499
- const stalledJobIds = [];
500
- for (const jobId of activeSet) {
501
- const job = this.jobs.get(queue)?.get(jobId);
502
- if (!job) continue;
503
- if ((job.state.lockedUntil ?? 0) + stalledThresholdMs < now) stalledJobIds.push(jobId);
504
- }
505
- for (const jobId of stalledJobIds) {
506
- const job = this.jobs.get(queue).get(jobId);
507
- const workerId = job.state.lockedBy;
508
- activeSet.delete(jobId);
509
- const maxAttempts = job.options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
510
- const hasMoreAttempts = job.state.attempts < maxAttempts;
511
- await this.emit({
512
- type: "stalled",
513
- queue,
514
- jobId,
515
- timestamp: now,
516
- workerId,
517
- willRetry: hasMoreAttempts
518
- });
519
- if (hasMoreAttempts) {
520
- job.state.status = "waiting";
521
- job.state.lockedBy = void 0;
522
- job.state.lockedUntil = void 0;
523
- job.state.error = "Job stalled (worker timeout)";
524
- this.insertWaiting(queue, job);
525
- this.log.warn(`Recovered stalled job ${jobId}, returning to waiting`, {
526
- queue,
527
- attempt: job.state.attempts
528
- });
529
- await this.emit({
530
- type: "waiting",
531
- queue,
532
- jobId,
533
- timestamp: now,
534
- job
535
- });
536
- } else {
537
- job.state.status = "failed";
538
- job.state.failedAt = now;
539
- job.state.lockedBy = void 0;
540
- job.state.lockedUntil = void 0;
541
- job.state.error = "Job stalled (worker timeout) - max attempts exceeded";
542
- const removeOnFail = job.options.removeOnFail;
543
- if (removeOnFail === true) this.jobs.get(queue)?.delete(jobId);
544
- else {
545
- this.failed.get(queue).unshift(jobId);
546
- if (typeof removeOnFail === "number" && removeOnFail > 0) await this.cleanJobs(queue, "failed", { maxCount: removeOnFail });
547
- }
548
- this.log.warn(`Stalled job ${jobId} permanently failed`, {
549
- queue,
550
- attempts: job.state.attempts
551
- });
552
- await this.emit({
553
- type: "failed",
554
- queue,
555
- jobId,
556
- timestamp: now,
557
- error: job.state.error,
558
- attempts: job.state.attempts
559
- });
560
- }
561
- }
562
- if (stalledJobIds.length > 0) this.notifyJobWaiters(queue);
563
- return stalledJobIds;
564
- }
565
- async cleanJobs(queue, status, options) {
566
- const jobsList = status === "completed" ? this.completed.get(queue) : this.failed.get(queue);
567
- if (!jobsList || jobsList.length === 0) return 0;
568
- const jobsMap = this.jobs.get(queue);
569
- if (!jobsMap) return 0;
570
- const now = Date.now();
571
- const maxAge = options?.maxAge;
572
- const maxCount = options?.maxCount;
573
- let removed = 0;
574
- if (maxAge !== void 0) {
575
- const cutoff = now - maxAge;
576
- const toRemove = [];
577
- for (const jobId of jobsList) {
578
- const job = jobsMap.get(jobId);
579
- if (job) {
580
- const timestamp = status === "completed" ? job.state.completedAt : job.state.failedAt;
581
- if (timestamp && timestamp < cutoff) toRemove.push(jobId);
582
- }
583
- }
584
- for (const jobId of toRemove) {
585
- const idx = jobsList.indexOf(jobId);
586
- if (idx !== -1) {
587
- jobsList.splice(idx, 1);
588
- jobsMap.delete(jobId);
589
- removed++;
590
- }
591
- }
592
- }
593
- if (maxCount !== void 0 && jobsList.length > maxCount) {
594
- const toRemove = jobsList.splice(maxCount);
595
- for (const jobId of toRemove) {
596
- jobsMap.delete(jobId);
597
- removed++;
598
- }
599
- }
600
- return removed;
601
- }
602
- async removeJob(queue, jobId) {
603
- const job = this.jobs.get(queue)?.get(jobId);
604
- if (!job) return;
605
- const previousStatus = job.state.status;
606
- switch (job.state.status) {
607
- case "waiting": {
608
- const list = this.waiting.get(queue);
609
- const idx = list?.indexOf(jobId) ?? -1;
610
- if (idx !== -1) list.splice(idx, 1);
611
- break;
612
- }
613
- case "delayed": {
614
- const list = this.delayed.get(queue);
615
- const idx = list?.indexOf(jobId) ?? -1;
616
- if (idx !== -1) list.splice(idx, 1);
617
- break;
618
- }
619
- case "active":
620
- this.active.get(queue)?.delete(jobId);
621
- break;
622
- case "completed": {
623
- const list = this.completed.get(queue);
624
- const idx = list?.indexOf(jobId) ?? -1;
625
- if (idx !== -1) list.splice(idx, 1);
626
- break;
627
- }
628
- case "failed": {
629
- const list = this.failed.get(queue);
630
- const idx = list?.indexOf(jobId) ?? -1;
631
- if (idx !== -1) list.splice(idx, 1);
632
- break;
633
- }
634
- }
635
- this.jobs.get(queue)?.delete(jobId);
636
- await this.emit({
637
- type: "removed",
638
- queue,
639
- jobId,
640
- timestamp: Date.now(),
641
- previousStatus
642
- });
643
- }
644
- cancelWaiters() {
645
- for (const waiter of this.jobWaiters) {
646
- clearTimeout(waiter.timer);
647
- waiter.resolve(void 0);
648
- }
649
- this.jobWaiters.clear();
650
- for (const waiter of this.messageWaiters) {
651
- clearTimeout(waiter.timer);
652
- waiter.resolve(void 0);
653
- }
654
- this.messageWaiters.clear();
655
- }
656
- };
657
-
658
- //#endregion
659
- //#region src/queue/descriptors/$queue.ts
660
- /**
661
- * Creates a queue descriptor for asynchronous message processing with background workers.
662
- *
663
- * The $queue descriptor enables powerful asynchronous communication patterns in your application.
664
- * It provides type-safe message queuing with automatic worker processing, making it perfect for
665
- * decoupling components and handling background tasks efficiently.
666
- *
667
- * **Background Processing**
668
- * - Automatic worker threads for non-blocking message processing
669
- * - Built-in retry mechanisms and error handling
670
- * - Dead letter queues for failed message handling
671
- * - Graceful shutdown and worker lifecycle management
672
- *
673
- * **Type Safety**
674
- * - Full TypeScript support with schema validation using TypeBox
675
- * - Type-safe message payloads with automatic inference
676
- * - Runtime validation of all queued messages
677
- * - Compile-time errors for invalid message structures
678
- *
679
- * **Storage Flexibility**
680
- * - Memory provider for development and testing
681
- * - Redis provider for production scalability and persistence
682
- * - Custom provider support for specialized backends
683
- * - Automatic failover and connection pooling
684
- *
685
- * **Performance & Scalability**
686
- * - Batch processing support for high-throughput scenarios
687
- * - Horizontal scaling with distributed queue backends
688
- * - Configurable concurrency and worker pools
689
- * - Efficient serialization and message routing
690
- *
691
- * **Reliability**
692
- * - Message persistence across application restarts
693
- * - Automatic retry with exponential backoff
694
- * - Dead letter handling for permanently failed messages
695
- * - Comprehensive logging and monitoring integration
696
- *
697
- * @example Basic notification queue
698
- * ```typescript
699
- * const emailQueue = $queue({
700
- * name: "email-notifications",
701
- * schema: t.object({
702
- * to: t.text(),
703
- * subject: t.text(),
704
- * body: t.text(),
705
- * priority: t.optional(t.enum(["high", "normal"]))
706
- * }),
707
- * handler: async (message) => {
708
- * await emailService.send(message.payload);
709
- * console.log(`Email sent to ${message.payload.to}`);
710
- * }
711
- * });
712
- *
713
- * // Push messages for background processing
714
- * await emailQueue.push({
715
- * to: "user@example.com",
716
- * subject: "Welcome!",
717
- * body: "Welcome to our platform",
718
- * priority: "high"
719
- * });
720
- * ```
721
- *
722
- * @example Batch processing with Redis
723
- * ```typescript
724
- * const imageQueue = $queue({
725
- * name: "image-processing",
726
- * provider: RedisQueueProvider,
727
- * schema: t.object({
728
- * imageId: t.text(),
729
- * operations: t.array(t.enum(["resize", "compress", "thumbnail"]))
730
- * }),
731
- * handler: async (message) => {
732
- * for (const op of message.payload.operations) {
733
- * await processImage(message.payload.imageId, op);
734
- * }
735
- * }
736
- * });
737
- *
738
- * // Batch processing multiple images
739
- * await imageQueue.push(
740
- * { imageId: "img1", operations: ["resize", "thumbnail"] },
741
- * { imageId: "img2", operations: ["compress"] },
742
- * { imageId: "img3", operations: ["resize", "compress", "thumbnail"] }
743
- * );
744
- * ```
745
- *
746
- * @example Development with memory provider
747
- * ```typescript
748
- * const taskQueue = $queue({
749
- * name: "dev-tasks",
750
- * provider: "memory",
751
- * schema: t.object({
752
- * taskType: t.enum(["cleanup", "backup", "report"]),
753
- * data: t.record(t.text(), t.any())
754
- * }),
755
- * handler: async (message) => {
756
- * switch (message.payload.taskType) {
757
- * case "cleanup":
758
- * await performCleanup(message.payload.data);
759
- * break;
760
- * case "backup":
761
- * await createBackup(message.payload.data);
762
- * break;
763
- * case "report":
764
- * await generateReport(message.payload.data);
765
- * break;
766
- * }
767
- * }
768
- * });
769
- * ```
770
- */
771
- const $queue = (options) => {
772
- return (0, alepha.createDescriptor)(QueueDescriptor, options);
773
- };
774
- var QueueDescriptor = class extends alepha.Descriptor {
775
- log = (0, alepha_logger.$logger)();
776
- provider = this.$provider();
777
- async push(payloadOrFirst, optionsOrSecond, ...rest) {
778
- if (optionsOrSecond != null && typeof optionsOrSecond === "object" && ("priority" in optionsOrSecond || "delay" in optionsOrSecond)) {
779
- const payload = this.alepha.codec.decode(this.options.schema, payloadOrFirst);
780
- await this.provider.addJob(this.name, payload, {
781
- ...this.getDefaultJobOptions(),
782
- priority: optionsOrSecond.priority,
783
- delay: optionsOrSecond.delay
784
- });
785
- this.log.debug(`Pushed job to queue ${this.name}`, {
786
- payload,
787
- options: optionsOrSecond
788
- });
789
- } else {
790
- const payloads = optionsOrSecond != null ? [
791
- payloadOrFirst,
792
- optionsOrSecond,
793
- ...rest
794
- ] : [payloadOrFirst, ...rest];
795
- await Promise.all(payloads.map((p) => {
796
- const payload = this.alepha.codec.decode(this.options.schema, p);
797
- return this.provider.addJob(this.name, payload, this.getDefaultJobOptions());
798
- }));
799
- this.log.debug(`Pushed ${payloads.length} job(s) to queue ${this.name}`, payloads);
800
- }
801
- }
802
- /**
803
- * Get default job options from descriptor configuration.
804
- */
805
- getDefaultJobOptions() {
806
- return {
807
- maxAttempts: this.options.maxAttempts,
808
- backoff: this.options.backoff,
809
- lockDuration: this.options.lockDuration,
810
- removeOnComplete: this.options.removeOnComplete,
811
- removeOnFail: this.options.removeOnFail
812
- };
813
- }
814
- get name() {
815
- return this.options.name || this.config.propertyKey;
816
- }
817
- $provider() {
818
- if (!this.options.provider) return this.alepha.inject(QueueProvider);
819
- if (this.options.provider === "memory") return this.alepha.inject(MemoryQueueProvider);
820
- return this.alepha.inject(this.options.provider);
821
- }
822
- };
823
- $queue[alepha.KIND] = QueueDescriptor;
824
-
825
- //#endregion
826
- //#region src/queue/providers/WorkerProvider.ts
827
- const envSchema = alepha.t.object({
828
- QUEUE_WORKER_BLOCKING_TIMEOUT: alepha.t.integer({ default: 5 }),
829
- QUEUE_WORKER_CONCURRENCY: alepha.t.integer({ default: 1 }),
830
- QUEUE_WORKER_LOCK_RENEWAL_INTERVAL: alepha.t.integer({ default: 1e4 }),
831
- QUEUE_SCHEDULER_INTERVAL: alepha.t.integer({ default: 5e3 }),
832
- QUEUE_STALLED_THRESHOLD: alepha.t.integer({ default: 5e3 })
833
- });
834
- var WorkerProvider = class {
835
- log = (0, alepha_logger.$logger)();
836
- env = (0, alepha.$env)(envSchema);
837
- alepha = (0, alepha.$inject)(alepha.Alepha);
838
- queueProvider = (0, alepha.$inject)(QueueProvider);
839
- dateTime = (0, alepha.$inject)(alepha_datetime.DateTimeProvider);
840
- workerPromises = [];
841
- workersRunning = 0;
842
- shouldStop = false;
843
- consumers = [];
844
- consumersByProvider = /* @__PURE__ */ new Map();
845
- schedulerPromise;
846
- schedulerRunning = false;
847
- abortController;
848
- workerId = `worker_${process.pid}_${Date.now()}`;
849
- get isRunning() {
850
- return this.workersRunning > 0;
851
- }
852
- start = (0, alepha.$hook)({
853
- on: "start",
854
- priority: "last",
855
- handler: () => {
856
- for (const queue of this.alepha.descriptors($queue)) {
857
- const handler = queue.options.handler;
858
- if (handler) this.consumers.push({
859
- handler,
860
- queue
861
- });
862
- }
863
- for (const consumer of this.alepha.descriptors($consumer)) this.consumers.push(consumer.options);
864
- for (const consumer of this.consumers) {
865
- const provider = consumer.queue.provider;
866
- const list = this.consumersByProvider.get(provider) ?? [];
867
- list.push(consumer);
868
- this.consumersByProvider.set(provider, list);
869
- }
870
- if (this.consumers.length > 0) {
871
- this.startWorkers();
872
- this.startScheduler();
873
- this.log.debug(`Watching for ${this.consumers.length} queue${this.consumers.length > 1 ? "s" : ""} with ${this.env.QUEUE_WORKER_CONCURRENCY} worker${this.env.QUEUE_WORKER_CONCURRENCY > 1 ? "s" : ""}.`);
874
- }
875
- }
876
- });
877
- /**
878
- * Start the workers.
879
- * Each worker acquires jobs and processes them with proper lifecycle management.
880
- */
881
- startWorkers() {
882
- const workerToStart = this.env.QUEUE_WORKER_CONCURRENCY - this.workersRunning;
883
- for (let i = 0; i < workerToStart; i++) {
884
- this.workersRunning += 1;
885
- const workerIndex = i;
886
- const localWorkerId = `${this.workerId}_${workerIndex}`;
887
- this.log.debug(`Starting worker n-${workerIndex}`);
888
- const workerLoop = async () => {
889
- while (!this.shouldStop) {
890
- this.log.trace(`Worker n-${workerIndex} is waiting for jobs`);
891
- const acquired = await this.acquireNextJob(localWorkerId);
892
- if (acquired) await this.processJob(acquired, localWorkerId);
893
- }
894
- this.log.info(`Worker n-${workerIndex} has stopped`);
895
- };
896
- this.workerPromises.push(workerLoop().catch((e) => {
897
- this.log.error(`Worker n-${workerIndex} has crashed`, e);
898
- this.workersRunning -= 1;
899
- }));
900
- }
901
- }
902
- /**
903
- * Start the scheduler for delayed job promotion and stalled job recovery.
904
- */
905
- startScheduler() {
906
- if (this.schedulerRunning) return;
907
- this.schedulerRunning = true;
908
- this.abortController = new AbortController();
909
- this.log.debug("Starting scheduler");
910
- const schedulerLoop = async () => {
911
- while (!this.shouldStop) {
912
- try {
913
- await this.runSchedulerCycle();
914
- } catch (e) {
915
- this.log.error("Scheduler cycle failed", e);
916
- }
917
- await this.dateTime.wait(this.env.QUEUE_SCHEDULER_INTERVAL, { signal: this.abortController?.signal });
918
- }
919
- this.log.debug("Scheduler stopped");
920
- };
921
- this.schedulerPromise = schedulerLoop();
922
- }
923
- /**
924
- * Run one cycle of the scheduler.
925
- * Promotes delayed jobs and recovers stalled jobs.
926
- */
927
- async runSchedulerCycle() {
928
- for (const [provider, consumers] of this.consumersByProvider) {
929
- const queues = new Set(consumers.map((c) => c.queue.name));
930
- for (const queue of queues) {
931
- const promoted = await provider.promoteDelayedJobs(queue);
932
- if (promoted > 0) this.log.debug(`Promoted ${promoted} delayed jobs in queue ${queue}`);
933
- const recovered = await provider.recoverStalledJobs(queue, this.env.QUEUE_STALLED_THRESHOLD);
934
- if (recovered.length > 0) this.log.warn(`Recovered ${recovered.length} stalled jobs in queue ${queue}`);
935
- }
936
- }
937
- }
938
- stop = (0, alepha.$hook)({
939
- on: "stop",
940
- handler: async () => {
941
- if (this.consumers.length > 0) await this.stopWorkers();
942
- }
943
- });
944
- /**
945
- * Acquire the next available job from any provider.
946
- */
947
- async acquireNextJob(localWorkerId) {
948
- for (const [provider, consumers] of this.consumersByProvider) {
949
- const queueNames = consumers.map((c) => c.queue.name);
950
- const acquired = await provider.acquireJob(queueNames, localWorkerId, this.env.QUEUE_WORKER_BLOCKING_TIMEOUT);
951
- if (acquired) {
952
- const consumer = consumers.find((c) => c.queue.name === acquired.queue);
953
- if (consumer) return {
954
- acquired,
955
- consumer,
956
- provider
957
- };
958
- }
959
- }
960
- }
961
- /**
962
- * Process a job with proper lifecycle management.
963
- * - Starts a lock renewal interval
964
- * - Calls the handler
965
- * - Marks job as completed or failed
966
- */
967
- async processJob({ acquired, consumer, provider }, localWorkerId) {
968
- const { queue, job } = acquired;
969
- const lockRenewalInterval = this.dateTime.createInterval(async () => {
970
- try {
971
- if (!await provider.renewJobLock(queue, job.id, localWorkerId)) this.log.warn(`Failed to renew lock for job ${job.id}, lock may have been stolen`);
972
- } catch (e) {
973
- this.log.error(`Error renewing lock for job ${job.id}`, e);
974
- }
975
- }, this.env.QUEUE_WORKER_LOCK_RENEWAL_INTERVAL, true);
976
- try {
977
- const payload = this.alepha.codec.decode(consumer.queue.options.schema, job.payload);
978
- await this.alepha.context.run(() => consumer.handler({ payload }));
979
- await provider.completeJob(queue, job.id);
980
- this.log.debug(`Job ${job.id} completed successfully`, { queue });
981
- } catch (e) {
982
- const error = e instanceof Error ? e.message : String(e);
983
- const stackTrace = e instanceof Error ? e.stack : void 0;
984
- await provider.failJob(queue, job.id, error, stackTrace);
985
- this.log.error(`Job ${job.id} failed`, e);
986
- } finally {
987
- this.dateTime.clearInterval(lockRenewalInterval);
988
- }
989
- }
990
- /**
991
- * Stop the workers and scheduler.
992
- */
993
- async stopWorkers() {
994
- this.shouldStop = true;
995
- this.workersRunning = 0;
996
- this.schedulerRunning = false;
997
- this.abortController?.abort();
998
- for (const provider of this.consumersByProvider.keys()) provider.cancelWaiters();
999
- this.log.trace("Stopping workers...");
1000
- this.log.trace("Waiting for workers to finish...");
1001
- const promises = [...this.workerPromises];
1002
- if (this.schedulerPromise) promises.push(this.schedulerPromise);
1003
- await Promise.all(promises);
1004
- }
1005
- };
1006
-
1007
- //#endregion
1008
- //#region src/queue/index.ts
1009
- /**
1010
- * Provides asynchronous message queuing and processing capabilities through declarative queue descriptors.
1011
- *
1012
- * The queue module enables reliable background job processing and message passing using the `$queue` descriptor
1013
- * on class properties. It supports schema validation, automatic retries, and multiple queue backends for
1014
- * building scalable, decoupled applications with robust error handling.
1015
- *
1016
- * @see {@link $queue}
1017
- * @see {@link $consumer}
1018
- * @module alepha.queue
1019
- */
1020
- const AlephaQueue = (0, alepha.$module)({
1021
- name: "alepha.queue",
1022
- descriptors: [$queue, $consumer],
1023
- services: [
1024
- QueueProvider,
1025
- MemoryQueueProvider,
1026
- WorkerProvider
1027
- ],
1028
- register: (alepha$1) => alepha$1.with({
1029
- optional: true,
1030
- provide: QueueProvider,
1031
- use: MemoryQueueProvider
1032
- }).with(WorkerProvider)
1033
- });
1034
-
1035
- //#endregion
1036
- exports.$consumer = $consumer;
1037
- exports.$queue = $queue;
1038
- exports.AlephaQueue = AlephaQueue;
1039
- exports.ConsumerDescriptor = ConsumerDescriptor;
1040
- exports.MemoryQueueProvider = MemoryQueueProvider;
1041
- exports.QueueDescriptor = QueueDescriptor;
1042
- exports.QueueProvider = QueueProvider;
1043
- exports.WorkerProvider = WorkerProvider;
1044
- //# sourceMappingURL=index.cjs.map