1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-09 00:22:08 +03:00

fix #1650 - add support for Buffer in some commands, add GET_BUFFER command

This commit is contained in:
leibale
2021-09-13 19:49:39 -04:00
parent 1413a69a6b
commit 08837c8648
65 changed files with 300 additions and 227 deletions

View File

@@ -195,6 +195,13 @@ describe('Client', () => {
assert.equal(await client.sendCommand(['PING']), 'PONG'); assert.equal(await client.sendCommand(['PING']), 'PONG');
}); });
itWithClient(TestRedisServers.OPEN, 'bufferMode', async client => {
assert.deepEqual(
await client.sendCommand(['PING'], undefined, true),
Buffer.from('PONG')
);
});
describe('AbortController', () => { describe('AbortController', () => {
before(function () { before(function () {
if (!global.AbortController) { if (!global.AbortController) {

View File

@@ -1,6 +1,6 @@
import RedisSocket, { RedisSocketOptions } from './socket'; import RedisSocket, { RedisSocketOptions } from './socket';
import RedisCommandsQueue, { PubSubListener, PubSubSubscribeCommands, PubSubUnsubscribeCommands, QueueCommandOptions } from './commands-queue'; import RedisCommandsQueue, { PubSubListener, PubSubSubscribeCommands, PubSubUnsubscribeCommands, QueueCommandOptions } from './commands-queue';
import COMMANDS from './commands'; import COMMANDS, { TransformArgumentsReply } from './commands';
import { RedisCommand, RedisModules, RedisReply } from './commands'; import { RedisCommand, RedisModules, RedisReply } from './commands';
import RedisMultiCommand, { MultiQueuedCommand, RedisMultiCommandType } from './multi-command'; import RedisMultiCommand, { MultiQueuedCommand, RedisMultiCommandType } from './multi-command';
import EventEmitter from 'events'; import EventEmitter from 'events';
@@ -62,12 +62,10 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
): Promise<ReturnType<typeof command['transformReply']>> { ): Promise<ReturnType<typeof command['transformReply']>> {
const { args: redisArgs, options } = transformCommandArguments<ClientCommandOptions>(command, args); const { args: redisArgs, options } = transformCommandArguments<ClientCommandOptions>(command, args);
const reply = command.transformReply( return command.transformReply(
await this.#sendCommand(redisArgs, options), await this.#sendCommand(redisArgs, options, command.BUFFER_MODE),
redisArgs.preserve redisArgs.preserve,
); );
return reply;
} }
static async #scriptsExecutor( static async #scriptsExecutor(
@@ -77,12 +75,10 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
): Promise<typeof script['transformArguments']> { ): Promise<typeof script['transformArguments']> {
const { args: redisArgs, options } = transformCommandArguments<ClientCommandOptions>(script, args); const { args: redisArgs, options } = transformCommandArguments<ClientCommandOptions>(script, args);
const reply = script.transformReply( return script.transformReply(
await this.executeScript(script, redisArgs, options), await this.executeScript(script, redisArgs, options, script.BUFFER_MODE),
redisArgs.preserve redisArgs.preserve
); );
return reply;
} }
static create<M extends RedisModules, S extends RedisLuaScripts>(options?: RedisClientOptions<M, S>): RedisClientType<M, S> { static create<M extends RedisModules, S extends RedisLuaScripts>(options?: RedisClientOptions<M, S>): RedisClientType<M, S> {
@@ -182,10 +178,7 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
} }
#initiateQueue(): RedisCommandsQueue { #initiateQueue(): RedisCommandsQueue {
return new RedisCommandsQueue( return new RedisCommandsQueue(this.#options?.commandsQueueMaxLength);
this.#options?.commandsQueueMaxLength,
encodedCommands => this.#socket.write(encodedCommands)
);
} }
#legacyMode(): void { #legacyMode(): void {
@@ -299,7 +292,7 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
QUIT(): Promise<void> { QUIT(): Promise<void> {
return this.#socket.quit(() => { return this.#socket.quit(() => {
const promise = this.#queue.addEncodedCommand(encodeCommand(['QUIT'])); const promise = this.#queue.addCommand(['QUIT']);
this.#tick(); this.#tick();
return promise; return promise;
}); });
@@ -307,46 +300,64 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
quit = this.QUIT; quit = this.QUIT;
sendCommand<T = unknown>(args: Array<string>, options?: ClientCommandOptions): Promise<T> { sendCommand<T = RedisReply>(args: TransformArgumentsReply, options?: ClientCommandOptions, bufferMode?: boolean): Promise<T> {
return this.#sendCommand(args, options); return this.#sendCommand(args, options, bufferMode);
} }
// using `#sendCommand` cause `sendCommand` is overwritten in legacy mode // using `#sendCommand` cause `sendCommand` is overwritten in legacy mode
#sendCommand<T = RedisReply>(args: Array<string>, options?: ClientCommandOptions): Promise<T> { async #sendCommand<T = RedisReply>(args: TransformArgumentsReply, options?: ClientCommandOptions, bufferMode?: boolean): Promise<T> {
return this.sendEncodedCommand(encodeCommand(args), options);
}
async sendEncodedCommand<T = RedisReply>(encodedCommand: string, options?: ClientCommandOptions): Promise<T> {
if (!this.#socket.isOpen) { if (!this.#socket.isOpen) {
throw new ClientClosedError(); throw new ClientClosedError();
} }
if (options?.isolated) { if (options?.isolated) {
return this.executeIsolated(isolatedClient => return this.executeIsolated(isolatedClient =>
isolatedClient.sendEncodedCommand(encodedCommand, { isolatedClient.sendCommand(args, {
...options, ...options,
isolated: false isolated: false
}) })
); );
} }
const promise = this.#queue.addEncodedCommand<T>(encodedCommand, options); const promise = this.#queue.addCommand<T>(args, options, bufferMode);
this.#tick(); this.#tick();
return await promise; return await promise;
} }
#tick(): void {
if (!this.#socket.isSocketExists) {
return;
}
this.#socket.cork();
while (true) {
const args = this.#queue.getCommandToSend();
if (args === undefined) break;
let writeResult;
for (const toWrite of encodeCommand(args)) {
writeResult = this.#socket.write(toWrite);
}
if (!writeResult) {
break;
}
}
}
executeIsolated<T>(fn: (client: RedisClientType<M, S>) => T | Promise<T>): Promise<T> { executeIsolated<T>(fn: (client: RedisClientType<M, S>) => T | Promise<T>): Promise<T> {
return this.#isolationPool.use(fn); return this.#isolationPool.use(fn);
} }
async executeScript(script: RedisLuaScript, args: Array<string>, options?: ClientCommandOptions): Promise<ReturnType<typeof script['transformReply']>> { async executeScript(script: RedisLuaScript, args: TransformArgumentsReply, options?: ClientCommandOptions, bufferMode?: boolean): Promise<ReturnType<typeof script['transformReply']>> {
try { try {
return await this.#sendCommand([ return await this.#sendCommand([
'EVALSHA', 'EVALSHA',
script.SHA1, script.SHA1,
script.NUMBER_OF_KEYS.toString(), script.NUMBER_OF_KEYS.toString(),
...args ...args
], options); ], options, bufferMode);
} catch (err: any) { } catch (err: any) {
if (!err?.message?.startsWith?.('NOSCRIPT')) { if (!err?.message?.startsWith?.('NOSCRIPT')) {
throw err; throw err;
@@ -357,14 +368,14 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
script.SCRIPT, script.SCRIPT,
script.NUMBER_OF_KEYS.toString(), script.NUMBER_OF_KEYS.toString(),
...args ...args
], options); ], options, bufferMode);
} }
} }
#multiExecutor(commands: Array<MultiQueuedCommand>, chainId?: symbol): Promise<Array<RedisReply>> { #multiExecutor(commands: Array<MultiQueuedCommand>, chainId?: symbol): Promise<Array<RedisReply>> {
const promise = Promise.all( const promise = Promise.all(
commands.map(({encodedCommand}) => { commands.map(({ args }) => {
return this.#queue.addEncodedCommand(encodedCommand, RedisClient.commandOptions({ return this.#queue.addCommand(args, RedisClient.commandOptions({
chainId chainId
})); }));
}) })
@@ -438,31 +449,6 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
await this.#isolationPool.drain(); await this.#isolationPool.drain();
await this.#isolationPool.clear(); await this.#isolationPool.clear();
} }
#isTickQueued = false;
#tick(): void {
const {chunkRecommendedSize} = this.#socket;
if (!chunkRecommendedSize) {
return;
}
if (!this.#isTickQueued && this.#queue.waitingToBeSentCommandsLength < chunkRecommendedSize) {
queueMicrotask(() => this.#tick());
this.#isTickQueued = true;
return;
}
const isBuffering = this.#queue.executeChunk(chunkRecommendedSize);
if (isBuffering === true) {
this.#socket.once('drain', () => this.#tick());
} else if (isBuffering === false) {
this.#tick();
return;
}
this.#isTickQueued = false;
}
} }
extendWithDefaultCommands(RedisClient, RedisClient.commandsExecutor); extendWithDefaultCommands(RedisClient, RedisClient.commandsExecutor);

