Review funkt

This commit is contained in:
2026-08-09 21:56:00 +00:00
parent f522ae9953
commit fe9dfa4519
11366 changed files with 2335077 additions and 97 deletions

331
node_modules/bullmq/dist/esm/classes/ioredis-client.js generated vendored Normal file
View File

@@ -0,0 +1,331 @@
/**
* Per-raw-client cache so repeated calls to `createIORedisClient` with the
* same underlying ioredis instance return the same proxy. This preserves
* event-listener identity for the BullMQ-facing client.
*/
const proxyCache = new WeakMap();
/**
* Wraps an ioredis `Redis` / `Cluster` instance with a `Proxy` so it conforms
* to {@link IRedisClient}.
*
* For backwards compatibility BullMQ continues to accept a raw `IORedis`
* instance through tehe `connection` option, even though internally it relies
* on the `IRedisClient` adapter interface. The returned proxy:
*
* - exposes `runCommand` (Lua script dispatch by name)
* - exposes structured-options variants of `hset`, `set`, `zrange`,
* `zrevrange`, `xadd`, `xread`, `xtrim`, `scan` (backward-compatible:
* they still accept native ioredis varargs if called that way)
* - returns augmented {@link IRedisTransaction}s from `pipeline()` / `multi()`
* - wraps the result of `duplicate()` in a new proxy
*
* The underlying ioredis instance is **not** mutated. Properties and methods
* not in the override table are forwarded to the raw client via the proxy
* traps, with `this === target` so EventEmitter / Commander internals work
* normally.
*/
export function createIORedisClient(client) {
// If the caller already passed a proxy produced by this function, return
// it as-is. Wrapping a proxy in a second proxy would defeat the WeakMap
// cache (the inner raw client is no longer reachable from the outer
// argument) and break listener-identity / equality checks for callers
// that hold on to the original wrapper.
if (client.__bullmq_iredis === true) {
return client;
}
const cached = proxyCache.get(client);
if (cached) {
return cached;
}
const isCluster = client.isCluster === true;
// Cache bound prototype methods so the returned function identity is
// stable across accesses (important for `once`/`removeListener` patterns).
const boundCache = new Map();
// Override table — properties returned by the proxy without touching the
// underlying ioredis instance. The arrow functions close over `client`
// directly so ioredis internals always see the raw instance as `this`.
const overrides = Object.create(null);
overrides.__bullmq_iredis = true;
overrides.isCluster = isCluster;
// Lua script engine.
overrides.runCommand = (name, args) => {
return client[name](args);
};
// Pipeline / Multi — wrap the ChainableCommander with structured overrides.
overrides.pipeline = (...args) => {
return augmentTransaction(client.pipeline(...args));
};
overrides.multi = (...args) => {
return augmentTransaction(client.multi(...args));
};
// duplicate — wrap the new raw client with a fresh proxy.
// ioredis Cluster.duplicate(startupNodes?, options?) expects connection
// options under `redisOptions`, while Redis.duplicate(options?) takes them
// at the top level. Normalise so callers can always pass `{ connectionName }`.
if (typeof client.duplicate === 'function') {
overrides.duplicate = (opts) => {
var _a;
if (isCluster) {
const existingRedisOpts = ((_a = client.options) === null || _a === void 0 ? void 0 : _a.redisOptions) || {};
const mergedRedisOpts = opts
? Object.assign(Object.assign({}, existingRedisOpts), opts) : existingRedisOpts;
return createIORedisClient(client.duplicate(undefined, {
redisOptions: mergedRedisOpts,
}));
}
return createIORedisClient(client.duplicate(opts));
};
}
// --- Structured → ioredis varargs translations ---
// Each override accepts both the IRedisClient structured-options form and
// the native ioredis varargs form, dispatching by argument shape.
// hset: structured { f1: v1 } → ioredis hset(key, f1, v1, …)
overrides.hset = (key, dataOrField, ...rest) => {
if (typeof dataOrField === 'string') {
return client.hset(key, dataOrField, ...rest);
}
const args = [key];
for (const [f, v] of Object.entries(dataOrField)) {
args.push(f, v);
}
return client.hset(...args);
};
// set: structured { PX?: n } → ioredis set(key, value, 'PX', n)
overrides.set = (key, value, optionsOrModifier, ...rest) => {
if (typeof optionsOrModifier === 'string' || optionsOrModifier == null) {
return client.set(key, value, ...(optionsOrModifier != null ? [optionsOrModifier, ...rest] : []));
}
const args = [key, value];
if (optionsOrModifier.PX != null) {
args.push('PX', optionsOrModifier.PX);
}
else if (optionsOrModifier.EX != null) {
args.push('EX', optionsOrModifier.EX);
}
return client.set(...args);
};
// zrange: structured { WITHSCORES? } → ioredis zrange(key, start, end, 'WITHSCORES')
overrides.zrange = (key, start, end, optionsOrStr, ...rest) => {
if (typeof optionsOrStr === 'string') {
return client.zrange(key, start, end, optionsOrStr, ...rest);
}
if (optionsOrStr === null || optionsOrStr === void 0 ? void 0 : optionsOrStr.WITHSCORES) {
return client.zrange(key, start, end, 'WITHSCORES');
}
return client.zrange(key, start, end);
};
// zrevrange: structured { WITHSCORES? } → ioredis zrevrange(key, start, end, 'WITHSCORES')
overrides.zrevrange = (key, start, end, optionsOrStr, ...rest) => {
if (typeof optionsOrStr === 'string') {
return client.zrevrange(key, start, end, optionsOrStr, ...rest);
}
if (optionsOrStr === null || optionsOrStr === void 0 ? void 0 : optionsOrStr.WITHSCORES) {
return client.zrevrange(key, start, end, 'WITHSCORES');
}
return client.zrevrange(key, start, end);
};
// xadd: structured (key, id, { field: value }, { MAXLEN? }) → ioredis varargs
overrides.xadd = (key, idOrModifier, fieldsOrArg, ...rest) => {
if (typeof fieldsOrArg === 'string') {
return client.xadd(key, idOrModifier, fieldsOrArg, ...rest);
}
const options = rest[0];
const args = [key];
if ((options === null || options === void 0 ? void 0 : options.MAXLEN) != null) {
args.push('MAXLEN');
if (options.approximate !== false) {
args.push('~');
}
args.push(options.MAXLEN);
}
args.push(idOrModifier);
for (const [f, v] of Object.entries(fieldsOrArg)) {
args.push(f, v);
}
return client.xadd(...args);
};
// xread: structured ([{ key, id }], { BLOCK?, COUNT? }) → ioredis varargs
overrides.xread = (streamsOrModifier, ...rest) => {
if (typeof streamsOrModifier === 'string') {
return client.xread(streamsOrModifier, ...rest);
}
const options = rest[0];
const args = [];
if ((options === null || options === void 0 ? void 0 : options.BLOCK) != null) {
args.push('BLOCK', options.BLOCK);
}
if ((options === null || options === void 0 ? void 0 : options.COUNT) != null) {
args.push('COUNT', options.COUNT);
}
args.push('STREAMS');
for (const s of streamsOrModifier) {
args.push(s.key);
}
for (const s of streamsOrModifier) {
args.push(s.id);
}
return client.xread(...args);
};
// xtrim: structured (key, 'MAXLEN', threshold, { approximate? })
overrides.xtrim = (key, strategy, thresholdOrApprox, ...rest) => {
if (typeof thresholdOrApprox === 'string' || rest.length === 0) {
return client.xtrim(key, strategy, thresholdOrApprox, ...rest);
}
const options = rest[0];
const args = [key, strategy];
if ((options === null || options === void 0 ? void 0 : options.approximate) !== false) {
args.push('~');
}
args.push(thresholdOrApprox);
return client.xtrim(...args);
};
// bzpopmin is not overridden — ioredis already returns
// `[key, member, score]`, which matches IRedisClient.
// clientSetName / clientList helpers that forward to CLIENT subcommands.
overrides.clientSetName = (name) => client.client('SETNAME', name);
overrides.clientList = () => client.client('LIST');
// scan(cursor, { MATCH?, COUNT? }) — accepts either structured options or
// ioredis varargs (used internally by `scanStream`).
overrides.scan = (cursor, ...rest) => {
if (rest.length === 0 ||
typeof rest[0] === 'string' ||
typeof rest[0] === 'function') {
return client.scan(cursor, ...rest);
}
const options = rest[0];
const args = [cursor];
if ((options === null || options === void 0 ? void 0 : options.MATCH) != null) {
args.push('MATCH', options.MATCH);
}
if ((options === null || options === void 0 ? void 0 : options.COUNT) != null) {
args.push('COUNT', options.COUNT);
}
return client.scan(...args);
};
const proxy = new Proxy(client, {
get(target, prop) {
if (prop in overrides) {
return overrides[prop];
}
// Read against the raw target so getters on the prototype (e.g.
// ioredis' EventEmitter internals) see `this === target` rather than
// the proxy. This avoids infinite recursion through the proxy traps.
const value = Reflect.get(target, prop, target);
if (typeof value !== 'function') {
return value;
}
// Own properties (including ioredis commands installed via
// `defineCommand` and test-time spies set via `obj.method = spy`)
// are bound fresh on each access so reassignment is honoured.
if (Object.prototype.hasOwnProperty.call(target, prop)) {
return value.bind(target);
}
// Prototype methods (EventEmitter, Commander, ...) are cached so
// identity is stable across accesses.
const cachedBound = boundCache.get(prop);
if (cachedBound !== undefined) {
return cachedBound;
}
const bound = value.bind(target);
boundCache.set(prop, bound);
return bound;
},
set(target, prop, value) {
// Two assignment paths:
// - Properties present in the override table are reassigned in the
// table itself, so subsequent `get` traps return the new value
// (used by sinon-style spies that stub `runCommand`, `pipeline`,
// etc. on the proxy).
// - All other properties are written through to the raw ioredis
// instance via `Reflect.set`, and any stale bound-method entry is
// invalidated so the next access rebinds the new function.
if (prop in overrides) {
overrides[prop] = value;
return true;
}
boundCache.delete(prop);
return Reflect.set(target, prop, value);
},
deleteProperty(target, prop) {
if (prop in overrides) {
return false;
}
boundCache.delete(prop);
return Reflect.deleteProperty(target, prop);
},
has(target, prop) {
return prop in overrides || Reflect.has(target, prop);
},
});
proxyCache.set(client, proxy);
return proxy;
}
/**
* Adds `runCommand` and structured overrides to an ioredis ChainableCommander
* so it satisfies {@link IRedisTransaction}.
*/
function augmentTransaction(commander) {
const transaction = commander;
transaction.runCommand = function (name, args) {
transaction[name](args);
return transaction;
};
// hset(key, { f1: v1 }) → ioredis pipeline.hset(key, f1, v1, …)
const origHset = transaction.hset.bind(transaction);
transaction.hset = function (key, data) {
const args = [key];
for (const [f, v] of Object.entries(data)) {
args.push(f, v);
}
origHset(...args);
return transaction;
};
// hscan(key, cursor, { COUNT? }) → ioredis hscan(key, cursor, 'COUNT', n)
const origHscan = transaction.hscan.bind(transaction);
transaction.hscan = function (key, cursor, options) {
if ((options === null || options === void 0 ? void 0 : options.COUNT) != null) {
origHscan(key, cursor, 'COUNT', options.COUNT);
}
else {
origHscan(key, cursor);
}
return transaction;
};
// sscan(key, cursor, { COUNT? })
const origSscan = transaction.sscan.bind(transaction);
transaction.sscan = function (key, cursor, options) {
if ((options === null || options === void 0 ? void 0 : options.COUNT) != null) {
origSscan(key, cursor, 'COUNT', options.COUNT);
}
else {
origSscan(key, cursor);
}
return transaction;
};
return transaction;
}
/**
* Check if an object already implements {@link IRedisClient}.
*/
export function isIRedisClient(obj) {
if (!obj || typeof obj !== 'object') {
return false;
}
// Fast path for ioredis instances already wrapped by `createIORedisClient`.
if (obj.__bullmq_iredis === true) {
return true;
}
// Fallback structural check for wrapper-based adapters
// (node-redis, Bun, or custom IRedisClient implementations).
return (typeof obj.runCommand === 'function' &&
typeof obj.defineCommand === 'function' &&
typeof obj.pipeline === 'function' &&
typeof obj.multi === 'function' &&
typeof obj.duplicate === 'function' &&
typeof obj.scanStream === 'function' &&
typeof obj.connect === 'function' &&
typeof obj.disconnect === 'function' &&
typeof obj.on === 'function' &&
typeof obj.status === 'string' &&
typeof obj.isCluster === 'boolean');
}