bullmq 5.77.2 → 5.77.4
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/cjs/classes/bun-redis-client.js +59 -56
- package/dist/cjs/commands/includes/requeueDeduplicatedJob.lua +10 -2
- package/dist/cjs/commands/includes/storeDeduplicatedNextJob.lua +3 -1
- package/dist/cjs/scripts/addDelayedJob-6.js +3 -1
- package/dist/cjs/scripts/addParentJob-6.js +3 -1
- package/dist/cjs/scripts/addPrioritizedJob-9.js +3 -1
- package/dist/cjs/scripts/addStandardJob-9.js +3 -1
- package/dist/cjs/scripts/moveToFinished-14.js +10 -2
- package/dist/cjs/tsconfig-cjs.tsbuildinfo +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/classes/bun-redis-client.js +59 -56
- package/dist/esm/commands/includes/requeueDeduplicatedJob.lua +10 -2
- package/dist/esm/commands/includes/storeDeduplicatedNextJob.lua +3 -1
- package/dist/esm/scripts/addDelayedJob-6.js +3 -1
- package/dist/esm/scripts/addParentJob-6.js +3 -1
- package/dist/esm/scripts/addPrioritizedJob-9.js +3 -1
- package/dist/esm/scripts/addStandardJob-9.js +3 -1
- package/dist/esm/scripts/moveToFinished-14.js +10 -2
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +1 -1
|
@@ -85,9 +85,6 @@ class BunRedisAdapter extends events_1.EventEmitter {
|
|
|
85
85
|
this.hasConnected = false;
|
|
86
86
|
this.closed = false;
|
|
87
87
|
this.closing = false;
|
|
88
|
-
// Serialize raw send() calls per connection to avoid Bun delivering
|
|
89
|
-
// concurrent command responses to the wrong pending promise.
|
|
90
|
-
this.sendQueue = Promise.resolve();
|
|
91
88
|
// Auto-reconnect state
|
|
92
89
|
this.reconnecting = false;
|
|
93
90
|
this.reconnectTimer = null;
|
|
@@ -404,31 +401,24 @@ class BunRedisAdapter extends events_1.EventEmitter {
|
|
|
404
401
|
}));
|
|
405
402
|
}
|
|
406
403
|
sendCommand(command, args) {
|
|
407
|
-
// If the connection is already closing/closed, return a rejected promise
|
|
408
|
-
// directly without going through sendQueue.
|
|
404
|
+
// If the connection is already closing/closed, return a rejected promise.
|
|
409
405
|
if (this.closing || this.closed) {
|
|
410
406
|
return Promise.reject(new connection_closed_error_1.ConnectionClosedError('Connection is closed'));
|
|
411
407
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
408
|
+
// Send directly to the underlying Bun client. Redis protocol guarantees
|
|
409
|
+
// responses arrive in the same order as requests on a single connection,
|
|
410
|
+
// so concurrent send() calls are safe and enable implicit pipelining
|
|
411
|
+
// (multiple commands written to the socket before any response is read).
|
|
412
|
+
// MULTI/EXEC transactions don't go through this path — they are issued
|
|
413
|
+
// as a synchronous burst of raw `send()` calls in
|
|
414
|
+
// `BunRedisTransaction.exec()`, which guarantees the MULTI…EXEC frames
|
|
415
|
+
// are written contiguously without any other command interleaving.
|
|
416
|
+
return this.raw.send(command, args).catch((err) => {
|
|
417
|
+
if (isBunConnectionClosedError(err)) {
|
|
418
|
+
return Promise.reject(new connection_closed_error_1.ConnectionClosedError(this.closing || this.closed ? 'Connection is closed' : err.message, err));
|
|
415
419
|
}
|
|
416
|
-
|
|
417
|
-
if (isBunConnectionClosedError(err)) {
|
|
418
|
-
return Promise.reject(new connection_closed_error_1.ConnectionClosedError(this.closing || this.closed
|
|
419
|
-
? 'Connection is closed'
|
|
420
|
-
: err.message, err));
|
|
421
|
-
}
|
|
422
|
-
throw err;
|
|
423
|
-
});
|
|
420
|
+
throw err;
|
|
424
421
|
});
|
|
425
|
-
this.sendQueue = run.then(() => undefined, () => undefined);
|
|
426
|
-
return run;
|
|
427
|
-
}
|
|
428
|
-
async queueExclusive(operation) {
|
|
429
|
-
const run = this.sendQueue.then(operation, operation);
|
|
430
|
-
this.sendQueue = run.then(() => undefined, () => undefined);
|
|
431
|
-
return run;
|
|
432
422
|
}
|
|
433
423
|
// ---------------------------------------------------------------
|
|
434
424
|
// Pipeline / Transaction
|
|
@@ -973,41 +963,54 @@ class BunRedisTransaction {
|
|
|
973
963
|
return [null, value];
|
|
974
964
|
});
|
|
975
965
|
}
|
|
976
|
-
// Execute as
|
|
977
|
-
//
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
});
|
|
966
|
+
// Execute as a pipelined MULTI/EXEC block. Redis supports multiple
|
|
967
|
+
// MULTI/EXEC blocks pipelined on the same connection — each MULTI starts
|
|
968
|
+
// a new transaction context and EXEC commits it, responses arrive in
|
|
969
|
+
// order. We fire MULTI + all commands + EXEC synchronously (no await
|
|
970
|
+
// between them) so they're written to the socket buffer as one contiguous
|
|
971
|
+
// burst with no opportunity for interleaving from other async contexts.
|
|
972
|
+
//
|
|
973
|
+
// The MULTI and per-command `send()` calls are intentionally fire-and-
|
|
974
|
+
// forget for performance, but their returned promises must still have a
|
|
975
|
+
// rejection handler attached to avoid Bun emitting "unhandled promise
|
|
976
|
+
// rejection" warnings if the connection drops or Redis returns an error
|
|
977
|
+
// reply before we reach EXEC. The actual failure is reported through the
|
|
978
|
+
// awaited EXEC promise (which Redis rejects in the same situations), or
|
|
979
|
+
// bubbles up via the surrounding `try/catch`.
|
|
980
|
+
const swallow = (_) => {
|
|
981
|
+
/* error surfaces via EXEC or the outer try/catch */
|
|
982
|
+
};
|
|
983
|
+
try {
|
|
984
|
+
// Fire MULTI without awaiting — no round-trip needed before commands.
|
|
985
|
+
this.raw.send('MULTI', []).catch(swallow);
|
|
986
|
+
// Fire all queued commands synchronously (no awaits).
|
|
987
|
+
for (const { cmd, args } of this.commands) {
|
|
988
|
+
this.raw.send(cmd, args).catch(swallow);
|
|
1000
989
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
990
|
+
// EXEC is the only await — it returns the array of results.
|
|
991
|
+
const results = await this.raw.send('EXEC', []);
|
|
992
|
+
if (!results) {
|
|
993
|
+
return null;
|
|
994
|
+
}
|
|
995
|
+
// Normalize to ioredis format: [Error | null, value][]
|
|
996
|
+
return results.map((result, i) => {
|
|
997
|
+
if (result instanceof Error) {
|
|
998
|
+
return [result, null];
|
|
1008
999
|
}
|
|
1009
|
-
|
|
1000
|
+
const transformer = this.transformers[i];
|
|
1001
|
+
const value = transformer ? transformer(result) : result;
|
|
1002
|
+
return [null, value];
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
catch (err) {
|
|
1006
|
+
// Try to discard the MULTI state on error
|
|
1007
|
+
try {
|
|
1008
|
+
await this.raw.send('DISCARD', []);
|
|
1010
1009
|
}
|
|
1011
|
-
|
|
1010
|
+
catch (_a) {
|
|
1011
|
+
// ignore
|
|
1012
|
+
}
|
|
1013
|
+
throw err;
|
|
1014
|
+
}
|
|
1012
1015
|
}
|
|
1013
1016
|
}
|
|
@@ -17,9 +17,17 @@ local function requeueDeduplicatedJob(prefix, deduplicationId, eventStreamKey,
|
|
|
17
17
|
local deduplicationNextKey = prefix .. "dn:" .. deduplicationId
|
|
18
18
|
if rcall("EXISTS", deduplicationNextKey) == 1 then
|
|
19
19
|
local nextData = rcall("HMGET", deduplicationNextKey,
|
|
20
|
-
"name", "data", "opts", "pk", "pd", "pdk", "rjk")
|
|
20
|
+
"name", "data", "opts", "pk", "pd", "pdk", "rjk", "jid")
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
-- Always increment the counter to keep it monotonic
|
|
23
|
+
local nextId = rcall("INCR", prefix .. "id") .. ""
|
|
24
|
+
local storedJobId = nextData[8] -- index 8 = "jid" (8th field in the HMGET call above)
|
|
25
|
+
local newJobId
|
|
26
|
+
if storedJobId then
|
|
27
|
+
newJobId = storedJobId
|
|
28
|
+
else
|
|
29
|
+
newJobId = nextId
|
|
30
|
+
end
|
|
23
31
|
local newJobIdKey = prefix .. newJobId
|
|
24
32
|
local newOpts = cjson.decode(nextData[3])
|
|
25
33
|
local deduplicationKey = prefix .. "de:" .. deduplicationId
|
|
@@ -14,7 +14,8 @@ local function storeDeduplicatedNextJob(deduplicationOpts, currentDebounceJobId,
|
|
|
14
14
|
local activeItems = rcall('LRANGE', activeKey, 0, -1)
|
|
15
15
|
if checkItemInList(activeItems, currentDebounceJobId) then
|
|
16
16
|
local deduplicationNextKey = prefix .. "dn:" .. deduplicationId
|
|
17
|
-
local fields = {'name', jobName, 'data', jobData, 'opts', cjson.encode(fullOpts)
|
|
17
|
+
local fields = {'name', jobName, 'data', jobData, 'opts', cjson.encode(fullOpts),
|
|
18
|
+
'jid', jobId}
|
|
18
19
|
|
|
19
20
|
if parentKey then
|
|
20
21
|
fields[#fields+1] = 'pk'
|
|
@@ -36,6 +37,7 @@ local function storeDeduplicatedNextJob(deduplicationOpts, currentDebounceJobId,
|
|
|
36
37
|
fields[#fields+1] = repeatJobKey
|
|
37
38
|
end
|
|
38
39
|
|
|
40
|
+
rcall('DEL', deduplicationNextKey)
|
|
39
41
|
rcall('HSET', deduplicationNextKey, unpack(fields))
|
|
40
42
|
|
|
41
43
|
-- Ensure the dedup key does not expire while the job is active,
|
|
@@ -155,7 +155,8 @@ local function storeDeduplicatedNextJob(deduplicationOpts, currentDebounceJobId,
|
|
|
155
155
|
local activeItems = rcall('LRANGE', activeKey, 0, -1)
|
|
156
156
|
if checkItemInList(activeItems, currentDebounceJobId) then
|
|
157
157
|
local deduplicationNextKey = prefix .. "dn:" .. deduplicationId
|
|
158
|
-
local fields = {'name', jobName, 'data', jobData, 'opts', cjson.encode(fullOpts)
|
|
158
|
+
local fields = {'name', jobName, 'data', jobData, 'opts', cjson.encode(fullOpts),
|
|
159
|
+
'jid', jobId}
|
|
159
160
|
if parentKey then
|
|
160
161
|
fields[#fields+1] = 'pk'
|
|
161
162
|
fields[#fields+1] = parentKey
|
|
@@ -172,6 +173,7 @@ local function storeDeduplicatedNextJob(deduplicationOpts, currentDebounceJobId,
|
|
|
172
173
|
fields[#fields+1] = 'rjk'
|
|
173
174
|
fields[#fields+1] = repeatJobKey
|
|
174
175
|
end
|
|
176
|
+
rcall('DEL', deduplicationNextKey)
|
|
175
177
|
rcall('HSET', deduplicationNextKey, unpack(fields))
|
|
176
178
|
-- Ensure the dedup key does not expire while the job is active,
|
|
177
179
|
-- so subsequent adds always hit the dedup path and never bypass
|
|
@@ -86,7 +86,8 @@ local function storeDeduplicatedNextJob(deduplicationOpts, currentDebounceJobId,
|
|
|
86
86
|
local activeItems = rcall('LRANGE', activeKey, 0, -1)
|
|
87
87
|
if checkItemInList(activeItems, currentDebounceJobId) then
|
|
88
88
|
local deduplicationNextKey = prefix .. "dn:" .. deduplicationId
|
|
89
|
-
local fields = {'name', jobName, 'data', jobData, 'opts', cjson.encode(fullOpts)
|
|
89
|
+
local fields = {'name', jobName, 'data', jobData, 'opts', cjson.encode(fullOpts),
|
|
90
|
+
'jid', jobId}
|
|
90
91
|
if parentKey then
|
|
91
92
|
fields[#fields+1] = 'pk'
|
|
92
93
|
fields[#fields+1] = parentKey
|
|
@@ -103,6 +104,7 @@ local function storeDeduplicatedNextJob(deduplicationOpts, currentDebounceJobId,
|
|
|
103
104
|
fields[#fields+1] = 'rjk'
|
|
104
105
|
fields[#fields+1] = repeatJobKey
|
|
105
106
|
end
|
|
107
|
+
rcall('DEL', deduplicationNextKey)
|
|
106
108
|
rcall('HSET', deduplicationNextKey, unpack(fields))
|
|
107
109
|
-- Ensure the dedup key does not expire while the job is active,
|
|
108
110
|
-- so subsequent adds always hit the dedup path and never bypass
|
|
@@ -120,7 +120,8 @@ local function storeDeduplicatedNextJob(deduplicationOpts, currentDebounceJobId,
|
|
|
120
120
|
local activeItems = rcall('LRANGE', activeKey, 0, -1)
|
|
121
121
|
if checkItemInList(activeItems, currentDebounceJobId) then
|
|
122
122
|
local deduplicationNextKey = prefix .. "dn:" .. deduplicationId
|
|
123
|
-
local fields = {'name', jobName, 'data', jobData, 'opts', cjson.encode(fullOpts)
|
|
123
|
+
local fields = {'name', jobName, 'data', jobData, 'opts', cjson.encode(fullOpts),
|
|
124
|
+
'jid', jobId}
|
|
124
125
|
if parentKey then
|
|
125
126
|
fields[#fields+1] = 'pk'
|
|
126
127
|
fields[#fields+1] = parentKey
|
|
@@ -137,6 +138,7 @@ local function storeDeduplicatedNextJob(deduplicationOpts, currentDebounceJobId,
|
|
|
137
138
|
fields[#fields+1] = 'rjk'
|
|
138
139
|
fields[#fields+1] = repeatJobKey
|
|
139
140
|
end
|
|
141
|
+
rcall('DEL', deduplicationNextKey)
|
|
140
142
|
rcall('HSET', deduplicationNextKey, unpack(fields))
|
|
141
143
|
-- Ensure the dedup key does not expire while the job is active,
|
|
142
144
|
-- so subsequent adds always hit the dedup path and never bypass
|
|
@@ -114,7 +114,8 @@ local function storeDeduplicatedNextJob(deduplicationOpts, currentDebounceJobId,
|
|
|
114
114
|
local activeItems = rcall('LRANGE', activeKey, 0, -1)
|
|
115
115
|
if checkItemInList(activeItems, currentDebounceJobId) then
|
|
116
116
|
local deduplicationNextKey = prefix .. "dn:" .. deduplicationId
|
|
117
|
-
local fields = {'name', jobName, 'data', jobData, 'opts', cjson.encode(fullOpts)
|
|
117
|
+
local fields = {'name', jobName, 'data', jobData, 'opts', cjson.encode(fullOpts),
|
|
118
|
+
'jid', jobId}
|
|
118
119
|
if parentKey then
|
|
119
120
|
fields[#fields+1] = 'pk'
|
|
120
121
|
fields[#fields+1] = parentKey
|
|
@@ -131,6 +132,7 @@ local function storeDeduplicatedNextJob(deduplicationOpts, currentDebounceJobId,
|
|
|
131
132
|
fields[#fields+1] = 'rjk'
|
|
132
133
|
fields[#fields+1] = repeatJobKey
|
|
133
134
|
end
|
|
135
|
+
rcall('DEL', deduplicationNextKey)
|
|
134
136
|
rcall('HSET', deduplicationNextKey, unpack(fields))
|
|
135
137
|
-- Ensure the dedup key does not expire while the job is active,
|
|
136
138
|
-- so subsequent adds always hit the dedup path and never bypass
|
|
@@ -829,8 +829,16 @@ local function requeueDeduplicatedJob(prefix, deduplicationId, eventStreamKey,
|
|
|
829
829
|
local deduplicationNextKey = prefix .. "dn:" .. deduplicationId
|
|
830
830
|
if rcall("EXISTS", deduplicationNextKey) == 1 then
|
|
831
831
|
local nextData = rcall("HMGET", deduplicationNextKey,
|
|
832
|
-
"name", "data", "opts", "pk", "pd", "pdk", "rjk")
|
|
833
|
-
|
|
832
|
+
"name", "data", "opts", "pk", "pd", "pdk", "rjk", "jid")
|
|
833
|
+
-- Always increment the counter to keep it monotonic
|
|
834
|
+
local nextId = rcall("INCR", prefix .. "id") .. ""
|
|
835
|
+
local storedJobId = nextData[8] -- index 8 = "jid" (8th field in the HMGET call above)
|
|
836
|
+
local newJobId
|
|
837
|
+
if storedJobId then
|
|
838
|
+
newJobId = storedJobId
|
|
839
|
+
else
|
|
840
|
+
newJobId = nextId
|
|
841
|
+
end
|
|
834
842
|
local newJobIdKey = prefix .. newJobId
|
|
835
843
|
local newOpts = cjson.decode(nextData[3])
|
|
836
844
|
local deduplicationKey = prefix .. "de:" .. deduplicationId
|