View File

@@ -172,7 +172,7 @@ export default class RedisClusterSlots<M extends RedisModules, S extends RedisLu
return value.client; return value.client;
} }
getClient(firstKey?: string, isReadonly?: boolean): RedisClientType<M, S> { getClient(firstKey?: string | Buffer, isReadonly?: boolean): RedisClientType<M, S> {
if (!firstKey) { if (!firstKey) {
return this.#getRandomClient(); return this.#getRandomClient();
} }

View File

@@ -1,4 +1,4 @@
import { RedisCommand, RedisModules } from './commands'; import { RedisCommand, RedisModules, TransformArgumentsReply } from './commands';
import RedisClient, { ClientCommandOptions, RedisClientType, WithPlugins } from './client'; import RedisClient, { ClientCommandOptions, RedisClientType, WithPlugins } from './client';
import { RedisSocketOptions } from './socket'; import { RedisSocketOptions } from './socket';
import RedisClusterSlots, { ClusterNode } from './cluster-slots'; import RedisClusterSlots, { ClusterNode } from './cluster-slots';
@@ -6,6 +6,7 @@ import { RedisLuaScript, RedisLuaScripts } from './lua-script';
import { extendWithModulesAndScripts, extendWithDefaultCommands, transformCommandArguments } from './commander'; import { extendWithModulesAndScripts, extendWithDefaultCommands, transformCommandArguments } from './commander';
import RedisMultiCommand, { MultiQueuedCommand, RedisMultiCommandType } from './multi-command'; import RedisMultiCommand, { MultiQueuedCommand, RedisMultiCommandType } from './multi-command';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import cluster from 'cluster';
export interface RedisClusterOptions<M = RedisModules, S = RedisLuaScripts> { export interface RedisClusterOptions<M = RedisModules, S = RedisLuaScripts> {
rootNodes: Array<RedisSocketOptions>; rootNodes: Array<RedisSocketOptions>;
@@ -19,7 +20,7 @@ export type RedisClusterType<M extends RedisModules, S extends RedisLuaScripts>
WithPlugins<M, S> & RedisCluster; WithPlugins<M, S> & RedisCluster;
export default class RedisCluster<M extends RedisModules = RedisModules, S extends RedisLuaScripts = RedisLuaScripts> extends EventEmitter { export default class RedisCluster<M extends RedisModules = RedisModules, S extends RedisLuaScripts = RedisLuaScripts> extends EventEmitter {
static #extractFirstKey(command: RedisCommand, originalArgs: Array<unknown>, redisArgs: Array<string>): string | undefined { static #extractFirstKey(command: RedisCommand, originalArgs: Array<unknown>, redisArgs: TransformArgumentsReply): string | Buffer | undefined {
if (command.FIRST_KEY_INDEX === undefined) { if (command.FIRST_KEY_INDEX === undefined) {
return undefined; return undefined;
} else if (typeof command.FIRST_KEY_INDEX === 'number') { } else if (typeof command.FIRST_KEY_INDEX === 'number') {
@@ -41,7 +42,8 @@ export default class RedisCluster<M extends RedisModules = RedisModules, S exten
RedisCluster.#extractFirstKey(command, args, redisArgs), RedisCluster.#extractFirstKey(command, args, redisArgs),
command.IS_READ_ONLY, command.IS_READ_ONLY,
redisArgs, redisArgs,
options options,
command.BUFFER_MODE
), ),
redisArgs.preserve redisArgs.preserve
); );
@@ -100,22 +102,23 @@ export default class RedisCluster<M extends RedisModules = RedisModules, S exten
} }
async sendCommand<C extends RedisCommand>( async sendCommand<C extends RedisCommand>(
firstKey: string | undefined, firstKey: string | Buffer | undefined,
isReadonly: boolean | undefined, isReadonly: boolean | undefined,
args: Array<string>, args: TransformArgumentsReply,
options?: ClientCommandOptions, options?: ClientCommandOptions,
bufferMode?: boolean,
redirections = 0 redirections = 0
): Promise<ReturnType<C['transformReply']>> { ): Promise<ReturnType<C['transformReply']>> {
const client = this.#slots.getClient(firstKey, isReadonly); const client = this.#slots.getClient(firstKey, isReadonly);
try { try {
return await client.sendCommand(args, options); return await client.sendCommand(args, options, bufferMode);
} catch (err: any) { } catch (err: any) {
const shouldRetry = await this.#handleCommandError(err, client, redirections); const shouldRetry = await this.#handleCommandError(err, client, redirections);
if (shouldRetry === true) { if (shouldRetry === true) {
return this.sendCommand(firstKey, isReadonly, args, options, redirections + 1); return this.sendCommand(firstKey, isReadonly, args, options, bufferMode, redirections + 1);
} else if (shouldRetry) { } else if (shouldRetry) {
return shouldRetry.sendCommand(args, options); return shouldRetry.sendCommand(args, options, bufferMode);
} }
throw err; throw err;
@@ -125,7 +128,7 @@ export default class RedisCluster<M extends RedisModules = RedisModules, S exten
async executeScript( async executeScript(
script: RedisLuaScript, script: RedisLuaScript,
originalArgs: Array<unknown>, originalArgs: Array<unknown>,
redisArgs: Array<string>, redisArgs: TransformArgumentsReply,
options?: ClientCommandOptions, options?: ClientCommandOptions,
redirections = 0 redirections = 0
): Promise<ReturnType<typeof script['transformReply']>> { ): Promise<ReturnType<typeof script['transformReply']>> {
@@ -135,13 +138,13 @@ export default class RedisCluster<M extends RedisModules = RedisModules, S exten
); );
try { try {
return await client.executeScript(script, redisArgs, options); return await client.executeScript(script, redisArgs, options, script.BUFFER_MODE);
} catch (err: any) { } catch (err: any) {
const shouldRetry = await this.#handleCommandError(err, client, redirections); const shouldRetry = await this.#handleCommandError(err, client, redirections);
if (shouldRetry === true) { if (shouldRetry === true) {
return this.executeScript(script, originalArgs, redisArgs, options, redirections + 1); return this.executeScript(script, originalArgs, redisArgs, options, redirections + 1);
} else if (shouldRetry) { } else if (shouldRetry) {
return shouldRetry.executeScript(script, redisArgs, options); return shouldRetry.executeScript(script, redisArgs, options, script.BUFFER_MODE);
} }
throw err; throw err;
@@ -181,8 +184,8 @@ export default class RedisCluster<M extends RedisModules = RedisModules, S exten
const client = this.#slots.getClient(routing); const client = this.#slots.getClient(routing);
return Promise.all( return Promise.all(
commands.map(({encodedCommand}) => { commands.map(({ args }) => {
return client.sendEncodedCommand(encodedCommand, RedisClient.commandOptions({ return client.sendCommand(args, RedisClient.commandOptions({
chainId chainId
})); }));
}) })

View File

@@ -2,27 +2,43 @@ import { strict as assert } from 'assert';
import { describe } from 'mocha'; import { describe } from 'mocha';
import { encodeCommand } from './commander'; import { encodeCommand } from './commander';
function encodeCommandToString(...args: Parameters<typeof encodeCommand>): string {
const arr = [];
for (const item of encodeCommand(...args)) {
arr.push(item.toString());
}
return arr.join('');
}
describe('Commander', () => { describe('Commander', () => {
describe('encodeCommand (see #1628)', () => { describe('encodeCommand (see #1628)', () => {
it('1 byte', () => { it('1 byte', () => {
assert.equal( assert.equal(
encodeCommand(['a', 'z']), encodeCommandToString(['a', 'z']),
'*2\r\n$1\r\na\r\n$1\r\nz\r\n' '*2\r\n$1\r\na\r\n$1\r\nz\r\n'
); );
}); });
it('2 bytes', () => { it('2 bytes', () => {
assert.equal( assert.equal(
encodeCommand(['א', 'ת']), encodeCommandToString(['א', 'ת']),
'*2\r\n$2\r\nא\r\n$2\r\nת\r\n' '*2\r\n$2\r\nא\r\n$2\r\nת\r\n'
); );
}); });
it('4 bytes', () => { it('4 bytes', () => {
assert.equal( assert.equal(
encodeCommand(['🐣', '🐤']), encodeCommandToString(['🐣', '🐤']),
'*2\r\n$4\r\n🐣\r\n$4\r\n🐤\r\n' '*2\r\n$4\r\n🐣\r\n$4\r\n🐤\r\n'
); );
}); });
it('with a buffer', () => {
assert.equal(
encodeCommandToString([Buffer.from('string')]),
'*1\r\n$6\r\nstring\r\n'
);
});
}); });
}); });

View File

@@ -2,6 +2,7 @@
import COMMANDS, { RedisCommand, RedisModules, TransformArgumentsReply } from './commands'; import COMMANDS, { RedisCommand, RedisModules, TransformArgumentsReply } from './commands';
import { RedisLuaScript, RedisLuaScripts } from './lua-script'; import { RedisLuaScript, RedisLuaScripts } from './lua-script';
import { CommandOptions, isCommandOptions } from './command-options'; import { CommandOptions, isCommandOptions } from './command-options';
import { off } from 'process';
type Instantiable<T = any> = new(...args: Array<any>) => T; type Instantiable<T = any> = new(...args: Array<any>) => T;
@@ -94,16 +95,15 @@ export function transformCommandArguments<T = unknown>(
}; };
} }
export function encodeCommand(args: Array<string>): string { const DELIMITER = '\r\n';
const encoded = [
`*${args.length}`,
`$${Buffer.byteLength(args[0]).toString()}`,
args[0]
];
for (let i = 1; i < args.length; i++) { export function* encodeCommand(args: TransformArgumentsReply): IterableIterator<string | Buffer> {
encoded.push(`$${Buffer.byteLength(args[i]).toString()}`, args[i]); yield `*${args.length}${DELIMITER}`;
for (const arg of args) {
const byteLength = typeof arg === 'string' ? Buffer.byteLength(arg): arg.length;
yield `$${byteLength.toString()}${DELIMITER}`;
yield arg;
yield DELIMITER;
} }
return encoded.join('\r\n') + '\r\n';
} }

View File

@@ -2,17 +2,15 @@ import LinkedList from 'yallist';
import RedisParser from 'redis-parser'; import RedisParser from 'redis-parser';
import { AbortError } from './errors'; import { AbortError } from './errors';
import { RedisReply } from './commands'; import { RedisReply } from './commands';
import { encodeCommand } from './commander';
export interface QueueCommandOptions { export interface QueueCommandOptions {
asap?: boolean; asap?: boolean;
signal?: any; // TODO: `AbortSignal` type is incorrect
chainId?: symbol; chainId?: symbol;
signal?: any; // TODO: `AbortSignal` type is incorrect
} }
interface CommandWaitingToBeSent extends CommandWaitingForReply { interface CommandWaitingToBeSent extends CommandWaitingForReply {
encodedCommand: string; args: Array<string | Buffer>;
byteLength: number;
chainId?: symbol; chainId?: symbol;
abort?: { abort?: {
signal: any; // TODO: `AbortSignal` type is incorrect signal: any; // TODO: `AbortSignal` type is incorrect
@@ -24,10 +22,9 @@ interface CommandWaitingForReply {
resolve(reply?: any): void; resolve(reply?: any): void;
reject(err: Error): void; reject(err: Error): void;
channelsCounter?: number; channelsCounter?: number;
bufferMode?: boolean;
} }
export type CommandsQueueExecutor = (encodedCommands: string) => boolean | undefined;
export enum PubSubSubscribeCommands { export enum PubSubSubscribeCommands {
SUBSCRIBE = 'SUBSCRIBE', SUBSCRIBE = 'SUBSCRIBE',
PSUBSCRIBE = 'PSUBSCRIBE' PSUBSCRIBE = 'PSUBSCRIBE'
@@ -57,16 +54,8 @@ export default class RedisCommandsQueue {
readonly #maxLength: number | null | undefined; readonly #maxLength: number | null | undefined;
readonly #executor: CommandsQueueExecutor;
readonly #waitingToBeSent = new LinkedList<CommandWaitingToBeSent>(); readonly #waitingToBeSent = new LinkedList<CommandWaitingToBeSent>();
#waitingToBeSentCommandsLength = 0;
get waitingToBeSentCommandsLength() {
return this.#waitingToBeSentCommandsLength;
}
readonly #waitingForReply = new LinkedList<CommandWaitingForReply>(); readonly #waitingForReply = new LinkedList<CommandWaitingForReply>();
readonly #pubSubState = { readonly #pubSubState = {
@@ -114,12 +103,11 @@ export default class RedisCommandsQueue {
#chainInExecution: symbol | undefined; #chainInExecution: symbol | undefined;
constructor(maxLength: number | null | undefined, executor: CommandsQueueExecutor) { constructor(maxLength: number | null | undefined) {
this.#maxLength = maxLength; this.#maxLength = maxLength;
this.#executor = executor;
} }
addEncodedCommand<T = RedisReply>(encodedCommand: string, options?: QueueCommandOptions): Promise<T> { addCommand<T = RedisReply>(args: Array<string | Buffer>, options?: QueueCommandOptions, bufferMode?: boolean): Promise<T> {
if (this.#pubSubState.subscribing || this.#pubSubState.subscribed) { if (this.#pubSubState.subscribing || this.#pubSubState.subscribed) {
return Promise.reject(new Error('Cannot send commands in PubSub mode')); return Promise.reject(new Error('Cannot send commands in PubSub mode'));
} else if (this.#maxLength && this.#waitingToBeSent.length + this.#waitingForReply.length >= this.#maxLength) { } else if (this.#maxLength && this.#waitingToBeSent.length + this.#waitingForReply.length >= this.#maxLength) {
@@ -130,11 +118,11 @@ export default class RedisCommandsQueue {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const node = new LinkedList.Node<CommandWaitingToBeSent>({ const node = new LinkedList.Node<CommandWaitingToBeSent>({
encodedCommand, args,
byteLength: Buffer.byteLength(encodedCommand),
chainId: options?.chainId, chainId: options?.chainId,
bufferMode,
resolve, resolve,
reject reject,
}); });
if (options?.signal) { if (options?.signal) {
@@ -157,8 +145,6 @@ export default class RedisCommandsQueue {
} else { } else {
this.#waitingToBeSent.pushNode(node); this.#waitingToBeSent.pushNode(node);
} }
this.#waitingToBeSentCommandsLength += node.value.byteLength;
}); });
} }
@@ -233,11 +219,8 @@ export default class RedisCommandsQueue {
this.#pubSubState[inProgressKey] += channelsCounter; this.#pubSubState[inProgressKey] += channelsCounter;
const encodedCommand = encodeCommand(commandArgs),
byteLength = Buffer.byteLength(encodedCommand);
this.#waitingToBeSent.push({ this.#waitingToBeSent.push({
encodedCommand, args: commandArgs,
byteLength,
channelsCounter, channelsCounter,
resolve: () => { resolve: () => {
this.#pubSubState[inProgressKey] -= channelsCounter; this.#pubSubState[inProgressKey] -= channelsCounter;
@@ -249,7 +232,6 @@ export default class RedisCommandsQueue {
reject(); reject();
} }
}); });
this.#waitingToBeSentCommandsLength += byteLength;
}); });
} }
@@ -267,47 +249,25 @@ export default class RedisCommandsQueue {
]); ]);
} }
executeChunk(recommendedSize: number): boolean | undefined { getCommandToSend(): Array<string | Buffer> | undefined {
if (!this.#waitingToBeSent.length) return; const toSend = this.#waitingToBeSent.shift();
const encoded: Array<string> = [];
let size = 0,
lastCommandChainId: symbol | undefined;
for (const command of this.#waitingToBeSent) {
encoded.push(command.encodedCommand);
size += command.byteLength;
if (size > recommendedSize) {
lastCommandChainId = command.chainId;
break;
}
}
if (!lastCommandChainId && encoded.length === this.#waitingToBeSent.length) {
lastCommandChainId = this.#waitingToBeSent.tail!.value.chainId;
}
lastCommandChainId ??= this.#waitingToBeSent.tail?.value.chainId;
this.#executor(encoded.join(''));
for (let i = 0; i < encoded.length; i++) {
const waitingToBeSent = this.#waitingToBeSent.shift()!;
if (waitingToBeSent.abort) {
waitingToBeSent.abort.signal.removeEventListener('abort', waitingToBeSent.abort.listener);
}
if (toSend) {
this.#waitingForReply.push({ this.#waitingForReply.push({
resolve: waitingToBeSent.resolve, resolve: toSend.resolve,
reject: waitingToBeSent.reject, reject: toSend.reject,
channelsCounter: waitingToBeSent.channelsCounter channelsCounter: toSend.channelsCounter,
bufferMode: toSend.bufferMode
}); });
} }
this.#chainInExecution = lastCommandChainId; this.#chainInExecution = toSend?.chainId;
this.#waitingToBeSentCommandsLength -= size;
return toSend?.args;
} }
parseResponse(data: Buffer): void { parseResponse(data: Buffer): void {
this.#parser.setReturnBuffers(!!this.#waitingForReply.head?.value.bufferMode);
this.#parser.execute(data); this.#parser.execute(data);
} }

