From 966c94a0786cf2cd09072f93ea6a4b23e41d3a88 Mon Sep 17 00:00:00 2001 From: leibale Date: Thu, 13 May 2021 18:42:59 -0400 Subject: [PATCH] add isOpen boolean getter on client, add maxLength option to command queue, add test for client.multi --- TODO.md | 18 +++++++++++------- lib/client.spec.ts | 15 ++++++++++++++- lib/client.ts | 30 +++++++++++++++++++----------- lib/commands-queue.ts | 20 ++++++++++++++++++-- lib/socket.ts | 4 ++++ 5 files changed, 66 insertions(+), 21 deletions(-) diff --git a/TODO.md b/TODO.md index a10d569c35..5d7a459337 100644 --- a/TODO.md +++ b/TODO.md @@ -2,18 +2,18 @@ * Missing Commands * Scan stream * PubSub -* [`return_buffers`](https://github.com/NodeRedis/node-redis#options-object-properties) (? supported v3, but have performance drawbacks) +* [`return_buffers`](https://github.com/NodeRedis/node-redis#options-object-properties) (? supported in v3, but have performance drawbacks) * Support options in a command function (`.get`, `.set`, ...) * Key prefixing (?) (partially supported in v3) ## Client * Blocking Commands * Events - * ready - * connect - * reconnecting - * error - * end + * ~~ready~~ + * ~~connect~~ + * ~~reconnecting~~ + * ~~error~~ + * ~~end~~ * warning (?) * Select command @@ -34,7 +34,11 @@ * In `RedisMultiCommand` (with TypeScript mapping) * In `RedisCluster` (with TypeScript mapping) +## Multi +* exec without the `MULTI` command +* support for constructor with array of commands (? supported in v3) + ## Tests * Write tests.. -* Coverage +* ~~Coverage~~ * Performance Tests diff --git a/lib/client.spec.ts b/lib/client.spec.ts index e455bce9e6..efca816706 100644 --- a/lib/client.spec.ts +++ b/lib/client.spec.ts @@ -27,7 +27,7 @@ describe('Client', () => { } ); - // TODO validate state + assert.equal(client.isOpen, false); }); }); @@ -59,4 +59,17 @@ describe('Client', () => { assert.equal(await client.sendCommand(['PING']), 'PONG'); await client.disconnect(); }); + + describe('multi', () => { + itWithClient(TestRedisServers.OPEN, 'simple', async client => { + assert.deepEqual( + await client.multi() + .ping() + .set('key', 'value') + .get('key') + .exec(), + ['PONG', 'OK', 'value'] + ); + }); + }); }); \ No newline at end of file diff --git a/lib/client.ts b/lib/client.ts index ada1b782ca..09a50452d1 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -8,6 +8,7 @@ import EventEmitter from 'events'; export interface RedisClientOptions { socket?: RedisSocketOptions; modules?: M; + commandsQueueMaxLength?: number; } export type RedisCommandSignature = (...args: Parameters) => Promise>; @@ -26,8 +27,8 @@ type WithMulti> = { export type RedisClientType = WithCommands & WithModules & WithMulti & RedisClient; -export default class RedisClient extends EventEmitter { - static defineCommand(on: any, name: string, command: RedisCommand) { +export default class RedisClient extends EventEmitter { + static defineCommand(on: any, name: string, command: RedisCommand): void { on[name] = async function (...args: Array): Promise { return command.transformReply( await this.sendCommand(command.transformArguments(...args)) @@ -41,13 +42,17 @@ export default class RedisClient extends EventEmitter { readonly #socket: RedisSocket; readonly #queue: RedisCommandsQueue; - readonly #Multi; - readonly #modules?: RedisModules; + readonly #Multi: typeof RedisMultiCommand & { new(): RedisMultiCommandType }; + readonly #modules?: M; - constructor(options?: RedisClientOptions) { + get isOpen(): boolean { + return this.#socket.isOpen; + } + + constructor(options?: RedisClientOptions) { super(); this.#socket = this.#initiateSocket(options?.socket); - this.#queue = this.#initiateQueue(); + this.#queue = this.#initiateQueue(options?.commandsQueueMaxLength); this.#Multi = this.#initiateMulti(); this.#modules = this.#initiateModules(options?.modules); } @@ -68,11 +73,14 @@ export default class RedisClient extends EventEmitter { .on('end', () => this.emit('end')); } - #initiateQueue(): RedisCommandsQueue { - return new RedisCommandsQueue((encodedCommands: string) => this.#socket.write(encodedCommands)); + #initiateQueue(maxLength: number | null | undefined): RedisCommandsQueue { + return new RedisCommandsQueue( + maxLength, + (encodedCommands: string) => this.#socket.write(encodedCommands) + ); } - #initiateMulti() { + #initiateMulti(): typeof RedisMultiCommand & { new(): RedisMultiCommandType } { const executor = async (commands: Array): Promise> => { const promise = Promise.all( commands.map(({encodedCommand}) => { @@ -94,7 +102,7 @@ export default class RedisClient extends EventEmitter { }; } - #initiateModules(modules?: RedisModules): RedisModules | undefined { + #initiateModules(modules?: M): M | undefined { if (!modules) return; for (const m of modules) { @@ -121,7 +129,7 @@ export default class RedisClient extends EventEmitter { return promise; } - multi() { + multi(): RedisMultiCommandType { return new this.#Multi(); } diff --git a/lib/commands-queue.ts b/lib/commands-queue.ts index 8f9e71197f..6659cbd9aa 100644 --- a/lib/commands-queue.ts +++ b/lib/commands-queue.ts @@ -45,6 +45,8 @@ export default class RedisCommandsQueue { } } + readonly #maxLength: number | null | undefined; + readonly #executor: CommandsQueueExecutor; readonly #waitingToBeSent = new LinkedList(); @@ -58,18 +60,32 @@ export default class RedisCommandsQueue { #chainInExecution: Symbol | undefined; - constructor(executor: CommandsQueueExecutor) { + constructor(maxLength: number | null | undefined, executor: CommandsQueueExecutor) { + this.#maxLength = maxLength; this.#executor = executor; } + #isQueueFull(): Promise | undefined { + if (!this.#maxLength) return; + + return this.#waitingToBeSent.length + this.#waitingForReply.length >= this.#maxLength ? + Promise.reject(new Error('The queue is full')) : + undefined; + } + addCommand(args: Array, options?: AddCommandOptions): Promise { - return this.addEncodedCommand( + return this.#isQueueFull() || this.addEncodedCommand( RedisCommandsQueue.encodeCommand(args), options ); } addEncodedCommand(encodedCommand: string, options?: AddCommandOptions): Promise { + const fullQueuePromise = this.#isQueueFull(); + if (fullQueuePromise) { + return fullQueuePromise; + } + return new Promise((resolve, reject) => { const node = new LinkedList.Node({ encodedCommand, diff --git a/lib/socket.ts b/lib/socket.ts index f522e4fb29..b400f0e3a6 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -78,6 +78,10 @@ export default class RedisSocket extends EventEmitter { #isOpen = false; + get isOpen(): boolean { + return this.#isOpen; + } + get chunkRecommendedSize(): number { if (!this.#socket) return 0;