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