bullmq 5.77.0 → 5.77.2
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/ioredis-client.js +150 -110
- package/dist/cjs/tsconfig-cjs.tsbuildinfo +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/classes/ioredis-client.d.ts +15 -17
- package/dist/esm/classes/ioredis-client.js +150 -110
- package/dist/esm/classes/node-redis-client.d.ts +1 -1
- 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
|
@@ -3,90 +3,101 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createIORedisClient = createIORedisClient;
|
|
4
4
|
exports.isIRedisClient = isIRedisClient;
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Per-raw-client cache so repeated calls to `createIORedisClient` with the
|
|
7
|
+
* same underlying ioredis instance return the same proxy. This preserves
|
|
8
|
+
* event-listener identity for the BullMQ-facing client.
|
|
9
|
+
*/
|
|
10
|
+
const proxyCache = new WeakMap();
|
|
11
|
+
/**
|
|
12
|
+
* Wraps an ioredis `Redis` / `Cluster` instance with a `Proxy` so it conforms
|
|
13
|
+
* to {@link IRedisClient}.
|
|
8
14
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
15
|
+
* For backwards compatibility BullMQ continues to accept a raw `IORedis`
|
|
16
|
+
* instance through tehe `connection` option, even though internally it relies
|
|
17
|
+
* on the `IRedisClient` adapter interface. The returned proxy:
|
|
12
18
|
*
|
|
13
|
-
* - `runCommand`
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
16
|
-
*
|
|
19
|
+
* - exposes `runCommand` (Lua script dispatch by name)
|
|
20
|
+
* - exposes structured-options variants of `hset`, `set`, `zrange`,
|
|
21
|
+
* `zrevrange`, `xadd`, `xread`, `xtrim`, `scan` (backward-compatible:
|
|
22
|
+
* they still accept native ioredis varargs if called that way)
|
|
23
|
+
* - returns augmented {@link IRedisTransaction}s from `pipeline()` / `multi()`
|
|
24
|
+
* - wraps the result of `duplicate()` in a new proxy
|
|
17
25
|
*
|
|
18
|
-
*
|
|
19
|
-
* in
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* they are called with the ioredis native varargs style or the IRedisClient
|
|
23
|
-
* structured-options style and dispatch accordingly. External code that
|
|
24
|
-
* calls e.g. `client.hset(key, 'f1', 'v1')` will continue to work after
|
|
25
|
-
* augmentation.
|
|
26
|
+
* The underlying ioredis instance is **not** mutated. Properties and methods
|
|
27
|
+
* not in the override table are forwarded to the raw client via the proxy
|
|
28
|
+
* traps, with `this === target` so EventEmitter / Commander internals work
|
|
29
|
+
* normally.
|
|
26
30
|
*/
|
|
27
31
|
function createIORedisClient(client) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
// If the caller already passed a proxy produced by this function, return
|
|
33
|
+
// it as-is. Wrapping a proxy in a second proxy would defeat the WeakMap
|
|
34
|
+
// cache (the inner raw client is no longer reachable from the outer
|
|
35
|
+
// argument) and break listener-identity / equality checks for callers
|
|
36
|
+
// that hold on to the original wrapper.
|
|
37
|
+
if (client.__bullmq_iredis === true) {
|
|
38
|
+
return client;
|
|
31
39
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (typeof adapter.isCluster !== 'boolean') {
|
|
36
|
-
adapter.isCluster = false;
|
|
40
|
+
const cached = proxyCache.get(client);
|
|
41
|
+
if (cached) {
|
|
42
|
+
return cached;
|
|
37
43
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
const isCluster = client.isCluster === true;
|
|
45
|
+
// Cache bound prototype methods so the returned function identity is
|
|
46
|
+
// stable across accesses (important for `once`/`removeListener` patterns).
|
|
47
|
+
const boundCache = new Map();
|
|
48
|
+
// Override table — properties returned by the proxy without touching the
|
|
49
|
+
// underlying ioredis instance. The arrow functions close over `client`
|
|
50
|
+
// directly so ioredis internals always see the raw instance as `this`.
|
|
51
|
+
const overrides = Object.create(null);
|
|
52
|
+
overrides.__bullmq_iredis = true;
|
|
53
|
+
overrides.isCluster = isCluster;
|
|
54
|
+
// Lua script engine.
|
|
55
|
+
overrides.runCommand = (name, args) => {
|
|
56
|
+
return client[name](args);
|
|
41
57
|
};
|
|
42
|
-
// Pipeline / Multi —
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return augmentTransaction(origPipeline(...args));
|
|
58
|
+
// Pipeline / Multi — wrap the ChainableCommander with structured overrides.
|
|
59
|
+
overrides.pipeline = (...args) => {
|
|
60
|
+
return augmentTransaction(client.pipeline(...args));
|
|
46
61
|
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return augmentTransaction(origMulti(...args));
|
|
62
|
+
overrides.multi = (...args) => {
|
|
63
|
+
return augmentTransaction(client.multi(...args));
|
|
50
64
|
};
|
|
51
|
-
//
|
|
65
|
+
// duplicate — wrap the new raw client with a fresh proxy.
|
|
52
66
|
// ioredis Cluster.duplicate(startupNodes?, options?) expects connection
|
|
53
67
|
// options under `redisOptions`, while Redis.duplicate(options?) takes them
|
|
54
|
-
// at the top level.
|
|
55
|
-
// a simple `{ connectionName }` object regardless of the client type.
|
|
68
|
+
// at the top level. Normalise so callers can always pass `{ connectionName }`.
|
|
56
69
|
if (typeof client.duplicate === 'function') {
|
|
57
|
-
|
|
58
|
-
const clientIsCluster = !!client.isCluster;
|
|
59
|
-
adapter.duplicate = function (opts) {
|
|
70
|
+
overrides.duplicate = (opts) => {
|
|
60
71
|
var _a;
|
|
61
|
-
if (
|
|
72
|
+
if (isCluster) {
|
|
62
73
|
const existingRedisOpts = ((_a = client.options) === null || _a === void 0 ? void 0 : _a.redisOptions) || {};
|
|
63
74
|
const mergedRedisOpts = opts
|
|
64
75
|
? Object.assign(Object.assign({}, existingRedisOpts), opts) : existingRedisOpts;
|
|
65
|
-
return createIORedisClient(
|
|
76
|
+
return createIORedisClient(client.duplicate(undefined, {
|
|
77
|
+
redisOptions: mergedRedisOpts,
|
|
78
|
+
}));
|
|
66
79
|
}
|
|
67
|
-
return createIORedisClient(
|
|
80
|
+
return createIORedisClient(client.duplicate(opts));
|
|
68
81
|
};
|
|
69
82
|
}
|
|
70
83
|
// --- Structured → ioredis varargs translations ---
|
|
84
|
+
// Each override accepts both the IRedisClient structured-options form and
|
|
85
|
+
// the native ioredis varargs form, dispatching by argument shape.
|
|
71
86
|
// hset: structured { f1: v1 } → ioredis hset(key, f1, v1, …)
|
|
72
|
-
|
|
73
|
-
const origHset = client.hset.bind(client);
|
|
74
|
-
adapter.hset = function (key, dataOrField, ...rest) {
|
|
87
|
+
overrides.hset = (key, dataOrField, ...rest) => {
|
|
75
88
|
if (typeof dataOrField === 'string') {
|
|
76
|
-
return
|
|
89
|
+
return client.hset(key, dataOrField, ...rest);
|
|
77
90
|
}
|
|
78
91
|
const args = [key];
|
|
79
92
|
for (const [f, v] of Object.entries(dataOrField)) {
|
|
80
93
|
args.push(f, v);
|
|
81
94
|
}
|
|
82
|
-
return
|
|
95
|
+
return client.hset(...args);
|
|
83
96
|
};
|
|
84
97
|
// set: structured { PX?: n } → ioredis set(key, value, 'PX', n)
|
|
85
|
-
|
|
86
|
-
const origSet = client.set.bind(client);
|
|
87
|
-
adapter.set = function (key, value, optionsOrModifier, ...rest) {
|
|
98
|
+
overrides.set = (key, value, optionsOrModifier, ...rest) => {
|
|
88
99
|
if (typeof optionsOrModifier === 'string' || optionsOrModifier == null) {
|
|
89
|
-
return
|
|
100
|
+
return client.set(key, value, ...(optionsOrModifier != null ? [optionsOrModifier, ...rest] : []));
|
|
90
101
|
}
|
|
91
102
|
const args = [key, value];
|
|
92
103
|
if (optionsOrModifier.PX != null) {
|
|
@@ -95,39 +106,32 @@ function createIORedisClient(client) {
|
|
|
95
106
|
else if (optionsOrModifier.EX != null) {
|
|
96
107
|
args.push('EX', optionsOrModifier.EX);
|
|
97
108
|
}
|
|
98
|
-
return
|
|
109
|
+
return client.set(...args);
|
|
99
110
|
};
|
|
100
111
|
// zrange: structured { WITHSCORES? } → ioredis zrange(key, start, end, 'WITHSCORES')
|
|
101
|
-
|
|
102
|
-
const origZrange = client.zrange.bind(client);
|
|
103
|
-
adapter.zrange = function (key, start, end, optionsOrStr, ...rest) {
|
|
112
|
+
overrides.zrange = (key, start, end, optionsOrStr, ...rest) => {
|
|
104
113
|
if (typeof optionsOrStr === 'string') {
|
|
105
|
-
return
|
|
114
|
+
return client.zrange(key, start, end, optionsOrStr, ...rest);
|
|
106
115
|
}
|
|
107
116
|
if (optionsOrStr === null || optionsOrStr === void 0 ? void 0 : optionsOrStr.WITHSCORES) {
|
|
108
|
-
return
|
|
117
|
+
return client.zrange(key, start, end, 'WITHSCORES');
|
|
109
118
|
}
|
|
110
|
-
return
|
|
119
|
+
return client.zrange(key, start, end);
|
|
111
120
|
};
|
|
112
121
|
// zrevrange: structured { WITHSCORES? } → ioredis zrevrange(key, start, end, 'WITHSCORES')
|
|
113
|
-
|
|
114
|
-
const origZrevrange = client.zrevrange.bind(client);
|
|
115
|
-
adapter.zrevrange = function (key, start, end, optionsOrStr, ...rest) {
|
|
122
|
+
overrides.zrevrange = (key, start, end, optionsOrStr, ...rest) => {
|
|
116
123
|
if (typeof optionsOrStr === 'string') {
|
|
117
|
-
return
|
|
124
|
+
return client.zrevrange(key, start, end, optionsOrStr, ...rest);
|
|
118
125
|
}
|
|
119
126
|
if (optionsOrStr === null || optionsOrStr === void 0 ? void 0 : optionsOrStr.WITHSCORES) {
|
|
120
|
-
return
|
|
127
|
+
return client.zrevrange(key, start, end, 'WITHSCORES');
|
|
121
128
|
}
|
|
122
|
-
return
|
|
129
|
+
return client.zrevrange(key, start, end);
|
|
123
130
|
};
|
|
124
131
|
// xadd: structured (key, id, { field: value }, { MAXLEN? }) → ioredis varargs
|
|
125
|
-
|
|
126
|
-
// (e.g. xadd(key, id, field1, val1, field2, val2)).
|
|
127
|
-
const origXadd = client.xadd.bind(client);
|
|
128
|
-
adapter.xadd = function (key, idOrModifier, fieldsOrArg, ...rest) {
|
|
132
|
+
overrides.xadd = (key, idOrModifier, fieldsOrArg, ...rest) => {
|
|
129
133
|
if (typeof fieldsOrArg === 'string') {
|
|
130
|
-
return
|
|
134
|
+
return client.xadd(key, idOrModifier, fieldsOrArg, ...rest);
|
|
131
135
|
}
|
|
132
136
|
const options = rest[0];
|
|
133
137
|
const args = [key];
|
|
@@ -142,15 +146,12 @@ function createIORedisClient(client) {
|
|
|
142
146
|
for (const [f, v] of Object.entries(fieldsOrArg)) {
|
|
143
147
|
args.push(f, v);
|
|
144
148
|
}
|
|
145
|
-
return
|
|
149
|
+
return client.xadd(...args);
|
|
146
150
|
};
|
|
147
151
|
// xread: structured ([{ key, id }], { BLOCK?, COUNT? }) → ioredis varargs
|
|
148
|
-
|
|
149
|
-
// (e.g. xread('BLOCK', 5000, 'STREAMS', key, id)).
|
|
150
|
-
const origXread = client.xread.bind(client);
|
|
151
|
-
adapter.xread = function (streamsOrModifier, ...rest) {
|
|
152
|
+
overrides.xread = (streamsOrModifier, ...rest) => {
|
|
152
153
|
if (typeof streamsOrModifier === 'string') {
|
|
153
|
-
return
|
|
154
|
+
return client.xread(streamsOrModifier, ...rest);
|
|
154
155
|
}
|
|
155
156
|
const options = rest[0];
|
|
156
157
|
const args = [];
|
|
@@ -167,15 +168,12 @@ function createIORedisClient(client) {
|
|
|
167
168
|
for (const s of streamsOrModifier) {
|
|
168
169
|
args.push(s.id);
|
|
169
170
|
}
|
|
170
|
-
return
|
|
171
|
+
return client.xread(...args);
|
|
171
172
|
};
|
|
172
173
|
// xtrim: structured (key, 'MAXLEN', threshold, { approximate? })
|
|
173
|
-
|
|
174
|
-
const origXtrim = client.xtrim.bind(client);
|
|
175
|
-
adapter.xtrim = function (key, strategy, thresholdOrApprox, ...rest) {
|
|
176
|
-
// Varargs passthrough: xtrim(key, 'MAXLEN', '~', 1000) or xtrim(key, 'MAXLEN', 1000)
|
|
174
|
+
overrides.xtrim = (key, strategy, thresholdOrApprox, ...rest) => {
|
|
177
175
|
if (typeof thresholdOrApprox === 'string' || rest.length === 0) {
|
|
178
|
-
return
|
|
176
|
+
return client.xtrim(key, strategy, thresholdOrApprox, ...rest);
|
|
179
177
|
}
|
|
180
178
|
const options = rest[0];
|
|
181
179
|
const args = [key, strategy];
|
|
@@ -183,35 +181,21 @@ function createIORedisClient(client) {
|
|
|
183
181
|
args.push('~');
|
|
184
182
|
}
|
|
185
183
|
args.push(thresholdOrApprox);
|
|
186
|
-
return
|
|
187
|
-
};
|
|
188
|
-
// bzpopmin
|
|
189
|
-
//
|
|
190
|
-
// We deliberately do NOT override ioredis' native `bzpopmin` here.
|
|
191
|
-
// ioredis returns `[key, member, score]` (a tuple) and that is also the
|
|
192
|
-
// shape required by IRedisClient. Overriding the method on a shared user
|
|
193
|
-
// instance would change the observable return shape for any non-BullMQ
|
|
194
|
-
// code that uses `bzpopmin` on the same client.
|
|
195
|
-
// clientSetName / clientList
|
|
196
|
-
adapter.clientSetName = function (name) {
|
|
197
|
-
return client.client('SETNAME', name);
|
|
184
|
+
return client.xtrim(...args);
|
|
198
185
|
};
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
// If called with varargs style (e.g. from scanStream internally),
|
|
208
|
-
// pass through unchanged.
|
|
186
|
+
// bzpopmin is not overridden — ioredis already returns
|
|
187
|
+
// `[key, member, score]`, which matches IRedisClient.
|
|
188
|
+
// clientSetName / clientList helpers that forward to CLIENT subcommands.
|
|
189
|
+
overrides.clientSetName = (name) => client.client('SETNAME', name);
|
|
190
|
+
overrides.clientList = () => client.client('LIST');
|
|
191
|
+
// scan(cursor, { MATCH?, COUNT? }) — accepts either structured options or
|
|
192
|
+
// ioredis varargs (used internally by `scanStream`).
|
|
193
|
+
overrides.scan = (cursor, ...rest) => {
|
|
209
194
|
if (rest.length === 0 ||
|
|
210
195
|
typeof rest[0] === 'string' ||
|
|
211
196
|
typeof rest[0] === 'function') {
|
|
212
|
-
return
|
|
197
|
+
return client.scan(cursor, ...rest);
|
|
213
198
|
}
|
|
214
|
-
// Structured options: { MATCH?, COUNT? }
|
|
215
199
|
const options = rest[0];
|
|
216
200
|
const args = [cursor];
|
|
217
201
|
if ((options === null || options === void 0 ? void 0 : options.MATCH) != null) {
|
|
@@ -220,9 +204,65 @@ function createIORedisClient(client) {
|
|
|
220
204
|
if ((options === null || options === void 0 ? void 0 : options.COUNT) != null) {
|
|
221
205
|
args.push('COUNT', options.COUNT);
|
|
222
206
|
}
|
|
223
|
-
return
|
|
207
|
+
return client.scan(...args);
|
|
224
208
|
};
|
|
225
|
-
|
|
209
|
+
const proxy = new Proxy(client, {
|
|
210
|
+
get(target, prop) {
|
|
211
|
+
if (prop in overrides) {
|
|
212
|
+
return overrides[prop];
|
|
213
|
+
}
|
|
214
|
+
// Read against the raw target so getters on the prototype (e.g.
|
|
215
|
+
// ioredis' EventEmitter internals) see `this === target` rather than
|
|
216
|
+
// the proxy. This avoids infinite recursion through the proxy traps.
|
|
217
|
+
const value = Reflect.get(target, prop, target);
|
|
218
|
+
if (typeof value !== 'function') {
|
|
219
|
+
return value;
|
|
220
|
+
}
|
|
221
|
+
// Own properties (including ioredis commands installed via
|
|
222
|
+
// `defineCommand` and test-time spies set via `obj.method = spy`)
|
|
223
|
+
// are bound fresh on each access so reassignment is honoured.
|
|
224
|
+
if (Object.prototype.hasOwnProperty.call(target, prop)) {
|
|
225
|
+
return value.bind(target);
|
|
226
|
+
}
|
|
227
|
+
// Prototype methods (EventEmitter, Commander, ...) are cached so
|
|
228
|
+
// identity is stable across accesses.
|
|
229
|
+
const cachedBound = boundCache.get(prop);
|
|
230
|
+
if (cachedBound !== undefined) {
|
|
231
|
+
return cachedBound;
|
|
232
|
+
}
|
|
233
|
+
const bound = value.bind(target);
|
|
234
|
+
boundCache.set(prop, bound);
|
|
235
|
+
return bound;
|
|
236
|
+
},
|
|
237
|
+
set(target, prop, value) {
|
|
238
|
+
// Two assignment paths:
|
|
239
|
+
// - Properties present in the override table are reassigned in the
|
|
240
|
+
// table itself, so subsequent `get` traps return the new value
|
|
241
|
+
// (used by sinon-style spies that stub `runCommand`, `pipeline`,
|
|
242
|
+
// etc. on the proxy).
|
|
243
|
+
// - All other properties are written through to the raw ioredis
|
|
244
|
+
// instance via `Reflect.set`, and any stale bound-method entry is
|
|
245
|
+
// invalidated so the next access rebinds the new function.
|
|
246
|
+
if (prop in overrides) {
|
|
247
|
+
overrides[prop] = value;
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
boundCache.delete(prop);
|
|
251
|
+
return Reflect.set(target, prop, value);
|
|
252
|
+
},
|
|
253
|
+
deleteProperty(target, prop) {
|
|
254
|
+
if (prop in overrides) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
boundCache.delete(prop);
|
|
258
|
+
return Reflect.deleteProperty(target, prop);
|
|
259
|
+
},
|
|
260
|
+
has(target, prop) {
|
|
261
|
+
return prop in overrides || Reflect.has(target, prop);
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
proxyCache.set(client, proxy);
|
|
265
|
+
return proxy;
|
|
226
266
|
}
|
|
227
267
|
/**
|
|
228
268
|
* Adds `runCommand` and structured overrides to an ioredis ChainableCommander
|
|
@@ -275,7 +315,7 @@ function isIRedisClient(obj) {
|
|
|
275
315
|
if (!obj || typeof obj !== 'object') {
|
|
276
316
|
return false;
|
|
277
317
|
}
|
|
278
|
-
// Fast path for
|
|
318
|
+
// Fast path for ioredis instances already wrapped by `createIORedisClient`.
|
|
279
319
|
if (obj.__bullmq_iredis === true) {
|
|
280
320
|
return true;
|
|
281
321
|
}
|