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

ref #1653 - better types

This commit is contained in:
leibale
2021-09-18 05:52:54 -04:00
parent d5fa6a3dc6
commit 0ab2245049
8 changed files with 176 additions and 190 deletions

View File

@@ -13,7 +13,7 @@ import { encodeCommand, extendWithDefaultCommands, extendWithModulesAndScripts,
import { Pool, Options as PoolOptions, createPool } from 'generic-pool';
import { ClientClosedError } from './errors';
export interface RedisClientOptions<M = RedisModules, S = RedisLuaScripts> {
export interface RedisClientOptions<M, S> {
socket?: RedisSocketOptions;
modules?: M;
scripts?: S;
@@ -43,51 +43,25 @@ type WithScripts<S extends RedisLuaScripts> = {
export type WithPlugins<M extends RedisModules, S extends RedisLuaScripts> =
WithCommands & WithModules<M> & WithScripts<S>;
export type RedisClientType<M extends RedisModules, S extends RedisLuaScripts> =
export type RedisClientType<M extends RedisModules = {}, S extends RedisLuaScripts = {}> =
WithPlugins<M, S> & RedisClient<M, S>;
export interface ClientCommandOptions extends QueueCommandOptions {
isolated?: boolean;
}
export default class RedisClient<M extends RedisModules = RedisModules, S extends RedisLuaScripts = RedisLuaScripts> extends EventEmitter {
export default class RedisClient<M extends RedisModules, S extends RedisLuaScripts> extends EventEmitter {
static commandOptions(options: ClientCommandOptions): CommandOptions<ClientCommandOptions> {
return commandOptions(options);
}
static async commandsExecutor(
this: RedisClient,
command: RedisCommand,
args: Array<unknown>
): Promise<ReturnType<typeof command['transformReply']>> {
const { args: redisArgs, options } = transformCommandArguments<ClientCommandOptions>(command, args);
return command.transformReply(
await this.#sendCommand(redisArgs, options, command.BUFFER_MODE),
redisArgs.preserve,
);
}
static async #scriptsExecutor(
this: RedisClient,
script: RedisLuaScript,
args: Array<unknown>
): Promise<typeof script['transformArguments']> {
const { args: redisArgs, options } = transformCommandArguments<ClientCommandOptions>(script, args);
return script.transformReply(
await this.executeScript(script, redisArgs, options, script.BUFFER_MODE),
redisArgs.preserve
);
}
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> {
const Client = (<any>extendWithModulesAndScripts({
BaseClass: RedisClient,
modules: options?.modules,
modulesCommandsExecutor: RedisClient.commandsExecutor,
modulesCommandsExecutor: RedisClient.prototype.commandsExecutor,
scripts: options?.scripts,
scriptsExecutor: RedisClient.#scriptsExecutor
scriptsExecutor: RedisClient.prototype.scriptsExecutor
}));
if (Client !== RedisClient) {
@@ -104,7 +78,7 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
readonly #v4: Record<string, any> = {};
#selectedDB = 0;
get options(): RedisClientOptions<M> | null | undefined {
get options(): RedisClientOptions<M, S> | undefined {
return this.#options;
}
@@ -240,6 +214,72 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
await this.#socket.connect();
}
async commandsExecutor(command: RedisCommand, args: Array<unknown>): Promise<ReturnType<typeof command['transformReply']>> {
const { args: redisArgs, options } = transformCommandArguments<ClientCommandOptions>(command, args);
return command.transformReply(
await this.#sendCommand(redisArgs, options, command.BUFFER_MODE),
redisArgs.preserve,
);
}
sendCommand<T = RedisReply>(args: TransformArgumentsReply, options?: ClientCommandOptions, bufferMode?: boolean): Promise<T> {
return this.#sendCommand(args, options, bufferMode);
}
// using `#sendCommand` cause `sendCommand` is overwritten in legacy mode
async #sendCommand<T = RedisReply>(args: TransformArgumentsReply, options?: ClientCommandOptions, bufferMode?: boolean): Promise<T> {
if (!this.#socket.isOpen) {
throw new ClientClosedError();
}
if (options?.isolated) {
return this.executeIsolated(isolatedClient =>
isolatedClient.sendCommand(args, {
...options,
isolated: false
})
);
}
const promise = this.#queue.addCommand<T>(args, options, bufferMode);
this.#tick();
return await promise;
}
async scriptsExecutor(script: RedisLuaScript, args: Array<unknown>): Promise<ReturnType<typeof script['transformReply']>> {
const { args: redisArgs, options } = transformCommandArguments<ClientCommandOptions>(script, args);
return script.transformReply(
await this.executeScript(script, redisArgs, options, script.BUFFER_MODE),
redisArgs.preserve
);
}
async executeScript(script: RedisLuaScript, args: TransformArgumentsReply, options?: ClientCommandOptions, bufferMode?: boolean): Promise<ReturnType<typeof script['transformReply']>> {
try {
return await this.#sendCommand([
'EVALSHA',
script.SHA1,
script.NUMBER_OF_KEYS.toString(),
...args
], options, bufferMode);
} catch (err: any) {
if (!err?.message?.startsWith?.('NOSCRIPT')) {
throw err;
}
return await this.#sendCommand([
'EVAL',
script.SCRIPT,
script.NUMBER_OF_KEYS.toString(),
...args
], options, bufferMode);
}
}
async SELECT(db: number): Promise<void>;
async SELECT(options: CommandOptions<ClientCommandOptions>, db: number): Promise<void>;
async SELECT(options?: any, db?: any): Promise<void> {
@@ -300,30 +340,6 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
quit = this.QUIT;
sendCommand<T = RedisReply>(args: TransformArgumentsReply, options?: ClientCommandOptions, bufferMode?: boolean): Promise<T> {
return this.#sendCommand(args, options, bufferMode);
}
// using `#sendCommand` cause `sendCommand` is overwritten in legacy mode
async #sendCommand<T = RedisReply>(args: TransformArgumentsReply, options?: ClientCommandOptions, bufferMode?: boolean): Promise<T> {
if (!this.#socket.isOpen) {
throw new ClientClosedError();
}
if (options?.isolated) {
return this.executeIsolated(isolatedClient =>
isolatedClient.sendCommand(args, {
...options,
isolated: false
})
);
}
const promise = this.#queue.addCommand<T>(args, options, bufferMode);
this.#tick();
return await promise;
}
#tick(): void {
if (!this.#socket.isSocketExists) {
return;
@@ -350,26 +366,11 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
return this.#isolationPool.use(fn);
}
async executeScript(script: RedisLuaScript, args: TransformArgumentsReply, options?: ClientCommandOptions, bufferMode?: boolean): Promise<ReturnType<typeof script['transformReply']>> {
try {
return await this.#sendCommand([
'EVALSHA',
script.SHA1,
script.NUMBER_OF_KEYS.toString(),
...args
], options, bufferMode);
} catch (err: any) {
if (!err?.message?.startsWith?.('NOSCRIPT')) {
throw err;
}
return await this.#sendCommand([
'EVAL',
script.SCRIPT,
script.NUMBER_OF_KEYS.toString(),
...args
], options, bufferMode);
}
multi(): RedisMultiCommandType<M, S> {
return new (this as any).Multi(
this.#multiExecutor.bind(this),
this.#options
);
}
#multiExecutor(commands: Array<MultiQueuedCommand>, chainId?: symbol): Promise<Array<RedisReply>> {
@@ -386,13 +387,6 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
return promise;
}
multi(): RedisMultiCommandType<M, S> {
return new (this as any).Multi(
this.#multiExecutor.bind(this),
this.#options
);
}
async* scanIterator(options?: ScanCommandOptions): AsyncIterable<string> {
let cursor = 0;
do {
@@ -451,5 +445,5 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
}
}
extendWithDefaultCommands(RedisClient, RedisClient.commandsExecutor);
extendWithDefaultCommands(RedisClient, RedisClient.prototype.commandsExecutor);
(RedisClient.prototype as any).Multi = RedisMultiCommand.extend();

