import { CommandOptions, isCommandOptions } from './command-options'; import { RedisCommand, RedisCommandArguments, RedisCommandRawReply, RedisCommandReply, RedisCommands, RedisModules, RedisScript, RedisScripts } from './commands'; type Instantiable = new(...args: Array) => T; interface ExtendWithCommandsConfig { BaseClass: T; commands: RedisCommands; executor(command: RedisCommand, args: Array): unknown; } export function extendWithCommands({ BaseClass, commands, executor }: ExtendWithCommandsConfig): void { for (const [name, command] of Object.entries(commands)) { BaseClass.prototype[name] = function (...args: Array): unknown { return executor.call(this, command, args); }; } } interface ExtendWithModulesAndScriptsConfig { BaseClass: T; modules?: RedisModules; modulesCommandsExecutor(this: InstanceType, command: RedisCommand, args: Array): unknown; scripts?: RedisScripts; scriptsExecutor(this: InstanceType, script: RedisScript, args: Array): unknown; } export function extendWithModulesAndScripts(config: ExtendWithModulesAndScriptsConfig): T { let Commander: T | undefined; if (config.modules) { Commander = class extends config.BaseClass { constructor(...args: Array) { super(...args); for (const module of Object.keys(config.modules!)) { this[module] = new this[module](this); } } }; for (const [moduleName, module] of Object.entries(config.modules)) { Commander.prototype[moduleName] = class { readonly self: T; constructor(self: InstanceType) { this.self = self; } }; for (const [commandName, command] of Object.entries(module)) { Commander.prototype[moduleName].prototype[commandName] = function (...args: Array): unknown { return config.modulesCommandsExecutor.call(this.self, command, args); }; } } } if (config.scripts) { Commander ??= class extends config.BaseClass {}; for (const [name, script] of Object.entries(config.scripts)) { Commander.prototype[name] = function (...args: Array): unknown { return config.scriptsExecutor.call(this, script, args); }; } } return (Commander ?? config.BaseClass) as any; } export function transformCommandArguments( command: RedisCommand, args: Array ): { args: RedisCommandArguments; options: CommandOptions | undefined; } { let options; if (isCommandOptions(args[0])) { options = args[0]; args = args.slice(1); } return { args: command.transformArguments(...args), options }; } const DELIMITER = '\r\n'; export function* encodeCommand(args: RedisCommandArguments): IterableIterator { 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; } } export function transformCommandReply( command: RedisCommand, rawReply: RedisCommandRawReply, preserved: unknown ): RedisCommandReply { if (!command.transformReply) { return rawReply; } return command.transformReply(rawReply, preserved); } export type LegacyCommandArguments = Array; export function transformLegacyCommandArguments(args: LegacyCommandArguments, flat: RedisCommandArguments = []): RedisCommandArguments { for (const arg of args) { if (Array.isArray(arg)) { transformLegacyCommandArguments(arg, flat); continue; } flat.push(typeof arg === 'number' ? arg.toString() : arg); } return flat; }