All files / lib cluster.ts

26.09% Statements 6/23
0% Branches 0/11
14.29% Functions 1/7
26.09% Lines 6/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 831x       1x                                     1x   76x                                                                                                           1x 76x    
import COMMANDS from './commands/cluster';
import { RedisCommand, RedisModule, RedisModules } from './commands';
import { RedisCommandSignature } from './client';
import { RedisSocketOptions } from './socket';
import RedisClusterSlots from './cluster-slots';
 
export interface RedisClusterOptions<M = RedisModules> {
    rootNodes: Array<RedisSocketOptions>;
    modules?: M;
    useReplicas?: boolean;
    maxCommandRedirections?: number;
}
 
type WithCommands = {
    [P in keyof typeof COMMANDS]: RedisCommandSignature<(typeof COMMANDS)[P]>
};
 
type WithModules<M extends Array<RedisModule>> = {
    [P in keyof M[number]]: RedisCommandSignature<M[number][P]>
};
 
export type RedisClusterType<M extends RedisModules> = WithCommands & WithModules<M> & RedisCluster;
 
export default class RedisCluster {
    static defineCommand(on: any, name: string, command: RedisCommand): void {
        on[name] = async function (...args: Array<unknown>): Promise<unknown> {
            const transformedArguments = command.transformArguments(...args);
            return command.transformReply(
                await this.sendCommand(
                    transformedArguments,
                    command.FIRST_KEY_INDEX,
                    command.IS_READ_ONLY
                )
            );
        };
    }
 
    static create<M extends RedisModules>(options: RedisClusterOptions): RedisClusterType<M> {
        return <any>new RedisCluster(options);
    }
 
    readonly #options: RedisClusterOptions;
    readonly #slots: RedisClusterSlots;
 
    constructor(options: RedisClusterOptions) {
        this.#options = options;
        this.#slots = new RedisClusterSlots(options);
    }
 
    async connect(): Promise<void> {
        return this.#slots.connect();
    }
 
    async sendCommand<T = unknown>(args: Array<string>, firstKeyIndex?: number, isReadOnly?: boolean, redirections: number = 0): Promise<T> {
        const firstKey = firstKeyIndex ? args[firstKeyIndex] : undefined,
            client = this.#slots.getClient(firstKey, isReadOnly);
 
        try {
            return await client.sendCommand(args);
        } catch (err) {
            if (err.message.startsWith('ASK')) {
                // TODO
            } else if (err.message.startsWith('MOVED')) {
                await this.#slots.discover();
 
                if (redirections < (this.#options.maxCommandRedirections ?? 16)) {
                    return this.sendCommand(args, firstKeyIndex, isReadOnly, redirections + 1);
                }
            }
 
            throw err;
        }
    }
 
    disconnect(): Promise<void> {
        return this.#slots.disconnect();
    }
}
 
for (const [name, command] of Object.entries(COMMANDS)) {
    RedisCluster.defineCommand(RedisCluster.prototype, name, command);
}