View File

@@ -16,10 +16,10 @@ export interface RedisClusterOptions<M = RedisModules, S = RedisLuaScripts> {
maxCommandRedirections?: number;
}
export type RedisClusterType<M extends RedisModules, S extends RedisLuaScripts> =
WithPlugins<M, S> & RedisCluster;
export type RedisClusterType<M extends RedisModules = {}, S extends RedisLuaScripts = {}> =
WithPlugins<M, S> & RedisCluster<M, S>;
export default class RedisCluster<M extends RedisModules = RedisModules, S extends RedisLuaScripts = RedisLuaScripts> extends EventEmitter {
export default class RedisCluster<M extends RedisModules = {}, S extends RedisLuaScripts = {}> extends EventEmitter {
static #extractFirstKey(command: RedisCommand, originalArgs: Array<unknown>, redisArgs: TransformArgumentsReply): string | Buffer | undefined {
if (command.FIRST_KEY_INDEX === undefined) {
return undefined;
@@ -30,54 +30,13 @@ export default class RedisCluster<M extends RedisModules = RedisModules, S exten
return command.FIRST_KEY_INDEX(...originalArgs);
}
static async commandsExecutor(
this: RedisCluster,
command: RedisCommand,
args: Array<unknown>
): Promise<ReturnType<typeof command['transformReply']>> {
const { args: redisArgs, options } = transformCommandArguments<ClientCommandOptions>(command, args);
const reply = command.transformReply(
await this.sendCommand(
RedisCluster.#extractFirstKey(command, args, redisArgs),
command.IS_READ_ONLY,
redisArgs,
options,
command.BUFFER_MODE
),
redisArgs.preserve
);
return reply;
}
static async #scriptsExecutor(
this: RedisCluster,
script: RedisLuaScript,
args: Array<unknown>
): Promise<typeof script['transformArguments']> {
const { args: redisArgs, options } = transformCommandArguments<ClientCommandOptions>(script, args);
const reply = script.transformReply(
await this.executeScript(
script,
args,
redisArgs,
options
),
redisArgs.preserve
);
return reply;
}
static create<M extends RedisModules, S extends RedisLuaScripts>(options?: RedisClusterOptions<M, S>): RedisClusterType<M, S> {
static create<M extends RedisModules = {}, S extends RedisLuaScripts = {}>(options?: RedisClusterOptions<M, S>): RedisClusterType<M, S> {
return new (<any>extendWithModulesAndScripts({
BaseClass: RedisCluster,
modules: options?.modules,
modulesCommandsExecutor: RedisCluster.commandsExecutor,
modulesCommandsExecutor: RedisCluster.prototype.commandsExecutor,
scripts: options?.scripts,
scriptsExecutor: RedisCluster.#scriptsExecutor
scriptsExecutor: RedisCluster.prototype.scriptsExecutor
}))(options);
}
@@ -101,6 +60,23 @@ export default class RedisCluster<M extends RedisModules = RedisModules, S exten
return this.#slots.connect();
}
async commandsExecutor(command: RedisCommand, args: Array<unknown>): Promise<ReturnType<typeof command['transformReply']>> {
const { args: redisArgs, options } = transformCommandArguments<ClientCommandOptions>(command, args);
const reply = command.transformReply(
await this.sendCommand(
RedisCluster.#extractFirstKey(command, args, redisArgs),
command.IS_READ_ONLY,
redisArgs,
options,
command.BUFFER_MODE
),
redisArgs.preserve
);
return reply;
}
async sendCommand<C extends RedisCommand>(
firstKey: string | Buffer | undefined,
isReadonly: boolean | undefined,
@@ -125,6 +101,22 @@ export default class RedisCluster<M extends RedisModules = RedisModules, S exten
}
}
async scriptsExecutor(script: RedisLuaScript, args: Array<unknown>): Promise<ReturnType<typeof script['transformReply']>> {
const { args: redisArgs, options } = transformCommandArguments<ClientCommandOptions>(script, args);
const reply = script.transformReply(
await this.executeScript(
script,
args,
redisArgs,
options
),
redisArgs.preserve
);
return reply;
}
async executeScript(
script: RedisLuaScript,
originalArgs: Array<unknown>,
@@ -208,5 +200,4 @@ export default class RedisCluster<M extends RedisModules = RedisModules, S exten
}
}
extendWithDefaultCommands(RedisCluster, RedisCluster.commandsExecutor);
extendWithDefaultCommands(RedisCluster, RedisCluster.prototype.commandsExecutor);