View File

@@ -1,6 +1,7 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export function transformArguments(username: string | Array<string>): Array<string> { export function transformArguments(username: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['ACL', 'DELUSER'], username); return pushVerdictArguments(['ACL', 'DELUSER'], username);
} }

View File

@@ -1,6 +1,7 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyString } from './generic-transformers'; import { pushVerdictArguments, transformReplyString } from './generic-transformers';
export function transformArguments(username: string, rule: string | Array<string>): Array<string> { export function transformArguments(username: string, rule: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['ACL', 'SETUSER', username], rule); return pushVerdictArguments(['ACL', 'SETUSER', username], rule);
} }

View File

@@ -1,10 +1,11 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 2; export const FIRST_KEY_INDEX = 2;
type BitOperations = 'AND' | 'OR' | 'XOR' | 'NOT'; type BitOperations = 'AND' | 'OR' | 'XOR' | 'NOT';
export function transformArguments(operation: BitOperations, destKey: string, key: string | Array<string>): Array<string> { export function transformArguments(operation: BitOperations, destKey: string, key: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['BITOP', operation, destKey], key); return pushVerdictArguments(['BITOP', operation, destKey], key);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments } from './generic-transformers'; import { pushVerdictArguments } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(keys: string | Array<string>, timeout: number): Array<string> { export function transformArguments(keys: string | Buffer | Array<string | Buffer>, timeout: number): TransformArgumentsReply {
const args = pushVerdictArguments(['BLPOP'], keys); const args = pushVerdictArguments(['BLPOP'], keys);
args.push(timeout.toString()); args.push(timeout.toString());

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments } from './generic-transformers'; import { pushVerdictArguments } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string | Array<string>, timeout: number): Array<string> { export function transformArguments(key: string | Array<string>, timeout: number): TransformArgumentsReply {
const args = pushVerdictArguments(['BRPOP'], key); const args = pushVerdictArguments(['BRPOP'], key);
args.push(timeout.toString()); args.push(timeout.toString());

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumberInfinity, ZMember } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumberInfinity, ZMember } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string | Array<string>, timeout: number): Array<string> { export function transformArguments(key: string | Array<string>, timeout: number): TransformArgumentsReply {
const args = pushVerdictArguments(['BZPOPMAX'], key); const args = pushVerdictArguments(['BZPOPMAX'], key);
args.push(timeout.toString()); args.push(timeout.toString());

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumberInfinity, ZMember } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumberInfinity, ZMember } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string | Array<string>, timeout: number): Array<string> { export function transformArguments(key: string | Array<string>, timeout: number): TransformArgumentsReply {
const args = pushVerdictArguments(['BZPOPMIN'], key); const args = pushVerdictArguments(['BZPOPMIN'], key);
args.push(timeout.toString()); args.push(timeout.toString());

View File

@@ -1,6 +1,7 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export function transformArguments(keys: string | Array<string>): Array<string> { export function transformArguments(keys: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['DEL'], keys); return pushVerdictArguments(['DEL'], keys);
} }

View File

@@ -1,10 +1,11 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyBoolean } from './generic-transformers'; import { pushVerdictArguments, transformReplyBoolean } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true; export const IS_READ_ONLY = true;
export function transformArguments(keys: string | Array<string>): Array<string> { export function transformArguments(keys: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['EXISTS'], keys); return pushVerdictArguments(['EXISTS'], keys);
} }

