1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-07 13:22:56 +03:00
Files
node-redis/lib/multi-command.ts
Leibale Eidelman 4e6d018d77 V4 (#1624)
* init v4

* add .gitignore to benchmark

* spawn redis-servers for tests,
add some tests,
fix client auth on connect

* add tests coverage report

* add tests workflow, replace nyc text reporter with text-summary

* run tests with node 16.x & redis 6.x only (for now)

* add socket events on client,
stop reconnectiong when manually calling disconnect,
remove abort signal listener when a command is written on the socket

* add isOpen boolean getter on client, add maxLength option to command queue, add test for client.multi

* move to use CommonJS

* add MULTI and EXEC commands to when executing multi command, make client.multi return type innerit the module commands, clean some tests, exclute spec files from coverage report

* missing file from commit 61edd4f1b5

* exclude spec files from coverage report

* add support for options in a command function (.get, .set, ...), add support for the SELECT command, implement a couple of commands, fix client socket reconnection strategy, add support for using replicas (RO) in cluster, and more..

* fix client.blPop test

* use which to find redis-server path

* change command options to work with Symbol rather then WeakSet

* implement more commands

* Add support for lua scripts in client & muilti, fix client socket initiator, implement simple cluster nodes discovery strategy

* replace `callbackify` with `legacyMode`

* add the SCAN command and client.scanIterator

* rename scanIterator

* init benchmark workflow

* fix benchmark workflow

* fix benchmark workflow

* fix benchmark workflow

* push coverage report to Coveralls

* fix Coveralls

* generator lcov (for Coveralls)

* fix .nycrc.json

* PubSub

* add support for all set commands (including sScanIterator)

* support pipeline

* fix KEEPTTL in SET

* remove console.log

* add HyperLogLog commands

* update README.md (thanks to @guyroyse)

* add support for most of the "keys commands"

* fix EXPIREAT.spec.ts

* add support for date in both EXPIREAT & EXPIRE

* add tests

* better cluster nodes discorvery strategy after MOVED error, add PubSub test

* fix PubSub UNSUBSCRIBE/PUNSUBSCRIBE without channel and/or listener

* fix PubSub

* add release-it to dev dependencies

* Release 4.0.0-next.0

* fix .npmignore

* Release 4.0.0-next.1

* fix links in README.md

* fix .npmignore

* Release 4.0.0-next.2

* add support for all sorted set commands

* add support for most stream commands

* add missing file from commit 53de279afe

* lots of todo commends

* make PubSub test more stable

* clean ZPOPMAX

* add support for lua scripts and modules in cluster, spawn cluster for tests, add some cluster tests, fix pubsub listener arguments

* GET.spec.ts

* add support for List commands, fix some Sorted Set commands, add some cluster commands, spawn cluster for testing, add support for command options in cluster, and more

* add missing file from commit faab94fab2

* clean ZRANK and ZREVRANK

* add XREAD and XREADGROUP commands

* remove unused files

* implement a couple of more commands, make cluster random iterator be per node (instead of per slot)

* Release 4.0.0-next.3

* app spec files to npmignore

* fix some code analyzers (LGTM, deepsource, codeclimate) issues

* fix CLUSTER_NODES, add some tests

* add HSCAN, clean some commands, add tests for generic transformers

* add missing files from 0feb35a1fb

* update README.md (thanks to @guyroyse)

* handle ASK errors, add some commands and tests

* Release 4.0.0-next.4

* replace "modern" with "v4"

* remove unused imports

* add all ACL subcommands, all MODULE subcommands, and some other commands

* remove 2 unused imports

* fix BITFIELD command

* fix XTRIM spec file

* clean code

* fix package.json types field

* better modules support, fix some bugs in legacy mode, add some tests

* remove unused function

* add test for hScanIterator

* change node mimimum version to 12 (latest LTS)

* update tsconfig.json to support node 12, run tests on Redis 5 & 6 and on all node live versions

* remove future node releases :P

* remove "lib" from ts compiler options

* Update tsconfig.json

* fix build

* run some tests only on supported redis versions, use coveralls parallel mode

* fix tests

* Do not use "timers/promises", fix "isRedisVersionGreaterThan"

* skip AbortController tests when not available

* use 'fs'.promises instead of 'fs/promises'

* add some missing commands

* run GETDEL tests only if the redis version is greater than 6.2

* implement some GEO commands, improve scan generic transformer, expose RPUSHX

* fix GEOSEARCH & GEOSEARCHSTORE

* use socket.setNoDelay and queueMicrotask to improve latency

* commands-queue.ts: String length / byte length counting issue (#1630)

* Update commands-queue.ts

Hopefully fixing #1628

* Reverted 2fa5ea6, and implemented test for byte length check

* Changed back to Buffer.byteLength, due to issue author input. Updated test to look for 4 bytes.

* Fixed. There were two places that length was calculated.

* Removed redundant string assignment

* add 2 bytes test as well

Co-authored-by: Leibale Eidelman <leibale1998@gmail.com>

* fix scripts in multi

* do not hide bugs in redis

* fix for e7bf09644b

* remove unused import

* implement WATCH command, fix ZRANGESTORE & GEOSEARCHSTORE tests

* update README.md

Co-authored-by: @GuyRoyse

* use typedoc to auto generate documentation

* run "npm install" before "npm run documentation"

* clean documentation workflow

* fix WATCH spec file

* increase "CLUSTER_NODE_TIMEOUT" to 5000ms to avoid "CLUSTERDOWN" errors in tests

* pull cluster state every 100 ms

* await meetPromises before pulling the cluster state

* enhance the way commanders (client/multi/cluster) get extended with modules and scripts

* add test for socket retry strategy

* implement more commands

* set GETEX minimum version to 6.2

* remove unused imports

* add support for multi in cluster

* upgrade dependencies

* Release 4.0.0-next.5

* remove unused imports

* improve benchmarking

* use the same Multi with duplicated clients

* exclude some files from the documentation, add some exports, clean code

* fix #1636 - handle null in multi.exec

* remove unused import

* add supoprt for tuples in HSET

* add FIRST_KEY_INDEX to HSET

* add a bunch of missing commands, fix MSET and HELLO, add some tests

* add FIRST_KEY_INDEX to MSET and MSETNX

* upgrade actions

* fix coverallsapp/github-action version

* Update documentation.yml

* Update documentation.yml

* clean code

* remove unused imports

* use "npm ci" instead of "npm install"

* fix `self` binding on client modules, use connection pool for `duplicateConnection`

* add client.executeIsolated, rename "duplicateConnection" to "isolated", update README.md (thanks to @GuyRoyse and @SimonPrickett)

* update README (thanks to @GuyRoyse), add some tests

* try to fix "cluster is down" errors in tests

* try to fix "cluster is down" errors in tests

* upgrade dependencies

* update package-lock

* Release 4.0.0-next.6

* fix #1636 - fix WatchError

* fix for f1bf0beebf - remove .only from multi tests

* Release 4.0.0-next.7

* update README and other markdown files

Co-authored-by: @GuyRoyse & @SimonPrickett

* Doc updates. (#1640)

* update docs, upgrade dependencies

* fix README

* Release 4.0.0-rc.0

* Update README.md

* update docs, add `connectTimeout` options, fix tls

Co-authored-by: Guy Royse <guy@guyroyse.com>

* npm update, "fix" some tests, clean code

* fix AssertionError import

* fix #1642 - fix XREAD, XREADGROUP and XTRIM

* fix #1644 - add the QUIT command

* add socket.noDelay and socket.keepAlive configurations

* Update README.md (#1645)

* Update README.md

Fixed issue with how connection string was specified.
Now you can have user@host without having to specify a password, which just makes more sense

* Update client-configuration.md as well

Co-authored-by: Leibale Eidelman <leibale1998@gmail.com>

* update socket.reconnectStrategy description

* fix borken link in v3-to-v4.md

* increase test coverage, fix bug in cluster redirection strategy, implement CLIENT_ID, remove unused EXEC command

Co-authored-by: Nova <novaw@warrenservices.co.uk>
Co-authored-by: Simon Prickett <simon@crudworks.org>
Co-authored-by: Guy Royse <guy@guyroyse.com>
2021-09-02 10:04:48 -04:00

211 lines
7.2 KiB
TypeScript

import COMMANDS, { TransformArgumentsReply } from './commands';
import { RedisCommand, RedisModules, RedisReply } from './commands';
import { RedisLuaScript, RedisLuaScripts } from './lua-script';
import { RedisClientOptions } from './client';
import { extendWithModulesAndScripts, extendWithDefaultCommands, encodeCommand } from './commander';
import { WatchError } from './errors';
type RedisMultiCommandSignature<C extends RedisCommand, M extends RedisModules, S extends RedisLuaScripts> = (...args: Parameters<C['transformArguments']>) => RedisMultiCommandType<M, S>;
type WithCommands<M extends RedisModules, S extends RedisLuaScripts> = {
[P in keyof typeof COMMANDS]: RedisMultiCommandSignature<(typeof COMMANDS)[P], M, S>
};
type WithModules<M extends RedisModules, S extends RedisLuaScripts> = {
[P in keyof M]: {
[C in keyof M[P]]: RedisMultiCommandSignature<M[P][C], M, S>;
};
};
type WithScripts<M extends RedisModules, S extends RedisLuaScripts> = {
[P in keyof S]: RedisMultiCommandSignature<S[P], M, S>
};
export type RedisMultiCommandType<M extends RedisModules, S extends RedisLuaScripts> = RedisMultiCommand & WithCommands<M, S> & WithModules<M, S> & WithScripts<M, S>;
export interface MultiQueuedCommand {
encodedCommand: string;
preservedArguments?: unknown;
transformReply?: RedisCommand['transformReply'];
}
export type RedisMultiExecutor = (queue: Array<MultiQueuedCommand>, chainId?: symbol) => Promise<Array<RedisReply>>;
export default class RedisMultiCommand<M extends RedisModules = RedisModules, S extends RedisLuaScripts = RedisLuaScripts> {
static commandsExecutor(this: RedisMultiCommand, command: RedisCommand, args: Array<unknown>): RedisMultiCommand {
return this.addCommand(
command.transformArguments(...args),
command.transformReply
);
}
static #scriptsExecutor(
this: RedisMultiCommand,
script: RedisLuaScript,
args: Array<unknown>
): RedisMultiCommand {
const transformedArguments: TransformArgumentsReply = [];
if (this.#scriptsInUse.has(script.SHA1)) {
transformedArguments.push(
'EVALSHA',
script.SHA1
);
} else {
this.#scriptsInUse.add(script.SHA1);
transformedArguments.push(
'EVAL',
script.SCRIPT
);
}
transformedArguments.push(script.NUMBER_OF_KEYS.toString());
const scriptArguments = script.transformArguments(...args);
transformedArguments.push(...scriptArguments);
transformedArguments.preserve = scriptArguments.preserve;
return this.addCommand(
transformedArguments,
script.transformReply
);
}
static extend<M extends RedisModules, S extends RedisLuaScripts>(
clientOptions?: RedisClientOptions<M, S>
): new (...args: ConstructorParameters<typeof RedisMultiCommand>) => RedisMultiCommandType<M, S> {
return <any>extendWithModulesAndScripts({
BaseClass: RedisMultiCommand,
modules: clientOptions?.modules,
modulesCommandsExecutor: RedisMultiCommand.commandsExecutor,
scripts: clientOptions?.scripts,
scriptsExecutor: RedisMultiCommand.#scriptsExecutor
});
}
static create<M extends RedisModules, S extends RedisLuaScripts>(
executor: RedisMultiExecutor,
clientOptions?: RedisClientOptions<M, S>
): RedisMultiCommandType<M, S> {
return <any>new this(executor, clientOptions);
}
readonly #executor: RedisMultiExecutor;
readonly #clientOptions: RedisClientOptions<M, S> | undefined;
readonly #queue: Array<MultiQueuedCommand> = [];
readonly #scriptsInUse = new Set<string>();
readonly #v4: Record<string, any> = {};
get v4(): Record<string, any> {
if (!this.#clientOptions?.legacyMode) {
throw new Error('client is not in "legacy mode"');
}
return this.#v4;
}
constructor(executor: RedisMultiExecutor, clientOptions?: RedisClientOptions<M, S>) {
this.#executor = executor;
this.#clientOptions = clientOptions;
this.#legacyMode();
}
#legacyMode(): void {
if (!this.#clientOptions?.legacyMode) return;
this.#v4.addCommand = this.addCommand.bind(this);
(this as any).addCommand = (...args: Array<unknown>): this => {
this.#queue.push({
encodedCommand: encodeCommand(args.flat() as Array<string>)
});
return this;
}
this.#v4.exec = this.exec.bind(this);
(this as any).exec = (callback?: (err: Error | null, replies?: Array<unknown>) => unknown): void => {
this.#v4.exec()
.then((reply: Array<unknown>) => {
if (!callback) return;
callback(null, reply);
})
.catch((err: Error) => {
if (!callback) {
// this.emit('error', err);
return;
}
callback(err);
});
};
for (const name of Object.keys(COMMANDS)) {
this.#defineLegacyCommand(name);
}
}
#defineLegacyCommand(name: string): void {
(this as any).#v4[name] = (this as any)[name].bind(this.#v4);
(this as any)[name] = (...args: Array<unknown>): void => (this as any).addCommand(name, args);
}
addCommand(args: TransformArgumentsReply, transformReply?: RedisCommand['transformReply']): this {
this.#queue.push({
encodedCommand: encodeCommand(args),
preservedArguments: args.preserve,
transformReply
});
return this;
}
async exec(execAsPipeline = false): Promise<Array<RedisReply>> {
if (execAsPipeline) {
return this.execAsPipeline();
} else if (!this.#queue.length) {
return [];
}
const queue = this.#queue.splice(0),
rawReplies = await this.#executor([
{
encodedCommand: encodeCommand(['MULTI'])
},
...queue,
{
encodedCommand: encodeCommand(['EXEC'])
}
], Symbol('[RedisMultiCommand] Chain ID')),
execReply = rawReplies[rawReplies.length - 1] as (null | Array<RedisReply>);
if (execReply === null) {
throw new WatchError();
}
return this.#transformReplies(execReply, queue);
}
async execAsPipeline(): Promise<Array<RedisReply>> {
if (!this.#queue.length) {
return [];
}
const queue = this.#queue.splice(0);
return this.#transformReplies(
await this.#executor(queue),
queue
);
}
#transformReplies(rawReplies: Array<RedisReply>, queue: Array<MultiQueuedCommand>): Array<RedisReply> {
return rawReplies.map((reply, i) => {
const { transformReply, preservedArguments } = queue[i];
return transformReply ? transformReply(reply, preservedArguments) : reply;
});
}
}
extendWithDefaultCommands(RedisMultiCommand, RedisMultiCommand.commandsExecutor);