mail-time 5.1.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -14
- package/adapters/mongo.d.ts +1 -0
- package/adapters/mongo.js +34 -16
- package/adapters/postgres.d.ts +1 -0
- package/adapters/postgres.js +45 -24
- package/adapters/redis.d.ts +6 -2
- package/adapters/redis.js +153 -72
- package/index.cjs +805 -127
- package/index.d.cts +136 -14
- package/index.d.ts +136 -14
- package/index.js +270 -15
- package/package.json +10 -3
- package/recipient-policy-lease.js +90 -0
- package/recipient-policy.js +166 -0
- package/recipient-rejections.js +62 -0
- package/scripts/migrate-redis-queue-to-cluster.mjs +5 -1
package/adapters/redis.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
debug,
|
|
3
3
|
logError,
|
|
4
|
+
hasOwnProp,
|
|
4
5
|
isSendClaimUpdate,
|
|
5
6
|
isSendLeaseGuardedUpdate,
|
|
6
7
|
isAppendMailOptionUpdate,
|
|
@@ -15,9 +16,12 @@ import { createHash } from 'crypto';
|
|
|
15
16
|
* @property {(key: string) => Promise<string|null>} get
|
|
16
17
|
* @property {(key: string, value: string, options?: object) => Promise<unknown>} set
|
|
17
18
|
* @property {(key: string|string[]) => Promise<number>} del
|
|
18
|
-
* @property {() => Promise<string>} ping
|
|
19
|
+
* @property {() => Promise<string>} [ping]
|
|
20
|
+
* @property {() => unknown} [getRandomNode]
|
|
21
|
+
* @property {(...args: any[]) => any} [nodeClient]
|
|
22
|
+
* @property {(firstKey: string, isReadonly: boolean, args: string[]) => Promise<unknown>} [sendCommand]
|
|
19
23
|
* @property {(options: object) => AsyncIterable<string|string[]>} [scanIterator]
|
|
20
|
-
* @property {(key: string, field: string) => Promise<string|null>} [hGet]
|
|
24
|
+
* @property {(key: string, field: string) => Promise<string|null|undefined>} [hGet]
|
|
21
25
|
* @property {(script: string, options: { keys: string[], arguments: string[] }) => Promise<unknown>} [eval]
|
|
22
26
|
* @property {(sha: string, options: { keys: string[], arguments: string[] }) => Promise<unknown>} [evalSha]
|
|
23
27
|
* @property {(script: string) => Promise<string>} [scriptLoad]
|
|
@@ -37,6 +41,7 @@ const KEY_TYPES = new Set(['letter', 'sendat', 'concatletter']);
|
|
|
37
41
|
const DEFAULT_PREFIX = 'default';
|
|
38
42
|
const VALID_PREFIX = /^[A-Za-z0-9_\-:.]+$/;
|
|
39
43
|
const TAGGED_ITERATE_LIMIT = 100;
|
|
44
|
+
const clientTransactions = new WeakMap();
|
|
40
45
|
|
|
41
46
|
const PUSH_TAGGED_TASK_SCRIPT = `
|
|
42
47
|
redis.call('HSET', KEYS[1], ARGV[1], ARGV[2])
|
|
@@ -69,17 +74,19 @@ const ITERATE_TAGGED_TASKS_SCRIPT = `
|
|
|
69
74
|
redis.call('ZREM', KEYS[2], uuid)
|
|
70
75
|
else
|
|
71
76
|
local task = cjson.decode(payload)
|
|
72
|
-
if task.isSent or task.isFailed or task.isCancelled or
|
|
77
|
+
if task.isSent or task.isFailed or task.isCancelled or task.isSettled then
|
|
78
|
+
redis.call('ZREM', KEYS[2], uuid)
|
|
79
|
+
elseif tonumber(task.tries or 0) >= maxTries and not (type(task.recipientResults) == 'table' and task.isSending) then
|
|
73
80
|
redis.call('ZREM', KEYS[2], uuid)
|
|
74
81
|
elseif task.isSending then
|
|
75
82
|
local eligibleAt = tonumber(task.sendingAt or 0) + sendingTimeout
|
|
76
83
|
if eligibleAt > now then
|
|
77
84
|
redis.call('ZADD', KEYS[2], eligibleAt, uuid)
|
|
78
85
|
else
|
|
79
|
-
table.insert(tasks,
|
|
86
|
+
table.insert(tasks, payload)
|
|
80
87
|
end
|
|
81
88
|
elseif tonumber(task.sendAt or 0) <= now then
|
|
82
|
-
table.insert(tasks,
|
|
89
|
+
table.insert(tasks, payload)
|
|
83
90
|
else
|
|
84
91
|
redis.call('ZADD', KEYS[2], tonumber(task.sendAt), uuid)
|
|
85
92
|
end
|
|
@@ -106,28 +113,33 @@ const UPDATE_TAGGED_TASK_SCRIPT = `
|
|
|
106
113
|
local sendingTimeout = tonumber(ARGV[5])
|
|
107
114
|
local expectedTries = tonumber(ARGV[6])
|
|
108
115
|
local leaseSendingAt = tonumber(ARGV[7])
|
|
116
|
+
if payload ~= ARGV[8] then return -1 end
|
|
109
117
|
|
|
110
118
|
if mode == 'claim' then
|
|
111
|
-
if task.isSent or task.isFailed or task.isCancelled or tonumber(task.tries or 0) ~= expectedTries then
|
|
119
|
+
if task.isSent or task.isFailed or task.isCancelled or task.isSettled or tonumber(task.tries or 0) ~= expectedTries then
|
|
112
120
|
return 0
|
|
113
121
|
end
|
|
114
122
|
if task.isSending and tonumber(task.sendingAt or 0) > now - sendingTimeout then
|
|
115
123
|
return 0
|
|
116
124
|
end
|
|
117
125
|
elseif mode == 'lease' then
|
|
118
|
-
if task.isCancelled or task.isFailed or not task.isSending
|
|
126
|
+
if task.isSent or task.isSettled or task.isCancelled or task.isFailed or not task.isSending
|
|
119
127
|
or tonumber(task.tries or 0) ~= expectedTries
|
|
120
128
|
or tonumber(task.sendingAt or 0) ~= leaseSendingAt then
|
|
121
129
|
return 0
|
|
122
130
|
end
|
|
123
131
|
end
|
|
124
132
|
|
|
133
|
+
if mode == 'cancel' and (task.isSent or task.isSettled or task.isCancelled
|
|
134
|
+
or (type(task.recipientResults) == 'table' and task.isFailed)) then
|
|
135
|
+
return 0
|
|
136
|
+
end
|
|
125
137
|
for key, value in pairs(update) do
|
|
126
138
|
task[key] = value
|
|
127
139
|
end
|
|
128
|
-
redis.call('HSET', KEYS[1], ARGV[1],
|
|
140
|
+
redis.call('HSET', KEYS[1], ARGV[1], ARGV[9])
|
|
129
141
|
|
|
130
|
-
if task.isSent or task.isFailed or task.isCancelled then
|
|
142
|
+
if task.isSent or task.isFailed or task.isCancelled or task.isSettled then
|
|
131
143
|
redis.call('ZREM', KEYS[2], ARGV[1])
|
|
132
144
|
if #KEYS > 3 then
|
|
133
145
|
local concatUuid = redis.call('GET', KEYS[3])
|
|
@@ -152,12 +164,11 @@ const APPEND_TAGGED_MAIL_OPTION_SCRIPT = `
|
|
|
152
164
|
return 0
|
|
153
165
|
end
|
|
154
166
|
local task = cjson.decode(payload)
|
|
155
|
-
if task.isSending or task.isSent or task.isFailed or task.isCancelled then
|
|
167
|
+
if task.isSending or task.isSent or task.isFailed or task.isCancelled or task.isSettled or type(task.recipientResults) == 'table' then
|
|
156
168
|
return 0
|
|
157
169
|
end
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
redis.call('HSET', KEYS[1], ARGV[1], cjson.encode(task))
|
|
170
|
+
if payload ~= ARGV[3] then return -1 end
|
|
171
|
+
redis.call('HSET', KEYS[1], ARGV[1], ARGV[4])
|
|
161
172
|
return 1
|
|
162
173
|
`;
|
|
163
174
|
|
|
@@ -168,7 +179,9 @@ const REMOVE_TAGGED_TASK_SCRIPT = `
|
|
|
168
179
|
return 0
|
|
169
180
|
end
|
|
170
181
|
local task = cjson.decode(payload)
|
|
171
|
-
if ARGV[2] == '
|
|
182
|
+
if ARGV[2] == 'cancel' and (task.isSent or task.isSettled or task.isCancelled
|
|
183
|
+
or (type(task.recipientResults) == 'table' and task.isFailed)) then return 0 end
|
|
184
|
+
if ARGV[2] == 'lease' and (task.isSent or task.isSettled or task.isCancelled or task.isFailed or not task.isSending
|
|
172
185
|
or tonumber(task.tries or 0) ~= tonumber(ARGV[3])
|
|
173
186
|
or tonumber(task.sendingAt or 0) ~= tonumber(ARGV[4])) then
|
|
174
187
|
return 0
|
|
@@ -187,6 +200,18 @@ const REMOVE_TAGGED_TASK_SCRIPT = `
|
|
|
187
200
|
return 1
|
|
188
201
|
`;
|
|
189
202
|
|
|
203
|
+
const normalizePolicyArrays = (task) => {
|
|
204
|
+
if (task.recipientResults && !Array.isArray(task.recipientResults) && Object.keys(task.recipientResults).length === 0) task.recipientResults = [];
|
|
205
|
+
if (Array.isArray(task.recipientResults)) {
|
|
206
|
+
for (const result of task.recipientResults) {
|
|
207
|
+
for (const field of ['sources', 'reasons']) {
|
|
208
|
+
if (result[field] && !Array.isArray(result[field]) && Object.keys(result[field]).length === 0) result[field] = [];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return task;
|
|
213
|
+
};
|
|
214
|
+
|
|
190
215
|
const sha1Hex = (string) => createHash('sha1').update(string).digest('hex');
|
|
191
216
|
|
|
192
217
|
const isNoScriptError = (error) => {
|
|
@@ -199,6 +224,8 @@ const canReleaseLease = (currentTask, updateObj) => {
|
|
|
199
224
|
&& currentTask.isSending === true
|
|
200
225
|
&& (typeof currentTask.sendingAt === 'number' ? currentTask.sendingAt : 0) === updateObj.leaseSendingAt
|
|
201
226
|
&& currentTask.isCancelled !== true
|
|
227
|
+
&& currentTask.isSent !== true
|
|
228
|
+
&& currentTask.isSettled !== true
|
|
202
229
|
&& currentTask.isFailed !== true;
|
|
203
230
|
};
|
|
204
231
|
|
|
@@ -206,7 +233,7 @@ const canClaimTask = (currentTask, task, now, sendingTimeout) => {
|
|
|
206
233
|
if (!currentTask) {
|
|
207
234
|
return false;
|
|
208
235
|
}
|
|
209
|
-
if (currentTask.isSent === true || currentTask.isFailed === true || currentTask.isCancelled === true) {
|
|
236
|
+
if (currentTask.isSent === true || currentTask.isFailed === true || currentTask.isCancelled === true || currentTask.isSettled === true) {
|
|
210
237
|
return false;
|
|
211
238
|
}
|
|
212
239
|
if (currentTask.tries !== task.tries) {
|
|
@@ -225,11 +252,11 @@ const isIterateCandidate = (candidate, now, sendingTimeout, maxTries) => {
|
|
|
225
252
|
if (!candidate || typeof candidate !== 'object') {
|
|
226
253
|
return false;
|
|
227
254
|
}
|
|
228
|
-
if (candidate.isSent === true || candidate.isFailed === true || candidate.isCancelled === true) {
|
|
255
|
+
if (candidate.isSent === true || candidate.isFailed === true || candidate.isCancelled === true || candidate.isSettled === true) {
|
|
229
256
|
return false;
|
|
230
257
|
}
|
|
231
258
|
const tries = typeof candidate.tries === 'number' ? candidate.tries : 0;
|
|
232
|
-
if (tries >= maxTries) {
|
|
259
|
+
if (tries >= maxTries && !(Array.isArray(candidate.recipientResults) && candidate.isSending === true)) {
|
|
233
260
|
return false;
|
|
234
261
|
}
|
|
235
262
|
if (candidate.isSending === true) {
|
|
@@ -257,6 +284,7 @@ class RedisQueue {
|
|
|
257
284
|
*/
|
|
258
285
|
constructor (opts) {
|
|
259
286
|
this.name = 'redis-queue';
|
|
287
|
+
this.supportsRecipientPolicies = true;
|
|
260
288
|
if (!opts || typeof opts !== 'object') {
|
|
261
289
|
throw new TypeError('[mail-time] Configuration object must be passed into RedisQueue constructor');
|
|
262
290
|
}
|
|
@@ -319,6 +347,17 @@ class RedisQueue {
|
|
|
319
347
|
throw new Error(`[mail-time] [RedisQueue] unknown script "${scriptKey}"`);
|
|
320
348
|
}
|
|
321
349
|
|
|
350
|
+
if (this.useHashTags && typeof this.client.nodeClient === 'function' && typeof this.client.sendCommand === 'function') {
|
|
351
|
+
const key = options.keys[0];
|
|
352
|
+
const args = [`${options.keys.length}`, ...options.keys, ...options.arguments];
|
|
353
|
+
try {
|
|
354
|
+
return await this.client.sendCommand(key, false, ['EVALSHA', sha, ...args]);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
if (!isNoScriptError(error)) throw error;
|
|
357
|
+
}
|
|
358
|
+
return await this.client.sendCommand(key, false, ['EVAL', source, ...args]);
|
|
359
|
+
}
|
|
360
|
+
|
|
322
361
|
if (this.__loadedShas.has(sha) && typeof this.client.evalSha === 'function') {
|
|
323
362
|
try {
|
|
324
363
|
return await this.client.evalSha(sha, options);
|
|
@@ -386,7 +425,9 @@ class RedisQueue {
|
|
|
386
425
|
}
|
|
387
426
|
|
|
388
427
|
try {
|
|
389
|
-
const
|
|
428
|
+
const pingClient = typeof this.client.ping === 'function' ? this.client
|
|
429
|
+
: await this.client.nodeClient(this.client.getRandomNode());
|
|
430
|
+
const ping = await pingClient.ping();
|
|
390
431
|
if (ping === 'PONG') {
|
|
391
432
|
return {
|
|
392
433
|
status: 'OK',
|
|
@@ -443,7 +484,8 @@ class RedisQueue {
|
|
|
443
484
|
if (!Array.isArray(candidates)) {
|
|
444
485
|
return;
|
|
445
486
|
}
|
|
446
|
-
for (const
|
|
487
|
+
for (const value of candidates) {
|
|
488
|
+
const candidate = normalizePolicyArrays(typeof value === 'string' ? JSON.parse(value) : value);
|
|
447
489
|
if (isIterateCandidate(candidate, now, sendingTimeout, maxTries)) {
|
|
448
490
|
await this.mailTimeInstance.___dispatch(candidate);
|
|
449
491
|
}
|
|
@@ -520,7 +562,7 @@ class RedisQueue {
|
|
|
520
562
|
}
|
|
521
563
|
|
|
522
564
|
const task = JSON.parse(taskJSON);
|
|
523
|
-
if (!task || task.isSent === true || task.isCancelled === true || task.isFailed === true || task.sendAt > sendAt || task.isSending === true || task.tries >= this.mailTimeInstance.maxTries) {
|
|
565
|
+
if (!task || task.isSettled === true || task.recipientResults != null || task.isSent === true || task.isCancelled === true || task.isFailed === true || task.sendAt > sendAt || task.isSending === true || task.tries >= this.mailTimeInstance.maxTries) {
|
|
524
566
|
return null;
|
|
525
567
|
}
|
|
526
568
|
|
|
@@ -593,35 +635,59 @@ class RedisQueue {
|
|
|
593
635
|
* @returns {Promise<boolean>} returns `true` if cancelled or `false` if not found, was sent, or was cancelled previously
|
|
594
636
|
*/
|
|
595
637
|
async cancel(uuid) {
|
|
638
|
+
return await this.__serialize(() => this.__cancel(uuid));
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/** @internal */
|
|
642
|
+
async __serialize(operation) {
|
|
643
|
+
if (this.useHashTags) return await operation();
|
|
644
|
+
const previous = clientTransactions.get(this.client) || Promise.resolve();
|
|
645
|
+
const pending = previous.then(operation);
|
|
646
|
+
const settled = pending.catch(() => void 0);
|
|
647
|
+
clientTransactions.set(this.client, settled);
|
|
648
|
+
try { return await pending; }
|
|
649
|
+
finally { if (clientTransactions.get(this.client) === settled) clientTransactions.delete(this.client); }
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/** @internal */
|
|
653
|
+
async __cancel(uuid) {
|
|
596
654
|
this.__debug('[cancel]', uuid);
|
|
597
655
|
if (typeof uuid !== 'string') {
|
|
598
656
|
return false;
|
|
599
657
|
}
|
|
600
658
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
?
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
659
|
+
this.__ensurePrefix();
|
|
660
|
+
const atomic = typeof this.client.watch === 'function' && typeof this.client.multi === 'function';
|
|
661
|
+
const letterKey = this.__getKey(uuid);
|
|
662
|
+
try {
|
|
663
|
+
if (!this.useHashTags && atomic) await this.client.watch(letterKey);
|
|
664
|
+
const payload = this.useHashTags ? await this.client.hGet(this.lettersKey, uuid) : await this.client.get(letterKey);
|
|
665
|
+
const task = payload ? JSON.parse(payload) : null;
|
|
666
|
+
if (!task || task.isSent || task.isCancelled || task.isSettled || (task.recipientResults != null && task.isFailed)) return false;
|
|
667
|
+
if (this.useHashTags) {
|
|
668
|
+
const keys = [this.lettersKey, this.scheduleKey];
|
|
669
|
+
if (task.to) keys.push(this.__getTaggedConcatKey(task.to), this.concatKeysKey);
|
|
670
|
+
const keep = this.mailTimeInstance.keepHistory;
|
|
671
|
+
const args = keep
|
|
672
|
+
? [uuid, JSON.stringify({ isCancelled: true }), 'cancel', `${Date.now()}`, `${this.mailTimeInstance.sendingTimeout || 300000}`, '0', '0', payload, JSON.stringify({ ...task, isCancelled: true })]
|
|
673
|
+
: [uuid, 'cancel', '0', '0'];
|
|
674
|
+
return Number(await this.__runScript(keep ? 'update' : 'remove', { keys, arguments: args })) >= 1;
|
|
675
|
+
}
|
|
676
|
+
if (!atomic) {
|
|
677
|
+
if (task.recipientResults != null) return false;
|
|
678
|
+
return this.mailTimeInstance.keepHistory ? await this.__update(task, { isCancelled: true }) : await this.__remove(task);
|
|
679
|
+
}
|
|
680
|
+
const multi = this.client.multi();
|
|
681
|
+
if (this.mailTimeInstance.keepHistory) multi.set(letterKey, JSON.stringify({ ...task, isCancelled: true }));
|
|
682
|
+
else multi.del(letterKey);
|
|
683
|
+
multi.del(this.__getKey(uuid, 'sendat'));
|
|
684
|
+
return (await multi.exec()) !== null;
|
|
685
|
+
} catch (error) {
|
|
686
|
+
logError('[cancel] storage error', error);
|
|
615
687
|
return false;
|
|
688
|
+
} finally {
|
|
689
|
+
if (!this.useHashTags && atomic) await this.client.unwatch?.();
|
|
616
690
|
}
|
|
617
|
-
|
|
618
|
-
if (!this.mailTimeInstance.keepHistory) {
|
|
619
|
-
return await this.remove(task);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
return await this.update(task, {
|
|
623
|
-
isCancelled: true,
|
|
624
|
-
});
|
|
625
691
|
}
|
|
626
692
|
|
|
627
693
|
/**
|
|
@@ -634,6 +700,11 @@ class RedisQueue {
|
|
|
634
700
|
* @returns {Promise<boolean>} returns `true` if removed or `false` if not found
|
|
635
701
|
*/
|
|
636
702
|
async remove(task, opts) {
|
|
703
|
+
return await this.__serialize(() => this.__remove(task, opts));
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/** @internal */
|
|
707
|
+
async __remove(task, opts) {
|
|
637
708
|
this.__debug('[remove]', task?.uuid);
|
|
638
709
|
if (!task || typeof task !== 'object' || typeof task.uuid !== 'string') {
|
|
639
710
|
return false;
|
|
@@ -680,6 +751,8 @@ class RedisQueue {
|
|
|
680
751
|
|| currentTask.isSending !== true
|
|
681
752
|
|| (typeof currentTask.sendingAt === 'number' ? currentTask.sendingAt : 0) !== opts.leaseSendingAt
|
|
682
753
|
|| currentTask.isCancelled === true
|
|
754
|
+
|| currentTask.isSent === true
|
|
755
|
+
|| currentTask.isSettled === true
|
|
683
756
|
|| currentTask.isFailed === true) {
|
|
684
757
|
await this.client.unwatch?.();
|
|
685
758
|
return false;
|
|
@@ -723,6 +796,11 @@ class RedisQueue {
|
|
|
723
796
|
* @returns {Promise<boolean>} returns `true` if updated or `false` if not found or no changes was made
|
|
724
797
|
*/
|
|
725
798
|
async update(task, updateObj) {
|
|
799
|
+
return await this.__serialize(() => this.__update(task, updateObj));
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/** @internal */
|
|
803
|
+
async __update(task, updateObj) {
|
|
726
804
|
this.__debug('[update]', task?.uuid);
|
|
727
805
|
if (!task || typeof task !== 'object' || typeof task.uuid !== 'string' || !updateObj || typeof updateObj !== 'object') {
|
|
728
806
|
return false;
|
|
@@ -733,38 +811,41 @@ class RedisQueue {
|
|
|
733
811
|
const isClaim = isSendClaimUpdate(updateObj);
|
|
734
812
|
const isAppend = isAppendMailOptionUpdate(updateObj);
|
|
735
813
|
const isLeaseRelease = isSendLeaseGuardedUpdate(updateObj);
|
|
814
|
+
if ((hasOwnProp(updateObj, 'recipientResults') || hasOwnProp(updateObj, 'isSettled')) && !isClaim && !isLeaseRelease) return false;
|
|
736
815
|
const now = isClaim && typeof updateObj.sendingAt === 'number' ? updateObj.sendingAt : Date.now();
|
|
737
816
|
const sendingTimeout = this.mailTimeInstance?.sendingTimeout || 300000;
|
|
738
817
|
|
|
739
818
|
try {
|
|
740
819
|
if (this.useHashTags) {
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
820
|
+
// JS serialization preserves empty arrays and scalar values that Lua cjson changes.
|
|
821
|
+
// The script compares the exact payload as well as the claim/lease predicates.
|
|
822
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
823
|
+
const payload = await this.client.hGet(this.lettersKey, task.uuid);
|
|
824
|
+
if (!payload) return false;
|
|
825
|
+
const current = normalizePolicyArrays(JSON.parse(payload));
|
|
826
|
+
const persist = stripInternalUpdateMeta(updateObj);
|
|
827
|
+
const next = isAppend
|
|
828
|
+
? { ...current, mailOptions: [...(current.mailOptions || []), updateObj.appendMailOption] }
|
|
829
|
+
: { ...current, ...persist };
|
|
830
|
+
if (isAppend) {
|
|
831
|
+
const result = Number(await this.__runScript('append', {
|
|
832
|
+
keys: [this.lettersKey],
|
|
833
|
+
arguments: [task.uuid, JSON.stringify(updateObj.appendMailOption), payload, JSON.stringify(next)],
|
|
834
|
+
}));
|
|
835
|
+
if (result === -1) continue;
|
|
836
|
+
return result >= 1;
|
|
837
|
+
}
|
|
838
|
+
const keys = [this.lettersKey, this.scheduleKey];
|
|
839
|
+
if (current.to) keys.push(this.__getTaggedConcatKey(current.to), this.concatKeysKey);
|
|
840
|
+
const result = Number(await this.__runScript('update', {
|
|
841
|
+
keys,
|
|
842
|
+
arguments: [task.uuid, JSON.stringify(persist), isClaim ? 'claim' : (isLeaseRelease ? 'lease' : 'plain'),
|
|
843
|
+
`${now}`, `${sendingTimeout}`, `${isClaim ? task.tries : (updateObj.leaseTries || 0)}`,
|
|
844
|
+
`${updateObj.leaseSendingAt || 0}`, payload, JSON.stringify(next)],
|
|
845
|
+
}));
|
|
846
|
+
if (result !== -1) return result >= 1;
|
|
754
847
|
}
|
|
755
|
-
|
|
756
|
-
keys,
|
|
757
|
-
arguments: [
|
|
758
|
-
task.uuid,
|
|
759
|
-
JSON.stringify(stripInternalUpdateMeta(updateObj)),
|
|
760
|
-
mode,
|
|
761
|
-
`${now}`,
|
|
762
|
-
`${sendingTimeout}`,
|
|
763
|
-
`${isClaim ? task.tries : (updateObj.leaseTries || 0)}`,
|
|
764
|
-
`${updateObj.leaseSendingAt || 0}`,
|
|
765
|
-
],
|
|
766
|
-
});
|
|
767
|
-
return Number(result) >= 1;
|
|
848
|
+
return false;
|
|
768
849
|
}
|
|
769
850
|
|
|
770
851
|
if (isAppend) {
|
|
@@ -778,7 +859,7 @@ class RedisQueue {
|
|
|
778
859
|
return false;
|
|
779
860
|
}
|
|
780
861
|
const currentTask = JSON.parse(taskJSON);
|
|
781
|
-
if (currentTask.isSending === true || currentTask.isSent === true || currentTask.isFailed === true || currentTask.isCancelled === true) {
|
|
862
|
+
if (currentTask.isSending === true || currentTask.isSent === true || currentTask.isFailed === true || currentTask.isCancelled === true || currentTask.isSettled === true || Array.isArray(currentTask.recipientResults)) {
|
|
782
863
|
return false;
|
|
783
864
|
}
|
|
784
865
|
currentTask.mailOptions = [...(currentTask.mailOptions || []), updateObj.appendMailOption];
|
|
@@ -793,7 +874,7 @@ class RedisQueue {
|
|
|
793
874
|
return false;
|
|
794
875
|
}
|
|
795
876
|
const currentTask = JSON.parse(taskJSON);
|
|
796
|
-
if (currentTask.isSending === true || currentTask.isSent === true || currentTask.isFailed === true || currentTask.isCancelled === true) {
|
|
877
|
+
if (currentTask.isSending === true || currentTask.isSent === true || currentTask.isFailed === true || currentTask.isCancelled === true || currentTask.isSettled === true || Array.isArray(currentTask.recipientResults)) {
|
|
797
878
|
await this.client.unwatch?.();
|
|
798
879
|
return false;
|
|
799
880
|
}
|
|
@@ -833,7 +914,7 @@ class RedisQueue {
|
|
|
833
914
|
const updatedTask = { ...currentTask, ...stripInternalUpdateMeta(updateObj) };
|
|
834
915
|
const multi = this.client.multi();
|
|
835
916
|
multi.set(letterKey, JSON.stringify(updatedTask));
|
|
836
|
-
if (updatedTask.isSent === true || updatedTask.isFailed === true || updatedTask.isCancelled === true) {
|
|
917
|
+
if (updatedTask.isSent === true || updatedTask.isFailed === true || updatedTask.isCancelled === true || updatedTask.isSettled === true) {
|
|
837
918
|
multi.del(sendatKey);
|
|
838
919
|
} else if (updatedTask.sendAt) {
|
|
839
920
|
multi.set(sendatKey, `${+updatedTask.sendAt}`);
|
|
@@ -852,7 +933,7 @@ class RedisQueue {
|
|
|
852
933
|
const updatedTask = { ...currentTask, ...stripInternalUpdateMeta(updateObj) };
|
|
853
934
|
await this.client.set(letterKey, JSON.stringify(updatedTask));
|
|
854
935
|
|
|
855
|
-
if (updatedTask.isSent === true || updatedTask.isFailed === true || updatedTask.isCancelled === true) {
|
|
936
|
+
if (updatedTask.isSent === true || updatedTask.isFailed === true || updatedTask.isCancelled === true || updatedTask.isSettled === true) {
|
|
856
937
|
await this.client.del(sendatKey);
|
|
857
938
|
} else if (updatedTask.sendAt) {
|
|
858
939
|
await this.client.set(sendatKey, `${+updatedTask.sendAt}`);
|