View File

@@ -1,10 +1,11 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyStringArray } from './generic-transformers'; import { pushVerdictArguments, transformReplyStringArray } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true; export const IS_READ_ONLY = true;
export function transformArguments(key: string, member: string | Array<string>): Array<string> { export function transformArguments(key: string, member: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['GEOHASH', key], member); return pushVerdictArguments(['GEOHASH', key], member);
} }

View File

@@ -1,10 +1,11 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments } from './generic-transformers'; import { pushVerdictArguments } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true; export const IS_READ_ONLY = true;
export function transformArguments(key: string, member: string | Array<string>): Array<string> { export function transformArguments(key: string, member: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['GEOPOS', key], member); return pushVerdictArguments(['GEOPOS', key], member);
} }

View File

@@ -1,10 +1,11 @@
import { TransformArgumentsReply } from '.';
import { transformReplyString } from './generic-transformers'; import { transformReplyString } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true; export const IS_READ_ONLY = true;
export function transformArguments(key: string): Array<string> { export function transformArguments(key: string | Buffer): TransformArgumentsReply {
return ['GET', key]; return ['GET', key];
} }

View File

@@ -0,0 +1,22 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient, TestRedisClusters, itWithCluster } from '../test-utils';
describe('GET_BUFFER', () => {
itWithClient(TestRedisServers.OPEN, 'client.getBuffer', async client => {
const buffer = Buffer.from('string');
await client.set('key', buffer);
assert.deepEqual(
buffer,
await client.getBuffer('key')
);
});
itWithCluster(TestRedisClusters.OPEN, 'cluster.getBuffer', async cluster => {
const buffer = Buffer.from('string');
await cluster.set('key', buffer);
assert.deepEqual(
buffer,
await cluster.getBuffer('key')
);
});
});

