Review funkt
This commit is contained in:
976
node_modules/bullmq/dist/esm/classes/valkey-glide-client.js
generated
vendored
Normal file
976
node_modules/bullmq/dist/esm/classes/valkey-glide-client.js
generated
vendored
Normal file
@@ -0,0 +1,976 @@
|
||||
import { createHash } from 'crypto';
|
||||
import { EventEmitter } from 'events';
|
||||
import { Readable } from 'stream';
|
||||
import { ConnectionClosedError } from './errors/connection-closed-error';
|
||||
const GLIDE_STRING_DECODER = 1;
|
||||
function createCompatibilityBatch() {
|
||||
return {
|
||||
commands: [],
|
||||
customCommand(args) {
|
||||
this.commands.push(args);
|
||||
},
|
||||
};
|
||||
}
|
||||
function createGlideBatch(isAtomic) {
|
||||
try {
|
||||
const { Batch } = require('@valkey/valkey-glide');
|
||||
return typeof Batch === 'function' ? new Batch(isAtomic) : null;
|
||||
}
|
||||
catch (_a) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function isBuffer(value) {
|
||||
return Buffer.isBuffer(value);
|
||||
}
|
||||
function toGlideArg(value) {
|
||||
if (isBuffer(value)) {
|
||||
return value;
|
||||
}
|
||||
if (value === undefined || value === null) {
|
||||
return '';
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
function toStringValue(value) {
|
||||
if (isBuffer(value)) {
|
||||
return value.toString();
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
function isKeyValueArray(value) {
|
||||
return (Array.isArray(value) &&
|
||||
value.every(item => item && typeof item === 'object' && 'key' in item && 'value' in item));
|
||||
}
|
||||
function normalizeScriptArgs(args) {
|
||||
return args.length === 1 && Array.isArray(args[0]) ? args[0] : args;
|
||||
}
|
||||
function normalizeHashReply(reply) {
|
||||
if (!reply) {
|
||||
return {};
|
||||
}
|
||||
if (isKeyValueArray(reply)) {
|
||||
return reply.reduce((acc, item) => {
|
||||
acc[toStringValue(item.key)] = toStringValue(item.value);
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
if (reply instanceof Map) {
|
||||
const out = {};
|
||||
for (const [key, value] of reply.entries()) {
|
||||
out[toStringValue(key)] = toStringValue(value);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (Array.isArray(reply)) {
|
||||
const out = {};
|
||||
for (let i = 0; i < reply.length; i += 2) {
|
||||
out[toStringValue(reply[i])] = toStringValue(reply[i + 1]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (typeof reply === 'object') {
|
||||
const out = {};
|
||||
for (const [key, value] of Object.entries(reply)) {
|
||||
out[key] = toStringValue(value);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
function flattenFieldPairs(fields) {
|
||||
if (!fields) {
|
||||
return [];
|
||||
}
|
||||
if (Array.isArray(fields)) {
|
||||
if (isKeyValueArray(fields)) {
|
||||
return fields.flatMap(pair => [
|
||||
toStringValue(pair.key),
|
||||
toStringValue(pair.value),
|
||||
]);
|
||||
}
|
||||
if (fields.every(item => Array.isArray(item) && item.length === 2)) {
|
||||
return fields.flatMap(pair => {
|
||||
const [field, value] = pair;
|
||||
return [toStringValue(field), toStringValue(value)];
|
||||
});
|
||||
}
|
||||
return fields.map(item => toStringValue(item));
|
||||
}
|
||||
if (typeof fields === 'object') {
|
||||
return Object.entries(fields).flatMap(([field, value]) => [field, toStringValue(value)]);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
function normalizeXReadReply(reply) {
|
||||
if (!reply) {
|
||||
return null;
|
||||
}
|
||||
if (Array.isArray(reply) &&
|
||||
reply.every(stream => Array.isArray(stream) &&
|
||||
stream.length === 2 &&
|
||||
typeof stream[0] !== 'undefined' &&
|
||||
Array.isArray(stream[1]))) {
|
||||
return reply;
|
||||
}
|
||||
if (!isKeyValueArray(reply)) {
|
||||
return reply;
|
||||
}
|
||||
return reply.map(streamEntry => {
|
||||
const streamName = toStringValue(streamEntry.key);
|
||||
const messages = isKeyValueArray(streamEntry.value)
|
||||
? streamEntry.value.map(message => [
|
||||
toStringValue(message.key),
|
||||
flattenFieldPairs(message.value),
|
||||
])
|
||||
: [];
|
||||
return [streamName, messages];
|
||||
});
|
||||
}
|
||||
function normalizeScanReply(reply) {
|
||||
if (Array.isArray(reply) && reply.length >= 2) {
|
||||
const [cursor, keys] = reply;
|
||||
const normalizedKeys = Array.isArray(keys)
|
||||
? keys.map(key => toStringValue(key))
|
||||
: [];
|
||||
return [toStringValue(cursor), normalizedKeys];
|
||||
}
|
||||
if (reply && typeof reply === 'object') {
|
||||
const result = reply;
|
||||
if (result.cursor !== undefined && Array.isArray(result.keys)) {
|
||||
return [
|
||||
toStringValue(result.cursor),
|
||||
result.keys.map(key => toStringValue(key)),
|
||||
];
|
||||
}
|
||||
}
|
||||
return ['0', []];
|
||||
}
|
||||
function normalizeXRangeReply(reply) {
|
||||
if (!reply) {
|
||||
return [];
|
||||
}
|
||||
if (Array.isArray(reply) &&
|
||||
reply.every(entry => Array.isArray(entry) &&
|
||||
entry.length === 2 &&
|
||||
typeof entry[0] !== 'undefined' &&
|
||||
Array.isArray(entry[1]))) {
|
||||
return reply.map(([id, fields]) => [
|
||||
toStringValue(id),
|
||||
fields.map((field) => toStringValue(field)),
|
||||
]);
|
||||
}
|
||||
if (!isKeyValueArray(reply)) {
|
||||
return [];
|
||||
}
|
||||
return reply.map(entry => [
|
||||
toStringValue(entry.key),
|
||||
flattenFieldPairs(entry.value),
|
||||
]);
|
||||
}
|
||||
function normalizeSortedSetReply(reply, withScores = false) {
|
||||
if (!Array.isArray(reply)) {
|
||||
return [];
|
||||
}
|
||||
if (!withScores) {
|
||||
return reply.map(item => toStringValue(item));
|
||||
}
|
||||
if (isKeyValueArray(reply)) {
|
||||
return reply.flatMap(entry => [
|
||||
toStringValue(entry.key),
|
||||
toStringValue(entry.value),
|
||||
]);
|
||||
}
|
||||
if (reply.every(item => Array.isArray(item) && item.length >= 2 && item[0] !== undefined)) {
|
||||
return reply.flatMap(item => [
|
||||
toStringValue(item[0]),
|
||||
toStringValue(item[1]),
|
||||
]);
|
||||
}
|
||||
return reply.map(item => toStringValue(item));
|
||||
}
|
||||
function isBlockingCommand(args) {
|
||||
const [command, ...rest] = args;
|
||||
const name = toStringValue(command).toUpperCase();
|
||||
if (name === 'BZPOPMIN') {
|
||||
return true;
|
||||
}
|
||||
if (name !== 'XREAD') {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < rest.length; i += 2) {
|
||||
if (toStringValue(rest[i]).toUpperCase() === 'BLOCK') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export function createValkeyGlideClient(client) {
|
||||
return new ValkeyGlideAdapter(client);
|
||||
}
|
||||
class ValkeyGlideAdapter extends EventEmitter {
|
||||
constructor(rawOrPromise, connectionName) {
|
||||
super();
|
||||
this.connectionName = connectionName;
|
||||
this.scripts = new Map();
|
||||
this.scriptsBySha = new Map();
|
||||
this.scriptLoadPromises = new Map();
|
||||
this.readyEmitted = false;
|
||||
this.closed = false;
|
||||
this.operationChain = Promise.resolve();
|
||||
this.activeBlockingCommands = 0;
|
||||
if (rawOrPromise instanceof Promise) {
|
||||
this.rawPromise = rawOrPromise;
|
||||
void this.connect().catch(error => this.emit('error', error));
|
||||
}
|
||||
else {
|
||||
this.raw = rawOrPromise;
|
||||
}
|
||||
}
|
||||
get status() {
|
||||
if (this.statusOverride) {
|
||||
return this.statusOverride;
|
||||
}
|
||||
if (this.closed) {
|
||||
return 'end';
|
||||
}
|
||||
if (this.readyEmitted) {
|
||||
return 'ready';
|
||||
}
|
||||
return 'wait';
|
||||
}
|
||||
set status(value) {
|
||||
if (value === 'end') {
|
||||
this.disconnect();
|
||||
}
|
||||
this.statusOverride = value;
|
||||
}
|
||||
get isCluster() {
|
||||
var _a, _b, _c;
|
||||
const name = (_c = (_b = (_a = this.raw) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.name) !== null && _c !== void 0 ? _c : '';
|
||||
return name.includes('Cluster');
|
||||
}
|
||||
get options() {
|
||||
var _a, _b, _c, _d;
|
||||
return (_d = (_b = (_a = this.raw) === null || _a === void 0 ? void 0 : _a.config) !== null && _b !== void 0 ? _b : (_c = this.raw) === null || _c === void 0 ? void 0 : _c.options) !== null && _d !== void 0 ? _d : {};
|
||||
}
|
||||
set options(_value) {
|
||||
// no-op
|
||||
}
|
||||
ensureOpen() {
|
||||
if (this.closed) {
|
||||
throw new ConnectionClosedError();
|
||||
}
|
||||
}
|
||||
normalizeError(error) {
|
||||
if (error instanceof ConnectionClosedError ||
|
||||
(error instanceof Error && error.name === 'ClosingError')) {
|
||||
throw new ConnectionClosedError(error.message, error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
async ensureRaw() {
|
||||
if (this.raw) {
|
||||
return this.raw;
|
||||
}
|
||||
if (!this.rawPromise) {
|
||||
throw new Error('BullMQ: Valkey Glide client not initialized - missing raw client ' +
|
||||
'and client promise. Please report this as a bug.');
|
||||
}
|
||||
this.raw = await this.rawPromise;
|
||||
return this.raw;
|
||||
}
|
||||
async runSerialized(fn) {
|
||||
this.ensureOpen();
|
||||
const previous = this.operationChain;
|
||||
let release;
|
||||
this.operationChain = new Promise(resolve => {
|
||||
release = resolve;
|
||||
});
|
||||
await previous;
|
||||
this.ensureOpen();
|
||||
try {
|
||||
const raw = await this.ensureRaw();
|
||||
this.ensureOpen();
|
||||
return await fn(raw);
|
||||
}
|
||||
catch (error) {
|
||||
this.normalizeError(error);
|
||||
}
|
||||
finally {
|
||||
release();
|
||||
}
|
||||
}
|
||||
async runRawCommand(args, options) {
|
||||
const blockingCommand = isBlockingCommand(args);
|
||||
return this.runSerialized(async (raw) => {
|
||||
if (blockingCommand) {
|
||||
this.activeBlockingCommands++;
|
||||
}
|
||||
try {
|
||||
return await raw.customCommand(args, options);
|
||||
}
|
||||
finally {
|
||||
if (blockingCommand) {
|
||||
this.activeBlockingCommands--;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
ensureScriptLoaded(script) {
|
||||
let pendingLoad = this.scriptLoadPromises.get(script.sha);
|
||||
if (!pendingLoad) {
|
||||
pendingLoad = this.runRawCommand(['SCRIPT', 'LOAD', script.lua])
|
||||
.then(() => { })
|
||||
.catch(() => {
|
||||
// Ignore script preload errors here – runCommand has NOSCRIPT fallback
|
||||
// for non-transactional usage, and transactional callers will surface
|
||||
// the underlying Redis error if the script still is not available.
|
||||
});
|
||||
this.scriptLoadPromises.set(script.sha, pendingLoad);
|
||||
}
|
||||
return pendingLoad;
|
||||
}
|
||||
async applyConnectionNameIfNeeded() {
|
||||
if (!this.connectionName) {
|
||||
return;
|
||||
}
|
||||
await this.runRawCommand(['CLIENT', 'SETNAME', this.connectionName], {
|
||||
decoder: GLIDE_STRING_DECODER,
|
||||
});
|
||||
}
|
||||
async clearConnectionNameIfNeeded(raw) {
|
||||
if (!this.connectionName) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await raw.customCommand(['CLIENT', 'SETNAME', ''], {
|
||||
decoder: GLIDE_STRING_DECODER,
|
||||
});
|
||||
}
|
||||
catch (_a) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
async recreateRaw() {
|
||||
var _a, _b;
|
||||
const raw = await this.ensureRaw();
|
||||
const clientConstructor = raw.constructor;
|
||||
const createClient = (_a = clientConstructor === null || clientConstructor === void 0 ? void 0 : clientConstructor.createClient) === null || _a === void 0 ? void 0 : _a.bind(clientConstructor);
|
||||
const config = (_b = raw.config) !== null && _b !== void 0 ? _b : raw.options;
|
||||
if (!createClient || !config) {
|
||||
throw new Error('BullMQ: Cannot recreate Valkey Glide client: missing createClient() method or ' +
|
||||
'config object. Ensure the client was created via GlideClient.createClient() ' +
|
||||
'or GlideClusterClient.createClient().');
|
||||
}
|
||||
this.raw = await createClient(config);
|
||||
// Glide closes clients permanently, so a recreated raw connection starts
|
||||
// with an empty server-side script cache.
|
||||
this.scriptLoadPromises.clear();
|
||||
}
|
||||
async connect() {
|
||||
if (this.connecting) {
|
||||
return this.connecting;
|
||||
}
|
||||
this.connecting = (async () => {
|
||||
if (this.closed && this.raw) {
|
||||
await this.recreateRaw();
|
||||
}
|
||||
this.closed = false;
|
||||
this.statusOverride = undefined;
|
||||
await this.ensureRaw();
|
||||
try {
|
||||
await this.applyConnectionNameIfNeeded();
|
||||
}
|
||||
catch (error) {
|
||||
if (this.closed && error instanceof ConnectionClosedError) {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
if (this.closed) {
|
||||
return;
|
||||
}
|
||||
this.readyEmitted = true;
|
||||
this.emit('ready');
|
||||
})().finally(() => {
|
||||
this.connecting = undefined;
|
||||
});
|
||||
return this.connecting;
|
||||
}
|
||||
closeRaw() {
|
||||
if (!this.closingPromise) {
|
||||
const closeRaw = () => {
|
||||
if (!this.raw) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.raw.close();
|
||||
}
|
||||
catch (_a) {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
this.closingPromise = (this.activeBlockingCommands > 0
|
||||
? Promise.resolve().then(closeRaw)
|
||||
: this.operationChain
|
||||
.catch(() => {
|
||||
// ignore
|
||||
})
|
||||
.then(async () => {
|
||||
if (!this.closed) {
|
||||
return;
|
||||
}
|
||||
if (this.raw) {
|
||||
await this.clearConnectionNameIfNeeded(this.raw);
|
||||
}
|
||||
closeRaw();
|
||||
})).finally(() => {
|
||||
this.closingPromise = undefined;
|
||||
});
|
||||
}
|
||||
return this.closingPromise;
|
||||
}
|
||||
disconnect(reconnect = false) {
|
||||
if (this.closed && !reconnect) {
|
||||
return;
|
||||
}
|
||||
this.closed = true;
|
||||
this.readyEmitted = false;
|
||||
this.statusOverride = reconnect ? undefined : 'end';
|
||||
this.emit('close');
|
||||
if (reconnect) {
|
||||
void this.closeRaw()
|
||||
.then(() => {
|
||||
this.emit('reconnecting');
|
||||
return this.connect();
|
||||
})
|
||||
.catch(error => this.emit('error', error));
|
||||
return;
|
||||
}
|
||||
void this.closeRaw();
|
||||
this.emit('end');
|
||||
}
|
||||
async quit() {
|
||||
if (this.closed) {
|
||||
setImmediate(() => {
|
||||
this.emit('end');
|
||||
this.emit('close');
|
||||
});
|
||||
return 'OK';
|
||||
}
|
||||
this.closed = true;
|
||||
this.readyEmitted = false;
|
||||
this.statusOverride = 'end';
|
||||
void this.closeRaw();
|
||||
setImmediate(() => {
|
||||
this.emit('end');
|
||||
this.emit('close');
|
||||
});
|
||||
return 'OK';
|
||||
}
|
||||
duplicate(...args) {
|
||||
var _a;
|
||||
const options = ((_a = args[0]) !== null && _a !== void 0 ? _a : {});
|
||||
const duplicated = (async () => {
|
||||
var _a, _b;
|
||||
const raw = await this.ensureRaw();
|
||||
const clientConstructor = raw.constructor;
|
||||
const createClient = (_a = clientConstructor === null || clientConstructor === void 0 ? void 0 : clientConstructor.createClient) === null || _a === void 0 ? void 0 : _a.bind(clientConstructor);
|
||||
const config = (_b = raw.config) !== null && _b !== void 0 ? _b : raw.options;
|
||||
if (!createClient || !config) {
|
||||
throw new Error('BullMQ: Cannot duplicate Valkey Glide client: missing createClient() or ' +
|
||||
'config. Ensure the client was created via GlideClient.createClient()/' +
|
||||
'GlideClusterClient.createClient().');
|
||||
}
|
||||
return createClient(config);
|
||||
})();
|
||||
return new ValkeyGlideAdapter(duplicated, options.connectionName);
|
||||
}
|
||||
defineCommand(name, definition) {
|
||||
const sha = createHash('sha1').update(definition.lua).digest('hex');
|
||||
const script = {
|
||||
sha,
|
||||
lua: definition.lua,
|
||||
numberOfKeys: definition.numberOfKeys,
|
||||
};
|
||||
this.scripts.set(name, script);
|
||||
this.scriptsBySha.set(sha, script);
|
||||
this[name] = (...args) => this.runCommand(name, args);
|
||||
void this.ensureScriptLoaded(script);
|
||||
}
|
||||
async runCommand(name, args) {
|
||||
const script = this.scripts.get(name);
|
||||
if (!script) {
|
||||
throw new Error(`BullMQ: command "${name}" is not defined. Use defineCommand() before runCommand().`);
|
||||
}
|
||||
const normalizedArgs = normalizeScriptArgs(args);
|
||||
const keys = normalizedArgs.slice(0, script.numberOfKeys).map(toGlideArg);
|
||||
const argv = normalizedArgs.slice(script.numberOfKeys).map(toGlideArg);
|
||||
const evalShaArgs = [
|
||||
'EVALSHA',
|
||||
script.sha,
|
||||
String(script.numberOfKeys),
|
||||
...keys,
|
||||
...argv,
|
||||
];
|
||||
try {
|
||||
return await this.runRawCommand(evalShaArgs);
|
||||
}
|
||||
catch (err) {
|
||||
if (typeof (err === null || err === void 0 ? void 0 : err.message) === 'string' &&
|
||||
err.message.toLowerCase().includes('noscript')) {
|
||||
return this.runRawCommand([
|
||||
'EVAL',
|
||||
script.lua,
|
||||
String(script.numberOfKeys),
|
||||
...keys,
|
||||
...argv,
|
||||
]);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
multi() {
|
||||
return new ValkeyGlideTransaction(this, this.scripts);
|
||||
}
|
||||
pipeline() {
|
||||
return this.multi();
|
||||
}
|
||||
async hgetall(key) {
|
||||
return normalizeHashReply(await this.runRawCommand(['HGETALL', key]));
|
||||
}
|
||||
async hget(key, field) {
|
||||
const value = await this.runRawCommand(['HGET', key, field]);
|
||||
return value == null ? null : toStringValue(value);
|
||||
}
|
||||
async hmget(key, ...fields) {
|
||||
const value = await this.runRawCommand(['HMGET', key, ...fields]);
|
||||
return Array.isArray(value)
|
||||
? value.map(item => (item == null ? null : toStringValue(item)))
|
||||
: [];
|
||||
}
|
||||
async hset(key, data) {
|
||||
const args = ['HSET', key];
|
||||
for (const [field, value] of Object.entries(data)) {
|
||||
args.push(field, toGlideArg(value));
|
||||
}
|
||||
return Number(await this.runRawCommand(args));
|
||||
}
|
||||
async hdel(key, ...fields) {
|
||||
return Number(await this.runRawCommand(['HDEL', key, ...fields]));
|
||||
}
|
||||
async hexists(key, field) {
|
||||
const result = await this.runRawCommand(['HEXISTS', key, field]);
|
||||
if (typeof result === 'boolean') {
|
||||
return result ? 1 : 0;
|
||||
}
|
||||
return Number(result);
|
||||
}
|
||||
async get(key) {
|
||||
const value = await this.runRawCommand(['GET', key]);
|
||||
return value == null ? null : toStringValue(value);
|
||||
}
|
||||
async set(key, value, options) {
|
||||
const args = ['SET', key, toGlideArg(value)];
|
||||
if ((options === null || options === void 0 ? void 0 : options.PX) != null) {
|
||||
args.push('PX', String(options.PX));
|
||||
}
|
||||
else if ((options === null || options === void 0 ? void 0 : options.EX) != null) {
|
||||
args.push('EX', String(options.EX));
|
||||
}
|
||||
const result = await this.runRawCommand(args);
|
||||
return result == null ? null : toStringValue(result);
|
||||
}
|
||||
async del(...keys) {
|
||||
if (keys.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
return Number(await this.runRawCommand(['DEL', ...keys]));
|
||||
}
|
||||
async zrange(key, start, end, options) {
|
||||
const args = ['ZRANGE', key, String(start), String(end)];
|
||||
if (options === null || options === void 0 ? void 0 : options.WITHSCORES) {
|
||||
args.push('WITHSCORES');
|
||||
}
|
||||
const result = await this.runRawCommand(args);
|
||||
return normalizeSortedSetReply(result, options === null || options === void 0 ? void 0 : options.WITHSCORES);
|
||||
}
|
||||
async zrevrange(key, start, end, options) {
|
||||
const args = ['ZREVRANGE', key, String(start), String(end)];
|
||||
if (options === null || options === void 0 ? void 0 : options.WITHSCORES) {
|
||||
args.push('WITHSCORES');
|
||||
}
|
||||
const result = await this.runRawCommand(args);
|
||||
return normalizeSortedSetReply(result, options === null || options === void 0 ? void 0 : options.WITHSCORES);
|
||||
}
|
||||
async zcard(key) {
|
||||
return Number(await this.runRawCommand(['ZCARD', key]));
|
||||
}
|
||||
async zscore(key, member) {
|
||||
const result = await this.runRawCommand(['ZSCORE', key, member]);
|
||||
return result == null ? null : toStringValue(result);
|
||||
}
|
||||
async lrange(key, start, end) {
|
||||
const result = await this.runRawCommand([
|
||||
'LRANGE',
|
||||
key,
|
||||
String(start),
|
||||
String(end),
|
||||
]);
|
||||
return Array.isArray(result) ? result.map(toStringValue) : [];
|
||||
}
|
||||
async llen(key) {
|
||||
return Number(await this.runRawCommand(['LLEN', key]));
|
||||
}
|
||||
async ltrim(key, start, end) {
|
||||
const result = await this.runRawCommand([
|
||||
'LTRIM',
|
||||
key,
|
||||
String(start),
|
||||
String(end),
|
||||
]);
|
||||
return result == null ? 'OK' : toStringValue(result);
|
||||
}
|
||||
async lpos(key, value) {
|
||||
const result = await this.runRawCommand(['LPOS', key, value]);
|
||||
return result == null ? null : Number(result);
|
||||
}
|
||||
async smembers(key) {
|
||||
const result = await this.runRawCommand(['SMEMBERS', key]);
|
||||
return Array.isArray(result) ? result.map(toStringValue) : [];
|
||||
}
|
||||
async xadd(key, id, fields, options) {
|
||||
const args = ['XADD', key];
|
||||
if ((options === null || options === void 0 ? void 0 : options.MAXLEN) != null) {
|
||||
args.push('MAXLEN');
|
||||
if (options.approximate !== false) {
|
||||
args.push('~');
|
||||
}
|
||||
args.push(String(options.MAXLEN));
|
||||
}
|
||||
args.push(id);
|
||||
for (const [field, value] of Object.entries(fields)) {
|
||||
args.push(field, toGlideArg(value));
|
||||
}
|
||||
return toStringValue(await this.runRawCommand(args));
|
||||
}
|
||||
async xread(streams, options) {
|
||||
const args = ['XREAD'];
|
||||
if ((options === null || options === void 0 ? void 0 : options.BLOCK) != null) {
|
||||
args.push('BLOCK', String(options.BLOCK));
|
||||
}
|
||||
if ((options === null || options === void 0 ? void 0 : options.COUNT) != null) {
|
||||
args.push('COUNT', String(options.COUNT));
|
||||
}
|
||||
args.push('STREAMS');
|
||||
for (const stream of streams) {
|
||||
args.push(stream.key);
|
||||
}
|
||||
for (const stream of streams) {
|
||||
args.push(stream.id);
|
||||
}
|
||||
return normalizeXReadReply(await this.runRawCommand(args));
|
||||
}
|
||||
async xtrim(key, strategy, threshold, options) {
|
||||
const args = ['XTRIM', key, strategy];
|
||||
if ((options === null || options === void 0 ? void 0 : options.approximate) !== false) {
|
||||
args.push('~');
|
||||
}
|
||||
args.push(String(threshold));
|
||||
return Number(await this.runRawCommand(args));
|
||||
}
|
||||
async bzpopmin(key, timeout) {
|
||||
const result = await this.runRawCommand(['BZPOPMIN', key, String(timeout)]);
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
if (Array.isArray(result) && result.length >= 3) {
|
||||
return [
|
||||
toStringValue(result[0]),
|
||||
toStringValue(result[1]),
|
||||
toStringValue(result[2]),
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
async info() {
|
||||
return toStringValue(await this.runRawCommand(['INFO']));
|
||||
}
|
||||
async clientSetName(name) {
|
||||
return this.runRawCommand(['CLIENT', 'SETNAME', name], {
|
||||
decoder: GLIDE_STRING_DECODER,
|
||||
});
|
||||
}
|
||||
async clientList() {
|
||||
return toStringValue(await this.runRawCommand(['CLIENT', 'LIST'], {
|
||||
decoder: GLIDE_STRING_DECODER,
|
||||
}));
|
||||
}
|
||||
async scan(cursor, options) {
|
||||
const args = ['SCAN', String(cursor)];
|
||||
if (options === null || options === void 0 ? void 0 : options.MATCH) {
|
||||
args.push('MATCH', options.MATCH);
|
||||
}
|
||||
if ((options === null || options === void 0 ? void 0 : options.COUNT) != null) {
|
||||
args.push('COUNT', String(options.COUNT));
|
||||
}
|
||||
return normalizeScanReply(await this.runRawCommand(args));
|
||||
}
|
||||
scanStream(options) {
|
||||
let cursor = '0';
|
||||
let running = false;
|
||||
const stream = new Readable({
|
||||
objectMode: true,
|
||||
read: () => {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
running = true;
|
||||
(async () => {
|
||||
do {
|
||||
const [nextCursor, keys] = await this.scan(cursor, {
|
||||
MATCH: options.match,
|
||||
COUNT: options.count,
|
||||
});
|
||||
cursor = nextCursor;
|
||||
if (keys.length > 0 && !stream.push(keys)) {
|
||||
return;
|
||||
}
|
||||
} while (cursor !== '0');
|
||||
stream.push(null);
|
||||
})()
|
||||
.catch(err => stream.destroy(err))
|
||||
.finally(() => {
|
||||
running = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
return stream;
|
||||
}
|
||||
async keys(pattern) {
|
||||
const result = await this.runRawCommand(['KEYS', pattern]);
|
||||
return Array.isArray(result) ? result.map(toStringValue) : [];
|
||||
}
|
||||
async exists(...keys) {
|
||||
if (keys.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const result = await this.runRawCommand(['EXISTS', ...keys]);
|
||||
if (typeof result === 'boolean') {
|
||||
return result ? 1 : 0;
|
||||
}
|
||||
return Number(result);
|
||||
}
|
||||
async zadd(key, ...args) {
|
||||
const commandArgs = ['ZADD', key];
|
||||
for (let i = 0; i < args.length; i += 2) {
|
||||
commandArgs.push(toGlideArg(args[i]), toGlideArg(args[i + 1]));
|
||||
}
|
||||
return Number(await this.runRawCommand(commandArgs));
|
||||
}
|
||||
async zrem(key, ...members) {
|
||||
return Number(await this.runRawCommand(['ZREM', key, ...members]));
|
||||
}
|
||||
async xlen(key) {
|
||||
return Number(await this.runRawCommand(['XLEN', key]));
|
||||
}
|
||||
async xrevrange(key, end, start, ...rest) {
|
||||
const args = ['XREVRANGE', key, end, start];
|
||||
if (rest[0] === 'COUNT') {
|
||||
args.push('COUNT', toGlideArg(rest[1]));
|
||||
}
|
||||
return normalizeXRangeReply(await this.runRawCommand(args));
|
||||
}
|
||||
async sadd(key, ...members) {
|
||||
return Number(await this.runRawCommand([
|
||||
'SADD',
|
||||
key,
|
||||
...members.map(member => toGlideArg(member)),
|
||||
]));
|
||||
}
|
||||
async scard(key) {
|
||||
return Number(await this.runRawCommand(['SCARD', key]));
|
||||
}
|
||||
async lpush(key, ...values) {
|
||||
return Number(await this.runRawCommand(['LPUSH', key, ...values]));
|
||||
}
|
||||
async rpop(key) {
|
||||
const result = await this.runRawCommand(['RPOP', key]);
|
||||
return result == null ? null : toStringValue(result);
|
||||
}
|
||||
async incr(key) {
|
||||
return Number(await this.runRawCommand(['INCR', key]));
|
||||
}
|
||||
async incrby(key, increment) {
|
||||
return Number(await this.runRawCommand(['INCRBY', key, String(increment)]));
|
||||
}
|
||||
async flushall() {
|
||||
const result = await this.runRawCommand(['FLUSHALL']);
|
||||
return result == null ? 'OK' : toStringValue(result);
|
||||
}
|
||||
async execQueuedCommands(commands) {
|
||||
const requiredScripts = commands
|
||||
.map(command => String(command.args[0]).toUpperCase() === 'EVALSHA'
|
||||
? this.scriptsBySha.get(toStringValue(command.args[1]))
|
||||
: undefined)
|
||||
.filter((script) => Boolean(script));
|
||||
if (requiredScripts.length > 0) {
|
||||
await Promise.all(requiredScripts.map(script => this.ensureScriptLoaded(script)));
|
||||
}
|
||||
return this.runSerialized(async (raw) => {
|
||||
if (typeof raw.exec === 'function') {
|
||||
const nativeBatch = createGlideBatch(true);
|
||||
const batch = nativeBatch !== null && nativeBatch !== void 0 ? nativeBatch : createCompatibilityBatch();
|
||||
if (batch) {
|
||||
for (const command of commands) {
|
||||
batch.customCommand(command.args);
|
||||
}
|
||||
let execResult;
|
||||
try {
|
||||
execResult = await raw.exec(batch, false);
|
||||
}
|
||||
catch (err) {
|
||||
if (nativeBatch) {
|
||||
throw err;
|
||||
}
|
||||
execResult = null;
|
||||
}
|
||||
if (!execResult) {
|
||||
if (nativeBatch) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return execResult.map((value, index) => {
|
||||
var _a;
|
||||
if (value instanceof Error) {
|
||||
return [value, null];
|
||||
}
|
||||
const transform = (_a = commands[index]) === null || _a === void 0 ? void 0 : _a.transform;
|
||||
return [null, transform ? transform(value) : value];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
await raw.customCommand(['MULTI']);
|
||||
try {
|
||||
for (const command of commands) {
|
||||
// During MULTI queueing Redis replies with "QUEUED". For commands such
|
||||
// as HGETALL, Glide's default decoder expects a structured reply and
|
||||
// throws if it receives this simple string, so force string decoding.
|
||||
await raw.customCommand(command.args, {
|
||||
decoder: GLIDE_STRING_DECODER,
|
||||
});
|
||||
}
|
||||
const execResult = await raw.customCommand(['EXEC']);
|
||||
if (!execResult) {
|
||||
return null;
|
||||
}
|
||||
const results = Array.isArray(execResult) ? execResult : [execResult];
|
||||
return results.map((value, index) => {
|
||||
var _a;
|
||||
if (value instanceof Error) {
|
||||
return [value, null];
|
||||
}
|
||||
const transform = (_a = commands[index]) === null || _a === void 0 ? void 0 : _a.transform;
|
||||
return [null, transform ? transform(value) : value];
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
try {
|
||||
await raw.customCommand(['DISCARD']);
|
||||
}
|
||||
catch (_a) {
|
||||
// ignore
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
class ValkeyGlideTransaction {
|
||||
constructor(adapter, scripts) {
|
||||
this.adapter = adapter;
|
||||
this.scripts = scripts;
|
||||
this.commands = [];
|
||||
}
|
||||
queueCommand(args, transform) {
|
||||
this.commands.push({ args, transform });
|
||||
return this;
|
||||
}
|
||||
hgetall(key) {
|
||||
return this.queueCommand(['HGETALL', key], normalizeHashReply);
|
||||
}
|
||||
hset(key, data) {
|
||||
const args = ['HSET', key];
|
||||
for (const [field, value] of Object.entries(data)) {
|
||||
args.push(field, toGlideArg(value));
|
||||
}
|
||||
return this.queueCommand(args);
|
||||
}
|
||||
hscan(key, cursor, options) {
|
||||
const args = ['HSCAN', key, String(cursor)];
|
||||
if ((options === null || options === void 0 ? void 0 : options.COUNT) != null) {
|
||||
args.push('COUNT', String(options.COUNT));
|
||||
}
|
||||
return this.queueCommand(args, value => {
|
||||
if (Array.isArray(value) && value.length >= 2) {
|
||||
return [toStringValue(value[0]), flattenFieldPairs(value[1])];
|
||||
}
|
||||
return ['0', []];
|
||||
});
|
||||
}
|
||||
smembers(key) {
|
||||
return this.queueCommand(['SMEMBERS', key], value => Array.isArray(value) ? value.map(item => toStringValue(item)) : []);
|
||||
}
|
||||
sscan(key, cursor, options) {
|
||||
const args = ['SSCAN', key, String(cursor)];
|
||||
if ((options === null || options === void 0 ? void 0 : options.COUNT) != null) {
|
||||
args.push('COUNT', String(options.COUNT));
|
||||
}
|
||||
return this.queueCommand(args, value => {
|
||||
if (Array.isArray(value) && value.length >= 2) {
|
||||
const members = Array.isArray(value[1])
|
||||
? value[1].map(item => toStringValue(item))
|
||||
: [];
|
||||
return [toStringValue(value[0]), members];
|
||||
}
|
||||
return ['0', []];
|
||||
});
|
||||
}
|
||||
zrange(key, start, end) {
|
||||
return this.queueCommand(['ZRANGE', key, String(start), String(end)]);
|
||||
}
|
||||
lrange(key, start, end) {
|
||||
return this.queueCommand(['LRANGE', key, String(start), String(end)]);
|
||||
}
|
||||
llen(key) {
|
||||
return this.queueCommand(['LLEN', key]);
|
||||
}
|
||||
del(...keys) {
|
||||
if (keys.length > 0) {
|
||||
this.queueCommand(['DEL', ...keys]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
runCommand(name, args) {
|
||||
const script = this.scripts.get(name);
|
||||
if (!script) {
|
||||
throw new Error(`BullMQ: command "${name}" is not defined. Use defineCommand() before adding it to transactions.`);
|
||||
}
|
||||
const normalizedArgs = normalizeScriptArgs(args);
|
||||
const keys = normalizedArgs.slice(0, script.numberOfKeys).map(toGlideArg);
|
||||
const argv = normalizedArgs.slice(script.numberOfKeys).map(toGlideArg);
|
||||
return this.queueCommand([
|
||||
'EVALSHA',
|
||||
script.sha,
|
||||
String(script.numberOfKeys),
|
||||
...keys,
|
||||
...argv,
|
||||
]);
|
||||
}
|
||||
exec() {
|
||||
return this.adapter.execQueuedCommands(this.commands);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user