queue-jobs-worker 1.0.4 → 1.0.5
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/CHANGELOG.md +51 -78
- package/README.md +111 -77
- package/assets/queue-jobs-worker-demo.gif +0 -0
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/queue.d.ts +3 -1
- package/dist/core/queue.d.ts.map +1 -1
- package/dist/index.cjs +188 -139
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +188 -139
- package/dist/index.js.map +1 -1
- package/dist/lib/scripts/claim.lua +27 -0
- package/dist/lib/scripts/index.d.ts +5 -0
- package/dist/lib/scripts/index.d.ts.map +1 -0
- package/dist/lib/scripts/rate-limit.lua +36 -0
- package/dist/lib/scripts/recover-stalled.lua +40 -0
- package/dist/lib/scripts/renew-lock.lua +16 -0
- package/dist/storage/mysql.adapter.d.ts.map +1 -1
- package/dist/storage/postgres.adapter.d.ts.map +1 -1
- package/dist/storage/redis.adapter.d.ts.map +1 -1
- package/package.json +1 -1
- package/assets/queue-jobs-worker-demo.mp4 +0 -0
package/dist/index.js
CHANGED
|
@@ -12,6 +12,89 @@ var __export = (target, all) => {
|
|
|
12
12
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
// src/lib/scripts/claim.lua
|
|
16
|
+
var claim_default;
|
|
17
|
+
var init_claim = __esm({
|
|
18
|
+
"src/lib/scripts/claim.lua"() {
|
|
19
|
+
claim_default = "local prefix = ARGV[4]\nlocal now_ms = tonumber(ARGV[3])\n\n-- Promote due delayed jobs into the waiting set.\nlocal due = redis.call('ZRANGEBYSCORE', KEYS[2], '-inf', now_ms)\nfor _, jid in ipairs(due) do\n local pri_raw = redis.call('HGET', prefix .. 'job:' .. jid, 'priority')\n local pri = tonumber(pri_raw) or 0\n redis.call('ZADD', KEYS[1], -pri, jid)\n redis.call('ZREM', KEYS[2], jid)\n redis.call('HSET', prefix .. 'job:' .. jid, 'status', 'waiting')\nend\n\n-- Pop the top-priority job.\nlocal items = redis.call('ZPOPMIN', KEYS[1], 1)\nif #items == 0 then return '' end\nlocal job_id = items[1]\n\n-- Lock it.\nredis.call('SADD', KEYS[3], job_id)\nredis.call('HSET', prefix .. 'job:' .. job_id,\n 'status', 'active',\n 'lockId', ARGV[1],\n 'lockExpiresAt', ARGV[2],\n 'updatedAt', ARGV[2]\n)\nreturn job_id\n";
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// src/lib/scripts/recover-stalled.lua
|
|
24
|
+
var recover_stalled_default;
|
|
25
|
+
var init_recover_stalled = __esm({
|
|
26
|
+
"src/lib/scripts/recover-stalled.lua"() {
|
|
27
|
+
recover_stalled_default = "local job_key = KEYS[1]\nlocal active_set = KEYS[2]\nlocal waiting_zset = KEYS[3]\n\nlocal job_id = ARGV[1]\nlocal expected_exp = ARGV[2]\nlocal expected_lock = ARGV[3]\nlocal recovery_ts = ARGV[4]\nlocal priority_score = tonumber(ARGV[5])\n\n-- Re-read the three guard fields in one atomic HMGET.\nlocal fields = redis.call('HMGET', job_key, 'lockExpiresAt', 'lockId', 'status')\nlocal current_exp = fields[1] or ''\nlocal current_lock = fields[2] or ''\nlocal current_status = fields[3] or ''\n\n-- Guard 1: job must still be active.\n-- If the worker already completed, failed, or moved to DLQ, skip.\nif current_status ~= 'active' then return 0 end\n\n-- Guard 2: lockId must not have changed.\n-- A different worker may have claimed the job after the original lock expired\n-- and before this script runs.\nif current_lock ~= expected_lock then return 0 end\n\n-- Guard 3: lockExpiresAt must be exactly what the caller observed.\n-- If the worker renewed its lock the timestamp will be later than observed;\n-- the CAS mismatch catches that without needing ISO\u2192epoch conversion in Lua.\nif current_exp ~= expected_exp then return 0 end\n\n-- All guards passed \u2014 recover atomically.\nredis.call('HSET', job_key,\n 'status', 'waiting',\n 'lockId', '',\n 'lockExpiresAt', '',\n 'updatedAt', recovery_ts\n)\nredis.call('SREM', active_set, job_id)\nredis.call('ZADD', waiting_zset, priority_score, job_id)\nreturn 1\n";
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// src/lib/scripts/renew-lock.lua
|
|
32
|
+
var renew_lock_default;
|
|
33
|
+
var init_renew_lock = __esm({
|
|
34
|
+
"src/lib/scripts/renew-lock.lua"() {
|
|
35
|
+
renew_lock_default = "local job_key = KEYS[1]\nlocal lock_id = ARGV[1]\nlocal new_exp = ARGV[2]\nlocal now_iso = ARGV[3]\n\nlocal fields = redis.call('HMGET', job_key, 'status', 'lockId', 'lockExpiresAt')\nlocal status = fields[1] or ''\nlocal current_lock = fields[2] or ''\nlocal current_exp = fields[3] or ''\n\nif status ~= 'active' then return 0 end\nif current_lock ~= lock_id then return 0 end\nif current_exp == '' or current_exp <= now_iso then return 0 end\n\nredis.call('HSET', job_key, 'lockExpiresAt', new_exp, 'updatedAt', now_iso)\nreturn 1\n";
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// src/lib/scripts/rate-limit.lua
|
|
40
|
+
var rate_limit_default;
|
|
41
|
+
var init_rate_limit = __esm({
|
|
42
|
+
"src/lib/scripts/rate-limit.lua"() {
|
|
43
|
+
rate_limit_default = `-- RATE_LIMIT_LUA
|
|
44
|
+
-- KEYS[1]: rate_count_key ("qjw:rate:{queue}")
|
|
45
|
+
-- KEYS[2]: rate_ts_key ("qjw:rate:{queue}:ts")
|
|
46
|
+
-- ARGV[1]: max (number)
|
|
47
|
+
-- ARGV[2]: windowMs (number)
|
|
48
|
+
-- ARGV[3]: nowMs (number)
|
|
49
|
+
-- ARGV[4]: ttlSec (number)
|
|
50
|
+
--
|
|
51
|
+
-- Returns:
|
|
52
|
+
-- 1: Allowed
|
|
53
|
+
-- 0: Rejected
|
|
54
|
+
|
|
55
|
+
local ck = KEYS[1]
|
|
56
|
+
local tk = KEYS[2]
|
|
57
|
+
local max = tonumber(ARGV[1])
|
|
58
|
+
local windowMs = tonumber(ARGV[2])
|
|
59
|
+
local nowMs = tonumber(ARGV[3])
|
|
60
|
+
local ttlSec = tonumber(ARGV[4])
|
|
61
|
+
|
|
62
|
+
local windowStart = redis.call('GET', tk)
|
|
63
|
+
|
|
64
|
+
if not windowStart or (nowMs - tonumber(windowStart)) >= windowMs then
|
|
65
|
+
redis.call('SET', ck, '1', 'EX', ttlSec)
|
|
66
|
+
redis.call('SET', tk, tostring(nowMs), 'EX', ttlSec)
|
|
67
|
+
return 1
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
local current = tonumber(redis.call('GET', ck) or '0')
|
|
71
|
+
if current >= max then
|
|
72
|
+
return 0
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
redis.call('INCR', ck)
|
|
76
|
+
redis.call('EXPIRE', ck, ttlSec)
|
|
77
|
+
redis.call('EXPIRE', tk, ttlSec)
|
|
78
|
+
return 1
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// src/lib/scripts/index.ts
|
|
84
|
+
var CLAIM_LUA, RECOVER_STALLED_LUA, RENEW_LOCK_LUA, RATE_LIMIT_LUA;
|
|
85
|
+
var init_scripts = __esm({
|
|
86
|
+
"src/lib/scripts/index.ts"() {
|
|
87
|
+
init_claim();
|
|
88
|
+
init_recover_stalled();
|
|
89
|
+
init_renew_lock();
|
|
90
|
+
init_rate_limit();
|
|
91
|
+
CLAIM_LUA = claim_default;
|
|
92
|
+
RECOVER_STALLED_LUA = recover_stalled_default;
|
|
93
|
+
RENEW_LOCK_LUA = renew_lock_default;
|
|
94
|
+
RATE_LIMIT_LUA = rate_limit_default;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
15
98
|
// src/storage/redis.adapter.ts
|
|
16
99
|
var redis_adapter_exports = {};
|
|
17
100
|
__export(redis_adapter_exports, {
|
|
@@ -75,9 +158,10 @@ function hashToJob(h) {
|
|
|
75
158
|
if (h["cron"]) job.cron = h["cron"];
|
|
76
159
|
return job;
|
|
77
160
|
}
|
|
78
|
-
var PREFIX, k,
|
|
161
|
+
var PREFIX, k, RedisStorageAdapter;
|
|
79
162
|
var init_redis_adapter = __esm({
|
|
80
163
|
"src/storage/redis.adapter.ts"() {
|
|
164
|
+
init_scripts();
|
|
81
165
|
PREFIX = "qjw:";
|
|
82
166
|
k = {
|
|
83
167
|
job: (id) => `${PREFIX}job:${id}`,
|
|
@@ -89,95 +173,6 @@ var init_redis_adapter = __esm({
|
|
|
89
173
|
rateCount: (q) => `${PREFIX}rate:${q}`,
|
|
90
174
|
rateTs: (q) => `${PREFIX}rate:${q}:ts`
|
|
91
175
|
};
|
|
92
|
-
CLAIM_LUA = `
|
|
93
|
-
local prefix = ARGV[4]
|
|
94
|
-
local now_ms = tonumber(ARGV[3])
|
|
95
|
-
|
|
96
|
-
-- Promote due delayed jobs into the waiting set.
|
|
97
|
-
local due = redis.call('ZRANGEBYSCORE', KEYS[2], '-inf', now_ms)
|
|
98
|
-
for _, jid in ipairs(due) do
|
|
99
|
-
local pri_raw = redis.call('HGET', prefix .. 'job:' .. jid, 'priority')
|
|
100
|
-
local pri = tonumber(pri_raw) or 0
|
|
101
|
-
redis.call('ZADD', KEYS[1], -pri, jid)
|
|
102
|
-
redis.call('ZREM', KEYS[2], jid)
|
|
103
|
-
redis.call('HSET', prefix .. 'job:' .. jid, 'status', 'waiting')
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
-- Pop the top-priority job.
|
|
107
|
-
local items = redis.call('ZPOPMIN', KEYS[1], 1)
|
|
108
|
-
if #items == 0 then return '' end
|
|
109
|
-
local job_id = items[1]
|
|
110
|
-
|
|
111
|
-
-- Lock it.
|
|
112
|
-
redis.call('SADD', KEYS[3], job_id)
|
|
113
|
-
redis.call('HSET', prefix .. 'job:' .. job_id,
|
|
114
|
-
'status', 'active',
|
|
115
|
-
'lockId', ARGV[1],
|
|
116
|
-
'lockExpiresAt', ARGV[2],
|
|
117
|
-
'updatedAt', ARGV[2]
|
|
118
|
-
)
|
|
119
|
-
return job_id
|
|
120
|
-
`;
|
|
121
|
-
RECOVER_STALLED_LUA = `
|
|
122
|
-
local job_key = KEYS[1]
|
|
123
|
-
local active_set = KEYS[2]
|
|
124
|
-
local waiting_zset = KEYS[3]
|
|
125
|
-
|
|
126
|
-
local job_id = ARGV[1]
|
|
127
|
-
local expected_exp = ARGV[2]
|
|
128
|
-
local expected_lock = ARGV[3]
|
|
129
|
-
local recovery_ts = ARGV[4]
|
|
130
|
-
local priority_score = tonumber(ARGV[5])
|
|
131
|
-
|
|
132
|
-
-- Re-read the three guard fields in one atomic HMGET.
|
|
133
|
-
local fields = redis.call('HMGET', job_key, 'lockExpiresAt', 'lockId', 'status')
|
|
134
|
-
local current_exp = fields[1] or ''
|
|
135
|
-
local current_lock = fields[2] or ''
|
|
136
|
-
local current_status = fields[3] or ''
|
|
137
|
-
|
|
138
|
-
-- Guard 1: job must still be active.
|
|
139
|
-
-- If the worker already completed, failed, or moved to DLQ, skip.
|
|
140
|
-
if current_status ~= 'active' then return 0 end
|
|
141
|
-
|
|
142
|
-
-- Guard 2: lockId must not have changed.
|
|
143
|
-
-- A different worker may have claimed the job after the original lock expired
|
|
144
|
-
-- and before this script runs.
|
|
145
|
-
if current_lock ~= expected_lock then return 0 end
|
|
146
|
-
|
|
147
|
-
-- Guard 3: lockExpiresAt must be exactly what the caller observed.
|
|
148
|
-
-- If the worker renewed its lock the timestamp will be later than observed;
|
|
149
|
-
-- the CAS mismatch catches that without needing ISO\u2192epoch conversion in Lua.
|
|
150
|
-
if current_exp ~= expected_exp then return 0 end
|
|
151
|
-
|
|
152
|
-
-- All guards passed \u2014 recover atomically.
|
|
153
|
-
redis.call('HSET', job_key,
|
|
154
|
-
'status', 'waiting',
|
|
155
|
-
'lockId', '',
|
|
156
|
-
'lockExpiresAt', '',
|
|
157
|
-
'updatedAt', recovery_ts
|
|
158
|
-
)
|
|
159
|
-
redis.call('SREM', active_set, job_id)
|
|
160
|
-
redis.call('ZADD', waiting_zset, priority_score, job_id)
|
|
161
|
-
return 1
|
|
162
|
-
`;
|
|
163
|
-
RENEW_LOCK_LUA = `
|
|
164
|
-
local job_key = KEYS[1]
|
|
165
|
-
local lock_id = ARGV[1]
|
|
166
|
-
local new_exp = ARGV[2]
|
|
167
|
-
local now_iso = ARGV[3]
|
|
168
|
-
|
|
169
|
-
local fields = redis.call('HMGET', job_key, 'status', 'lockId', 'lockExpiresAt')
|
|
170
|
-
local status = fields[1] or ''
|
|
171
|
-
local current_lock = fields[2] or ''
|
|
172
|
-
local current_exp = fields[3] or ''
|
|
173
|
-
|
|
174
|
-
if status ~= 'active' then return 0 end
|
|
175
|
-
if current_lock ~= lock_id then return 0 end
|
|
176
|
-
if current_exp == '' or current_exp <= now_iso then return 0 end
|
|
177
|
-
|
|
178
|
-
redis.call('HSET', job_key, 'lockExpiresAt', new_exp, 'updatedAt', now_iso)
|
|
179
|
-
return 1
|
|
180
|
-
`;
|
|
181
176
|
RedisStorageAdapter = class {
|
|
182
177
|
client;
|
|
183
178
|
url;
|
|
@@ -500,23 +495,12 @@ return 1
|
|
|
500
495
|
const nowMs = new Date(now).getTime();
|
|
501
496
|
const ck = k.rateCount(queue);
|
|
502
497
|
const tk = k.rateTs(queue);
|
|
503
|
-
const
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
multi.set(tk, String(nowMs));
|
|
510
|
-
multi.expire(tk, ttlSec);
|
|
511
|
-
await multi.exec();
|
|
512
|
-
return true;
|
|
513
|
-
}
|
|
514
|
-
const count = await this.client.incr(ck);
|
|
515
|
-
if (count > max) {
|
|
516
|
-
await this.client.decr(ck);
|
|
517
|
-
return false;
|
|
518
|
-
}
|
|
519
|
-
return true;
|
|
498
|
+
const ttlSec = Math.max(1, Math.ceil(windowMs / 1e3));
|
|
499
|
+
const res = await this.client.eval(RATE_LIMIT_LUA, {
|
|
500
|
+
keys: [ck, tk],
|
|
501
|
+
arguments: [String(max), String(windowMs), String(nowMs), String(ttlSec)]
|
|
502
|
+
});
|
|
503
|
+
return Number(res) === 1;
|
|
520
504
|
}
|
|
521
505
|
};
|
|
522
506
|
}
|
|
@@ -911,24 +895,42 @@ CREATE TABLE IF NOT EXISTS qjw_rate_limits (
|
|
|
911
895
|
// -------------------------------------------------------------------------
|
|
912
896
|
async checkAndIncrementRateLimit(queue, max, windowMs, now) {
|
|
913
897
|
const nowMs = new Date(now).getTime();
|
|
914
|
-
const
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
[queue
|
|
898
|
+
const client = await this.pool.connect();
|
|
899
|
+
try {
|
|
900
|
+
await client.query("BEGIN");
|
|
901
|
+
await client.query(
|
|
902
|
+
`INSERT INTO qjw_rate_limits (queue, count, window_start) VALUES ($1, 0, 0)
|
|
903
|
+
ON CONFLICT (queue) DO NOTHING`,
|
|
904
|
+
[queue]
|
|
905
|
+
);
|
|
906
|
+
const res = await client.query(
|
|
907
|
+
"SELECT count, window_start FROM qjw_rate_limits WHERE queue = $1 FOR UPDATE",
|
|
908
|
+
[queue]
|
|
925
909
|
);
|
|
910
|
+
const row = res.rows[0];
|
|
911
|
+
const windowStart = Number(row?.window_start ?? 0);
|
|
912
|
+
const count = Number(row?.count ?? 0);
|
|
913
|
+
if (!row || nowMs - windowStart >= windowMs) {
|
|
914
|
+
await client.query(
|
|
915
|
+
"UPDATE qjw_rate_limits SET count = 1, window_start = $2 WHERE queue = $1",
|
|
916
|
+
[queue, nowMs]
|
|
917
|
+
);
|
|
918
|
+
await client.query("COMMIT");
|
|
919
|
+
return true;
|
|
920
|
+
}
|
|
921
|
+
if (count >= max) {
|
|
922
|
+
await client.query("COMMIT");
|
|
923
|
+
return false;
|
|
924
|
+
}
|
|
925
|
+
await client.query("UPDATE qjw_rate_limits SET count = count + 1 WHERE queue = $1", [queue]);
|
|
926
|
+
await client.query("COMMIT");
|
|
926
927
|
return true;
|
|
928
|
+
} catch (err) {
|
|
929
|
+
await client.query("ROLLBACK");
|
|
930
|
+
throw err;
|
|
931
|
+
} finally {
|
|
932
|
+
client.release();
|
|
927
933
|
}
|
|
928
|
-
const current = res.rows[0].count;
|
|
929
|
-
if (current >= max) return false;
|
|
930
|
-
await this.pool.query("UPDATE qjw_rate_limits SET count = count + 1 WHERE queue = $1", [queue]);
|
|
931
|
-
return true;
|
|
932
934
|
}
|
|
933
935
|
};
|
|
934
936
|
}
|
|
@@ -1342,22 +1344,42 @@ CREATE TABLE IF NOT EXISTS qjw_rate_limits (
|
|
|
1342
1344
|
// -------------------------------------------------------------------------
|
|
1343
1345
|
async checkAndIncrementRateLimit(queue, max, windowMs, now) {
|
|
1344
1346
|
const nowMs = new Date(now).getTime();
|
|
1345
|
-
const
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1347
|
+
const conn = await this.pool.getConnection();
|
|
1348
|
+
try {
|
|
1349
|
+
await conn.beginTransaction();
|
|
1350
|
+
await conn.query(
|
|
1351
|
+
`INSERT INTO qjw_rate_limits (queue, count, window_start) VALUES (?, 0, 0)
|
|
1352
|
+
ON DUPLICATE KEY UPDATE queue = queue`,
|
|
1353
|
+
[queue]
|
|
1354
|
+
);
|
|
1355
|
+
const [rows] = await conn.query(
|
|
1356
|
+
"SELECT count, window_start FROM qjw_rate_limits WHERE queue = ? FOR UPDATE",
|
|
1357
|
+
[queue]
|
|
1355
1358
|
);
|
|
1359
|
+
const existing = rows[0];
|
|
1360
|
+
const windowStart = Number(existing?.["window_start"] ?? 0);
|
|
1361
|
+
const count = Number(existing?.["count"] ?? 0);
|
|
1362
|
+
if (!existing || nowMs - windowStart >= windowMs) {
|
|
1363
|
+
await conn.query("UPDATE qjw_rate_limits SET count = 1, window_start = ? WHERE queue = ?", [
|
|
1364
|
+
nowMs,
|
|
1365
|
+
queue
|
|
1366
|
+
]);
|
|
1367
|
+
await conn.commit();
|
|
1368
|
+
return true;
|
|
1369
|
+
}
|
|
1370
|
+
if (count >= max) {
|
|
1371
|
+
await conn.commit();
|
|
1372
|
+
return false;
|
|
1373
|
+
}
|
|
1374
|
+
await conn.query("UPDATE qjw_rate_limits SET count = count + 1 WHERE queue = ?", [queue]);
|
|
1375
|
+
await conn.commit();
|
|
1356
1376
|
return true;
|
|
1377
|
+
} catch (err) {
|
|
1378
|
+
await conn.rollback();
|
|
1379
|
+
throw err;
|
|
1380
|
+
} finally {
|
|
1381
|
+
conn.release();
|
|
1357
1382
|
}
|
|
1358
|
-
if (Number(existing["count"]) >= max) return false;
|
|
1359
|
-
await this.pool.query("UPDATE qjw_rate_limits SET count = count + 1 WHERE queue = ?", [queue]);
|
|
1360
|
-
return true;
|
|
1361
1383
|
}
|
|
1362
1384
|
};
|
|
1363
1385
|
}
|
|
@@ -1895,16 +1917,25 @@ var Queue = class {
|
|
|
1895
1917
|
emitter;
|
|
1896
1918
|
resolvedConfig;
|
|
1897
1919
|
clientDefaults;
|
|
1920
|
+
isInitialisedCheck;
|
|
1898
1921
|
/** Registered processors keyed by job type. */
|
|
1899
1922
|
processors = /* @__PURE__ */ new Map();
|
|
1900
1923
|
/** Active worker instances created by this queue. */
|
|
1901
1924
|
workers = [];
|
|
1902
|
-
constructor(name, storage, emitter, options, defaults) {
|
|
1925
|
+
constructor(name, storage, emitter, options, defaults, isInitialisedCheck) {
|
|
1903
1926
|
this.name = name;
|
|
1904
1927
|
this.storage = storage;
|
|
1905
1928
|
this.emitter = emitter;
|
|
1906
1929
|
this.clientDefaults = defaults;
|
|
1907
1930
|
this.resolvedConfig = mergeQueueConfig(options, defaults);
|
|
1931
|
+
this.isInitialisedCheck = isInitialisedCheck;
|
|
1932
|
+
}
|
|
1933
|
+
assertInitialised() {
|
|
1934
|
+
if (this.isInitialisedCheck && !this.isInitialisedCheck()) {
|
|
1935
|
+
throw new Error(
|
|
1936
|
+
`Cannot execute queue operations on queue "${this.name}" because the QueueClient is not initialised. Call "await client.init()" first.`
|
|
1937
|
+
);
|
|
1938
|
+
}
|
|
1908
1939
|
}
|
|
1909
1940
|
// -------------------------------------------------------------------------
|
|
1910
1941
|
// Enqueue
|
|
@@ -1917,6 +1948,7 @@ var Queue = class {
|
|
|
1917
1948
|
* @param options - Per-job overrides (attempts, delay, priority, etc.).
|
|
1918
1949
|
*/
|
|
1919
1950
|
async enqueue(type, payload, options) {
|
|
1951
|
+
this.assertInitialised();
|
|
1920
1952
|
const cfg = this.resolvedConfig;
|
|
1921
1953
|
const now = /* @__PURE__ */ new Date();
|
|
1922
1954
|
let runAt;
|
|
@@ -1965,6 +1997,7 @@ var Queue = class {
|
|
|
1965
1997
|
* Worker-level `concurrency` overrides queue-level concurrency.
|
|
1966
1998
|
*/
|
|
1967
1999
|
createWorker(options) {
|
|
2000
|
+
this.assertInitialised();
|
|
1968
2001
|
const worker = new Worker(
|
|
1969
2002
|
this.name,
|
|
1970
2003
|
this.storage,
|
|
@@ -1983,12 +2016,14 @@ var Queue = class {
|
|
|
1983
2016
|
// -------------------------------------------------------------------------
|
|
1984
2017
|
/** Fetch a single job by its ID. Returns null if not found or wrong queue. */
|
|
1985
2018
|
async getJob(jobId) {
|
|
2019
|
+
this.assertInitialised();
|
|
1986
2020
|
const raw = await this.storage.getJob(jobId);
|
|
1987
2021
|
if (!raw || raw.queue !== this.name) return null;
|
|
1988
2022
|
return new Job(raw);
|
|
1989
2023
|
}
|
|
1990
2024
|
/** Fetch jobs from this queue, optionally filtered by status. */
|
|
1991
2025
|
async getJobs(status, limit = 100, offset = 0) {
|
|
2026
|
+
this.assertInitialised();
|
|
1992
2027
|
const raws = await this.storage.getJobs({
|
|
1993
2028
|
queue: this.name,
|
|
1994
2029
|
...status !== void 0 && { status },
|
|
@@ -1999,6 +2034,7 @@ var Queue = class {
|
|
|
1999
2034
|
}
|
|
2000
2035
|
/** Get job counts by status for this queue. */
|
|
2001
2036
|
async getJobCounts() {
|
|
2037
|
+
this.assertInitialised();
|
|
2002
2038
|
return this.storage.getJobCounts(this.name);
|
|
2003
2039
|
}
|
|
2004
2040
|
// -------------------------------------------------------------------------
|
|
@@ -2355,6 +2391,9 @@ var QueueClient = class _QueueClient {
|
|
|
2355
2391
|
this.dialect = options.dialect ?? "memory";
|
|
2356
2392
|
this.connectionString = options.connectionString;
|
|
2357
2393
|
this._storage = this.buildMemoryOrEagerAdapter();
|
|
2394
|
+
if (this.dialect === "memory") {
|
|
2395
|
+
this.initialised = true;
|
|
2396
|
+
}
|
|
2358
2397
|
this.emitter = new QueueEventEmitter();
|
|
2359
2398
|
}
|
|
2360
2399
|
// -------------------------------------------------------------------------
|
|
@@ -2433,10 +2472,19 @@ var QueueClient = class _QueueClient {
|
|
|
2433
2472
|
if (this.queues.has(name)) {
|
|
2434
2473
|
throw new Error(`A queue named "${name}" already exists on this client.`);
|
|
2435
2474
|
}
|
|
2436
|
-
if (!this.initialised
|
|
2437
|
-
|
|
2475
|
+
if (!this.initialised) {
|
|
2476
|
+
throw new Error(
|
|
2477
|
+
`QueueClient is not initialised. Call "await client.init()" before creating queues for dialect "${this.dialect}".`
|
|
2478
|
+
);
|
|
2438
2479
|
}
|
|
2439
|
-
const queue = new Queue(
|
|
2480
|
+
const queue = new Queue(
|
|
2481
|
+
name,
|
|
2482
|
+
this._storage,
|
|
2483
|
+
this.emitter,
|
|
2484
|
+
options,
|
|
2485
|
+
this.defaults,
|
|
2486
|
+
() => this.initialised
|
|
2487
|
+
);
|
|
2440
2488
|
this.queues.set(name, queue);
|
|
2441
2489
|
return queue;
|
|
2442
2490
|
}
|
|
@@ -2505,6 +2553,7 @@ var QueueClient = class _QueueClient {
|
|
|
2505
2553
|
const client = new _QueueClient(options);
|
|
2506
2554
|
client._storage = adapter;
|
|
2507
2555
|
client._customAdapter = true;
|
|
2556
|
+
client.initialised = false;
|
|
2508
2557
|
return client;
|
|
2509
2558
|
}
|
|
2510
2559
|
};
|