View File

@@ -0,0 +1,7 @@
import { transformReplyBuffer } from './generic-transformers';
export { FIRST_KEY_INDEX, IS_READ_ONLY, transformArguments } from './GET';
export const BUFFER_MODE = true;
export const transformReply = transformReplyBuffer;

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, field: string | Array<string>): Array<string> { export function transformArguments(key: string, field: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['HDEL', key], field); return pushVerdictArguments(['HDEL', key], field);
} }

View File

@@ -1,10 +1,11 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyStringArray } from './generic-transformers'; import { pushVerdictArguments, transformReplyStringArray } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true; export const IS_READ_ONLY = true;
export function transformArguments(key: string, fields: string | Array<string>): Array<string> { export function transformArguments(key: string, fields: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['HMGET', key], fields); return pushVerdictArguments(['HMGET', key], fields);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, elements: string | Array<string>): Array<string> { export function transformArguments(key: string, elements: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['LPUSH', key], elements);} return pushVerdictArguments(['LPUSH', key], elements);}
export const transformReply = transformReplyNumber; export const transformReply = transformReplyNumber;

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, element: string | Array<string>): Array<string> { export function transformArguments(key: string, element: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['LPUSHX', key], element); return pushVerdictArguments(['LPUSHX', key], element);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyBoolean } from './generic-transformers'; import { pushVerdictArguments, transformReplyBoolean } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, element: string | Array<string>): Array<string> { export function transformArguments(key: string, element: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['PFADD', key], element); return pushVerdictArguments(['PFADD', key], element);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string | Array<string>): Array<string> { export function transformArguments(key: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['PFCOUNT'], key); return pushVerdictArguments(['PFCOUNT'], key);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyString } from './generic-transformers'; import { pushVerdictArguments, transformReplyString } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(destination: string, source: string | Array<string>): Array<string> { export function transformArguments(destination: string, source: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['PFMERGE', destination], source); return pushVerdictArguments(['PFMERGE', destination], source);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, element: string | Array<string>): Array<string> { export function transformArguments(key: string, element: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['RPUSH', key], element); return pushVerdictArguments(['RPUSH', key], element);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, element: string | Array<string>): Array<string> { export function transformArguments(key: string, element: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['RPUSHX', key], element); return pushVerdictArguments(['RPUSHX', key], element);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, members: string | Array<string>): Array<string> { export function transformArguments(key: string, members: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['SADD', key], members); return pushVerdictArguments(['SADD', key], members);
} }

View File

@@ -1,6 +1,7 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyBooleanArray } from './generic-transformers'; import { pushVerdictArguments, transformReplyBooleanArray } from './generic-transformers';
export function transformArguments(sha1: string | Array<string>): Array<string> { export function transformArguments(sha1: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['SCRIPT', 'EXISTS'], sha1); return pushVerdictArguments(['SCRIPT', 'EXISTS'], sha1);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyStringArray } from './generic-transformers'; import { pushVerdictArguments, transformReplyStringArray } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(keys: string | Array<string>): Array<string> { export function transformArguments(keys: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['SDIFF'], keys); return pushVerdictArguments(['SDIFF'], keys);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(destination: string, keys: string | Array<string>): Array<string> { export function transformArguments(destination: string, keys: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['SDIFFSTORE', destination], keys); return pushVerdictArguments(['SDIFFSTORE', destination], keys);
} }

View File