View File

@@ -8,7 +8,7 @@ describe('GEOSEARCHSTORE', () => {
describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
transformArguments('destination', 'source', 'member', {
transformArguments('destination', '/home/leibale/Workspace/node-redis/lib/commands/GEOSEARCHSTORE.spec.tssource', 'member', {
radius: 1,
unit: 'm'
}, {

View File

@@ -1,3 +1,4 @@
import { TransformArgumentsReply } from '.';
import { transformEXAT, transformPXAT, transformReplyStringNull } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
@@ -14,7 +15,7 @@ type GetExModes = {
PERSIST: true;
};
export function transformArguments(key: string, mode: GetExModes) {
export function transformArguments(key: string, mode: GetExModes): TransformArgumentsReply {
const args = ['GETEX', key];
if ('EX' in mode) {

View File

@@ -753,7 +753,10 @@ export interface RedisCommands {
}
export interface RedisModule {
[key: string]: RedisCommand;
[command: string]: RedisCommand;
}
export type RedisModules = Record<string, RedisModule>;
export interface RedisModules {
[module: string]: RedisModule;
}
// export type RedisModules = Record<string, RedisModule>;

View File

@@ -13,10 +13,10 @@ export interface SHA1 {
export type RedisLuaScript = RedisLuaScriptConfig & SHA1;
export interface RedisLuaScripts {
[key: string]: RedisLuaScript;
[script: string]: RedisLuaScript;
}
export function defineScript<S extends RedisLuaScriptConfig>(script: S): S & SHA1 {
export function defineScript(script: RedisLuaScriptConfig): typeof script & SHA1 {
return {
...script,
SHA1: scriptSha1(script.SCRIPT)

View File

@@ -21,7 +21,8 @@ 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 type RedisMultiCommandType<M extends RedisModules = {}, S extends RedisLuaScripts = {}> =
RedisMultiCommand<M, S> & WithCommands<M, S> & WithModules<M, S> & WithScripts<M, S>;
export interface MultiQueuedCommand {
args: TransformArgumentsReply;
@@ -31,60 +32,20 @@ export interface MultiQueuedCommand {
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);
if (scriptArguments.preserve) {
transformedArguments.preserve = scriptArguments.preserve;
}
return this.addCommand(
transformedArguments,
script.transformReply
);
}
export default class RedisMultiCommand<M extends RedisModules, S extends RedisLuaScripts> {
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,
modulesCommandsExecutor: RedisMultiCommand.prototype.commandsExecutor,
scripts: clientOptions?.scripts,
scriptsExecutor: RedisMultiCommand.#scriptsExecutor
scriptsExecutor: RedisMultiCommand.prototype.scriptsExecutor
});
}
static create<M extends RedisModules, S extends RedisLuaScripts>(
static create<M extends RedisModules = {}, S extends RedisLuaScripts = {}>(
executor: RedisMultiExecutor,
clientOptions?: RedisClientOptions<M, S>
): RedisMultiCommandType<M, S> {
@@ -153,6 +114,42 @@ export default class RedisMultiCommand<M extends RedisModules = RedisModules, S
(this as any)[name] = (...args: Array<unknown>): void => (this as any).addCommand(name, args);
}
commandsExecutor(command: RedisCommand, args: Array<unknown>): this {
return this.addCommand(
command.transformArguments(...args),
command.transformReply
);
}
scriptsExecutor(script: RedisLuaScript, args: Array<unknown>): this {
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);
if (scriptArguments.preserve) {
transformedArguments.preserve = scriptArguments.preserve;
}
return this.addCommand(
transformedArguments,
script.transformReply
);
}
addCommand(args: TransformArgumentsReply, transformReply?: RedisCommand['transformReply']): this {
this.#queue.push({
args,
@@ -205,4 +202,4 @@ export default class RedisMultiCommand<M extends RedisModules = RedisModules, S
}
}
extendWithDefaultCommands(RedisMultiCommand, RedisMultiCommand.commandsExecutor);
extendWithDefaultCommands(RedisMultiCommand, RedisMultiCommand.prototype.commandsExecutor);

View File

@@ -112,7 +112,7 @@ async function spawnGlobalRedisServer(args?: Array<string>): Promise<number> {
const SLOTS = 16384;
interface SpawnRedisClusterNodeResult extends SpawnRedisServerResult {
client: RedisClientType<RedisModules, RedisLuaScripts>
client: RedisClientType
}
async function spawnRedisClusterNode(
@@ -281,7 +281,7 @@ export function describeHandleMinimumRedisVersion(minimumVersion: PartialRedisVe
export function itWithClient(
type: TestRedisServers,
title: string,
fn: (client: RedisClientType<RedisModules, RedisLuaScripts>) => Promise<void>,
fn: (client: RedisClientType) => Promise<void>,
options?: RedisTestOptions
): void {
it(title, async function () {
@@ -306,7 +306,7 @@ export function itWithClient(
export function itWithCluster(
type: TestRedisClusters,
title: string,
fn: (cluster: RedisClusterType<RedisModules, RedisLuaScripts>) => Promise<void>,
fn: (cluster: RedisClusterType) => Promise<void>,
options?: RedisTestOptions
): void {
it(title, async function () {
@@ -328,7 +328,7 @@ export function itWithCluster(
});
}
export function itWithDedicatedCluster(title: string, fn: (cluster: RedisClusterType<RedisModules, RedisLuaScripts>) => Promise<void>): void {
export function itWithDedicatedCluster(title: string, fn: (cluster: RedisClusterType) => Promise<void>): void {
it(title, async function () {
this.timeout(10000);