power-queues 1.0.9 → 2.0.2

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.
package/dist/index.cjs ADDED
@@ -0,0 +1,782 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ PowerQueue: () => PowerQueue
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/PowerQueue.ts
28
+ var import_uuid = require("uuid");
29
+ var import_full_utils = require("full-utils");
30
+ var import_power_redis = require("power-redis");
31
+ var PowerQueue = class extends import_power_redis.PowerRedis {
32
+ constructor() {
33
+ super(...arguments);
34
+ this.iterationTimeout = 1e3;
35
+ this.portionLength = 1e3;
36
+ this.expireStatusSec = 300;
37
+ this.maxAttempts = 1;
38
+ this.concurrency = 32;
39
+ this.visibilityTimeoutSec = 60;
40
+ this.retryBaseSec = 1;
41
+ this.retryMaxSec = 3600;
42
+ this.runners = /* @__PURE__ */ new Map();
43
+ this.processingRaw = /* @__PURE__ */ new Map();
44
+ this.heartbeatTimers = /* @__PURE__ */ new Map();
45
+ }
46
+ nowSec() {
47
+ return Math.floor(Date.now() / 1e3);
48
+ }
49
+ readyKey(queueName) {
50
+ return this.toKeyString(queueName);
51
+ }
52
+ processingKey(queueName) {
53
+ return this.toKeyString(queueName, "processing");
54
+ }
55
+ processingVtKey(queueName) {
56
+ return this.toKeyString(queueName, "processing", "vt");
57
+ }
58
+ delayedKey(queueName) {
59
+ return this.toKeyString(queueName, "delayed");
60
+ }
61
+ toKeyString(...parts) {
62
+ return super.toKeyString("queue", ...parts);
63
+ }
64
+ getReserveScriptLMOVE() {
65
+ return `
66
+ local source = KEYS[1]
67
+ local processing = KEYS[2]
68
+ local vtkey = KEYS[3]
69
+ local limit = tonumber(ARGV[1])
70
+ local deadline = tonumber(ARGV[2])
71
+ local moved = {}
72
+ for i = 1, limit do
73
+ local v = redis.call('LMOVE', source, processing, 'LEFT', 'RIGHT')
74
+ if not v then break end
75
+ table.insert(moved, v)
76
+ end
77
+ if #moved > 0 then
78
+ for i = 1, #moved do
79
+ redis.call('ZADD', vtkey, deadline, moved[i])
80
+ end
81
+ end
82
+ return moved
83
+ `;
84
+ }
85
+ getReserveScriptRPOPLPUSH() {
86
+ return `
87
+ local source = KEYS[1]
88
+ local processing = KEYS[2]
89
+ local vtkey = KEYS[3]
90
+ local limit = tonumber(ARGV[1])
91
+ local deadline = tonumber(ARGV[2])
92
+ local moved = {}
93
+ for i = 1, limit do
94
+ local v = redis.call('RPOPLPUSH', source, processing)
95
+ if not v then break end
96
+ table.insert(moved, v)
97
+ end
98
+ if #moved > 0 then
99
+ for i = 1, #moved do
100
+ redis.call('ZADD', vtkey, deadline, moved[i])
101
+ end
102
+ end
103
+ return moved
104
+ `;
105
+ }
106
+ getRequeueScript() {
107
+ return `
108
+ -- KEYS: 1=processing, 2=processingVt, 3=ready
109
+ -- ARGV: 1=now, 2=limit
110
+ local processing = KEYS[1]
111
+ local vt = KEYS[2]
112
+ local ready = KEYS[3]
113
+ local now = tonumber(ARGV[1])
114
+ local limit = tonumber(ARGV[2])
115
+
116
+ local members = redis.call('ZRANGEBYSCORE', vt, 0, now, 'LIMIT', 0, limit)
117
+ for i=1,#members do
118
+ local m = members[i]
119
+ redis.call('ZREM', vt, m)
120
+ redis.call('LREM', processing, 1, m)
121
+ redis.call('RPUSH', ready, m)
122
+ end
123
+ return #members
124
+ `;
125
+ }
126
+ getPromoteScript() {
127
+ return `
128
+ -- KEYS: 1=delayed, 2=ready
129
+ -- ARGV: 1=now, 2=limit
130
+ local delayed = KEYS[1]
131
+ local ready = KEYS[2]
132
+ local now = tonumber(ARGV[1])
133
+ local limit = tonumber(ARGV[2])
134
+
135
+ local due = redis.call('ZRANGEBYSCORE', delayed, 0, now, 'LIMIT', 0, limit)
136
+ for i=1,#due do
137
+ local m = due[i]
138
+ redis.call('ZREM', delayed, m)
139
+ redis.call('RPUSH', ready, m)
140
+ end
141
+ return #due
142
+ `;
143
+ }
144
+ async ensureReserveScript(force = false) {
145
+ if (!force && (this.reserveSha || this.reserveShaRpoplpush || !(0, import_full_utils.isFunc)(this.redis?.script))) {
146
+ return;
147
+ }
148
+ this.reserveSha = void 0;
149
+ this.reserveShaRpoplpush = void 0;
150
+ try {
151
+ this.reserveSha = await this.redis?.script("LOAD", this.getReserveScriptLMOVE());
152
+ } catch {
153
+ this.reserveShaRpoplpush = await this.redis?.script("LOAD", this.getReserveScriptRPOPLPUSH());
154
+ }
155
+ }
156
+ async ensureRequeueScript(force = false) {
157
+ if (!force && this.requeueSha) {
158
+ return;
159
+ }
160
+ const scriptFn = this.redis?.script;
161
+ if (!scriptFn) {
162
+ return;
163
+ }
164
+ this.requeueSha = await scriptFn("LOAD", this.getRequeueScript());
165
+ }
166
+ async ensurePromoteScript(force = false) {
167
+ if (!force && this.promoteSha) {
168
+ return;
169
+ }
170
+ const scriptFn = this.redis?.script;
171
+ if (!scriptFn) {
172
+ return;
173
+ }
174
+ this.promoteSha = await scriptFn("LOAD", this.getPromoteScript());
175
+ }
176
+ async moveOneToProcessing(source, processing) {
177
+ const cli = this.redis;
178
+ try {
179
+ if ((0, import_full_utils.isFunc)(cli.lmove)) {
180
+ const v = await cli.lmove(source, processing, "LEFT", "RIGHT");
181
+ return (0, import_full_utils.isStr)(v) ? v : null;
182
+ }
183
+ } catch {
184
+ }
185
+ try {
186
+ if ((0, import_full_utils.isFunc)(cli.rpoplpush)) {
187
+ const v = await cli.rpoplpush(source, processing);
188
+ return (0, import_full_utils.isStr)(v) ? v : null;
189
+ }
190
+ } catch {
191
+ }
192
+ return null;
193
+ }
194
+ async evalshaWithReload(shaGetter, ensure, numKeys, keysAndArgs) {
195
+ await ensure(false);
196
+ const sha = shaGetter();
197
+ const evalshaFn = this.redis?.evalsha;
198
+ if (!sha || !evalshaFn) {
199
+ throw new Error("EVALSHA not available or SHA missing");
200
+ }
201
+ try {
202
+ return await evalshaFn(sha, numKeys, ...keysAndArgs.map(String));
203
+ } catch (err) {
204
+ const msg = err?.message;
205
+ if (typeof msg === "string" && msg.includes("NOSCRIPT")) {
206
+ await ensure(true);
207
+ const sha2 = shaGetter();
208
+ if (!sha2) {
209
+ throw new Error("EVALSHA NOSCRIPT and reload failed (no SHA)");
210
+ }
211
+ return await evalshaFn(sha2, numKeys, ...keysAndArgs.map(String));
212
+ }
213
+ throw err;
214
+ }
215
+ }
216
+ async zaddCompatXXCH(key, score, member) {
217
+ const zadd = this.redis?.zadd;
218
+ try {
219
+ if (zadd) {
220
+ await zadd.call(this.redis, key, "XX", "CH", score, member);
221
+ return;
222
+ }
223
+ } catch {
224
+ }
225
+ try {
226
+ if (zadd) {
227
+ await zadd.call(this.redis, key, "CH", "XX", score, member);
228
+ return;
229
+ }
230
+ } catch {
231
+ }
232
+ try {
233
+ await this.redis.zadd(key, score, member);
234
+ } catch {
235
+ }
236
+ }
237
+ startHeartbeat(task) {
238
+ const raw = this.processingRaw.get(task.id);
239
+ if (!raw) {
240
+ return;
241
+ }
242
+ const vtKey = this.processingVtKey(task.queueName);
243
+ const periodMs = Math.max(1e3, Math.floor(this.visibilityTimeoutSec * 1e3 * 0.4));
244
+ const t = setInterval(() => {
245
+ this.extendVisibility(vtKey, raw, this.visibilityTimeoutSec).catch(() => {
246
+ });
247
+ }, periodMs);
248
+ t.unref?.();
249
+ this.heartbeatTimers.set(task.id, t);
250
+ }
251
+ stopHeartbeat(task) {
252
+ const t = this.heartbeatTimers.get(task.id);
253
+ if (t) {
254
+ clearInterval(t);
255
+ }
256
+ this.heartbeatTimers.delete(task.id);
257
+ }
258
+ async reserveMany(source, processing, processingVt, limit = 100, visibilitySec = 60) {
259
+ if (!this.checkConnection()) {
260
+ throw new Error("Redis connection error.");
261
+ }
262
+ if (!(0, import_full_utils.isStrFilled)(source) || !(0, import_full_utils.isStrFilled)(processing) || !(0, import_full_utils.isStrFilled)(processingVt)) {
263
+ throw new Error("Key format error.");
264
+ }
265
+ if (!(0, import_full_utils.isNumP)(limit) || !(0, import_full_utils.isNumP)(visibilitySec)) {
266
+ throw new Error("Limit/visibility format error.");
267
+ }
268
+ await this.ensureReserveScript();
269
+ const deadline = this.nowSec() + visibilitySec;
270
+ const tryEval = async () => {
271
+ if ((0, import_full_utils.isFunc)(this.redis?.evalsha)) {
272
+ if (this.reserveSha) {
273
+ return await this.redis?.evalsha(this.reserveSha, 3, source, processing, processingVt, String(limit), String(deadline));
274
+ }
275
+ if (this.reserveShaRpoplpush) {
276
+ return await this.redis?.evalsha(this.reserveShaRpoplpush, 3, source, processing, processingVt, String(limit), String(deadline));
277
+ }
278
+ }
279
+ return null;
280
+ };
281
+ try {
282
+ const res = await tryEval();
283
+ if ((0, import_full_utils.isArr)(res)) {
284
+ return Array.from(res).map(String);
285
+ }
286
+ } catch (err) {
287
+ if ((0, import_full_utils.isStr)(err?.message) && String(err?.message ?? "").includes("NOSCRIPT")) {
288
+ await this.ensureReserveScript(true);
289
+ try {
290
+ const res2 = await tryEval();
291
+ if ((0, import_full_utils.isArr)(res2)) {
292
+ return Array.from(res2).map(String);
293
+ }
294
+ } catch {
295
+ }
296
+ }
297
+ }
298
+ const moved = [];
299
+ for (let i = 0; i < limit; i++) {
300
+ const v = await this.moveOneToProcessing(source, processing);
301
+ if (!v) {
302
+ break;
303
+ }
304
+ moved.push(v);
305
+ }
306
+ if (moved.length) {
307
+ const tx = this.redis?.multi();
308
+ for (const v of moved) {
309
+ tx.zadd(processingVt, deadline, v);
310
+ }
311
+ await tx.exec();
312
+ }
313
+ return moved;
314
+ }
315
+ async ackProcessing(processing, processingVt, raw) {
316
+ if (!this.checkConnection()) {
317
+ throw new Error("Redis connection error.");
318
+ }
319
+ const tx = this.redis?.multi();
320
+ tx.lrem(processing, 1, raw);
321
+ tx.zrem(processingVt, raw);
322
+ await tx.exec();
323
+ return;
324
+ }
325
+ async requeueExpired(processing, processingVt, ready, nowTs, chunk = 1e3) {
326
+ if (!this.checkConnection()) {
327
+ throw new Error("Redis connection error.");
328
+ }
329
+ const now = (0, import_full_utils.isNumP)(nowTs) ? nowTs : this.nowSec();
330
+ try {
331
+ const moved = await this.evalshaWithReload(
332
+ () => this.requeueSha,
333
+ (force) => this.ensureRequeueScript(!!force),
334
+ 3,
335
+ [processing, processingVt, ready, String(now), String(chunk)]
336
+ );
337
+ return (0, import_full_utils.isNumP)(moved) ? moved : 0;
338
+ } catch {
339
+ const expired = await this.redis?.zrangebyscore(processingVt, 0, now, "LIMIT", 0, chunk);
340
+ if (!(0, import_full_utils.isArrFilled)(expired)) {
341
+ return 0;
342
+ }
343
+ const tx = this.redis?.multi();
344
+ for (const raw of expired) {
345
+ tx.lrem(processing, 1, raw);
346
+ tx.zrem(processingVt, raw);
347
+ tx.rpush(ready, raw);
348
+ }
349
+ await tx.exec();
350
+ return expired.length;
351
+ }
352
+ }
353
+ async promoteDelayed(delayed, ready, nowTs, chunk = 1e3) {
354
+ if (!this.checkConnection()) {
355
+ throw new Error("Redis connection error.");
356
+ }
357
+ const now = (0, import_full_utils.isNumP)(nowTs) ? nowTs : this.nowSec();
358
+ try {
359
+ const promoted = await this.evalshaWithReload(
360
+ () => this.promoteSha,
361
+ (force) => this.ensurePromoteScript(!!force),
362
+ 2,
363
+ [delayed, ready, String(now), String(chunk)]
364
+ );
365
+ return (0, import_full_utils.isNumP)(promoted) ? promoted : 0;
366
+ } catch {
367
+ const due = await this.redis?.zrangebyscore(delayed, 0, now, "LIMIT", 0, chunk);
368
+ if (!(0, import_full_utils.isArrFilled)(due)) {
369
+ return 0;
370
+ }
371
+ const tx = this.redis?.multi();
372
+ for (const raw of due) {
373
+ tx.zrem(delayed, raw);
374
+ tx.rpush(ready, raw);
375
+ }
376
+ await tx.exec();
377
+ return due.length;
378
+ }
379
+ }
380
+ async enqueue(ready, delayed, payload, delaySec) {
381
+ if (!this.checkConnection()) {
382
+ throw new Error("Redis connection error.");
383
+ }
384
+ const raw = this.toPayload(payload);
385
+ if ((0, import_full_utils.isNumP)(delaySec) && delaySec > 0) {
386
+ const score = this.nowSec() + delaySec;
387
+ return await this.redis?.zadd(delayed, score, raw);
388
+ }
389
+ return await this.redis?.rpush(ready, raw);
390
+ }
391
+ async extendVisibility(processingVt, raw, visibilitySec) {
392
+ const deadline = this.nowSec() + Math.max(1, visibilitySec);
393
+ await this.zaddCompatXXCH(processingVt, deadline, raw);
394
+ }
395
+ run(queueName) {
396
+ if (!(0, import_full_utils.isStrFilled)(queueName)) {
397
+ throw new Error("Queue name is not valid.");
398
+ }
399
+ const r = this.runners.get(queueName) ?? { running: false };
400
+ if (r.running) {
401
+ throw new Error(`Queue "${queueName}" already started.`);
402
+ }
403
+ r.running = true;
404
+ this.runners.set(queueName, r);
405
+ this.loop(queueName, r).catch(() => {
406
+ r.running = false;
407
+ });
408
+ }
409
+ stop(queueName) {
410
+ const r = this.runners.get(queueName);
411
+ if (r) {
412
+ r.running = false;
413
+ this.runners.delete(queueName);
414
+ }
415
+ }
416
+ buildTask(data) {
417
+ if (!(0, import_full_utils.isObjFilled)(data)) {
418
+ throw new Error("Data property is not valid.");
419
+ }
420
+ if (!(0, import_full_utils.isStrFilled)(data.queueName)) {
421
+ throw new Error("Queue name is not valid.");
422
+ }
423
+ return {
424
+ queueName: data.queueName,
425
+ iterationId: (0, import_full_utils.isStrFilled)(data.iterationId) ? data.iterationId : (0, import_uuid.v4)(),
426
+ iterationLength: Number(data.iterationLength || 0),
427
+ id: (0, import_full_utils.isStrFilled)(data.id) ? data.id : (0, import_uuid.v4)(),
428
+ maxAttempts: (0, import_full_utils.isNumPZ)(data.maxAttempts) ? data.maxAttempts : this.maxAttempts,
429
+ currentAttempt: (0, import_full_utils.isNumPZ)(data.currentAttempt) ? data.currentAttempt : 0,
430
+ chain: (0, import_full_utils.isObjFilled)(data.chain) && (0, import_full_utils.isArrFilled)(data.chain.queues) && (0, import_full_utils.isNumPZ)(data.chain.index) ? data.chain : {
431
+ queues: [],
432
+ index: 0
433
+ },
434
+ payload: (0, import_full_utils.isObjFilled)(data.payload) ? data.payload : {},
435
+ progress: {
436
+ createdAt: Date.now(),
437
+ successAt: 0,
438
+ errorAt: 0,
439
+ failAt: 0,
440
+ fatalAt: 0,
441
+ retries: [],
442
+ chain: [],
443
+ ...(0, import_full_utils.isObjFilled)(data.progress) ? data.progress : {}
444
+ },
445
+ result: (0, import_full_utils.isObjFilled)(data.result) ? data.result : {}
446
+ };
447
+ }
448
+ async addTask(data, delaySec) {
449
+ const ready = this.readyKey(String(data.queueName));
450
+ const delayed = this.delayedKey(String(data.queueName));
451
+ return await this.enqueue(ready, delayed, this.buildTask(data), (0, import_full_utils.isNumP)(delaySec) ? delaySec : 0);
452
+ }
453
+ async addTasks(data) {
454
+ if (!this.checkConnection()) {
455
+ throw new Error("Redis connection error.");
456
+ }
457
+ if (!(0, import_full_utils.isObjFilled)(data) || !(0, import_full_utils.isStrFilled)(data.queueName)) {
458
+ throw new Error("Queue name is not valid.");
459
+ }
460
+ if (!(0, import_full_utils.isArrFilled)(data.payloads)) {
461
+ return 0;
462
+ }
463
+ const queueName = String(data.queueName);
464
+ const ready = this.readyKey(queueName);
465
+ const delayed = this.delayedKey(queueName);
466
+ const now = this.nowSec();
467
+ const uniformDelay = (0, import_full_utils.isNumP)(data.delaySec) ? Math.max(0, Number(data.delaySec)) : void 0;
468
+ const perItemDelays = (0, import_full_utils.isArr)(data.delaySec) ? data.delaySec.map((v) => Math.max(0, Number(v || 0))) : void 0;
469
+ const batchSize = Math.max(1, Math.min(this.portionLength, 1e3));
470
+ let idx = 0, total = 0;
471
+ while (idx < data.payloads.length) {
472
+ const end = Math.min(idx + batchSize, data.payloads.length);
473
+ const tx = this.redis?.multi();
474
+ for (let i = idx; i < end; i++) {
475
+ const item = data.payloads[i];
476
+ let partial;
477
+ if ((0, import_full_utils.isObjFilled)(item) && Object.prototype.hasOwnProperty.call(item, "payload")) {
478
+ partial = { ...item, queueName };
479
+ } else {
480
+ partial = { queueName, payload: item };
481
+ }
482
+ const task = this.buildTask(partial);
483
+ const raw = this.toPayload(task);
484
+ let d = 0;
485
+ if ((0, import_full_utils.isNumP)(uniformDelay)) {
486
+ d = uniformDelay;
487
+ } else if ((0, import_full_utils.isArr)(perItemDelays)) {
488
+ d = Number(perItemDelays[i] || 0);
489
+ }
490
+ if (d > 0) {
491
+ tx.zadd(delayed, now + d, raw);
492
+ } else {
493
+ tx.rpush(ready, raw);
494
+ }
495
+ total++;
496
+ }
497
+ await tx.exec();
498
+ idx = end;
499
+ }
500
+ return total;
501
+ }
502
+ async iteration(tasks) {
503
+ const tasksProcessed = await this.beforeIterationExecution(tasks);
504
+ const limit = Math.max(1, Number(this.concurrency) || 1);
505
+ let i = 0;
506
+ while (i < tasksProcessed.length) {
507
+ const slice = tasksProcessed.slice(i, i + limit);
508
+ await Promise.all(slice.map((task) => this.logic(task)));
509
+ i += limit;
510
+ }
511
+ await this.afterIterationExecution(tasksProcessed, tasksProcessed.map((t) => t.result ?? {}));
512
+ }
513
+ async beforeIterationExecution(data) {
514
+ return data;
515
+ }
516
+ async afterIterationExecution(data, results) {
517
+ }
518
+ async beforeExecution(task) {
519
+ return task;
520
+ }
521
+ async afterExecution(task, result) {
522
+ return result;
523
+ }
524
+ async execute(task) {
525
+ return {};
526
+ }
527
+ async onRetry(task) {
528
+ }
529
+ async onError(err, task) {
530
+ }
531
+ async onFail(err, task) {
532
+ }
533
+ async onFatal(err, task) {
534
+ }
535
+ async onSuccess(task, result) {
536
+ }
537
+ async onChainSuccess(task, result) {
538
+ }
539
+ async onIterationError(err, queueName) {
540
+ }
541
+ async logic(task) {
542
+ let data = task;
543
+ try {
544
+ data = await this.beforeExecution(task);
545
+ const before = data?.result ?? {};
546
+ const after = await this.execute(data);
547
+ data.result = {
548
+ ...(0, import_full_utils.isObjFilled)(before) ? before : {},
549
+ ...(0, import_full_utils.isObjFilled)(after) ? after : {}
550
+ };
551
+ await this.success(data, data.result);
552
+ return await this.afterExecution(data, data.result);
553
+ } catch (err) {
554
+ try {
555
+ await this.retry(data);
556
+ } catch (err2) {
557
+ await this.error(err2, data);
558
+ }
559
+ } finally {
560
+ try {
561
+ this.stopHeartbeat(data);
562
+ await this.ack(data).catch(() => {
563
+ });
564
+ } catch {
565
+ }
566
+ }
567
+ return {};
568
+ }
569
+ jitteredBackoffSec(attempt) {
570
+ const base = Math.max(1, Number(this.retryBaseSec) || 1);
571
+ const maxD = Math.max(base, Number(this.retryMaxSec) || 3600);
572
+ const pow = Math.min(maxD, base * Math.pow(2, Math.max(0, attempt - 1)));
573
+ const jitter = Math.floor(Math.random() * base);
574
+ return Math.min(maxD, pow + jitter);
575
+ }
576
+ async retry(task) {
577
+ if (!(0, import_full_utils.isObjFilled)(task) || !(0, import_full_utils.isStrFilled)(task.iterationId) || !(0, import_full_utils.isStrFilled)(task.id) || !(0, import_full_utils.isStrFilled)(task.queueName) || !(0, import_full_utils.isNumPZ)(task.currentAttempt) || !(0, import_full_utils.isNumPZ)(task.maxAttempts)) {
578
+ await this.error(new Error("Task format error."), task);
579
+ return;
580
+ }
581
+ const maxAttempts = task.maxAttempts ?? this.maxAttempts;
582
+ try {
583
+ if (task.currentAttempt < maxAttempts - 1) {
584
+ const taskProcessed = { ...task, currentAttempt: task.currentAttempt + 1 };
585
+ const delaySec = this.jitteredBackoffSec(taskProcessed.currentAttempt);
586
+ await this.addTask(taskProcessed, delaySec);
587
+ await this.onRetry(taskProcessed);
588
+ return;
589
+ }
590
+ } catch (err) {
591
+ await this.fail(err, task);
592
+ return;
593
+ }
594
+ await this.fail(new Error("The attempt limit has been reached."), task);
595
+ }
596
+ async iterationError(err, queueName, data) {
597
+ try {
598
+ await this.onIterationError(err, queueName);
599
+ } catch (err2) {
600
+ }
601
+ for (const t of data || []) {
602
+ if ((0, import_full_utils.isStrFilled)(t.id)) {
603
+ this.processingRaw.delete(t.id);
604
+ }
605
+ }
606
+ }
607
+ async error(err, task) {
608
+ try {
609
+ await this.addTask({
610
+ ...task,
611
+ queueName: [task.queueName, task.iterationId, "error", "list"].join(":"),
612
+ currentAttempt: 0,
613
+ payload: {
614
+ ...task.payload,
615
+ errorMessage: String(err?.message ?? "")
616
+ }
617
+ });
618
+ await this.onError(err, task);
619
+ } catch (err2) {
620
+ try {
621
+ await this.onFatal(err2, task);
622
+ } catch {
623
+ }
624
+ }
625
+ try {
626
+ await this.status(task, "error");
627
+ } catch {
628
+ }
629
+ }
630
+ async fail(err, task) {
631
+ try {
632
+ await this.addTask({
633
+ ...task,
634
+ queueName: [task.queueName, task.iterationId, "fail", "list"].join(":"),
635
+ currentAttempt: 0,
636
+ payload: {
637
+ ...task.payload,
638
+ errorMessage: String(err?.message ?? "")
639
+ }
640
+ });
641
+ await this.onFail(err, task);
642
+ } catch (err2) {
643
+ try {
644
+ await this.onFatal(err2, task);
645
+ } catch {
646
+ }
647
+ }
648
+ try {
649
+ await this.status(task, "fail");
650
+ } catch {
651
+ }
652
+ }
653
+ async success(task, result) {
654
+ const taskProcessed = {
655
+ ...task,
656
+ progress: {
657
+ ...task.progress,
658
+ successAt: Date.now()
659
+ }
660
+ };
661
+ try {
662
+ if ((0, import_full_utils.isObjFilled)(taskProcessed.chain) && (0, import_full_utils.isArrFilled)(taskProcessed.chain.queues) && (0, import_full_utils.isNumPZ)(taskProcessed.chain.index)) {
663
+ const currentIndex = taskProcessed.chain.index;
664
+ const newIndex = currentIndex + 1;
665
+ taskProcessed.progress.chain.push(Date.now());
666
+ if (currentIndex === taskProcessed.chain.queues.length - 1) {
667
+ await this.status(taskProcessed, "success");
668
+ await this.onChainSuccess(taskProcessed, result);
669
+ } else if (newIndex <= taskProcessed.chain.queues.length - 1) {
670
+ const newQueueName = taskProcessed.chain.queues[newIndex];
671
+ if ((0, import_full_utils.isStrFilled)(newQueueName)) {
672
+ await this.addTask({
673
+ ...taskProcessed,
674
+ queueName: newQueueName,
675
+ currentAttempt: 0,
676
+ chain: {
677
+ ...taskProcessed.chain,
678
+ index: newIndex
679
+ },
680
+ result
681
+ });
682
+ } else {
683
+ await this.fail(new Error("Next queue format error."), taskProcessed);
684
+ }
685
+ }
686
+ } else {
687
+ await this.status(taskProcessed, "success");
688
+ }
689
+ await this.onSuccess(taskProcessed, result);
690
+ } catch (err) {
691
+ try {
692
+ await this.status(taskProcessed, "fatal");
693
+ } catch {
694
+ }
695
+ try {
696
+ await this.onFatal(err, taskProcessed);
697
+ } catch {
698
+ }
699
+ }
700
+ }
701
+ async status(task, category = "success") {
702
+ if (!this.checkConnection()) {
703
+ throw new Error("Redis connection error.");
704
+ }
705
+ const processedKey = this.toKeyString(task.queueName, task.iterationId, "processed");
706
+ const categoryKey = this.toKeyString(task.queueName, task.iterationId, category);
707
+ await this.redis?.incr(processedKey);
708
+ await this.redis?.incr(categoryKey);
709
+ await this.redis?.expire(processedKey, this.expireStatusSec);
710
+ await this.redis?.expire(categoryKey, this.expireStatusSec);
711
+ }
712
+ async loop(queueName, runner) {
713
+ if (!(0, import_full_utils.isStrFilled)(queueName)) {
714
+ throw new Error(`Queue name is not valid: "${queueName}"; Type: "${typeof queueName}".`);
715
+ }
716
+ const ready = this.readyKey(queueName);
717
+ const processing = this.processingKey(queueName);
718
+ const processingVt = this.processingVtKey(queueName);
719
+ const delayed = this.delayedKey(queueName);
720
+ while (runner.running) {
721
+ if (!this.checkConnection()) {
722
+ await (0, import_full_utils.wait)(this.iterationTimeout);
723
+ continue;
724
+ }
725
+ try {
726
+ await this.promoteDelayed(delayed, ready);
727
+ } catch {
728
+ }
729
+ try {
730
+ await this.requeueExpired(processing, processingVt, ready);
731
+ } catch {
732
+ }
733
+ let data = [];
734
+ try {
735
+ data = await this.data(queueName);
736
+ if (!(0, import_full_utils.isArrFilled)(data)) {
737
+ await (0, import_full_utils.wait)(this.iterationTimeout);
738
+ continue;
739
+ }
740
+ await this.iteration(data);
741
+ } catch (err) {
742
+ await this.iterationError(err, queueName, data);
743
+ await (0, import_full_utils.wait)(this.iterationTimeout);
744
+ }
745
+ }
746
+ }
747
+ async data(queueName) {
748
+ const ready = this.readyKey(queueName);
749
+ const processing = this.processingKey(queueName);
750
+ const processingVt = this.processingVtKey(queueName);
751
+ const raws = await this.reserveMany(ready, processing, processingVt, this.portionLength, this.visibilityTimeoutSec);
752
+ if (!(0, import_full_utils.isArrFilled)(raws)) {
753
+ return [];
754
+ }
755
+ const tasks = [];
756
+ for (const raw of raws) {
757
+ const obj = this.fromPayload(raw);
758
+ if ((0, import_full_utils.isObjFilled)(obj) && (0, import_full_utils.isStrFilled)(obj.iterationId) && (0, import_full_utils.isStrFilled)(obj.id) && (0, import_full_utils.isStrFilled)(obj.queueName) && (0, import_full_utils.isNumPZ)(obj.maxAttempts) && (0, import_full_utils.isNumPZ)(obj.currentAttempt)) {
759
+ const t = obj;
760
+ this.processingRaw.set(t.id, raw);
761
+ this.startHeartbeat(t);
762
+ tasks.push(t);
763
+ }
764
+ }
765
+ return tasks;
766
+ }
767
+ async ack(task) {
768
+ try {
769
+ const raw = this.processingRaw.get(task.id);
770
+ if (!(0, import_full_utils.isStrFilled)(raw)) {
771
+ return;
772
+ }
773
+ this.processingRaw.delete(task.id);
774
+ await this.ackProcessing(this.processingKey(task.queueName), this.processingVtKey(task.queueName), raw);
775
+ } catch {
776
+ }
777
+ }
778
+ };
779
+ // Annotate the CommonJS export names for ESM import in node:
780
+ 0 && (module.exports = {
781
+ PowerQueue
782
+ });