@@ -1,3 +1,5 @@
import { TransformArgumentsReply } from '.';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
interface EX { interface EX {
@@ -38,7 +40,7 @@ interface SetCommonOptions {
type SetOptions = SetTTL & SetGuards & (SetCommonOptions | {}); type SetOptions = SetTTL & SetGuards & (SetCommonOptions | {});
export function transformArguments(key: string, value: string, options?: SetOptions): Array<string> { export function transformArguments(key: string | Buffer, value: string | Buffer, options?: SetOptions): TransformArgumentsReply {
const args = ['SET', key, value]; const args = ['SET', key, value];
if (!options) { if (!options) {

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { transformReplyString } from './generic-transformers'; import { transformReplyString } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, seconds: number, value: string): Array<string> { export function transformArguments(key: string | Buffer, seconds: number, value: string): TransformArgumentsReply {
return [ return [
'SETEX', 'SETEX',
key, key,

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyStringArray } from './generic-transformers'; import { pushVerdictArguments, transformReplyStringArray } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(keys: string | Array<string>): Array<string> { export function transformArguments(keys: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['SINTER'], keys); return pushVerdictArguments(['SINTER'], keys);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyStringArray } from './generic-transformers'; import { pushVerdictArguments, transformReplyStringArray } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(destination: string, keys: string | Array<string>): Array<string> { export function transformArguments(destination: string, keys: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['SINTERSTORE', destination], keys); return pushVerdictArguments(['SINTERSTORE', destination], keys);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, members: string | Array<string>): Array<string> { export function transformArguments(key: string, members: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['SREM', key], members); return pushVerdictArguments(['SREM', key], members);
} }

View File

@@ -1,10 +1,11 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyStringArray } from './generic-transformers'; import { pushVerdictArguments, transformReplyStringArray } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true; export const IS_READ_ONLY = true;
export function transformArguments(keys: string | Array<string>): Array<string> { export function transformArguments(keys: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['SUNION'], keys); return pushVerdictArguments(['SUNION'], keys);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(destination: string, keys: string | Array<string>): Array<string> { export function transformArguments(destination: string, keys: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['SUNIONSTORE', destination], keys); return pushVerdictArguments(['SUNIONSTORE', destination], keys);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string | Array<string>): Array<string> { export function transformArguments(key: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['TOUCH'], key); return pushVerdictArguments(['TOUCH'], key);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string | Array<string>): Array<string> { export function transformArguments(key: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['UNLINK'], key); return pushVerdictArguments(['UNLINK'], key);
} }

View File

@@ -1,6 +1,7 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyString } from './generic-transformers'; import { pushVerdictArguments, transformReplyString } from './generic-transformers';
export function transformArguments(key: string | Array<string>): Array<string> { export function transformArguments(key: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['WATCH'], key); return pushVerdictArguments(['WATCH'], key);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, group: string, id: string | Array<string>): Array<string> { export function transformArguments(key: string, group: string, id: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['XACK', key, group], id); return pushVerdictArguments(['XACK', key, group], id);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, id: string | Array<string>): Array<string> { export function transformArguments(key: string, id: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['XDEL', key], id); return pushVerdictArguments(['XDEL', key], id);
} }

View File

@@ -1,10 +1,11 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArgument, transformReplyStringArray } from './generic-transformers'; import { pushVerdictArgument, transformReplyStringArray } from './generic-transformers';
export const FIRST_KEY_INDEX = 2; export const FIRST_KEY_INDEX = 2;
export const IS_READ_ONLY = true; export const IS_READ_ONLY = true;
export function transformArguments(keys: Array<string> | string): Array<string> { export function transformArguments(keys: Array<string> | string): TransformArgumentsReply {
return pushVerdictArgument(['ZDIFF'], keys); return pushVerdictArgument(['ZDIFF'], keys);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArgument, transformReplyNumber } from './generic-transformers'; import { pushVerdictArgument, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(destination: string, keys: Array<string> | string): Array<string> { export function transformArguments(destination: string, keys: Array<string> | string): TransformArgumentsReply {
return pushVerdictArgument(['ZDIFFSTORE', destination], keys); return pushVerdictArgument(['ZDIFFSTORE', destination], keys);
} }

View File

@@ -1,9 +1,10 @@
import { TransformArgumentsReply } from '.';
import { transformReplySortedSetWithScores } from './generic-transformers'; import { transformReplySortedSetWithScores } from './generic-transformers';
import { transformArguments as transformZDiffArguments } from './ZDIFF'; import { transformArguments as transformZDiffArguments } from './ZDIFF';
export { FIRST_KEY_INDEX, IS_READ_ONLY } from './ZDIFF'; export { FIRST_KEY_INDEX, IS_READ_ONLY } from './ZDIFF';
export function transformArguments(...args: Parameters<typeof transformZDiffArguments>): Array<string> { export function transformArguments(...args: Parameters<typeof transformZDiffArguments>): TransformArgumentsReply {
return [ return [
...transformZDiffArguments(...args), ...transformZDiffArguments(...args),
'WITHSCORES' 'WITHSCORES'

View File

@@ -1,3 +1,4 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArgument, transformReplyStringArray } from './generic-transformers'; import { pushVerdictArgument, transformReplyStringArray } from './generic-transformers';
export const FIRST_KEY_INDEX = 2; export const FIRST_KEY_INDEX = 2;
@@ -9,7 +10,7 @@ interface ZInterOptions {
AGGREGATE?: 'SUM' | 'MIN' | 'MAX'; AGGREGATE?: 'SUM' | 'MIN' | 'MAX';
} }
export function transformArguments(keys: Array<string> | string, options?: ZInterOptions): Array<string> { export function transformArguments(keys: Array<string> | string, options?: ZInterOptions): TransformArgumentsReply {
const args = pushVerdictArgument(['ZINTER'], keys); const args = pushVerdictArgument(['ZINTER'], keys);
if (options?.WEIGHTS) { if (options?.WEIGHTS) {

View File

@@ -1,3 +1,4 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArgument, transformReplyNumber } from './generic-transformers'; import { pushVerdictArgument, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
@@ -7,7 +8,7 @@ interface ZInterStoreOptions {
AGGREGATE?: 'SUM' | 'MIN' | 'MAX'; AGGREGATE?: 'SUM' | 'MIN' | 'MAX';
} }
export function transformArguments(destination: string, keys: Array<string> | string, options?: ZInterStoreOptions): Array<string> { export function transformArguments(destination: string, keys: Array<string> | string, options?: ZInterStoreOptions): TransformArgumentsReply {
const args = pushVerdictArgument(['ZINTERSTORE', destination], keys); const args = pushVerdictArgument(['ZINTERSTORE', destination], keys);
if (options?.WEIGHTS) { if (options?.WEIGHTS) {

View File

@@ -1,9 +1,10 @@
import { TransformArgumentsReply } from '.';
import { transformReplySortedSetWithScores } from './generic-transformers'; import { transformReplySortedSetWithScores } from './generic-transformers';
import { transformArguments as transformZInterArguments } from './ZINTER'; import { transformArguments as transformZInterArguments } from './ZINTER';
export { FIRST_KEY_INDEX, IS_READ_ONLY } from './ZINTER'; export { FIRST_KEY_INDEX, IS_READ_ONLY } from './ZINTER';
export function transformArguments(...args: Parameters<typeof transformZInterArguments>): Array<string> { export function transformArguments(...args: Parameters<typeof transformZInterArguments>): TransformArgumentsReply {
return [ return [
...transformZInterArguments(...args), ...transformZInterArguments(...args),
'WITHSCORES' 'WITHSCORES'

View File

@@ -1,10 +1,11 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumberInfinityNullArray } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumberInfinityNullArray } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true; export const IS_READ_ONLY = true;
export function transformArguments(key: string, member: string | Array<string>): Array<string> { export function transformArguments(key: string, member: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['ZMSCORE', key], member); return pushVerdictArguments(['ZMSCORE', key], member);
} }

View File

@@ -1,8 +1,9 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers'; import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, member: string | Array<string>): Array<string> { export function transformArguments(key: string, member: string | Array<string>): TransformArgumentsReply {
return pushVerdictArguments(['ZREM', key], member); return pushVerdictArguments(['ZREM', key], member);
} }

View File

@@ -1,3 +1,4 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArgument, transformReplyStringArray } from './generic-transformers'; import { pushVerdictArgument, transformReplyStringArray } from './generic-transformers';
export const FIRST_KEY_INDEX = 2; export const FIRST_KEY_INDEX = 2;
@@ -9,7 +10,7 @@ interface ZUnionOptions {
AGGREGATE?: 'SUM' | 'MIN' | 'MAX'; AGGREGATE?: 'SUM' | 'MIN' | 'MAX';
} }
export function transformArguments(keys: Array<string> | string, options?: ZUnionOptions): Array<string> { export function transformArguments(keys: Array<string> | string, options?: ZUnionOptions): TransformArgumentsReply {
const args = pushVerdictArgument(['ZUNION'], keys); const args = pushVerdictArgument(['ZUNION'], keys);
if (options?.WEIGHTS) { if (options?.WEIGHTS) {

View File

@@ -1,3 +1,4 @@
import { TransformArgumentsReply } from '.';
import { pushVerdictArgument, transformReplyNumber } from './generic-transformers'; import { pushVerdictArgument, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1; export const FIRST_KEY_INDEX = 1;
@@ -7,7 +8,7 @@ interface ZUnionOptions {
AGGREGATE?: 'SUM' | 'MIN' | 'MAX'; AGGREGATE?: 'SUM' | 'MIN' | 'MAX';
} }
export function transformArguments(destination: string, keys: Array<string> | string, options?: ZUnionOptions): Array<string> { export function transformArguments(destination: string, keys: Array<string> | string, options?: ZUnionOptions): TransformArgumentsReply {
const args = pushVerdictArgument(['ZUNIONSTORE', destination], keys); const args = pushVerdictArgument(['ZUNIONSTORE', destination], keys);
if (options?.WEIGHTS) { if (options?.WEIGHTS) {

View File

@@ -1,9 +1,10 @@
import { TransformArgumentsReply } from '.';
import { transformReplySortedSetWithScores } from './generic-transformers'; import { transformReplySortedSetWithScores } from './generic-transformers';
import { transformArguments as transformZUnionArguments } from './ZUNION'; import { transformArguments as transformZUnionArguments } from './ZUNION';
export { FIRST_KEY_INDEX, IS_READ_ONLY } from './ZUNION'; export { FIRST_KEY_INDEX, IS_READ_ONLY } from './ZUNION';
export function transformArguments(...args: Parameters<typeof transformZUnionArguments>): Array<string> { export function transformArguments(...args: Parameters<typeof transformZUnionArguments>): TransformArgumentsReply {
return [ return [
...transformZUnionArguments(...args), ...transformZUnionArguments(...args),
'WITHSCORES' 'WITHSCORES'

View File

@@ -20,6 +20,10 @@ export function transformReplyString(reply: string): string {
return reply; return reply;
} }
export function transformReplyBuffer(reply: Buffer): Buffer {
return reply;
}
export function transformReplyStringNull(reply: string | null): string | null { export function transformReplyStringNull(reply: string | null): string | null {
return reply; return reply;
} }
@@ -352,11 +356,11 @@ export function pushStringTuplesArguments(args: Array<string>, tuples: StringTup
return args; return args;
} }
export function pushVerdictArguments(args: TransformArgumentsReply, value: string | Array<string>): TransformArgumentsReply { export function pushVerdictArguments(args: TransformArgumentsReply, value: string | Buffer | Array<string | Buffer>): TransformArgumentsReply {
if (typeof value === 'string') { if (Array.isArray(value)) {
args.push(value);
} else {
args.push(...value); args.push(...value);
} else {
args.push(value);
} }
return args; return args;

View File

@@ -61,6 +61,7 @@ import * as GEOPOS from './GEOPOS';
import * as GEOSEARCH_WITH from './GEOSEARCH_WITH'; import * as GEOSEARCH_WITH from './GEOSEARCH_WITH';
import * as GEOSEARCH from './GEOSEARCH'; import * as GEOSEARCH from './GEOSEARCH';
import * as GEOSEARCHSTORE from './GEOSEARCHSTORE'; import * as GEOSEARCHSTORE from './GEOSEARCHSTORE';
import * as GET_BUFFER from './GET_BUFFER';
import * as GET from './GET'; import * as GET from './GET';
import * as GETBIT from './GETBIT'; import * as GETBIT from './GETBIT';
import * as GETDEL from './GETDEL'; import * as GETDEL from './GETDEL';
@@ -370,6 +371,8 @@ export default {
geoSearch: GEOSEARCH, geoSearch: GEOSEARCH,
GEOSEARCHSTORE, GEOSEARCHSTORE,
geoSearchStore: GEOSEARCHSTORE, geoSearchStore: GEOSEARCHSTORE,
GET_BUFFER,
getBuffer: GET_BUFFER,
GET, GET,
get: GET, get: GET,
GETBIT, GETBIT,
@@ -733,15 +736,16 @@ export default {
zUnionStore: ZUNIONSTORE zUnionStore: ZUNIONSTORE
}; };
export type RedisReply = string | number | Array<RedisReply> | null | undefined; export type RedisReply = string | number | Buffer | Array<RedisReply> | null | undefined;
export type TransformArgumentsReply = Array<string> & { preserve?: unknown }; export type TransformArgumentsReply = Array<string | Buffer> & { preserve?: unknown };
export interface RedisCommand { export interface RedisCommand {
FIRST_KEY_INDEX?: number | ((...args: Array<any>) => string); FIRST_KEY_INDEX?: number | ((...args: Array<any>) => string);
IS_READ_ONLY?: boolean; IS_READ_ONLY?: boolean;
transformArguments(...args: Array<any>): TransformArgumentsReply; transformArguments(...args: Array<any>): TransformArgumentsReply;
transformReply(reply: RedisReply, preserved: unknown): any; BUFFER_MODE?: boolean;
transformReply(reply: RedisReply, preserved?: unknown): any;
} }
export interface RedisCommands { export interface RedisCommands {

View File

@@ -1,6 +1,5 @@
import { strict as assert } from 'assert'; import { strict as assert } from 'assert';
import RedisMultiCommand from './multi-command'; import RedisMultiCommand from './multi-command';
import { encodeCommand } from './commander';
import { WatchError } from './errors'; import { WatchError } from './errors';
import { spy } from 'sinon'; import { spy } from 'sinon';
import { SQUARE_SCRIPT } from './client.spec'; import { SQUARE_SCRIPT } from './client.spec';
@@ -10,11 +9,11 @@ describe('Multi Command', () => {
it('simple', async () => { it('simple', async () => {
const multi = RedisMultiCommand.create((queue, symbol) => { const multi = RedisMultiCommand.create((queue, symbol) => {
assert.deepEqual( assert.deepEqual(
queue.map(({encodedCommand}) => encodedCommand), queue.map(({ args }) => args),
[ [
encodeCommand(['MULTI']), ['MULTI'],
encodeCommand(['PING']), ['PING'],
encodeCommand(['EXEC']), ['EXEC'],
] ]
); );
@@ -55,8 +54,8 @@ describe('Multi Command', () => {
it('execAsPipeline', async () => { it('execAsPipeline', async () => {
const multi = RedisMultiCommand.create(queue => { const multi = RedisMultiCommand.create(queue => {
assert.deepEqual( assert.deepEqual(
queue.map(({encodedCommand}) => encodedCommand), queue.map(({ args }) => args),
[encodeCommand(['PING'])] [['PING']]
); );
return Promise.resolve(['PONG']); return Promise.resolve(['PONG']);
@@ -75,8 +74,8 @@ describe('Multi Command', () => {
it('simple', async () => { it('simple', async () => {
const multi = RedisMultiCommand.create(queue => { const multi = RedisMultiCommand.create(queue => {
assert.deepEqual( assert.deepEqual(
queue.map(({encodedCommand}) => encodedCommand), queue.map(({ args }) => args),
[encodeCommand(['PING'])] [['PING']]
); );
return Promise.resolve(['PONG']); return Promise.resolve(['PONG']);
@@ -111,10 +110,10 @@ describe('Multi Command', () => {
assert.deepEqual( assert.deepEqual(
await new MultiWithScript(queue => { await new MultiWithScript(queue => {
assert.deepEqual( assert.deepEqual(
queue.map(({encodedCommand}) => encodedCommand), queue.map(({ args }) => args),
[ [
encodeCommand(['EVAL', SQUARE_SCRIPT.SCRIPT, '0', '2']), ['EVAL', SQUARE_SCRIPT.SCRIPT, '0', '2'],
encodeCommand(['EVALSHA', SQUARE_SCRIPT.SHA1, '0', '3']), ['EVALSHA', SQUARE_SCRIPT.SHA1, '0', '3'],
] ]
); );

View File

@@ -2,7 +2,7 @@ import COMMANDS, { TransformArgumentsReply } from './commands';
import { RedisCommand, RedisModules, RedisReply } from './commands'; import { RedisCommand, RedisModules, RedisReply } from './commands';
import { RedisLuaScript, RedisLuaScripts } from './lua-script'; import { RedisLuaScript, RedisLuaScripts } from './lua-script';
import { RedisClientOptions } from './client'; import { RedisClientOptions } from './client';
import { extendWithModulesAndScripts, extendWithDefaultCommands, encodeCommand } from './commander'; import { extendWithModulesAndScripts, extendWithDefaultCommands } from './commander';
import { WatchError } from './errors'; import { WatchError } from './errors';
type RedisMultiCommandSignature<C extends RedisCommand, M extends RedisModules, S extends RedisLuaScripts> = (...args: Parameters<C['transformArguments']>) => RedisMultiCommandType<M, S>; type RedisMultiCommandSignature<C extends RedisCommand, M extends RedisModules, S extends RedisLuaScripts> = (...args: Parameters<C['transformArguments']>) => RedisMultiCommandType<M, S>;
@@ -24,7 +24,7 @@ type WithScripts<M extends RedisModules, S extends RedisLuaScripts> = {
export type RedisMultiCommandType<M extends RedisModules, S extends RedisLuaScripts> = RedisMultiCommand & WithCommands<M, S> & WithModules<M, S> & WithScripts<M, S>; export type RedisMultiCommandType<M extends RedisModules, S extends RedisLuaScripts> = RedisMultiCommand & WithCommands<M, S> & WithModules<M, S> & WithScripts<M, S>;
export interface MultiQueuedCommand { export interface MultiQueuedCommand {
encodedCommand: string; args: TransformArgumentsReply;
preservedArguments?: unknown; preservedArguments?: unknown;
transformReply?: RedisCommand['transformReply']; transformReply?: RedisCommand['transformReply'];
} }
@@ -62,7 +62,9 @@ export default class RedisMultiCommand<M extends RedisModules = RedisModules, S
const scriptArguments = script.transformArguments(...args); const scriptArguments = script.transformArguments(...args);
transformedArguments.push(...scriptArguments); transformedArguments.push(...scriptArguments);
transformedArguments.preserve = scriptArguments.preserve; if (scriptArguments.preserve) {
transformedArguments.preserve = scriptArguments.preserve;
}
return this.addCommand( return this.addCommand(
transformedArguments, transformedArguments,
@@ -119,7 +121,7 @@ export default class RedisMultiCommand<M extends RedisModules = RedisModules, S
this.#v4.addCommand = this.addCommand.bind(this); this.#v4.addCommand = this.addCommand.bind(this);
(this as any).addCommand = (...args: Array<unknown>): this => { (this as any).addCommand = (...args: Array<unknown>): this => {
this.#queue.push({ this.#queue.push({
encodedCommand: encodeCommand(args.flat() as Array<string>) args: args.flat() as Array<string>
}); });
return this; return this;
} }
@@ -153,7 +155,7 @@ export default class RedisMultiCommand<M extends RedisModules = RedisModules, S
addCommand(args: TransformArgumentsReply, transformReply?: RedisCommand['transformReply']): this { addCommand(args: TransformArgumentsReply, transformReply?: RedisCommand['transformReply']): this {
this.#queue.push({ this.#queue.push({
encodedCommand: encodeCommand(args), args,
preservedArguments: args.preserve, preservedArguments: args.preserve,
transformReply transformReply
}); });
@@ -170,13 +172,9 @@ export default class RedisMultiCommand<M extends RedisModules = RedisModules, S
const queue = this.#queue.splice(0), const queue = this.#queue.splice(0),
rawReplies = await this.#executor([ rawReplies = await this.#executor([
{ { args: ['MULTI'] },
encodedCommand: encodeCommand(['MULTI'])
},
...queue, ...queue,
{ { args: ['EXEC'] }
encodedCommand: encodeCommand(['EXEC'])
}
], Symbol('[RedisMultiCommand] Chain ID')), ], Symbol('[RedisMultiCommand] Chain ID')),
execReply = rawReplies[rawReplies.length - 1] as (null | Array<RedisReply>); execReply = rawReplies[rawReplies.length - 1] as (null | Array<RedisReply>);

View File

@@ -91,10 +91,8 @@ export default class RedisSocket extends EventEmitter {
return this.#isOpen; return this.#isOpen;
} }
get chunkRecommendedSize(): number { get isSocketExists(): boolean {
if (!this.#socket) return 0; return !!this.#socket;
return this.#socket.writableHighWaterMark - this.#socket.writableLength;
} }
constructor(initiator?: RedisSocketInitiator, options?: RedisSocketOptions) { constructor(initiator?: RedisSocketInitiator, options?: RedisSocketOptions) {
@@ -214,12 +212,12 @@ export default class RedisSocket extends EventEmitter {
.catch(err => this.emit('error', err)); .catch(err => this.emit('error', err));
} }
write(encodedCommands: string): boolean { write(toWrite: string | Buffer): boolean {
if (!this.#socket) { if (!this.#socket) {
throw new ClientClosedError(); throw new ClientClosedError();
} }
return this.#socket.write(encodedCommands); return this.#socket.write(toWrite);
} }
async disconnect(ignoreIsOpen = false): Promise<void> { async disconnect(ignoreIsOpen = false): Promise<void> {
@@ -251,4 +249,22 @@ export default class RedisSocket extends EventEmitter {
throw err; throw err;
} }
} }
#isCorked = false;
cork(): void {
if (!this.#socket) {
return;
}
if (!this.#isCorked) {
this.#socket.cork();
this.#isCorked = true;
queueMicrotask(() => {
this.#socket?.uncork();
this.#isCorked = false;
});
}
}
} }

View File

@@ -1,3 +1,3 @@
declare module 'cluster-key-slot' { declare module 'cluster-key-slot' {
export default function calculateSlot(key: string): number; export default function calculateSlot(key: string | Buffer): number;
} }

View File

@@ -8,6 +8,8 @@ declare module 'redis-parser' {
export default class RedisParser { export default class RedisParser {
constructor(callbacks: RedisParserCallbacks); constructor(callbacks: RedisParserCallbacks);
setReturnBuffers(returnBuffers?: boolean): void;
execute(buffer: Buffer): void; execute(buffer: Buffer): void;
} }
} }