bullmq 5.81.0 → 5.81.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.
- package/dist/cjs/classes/queue-getters.js +7 -2
- package/dist/cjs/classes/scripts.js +33 -0
- package/dist/cjs/commands/getJobs-1.lua +151 -0
- package/dist/cjs/scripts/getJobs-1.js +142 -0
- package/dist/cjs/scripts/index.js +1 -0
- package/dist/cjs/tsconfig-cjs.tsbuildinfo +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/classes/queue-getters.js +8 -3
- package/dist/esm/classes/scripts.d.ts +8 -0
- package/dist/esm/classes/scripts.js +33 -0
- package/dist/esm/commands/getJobs-1.lua +151 -0
- package/dist/esm/scripts/getJobs-1.d.ts +5 -0
- package/dist/esm/scripts/getJobs-1.js +139 -0
- package/dist/esm/scripts/index.d.ts +1 -0
- package/dist/esm/scripts/index.js +1 -0
- 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
|
@@ -389,8 +389,13 @@ class QueueGetters extends queue_base_1.QueueBase {
|
|
|
389
389
|
*/
|
|
390
390
|
async getJobs(types, start = 0, end = -1, asc = false) {
|
|
391
391
|
const currentTypes = this.sanitizeJobTypes(types);
|
|
392
|
-
const
|
|
393
|
-
return
|
|
392
|
+
const jobDataByType = await this.scripts.getJobs(currentTypes, start, end, asc);
|
|
393
|
+
return jobDataByType.reduce((jobs, jobData) => {
|
|
394
|
+
for (const [jobId, jobHashFields] of jobData || []) {
|
|
395
|
+
jobs.push(this.Job.fromJSON(this, (0, utils_1.array2obj)(jobHashFields), jobId));
|
|
396
|
+
}
|
|
397
|
+
return jobs;
|
|
398
|
+
}, []);
|
|
394
399
|
}
|
|
395
400
|
/**
|
|
396
401
|
* Returns the logs for a given Job.
|
|
@@ -15,6 +15,13 @@ const enums_1 = require("../enums");
|
|
|
15
15
|
const utils_1 = require("../utils");
|
|
16
16
|
const version_1 = require("../version");
|
|
17
17
|
const errors_1 = require("./errors");
|
|
18
|
+
/**
|
|
19
|
+
* Upper bound on the number of forward backfill iterations the `getJobs` Lua
|
|
20
|
+
* script performs to replace skipped ids (missing job hashes) within a bounded
|
|
21
|
+
* range. It caps the work done per call so a range full of missing jobs cannot
|
|
22
|
+
* scan the whole state unboundedly.
|
|
23
|
+
*/
|
|
24
|
+
const GET_JOBS_MAX_BACKFILL_ITERATIONS = 5;
|
|
18
25
|
class Scripts {
|
|
19
26
|
constructor(queue) {
|
|
20
27
|
this.queue = queue;
|
|
@@ -612,6 +619,32 @@ class Scripts {
|
|
|
612
619
|
const args = this.getRangesArgs(types, start, end, asc);
|
|
613
620
|
return await this.execCommand(client, 'getRanges', args);
|
|
614
621
|
}
|
|
622
|
+
getJobsArgs(types, start, end, asc) {
|
|
623
|
+
const queueKeys = this.queue.keys;
|
|
624
|
+
const transformedTypes = [
|
|
625
|
+
...new Set(types.map(type => (type === 'waiting' ? 'wait' : type))),
|
|
626
|
+
];
|
|
627
|
+
const keys = [queueKeys['']];
|
|
628
|
+
const args = [
|
|
629
|
+
start,
|
|
630
|
+
end,
|
|
631
|
+
asc ? '1' : '0',
|
|
632
|
+
GET_JOBS_MAX_BACKFILL_ITERATIONS,
|
|
633
|
+
...transformedTypes,
|
|
634
|
+
];
|
|
635
|
+
return keys.concat(args);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Fetches job ids and their job hashes for the provided states in a single
|
|
639
|
+
* script, skipping ids whose job hash is missing (for example the deprecated
|
|
640
|
+
* wait list marker or jobs removed after their id was read). Each returned
|
|
641
|
+
* entry is a `[jobId, jobHashFields]` tuple grouped per requested type.
|
|
642
|
+
*/
|
|
643
|
+
async getJobs(types, start = 0, end = -1, asc = false) {
|
|
644
|
+
const client = await this.queue.client;
|
|
645
|
+
const args = this.getJobsArgs(types, start, end, asc);
|
|
646
|
+
return await this.execCommand(client, 'getJobs', args);
|
|
647
|
+
}
|
|
615
648
|
getCountsArgs(types) {
|
|
616
649
|
const queueKeys = this.queue.keys;
|
|
617
650
|
const transformedTypes = types.map(type => {
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
Get jobs (id + data) for the provided states.
|
|
3
|
+
|
|
4
|
+
Job ids and their hashes are read in the same script so that ids whose hash
|
|
5
|
+
disappears after the id is read (but before the job is loaded) do not appear
|
|
6
|
+
in the result set. Ids without a job hash (for example the deprecated wait
|
|
7
|
+
list marker entry stored in the list) are skipped. For bounded ranges the
|
|
8
|
+
script iterates forward using the range offset as a cursor to backfill
|
|
9
|
+
skipped ids, preserving the requested page size when possible.
|
|
10
|
+
|
|
11
|
+
Input:
|
|
12
|
+
KEYS[1] 'prefix'
|
|
13
|
+
|
|
14
|
+
ARGV[1] start
|
|
15
|
+
ARGV[2] end
|
|
16
|
+
ARGV[3] asc ('1' | '0')
|
|
17
|
+
ARGV[4] max iterations (backfill bound)
|
|
18
|
+
ARGV[5...] types
|
|
19
|
+
|
|
20
|
+
Output:
|
|
21
|
+
results grouped per requested type; each entry is a
|
|
22
|
+
{jobId, {field, value, ...}} tuple
|
|
23
|
+
]]
|
|
24
|
+
local rcall = redis.call
|
|
25
|
+
local prefix = KEYS[1]
|
|
26
|
+
local rangeStart = tonumber(ARGV[1])
|
|
27
|
+
local rangeEnd = tonumber(ARGV[2])
|
|
28
|
+
local asc = ARGV[3] == "1"
|
|
29
|
+
local max_iterations = tonumber(ARGV[4])
|
|
30
|
+
local results = {}
|
|
31
|
+
|
|
32
|
+
local function isListType(stateType)
|
|
33
|
+
return stateType == "wait" or stateType == "paused" or stateType == "active"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
-- Fetch a slice of ids for the given state respecting the requested order.
|
|
37
|
+
local function fetchIds(stateKey, stateType, sliceStart, sliceEnd, listLength)
|
|
38
|
+
if isListType(stateType) then
|
|
39
|
+
if asc then
|
|
40
|
+
local modifiedRangeStart
|
|
41
|
+
local modifiedRangeEnd
|
|
42
|
+
if sliceStart == -1 then
|
|
43
|
+
modifiedRangeStart = 0
|
|
44
|
+
else
|
|
45
|
+
modifiedRangeStart = -(sliceStart + 1)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if sliceEnd == -1 then
|
|
49
|
+
modifiedRangeEnd = 0
|
|
50
|
+
else
|
|
51
|
+
modifiedRangeEnd = -(sliceEnd + 1)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
-- Ascending list slices use negative indexes. When the whole window is
|
|
55
|
+
-- beyond the list length Redis clamps both indexes to 0 and LRANGE would
|
|
56
|
+
-- return the head element, so guard against out-of-range slices with LLEN.
|
|
57
|
+
if listLength ~= nil and sliceStart >= 0 and sliceEnd >= 0 and sliceStart >= listLength then
|
|
58
|
+
return {}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
local ids = rcall("LRANGE", stateKey, modifiedRangeEnd, modifiedRangeStart)
|
|
62
|
+
local reversed = {}
|
|
63
|
+
for i = #ids, 1, -1 do
|
|
64
|
+
reversed[#reversed + 1] = ids[i]
|
|
65
|
+
end
|
|
66
|
+
return reversed
|
|
67
|
+
else
|
|
68
|
+
return rcall("LRANGE", stateKey, sliceStart, sliceEnd)
|
|
69
|
+
end
|
|
70
|
+
else
|
|
71
|
+
if asc then
|
|
72
|
+
return rcall("ZRANGE", stateKey, sliceStart, sliceEnd)
|
|
73
|
+
else
|
|
74
|
+
return rcall("ZREVRANGE", stateKey, sliceStart, sliceEnd)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
-- Fetch the job hash for an id and append it when present.
|
|
80
|
+
local function appendJob(entries, jobId)
|
|
81
|
+
local jobData = rcall("HGETALL", prefix .. jobId)
|
|
82
|
+
if #jobData > 0 then
|
|
83
|
+
entries[#entries + 1] = {jobId, jobData}
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
local function cleanupDeprecatedMarker(stateKey, stateType)
|
|
88
|
+
if stateType == "wait" or stateType == "paused" then
|
|
89
|
+
local marker = rcall("LINDEX", stateKey, -1)
|
|
90
|
+
if marker and string.sub(marker, 1, 2) == "0:" then
|
|
91
|
+
local count = rcall("LLEN", stateKey)
|
|
92
|
+
if count > 1 then
|
|
93
|
+
rcall("RPOP", stateKey)
|
|
94
|
+
return count - 1
|
|
95
|
+
end
|
|
96
|
+
return 0
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
local function collectJobs(stateKey, stateType)
|
|
102
|
+
local entries = {}
|
|
103
|
+
local listLength
|
|
104
|
+
local cleanedListLength = cleanupDeprecatedMarker(stateKey, stateType)
|
|
105
|
+
|
|
106
|
+
if asc and isListType(stateType) and rangeStart >= 0 and rangeEnd >= 0 then
|
|
107
|
+
if cleanedListLength ~= nil then
|
|
108
|
+
listLength = cleanedListLength
|
|
109
|
+
else
|
|
110
|
+
listLength = rcall("LLEN", stateKey)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
-- Unbounded or negative ranges: fetch the exact window and skip missing ids.
|
|
115
|
+
if rangeStart < 0 or rangeEnd < 0 then
|
|
116
|
+
local ids = fetchIds(stateKey, stateType, rangeStart, rangeEnd, listLength)
|
|
117
|
+
for i = 1, #ids do
|
|
118
|
+
appendJob(entries, ids[i])
|
|
119
|
+
end
|
|
120
|
+
return entries
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
-- Bounded range: iterate forward to backfill skipped ids.
|
|
124
|
+
local needed = rangeEnd - rangeStart + 1
|
|
125
|
+
local cursor = rangeStart
|
|
126
|
+
local iterations = 0
|
|
127
|
+
while #entries < needed and iterations < max_iterations do
|
|
128
|
+
local ids = fetchIds(stateKey, stateType, cursor, cursor + needed - 1, listLength)
|
|
129
|
+
if #ids == 0 then
|
|
130
|
+
break
|
|
131
|
+
end
|
|
132
|
+
for i = 1, #ids do
|
|
133
|
+
if #entries >= needed then
|
|
134
|
+
break
|
|
135
|
+
end
|
|
136
|
+
appendJob(entries, ids[i])
|
|
137
|
+
end
|
|
138
|
+
cursor = cursor + #ids
|
|
139
|
+
iterations = iterations + 1
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
return entries
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
for i = 5, #ARGV do
|
|
146
|
+
local stateType = ARGV[i]
|
|
147
|
+
local stateKey = prefix .. stateType
|
|
148
|
+
results[#results + 1] = collectJobs(stateKey, stateType)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
return results
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getJobs = void 0;
|
|
4
|
+
const content = `--[[
|
|
5
|
+
Get jobs (id + data) for the provided states.
|
|
6
|
+
Job ids and their hashes are read in the same script so that ids whose hash
|
|
7
|
+
disappears after the id is read (but before the job is loaded) do not appear
|
|
8
|
+
in the result set. Ids without a job hash (for example the deprecated wait
|
|
9
|
+
list marker entry stored in the list) are skipped. For bounded ranges the
|
|
10
|
+
script iterates forward using the range offset as a cursor to backfill
|
|
11
|
+
skipped ids, preserving the requested page size when possible.
|
|
12
|
+
Input:
|
|
13
|
+
KEYS[1] 'prefix'
|
|
14
|
+
ARGV[1] start
|
|
15
|
+
ARGV[2] end
|
|
16
|
+
ARGV[3] asc ('1' | '0')
|
|
17
|
+
ARGV[4] max iterations (backfill bound)
|
|
18
|
+
ARGV[5...] types
|
|
19
|
+
Output:
|
|
20
|
+
results grouped per requested type; each entry is a
|
|
21
|
+
{jobId, {field, value, ...}} tuple
|
|
22
|
+
]]
|
|
23
|
+
local rcall = redis.call
|
|
24
|
+
local prefix = KEYS[1]
|
|
25
|
+
local rangeStart = tonumber(ARGV[1])
|
|
26
|
+
local rangeEnd = tonumber(ARGV[2])
|
|
27
|
+
local asc = ARGV[3] == "1"
|
|
28
|
+
local max_iterations = tonumber(ARGV[4])
|
|
29
|
+
local results = {}
|
|
30
|
+
local function isListType(stateType)
|
|
31
|
+
return stateType == "wait" or stateType == "paused" or stateType == "active"
|
|
32
|
+
end
|
|
33
|
+
-- Fetch a slice of ids for the given state respecting the requested order.
|
|
34
|
+
local function fetchIds(stateKey, stateType, sliceStart, sliceEnd, listLength)
|
|
35
|
+
if isListType(stateType) then
|
|
36
|
+
if asc then
|
|
37
|
+
local modifiedRangeStart
|
|
38
|
+
local modifiedRangeEnd
|
|
39
|
+
if sliceStart == -1 then
|
|
40
|
+
modifiedRangeStart = 0
|
|
41
|
+
else
|
|
42
|
+
modifiedRangeStart = -(sliceStart + 1)
|
|
43
|
+
end
|
|
44
|
+
if sliceEnd == -1 then
|
|
45
|
+
modifiedRangeEnd = 0
|
|
46
|
+
else
|
|
47
|
+
modifiedRangeEnd = -(sliceEnd + 1)
|
|
48
|
+
end
|
|
49
|
+
-- Ascending list slices use negative indexes. When the whole window is
|
|
50
|
+
-- beyond the list length Redis clamps both indexes to 0 and LRANGE would
|
|
51
|
+
-- return the head element, so guard against out-of-range slices with LLEN.
|
|
52
|
+
if listLength ~= nil and sliceStart >= 0 and sliceEnd >= 0 and sliceStart >= listLength then
|
|
53
|
+
return {}
|
|
54
|
+
end
|
|
55
|
+
local ids = rcall("LRANGE", stateKey, modifiedRangeEnd, modifiedRangeStart)
|
|
56
|
+
local reversed = {}
|
|
57
|
+
for i = #ids, 1, -1 do
|
|
58
|
+
reversed[#reversed + 1] = ids[i]
|
|
59
|
+
end
|
|
60
|
+
return reversed
|
|
61
|
+
else
|
|
62
|
+
return rcall("LRANGE", stateKey, sliceStart, sliceEnd)
|
|
63
|
+
end
|
|
64
|
+
else
|
|
65
|
+
if asc then
|
|
66
|
+
return rcall("ZRANGE", stateKey, sliceStart, sliceEnd)
|
|
67
|
+
else
|
|
68
|
+
return rcall("ZREVRANGE", stateKey, sliceStart, sliceEnd)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
-- Fetch the job hash for an id and append it when present.
|
|
73
|
+
local function appendJob(entries, jobId)
|
|
74
|
+
local jobData = rcall("HGETALL", prefix .. jobId)
|
|
75
|
+
if #jobData > 0 then
|
|
76
|
+
entries[#entries + 1] = {jobId, jobData}
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
local function cleanupDeprecatedMarker(stateKey, stateType)
|
|
80
|
+
if stateType == "wait" or stateType == "paused" then
|
|
81
|
+
local marker = rcall("LINDEX", stateKey, -1)
|
|
82
|
+
if marker and string.sub(marker, 1, 2) == "0:" then
|
|
83
|
+
local count = rcall("LLEN", stateKey)
|
|
84
|
+
if count > 1 then
|
|
85
|
+
rcall("RPOP", stateKey)
|
|
86
|
+
return count - 1
|
|
87
|
+
end
|
|
88
|
+
return 0
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
local function collectJobs(stateKey, stateType)
|
|
93
|
+
local entries = {}
|
|
94
|
+
local listLength
|
|
95
|
+
local cleanedListLength = cleanupDeprecatedMarker(stateKey, stateType)
|
|
96
|
+
if asc and isListType(stateType) and rangeStart >= 0 and rangeEnd >= 0 then
|
|
97
|
+
if cleanedListLength ~= nil then
|
|
98
|
+
listLength = cleanedListLength
|
|
99
|
+
else
|
|
100
|
+
listLength = rcall("LLEN", stateKey)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
-- Unbounded or negative ranges: fetch the exact window and skip missing ids.
|
|
104
|
+
if rangeStart < 0 or rangeEnd < 0 then
|
|
105
|
+
local ids = fetchIds(stateKey, stateType, rangeStart, rangeEnd, listLength)
|
|
106
|
+
for i = 1, #ids do
|
|
107
|
+
appendJob(entries, ids[i])
|
|
108
|
+
end
|
|
109
|
+
return entries
|
|
110
|
+
end
|
|
111
|
+
-- Bounded range: iterate forward to backfill skipped ids.
|
|
112
|
+
local needed = rangeEnd - rangeStart + 1
|
|
113
|
+
local cursor = rangeStart
|
|
114
|
+
local iterations = 0
|
|
115
|
+
while #entries < needed and iterations < max_iterations do
|
|
116
|
+
local ids = fetchIds(stateKey, stateType, cursor, cursor + needed - 1, listLength)
|
|
117
|
+
if #ids == 0 then
|
|
118
|
+
break
|
|
119
|
+
end
|
|
120
|
+
for i = 1, #ids do
|
|
121
|
+
if #entries >= needed then
|
|
122
|
+
break
|
|
123
|
+
end
|
|
124
|
+
appendJob(entries, ids[i])
|
|
125
|
+
end
|
|
126
|
+
cursor = cursor + #ids
|
|
127
|
+
iterations = iterations + 1
|
|
128
|
+
end
|
|
129
|
+
return entries
|
|
130
|
+
end
|
|
131
|
+
for i = 5, #ARGV do
|
|
132
|
+
local stateType = ARGV[i]
|
|
133
|
+
local stateKey = prefix .. stateType
|
|
134
|
+
results[#results + 1] = collectJobs(stateKey, stateType)
|
|
135
|
+
end
|
|
136
|
+
return results
|
|
137
|
+
`;
|
|
138
|
+
exports.getJobs = {
|
|
139
|
+
name: 'getJobs',
|
|
140
|
+
content,
|
|
141
|
+
keys: 1,
|
|
142
|
+
};
|
|
@@ -18,6 +18,7 @@ tslib_1.__exportStar(require("./getCounts-1"), exports);
|
|
|
18
18
|
tslib_1.__exportStar(require("./getCountsPerPriority-4"), exports);
|
|
19
19
|
tslib_1.__exportStar(require("./getDependencyCounts-4"), exports);
|
|
20
20
|
tslib_1.__exportStar(require("./getJobScheduler-1"), exports);
|
|
21
|
+
tslib_1.__exportStar(require("./getJobs-1"), exports);
|
|
21
22
|
tslib_1.__exportStar(require("./getMetrics-2"), exports);
|
|
22
23
|
tslib_1.__exportStar(require("./getRanges-1"), exports);
|
|
23
24
|
tslib_1.__exportStar(require("./getRateLimitTtl-2"), exports);
|