diff --git a/CHANGES.md b/CHANGES.md index 90fe9cf..db12ca9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,8 @@ ## v3.0.3 - fix(command): Return keys in `HMGET` command correctly. +- fix(protocol): decode `*-1` correctly +- feat(command): added lmove and blmove commands ## v3.0.2 diff --git a/src/lib/Commands.ts b/src/lib/Commands.ts index d112cbd..4e12055 100644 --- a/src/lib/Commands.ts +++ b/src/lib/Commands.ts @@ -2085,6 +2085,64 @@ export const COMMANDS: Record = { } }, + /** + * Command: lMove + * @see https://redis.io/docs/latest/commands/lmove + */ + 'lMove': { + prepare(srcKey: string, destKey: string, srcDir: 'LEFT' | 'RIGHT', destDir: 'LEFT' | 'RIGHT'): IPrepareResult { + + return { + cmd: 'LMOVE', + args: [srcKey, destKey, srcDir, destDir] + }; + }, + process: U.nullableBuffer2String + }, + + /** + * Command: lMove + * @see https://redis.io/docs/latest/commands/lmove + */ + 'lMove$': { + prepare(srcKey: string, destKey: string, srcDir: 'LEFT' | 'RIGHT', destDir: 'LEFT' | 'RIGHT'): IPrepareResult { + + return { + cmd: 'LMOVE', + args: [srcKey, destKey, srcDir, destDir] + }; + } + }, + + /** + * Command: bLMove + * @see https://redis.io/docs/latest/commands/blmove + */ + 'bLMove': { + prepare(srcKey: string, destKey: string, srcDir: 'LEFT' | 'RIGHT', destDir: 'LEFT' | 'RIGHT', timeout: number): IPrepareResult { + + return { + cmd: 'BLMOVE', + args: [srcKey, destKey, srcDir, destDir, timeout] + }; + }, + process: U.nullableBuffer2String + }, + + /** + * Command: bLMove + * @see https://redis.io/docs/latest/commands/blmove + */ + 'bLMove$': { + prepare(srcKey: string, destKey: string, srcDir: 'LEFT' | 'RIGHT', destDir: 'LEFT' | 'RIGHT', timeout: number): IPrepareResult { + + return { + cmd: 'BLMOVE', + args: [srcKey, destKey, srcDir, destDir, timeout] + }; + } + }, + /** * Command: zRem * @see https://redis.io/docs/latest/commands/zrem diff --git a/src/lib/Common.ts b/src/lib/Common.ts index b5eaba0..306929a 100644 --- a/src/lib/Common.ts +++ b/src/lib/Common.ts @@ -1221,6 +1221,30 @@ export interface ICommandAPIs { */ rPushX(key: string, values: Array): Promise; + /** + * Command: lMove + * @see https://redis.io/docs/latest/commands/lmove + */ + lMove(srcKey: string, destKey: string, srcDir: 'LEFT' | 'RIGHT', destDir: 'LEFT' | 'RIGHT'): Promise; + + /** + * Command: lMove + * @see https://redis.io/docs/latest/commands/lmove + */ + lMove$(srcKey: string, destKey: string, srcDir: 'LEFT' | 'RIGHT', destDir: 'LEFT' | 'RIGHT'): Promise; + + /** + * Command: bLMove + * @see https://redis.io/docs/latest/commands/blmove + */ + bLMove(srcKey: string, destKey: string, srcDir: 'LEFT' | 'RIGHT', destDir: 'LEFT' | 'RIGHT', timeout: number): Promise; + + /** + * Command: bLMove + * @see https://redis.io/docs/latest/commands/blmove + */ + bLMove$(srcKey: string, destKey: string, srcDir: 'LEFT' | 'RIGHT', destDir: 'LEFT' | 'RIGHT', timeout: number): Promise; + /** * Command: zAdd * @see https://redis.io/docs/latest/commands/zadd diff --git a/src/lib/Decoder.ts b/src/lib/Decoder.ts index a02c7cf..511e13d 100644 --- a/src/lib/Decoder.ts +++ b/src/lib/Decoder.ts @@ -234,24 +234,32 @@ export class Decoder extends EventEmitter implements C.IDecoder { if (end > -1) { - this._ctx.type = C.EDataType.LIST; - - this._ctx.value = []; - this._ctx.data.length = parseInt(this._buf.subarray( this._ctx.pos, end ).toString()); - if (this._ctx.data.length === 0 || this._ctx.data.length === -1) { + if (this._ctx.data.length === -1) { + this._ctx.type = C.EDataType.NULL; + this._ctx.value = null; this._pop(end + 2); } else { - this._cut(end + 2); - this._ctx.pos = this._cursor; - this._ctx.status = EDecodeStatus.READING_LIST; + this._ctx.type = C.EDataType.LIST; + this._ctx.value = []; + + if (this._ctx.data.length === 0) { + + this._pop(end + 2); + } + else { + + this._cut(end + 2); + this._ctx.pos = this._cursor; + this._ctx.status = EDecodeStatus.READING_LIST; + } } } else { diff --git a/src/test/commands/blmove.ts b/src/test/commands/blmove.ts new file mode 100644 index 0000000..52e5a24 --- /dev/null +++ b/src/test/commands/blmove.ts @@ -0,0 +1,56 @@ +import { test } from 'node:test'; +import * as Assert from 'node:assert'; +import { cmdCli } from '../common'; + +const TEST_KEY_PREFIX = 'test_blmove_'; + +test('Command For Lmove/Blmove', async (t) => { + + await cmdCli.del([...await cmdCli.keys(`${TEST_KEY_PREFIX}*`), 'any']); + + await t.test('Append multiple items to the end of mylist', async () => { + + Assert.strictEqual(await cmdCli.rPush(`${TEST_KEY_PREFIX}mylist`, ['one', 'two']), 2); + Assert.strictEqual(await cmdCli.rPush(`${TEST_KEY_PREFIX}mylist`, ['three', 'four']), 4); + + }); + + await t.test('Manipulate mylist using the lMove operation', async () => { + + Assert.strictEqual(await cmdCli.lMove(`${TEST_KEY_PREFIX}mylist`, `${TEST_KEY_PREFIX}myotherlist`, 'RIGHT', 'LEFT'), 'four'); + + }); + + await t.test('Manipulate mylist using the bLMove operation', async () => { + + Assert.strictEqual(await cmdCli.bLMove(`${TEST_KEY_PREFIX}mylist`, `${TEST_KEY_PREFIX}myotherlist`, 'LEFT', 'RIGHT', 2), 'one'); + + }); + + await t.test('Detect and remove data from mylist and myotherlist after operations', async () => { + + Assert.deepEqual(await cmdCli.lRange(`${TEST_KEY_PREFIX}mylist`, 0, -1), ['two', 'three']); + Assert.deepEqual(await cmdCli.lRange(`${TEST_KEY_PREFIX}myotherlist`,0, -1), ['four', 'one']); + Assert.deepEqual(await cmdCli.lPop(`${TEST_KEY_PREFIX}mylist`, 2), ['two', 'three']); + Assert.deepEqual(await cmdCli.lPop(`${TEST_KEY_PREFIX}myotherlist`, 2), ['four', 'one']); + + }); + + await t.test('Perform the lMove operation on an empty list', async () => { + + Assert.strictEqual(await cmdCli.lMove(`${TEST_KEY_PREFIX}mylist`, `${TEST_KEY_PREFIX}myotherlist`, 'RIGHT', 'LEFT'), null); + + }); + + await t.test('Perform the bLMove operation on an empty list', async () => { + + Assert.strictEqual(await cmdCli.bLMove(`${TEST_KEY_PREFIX}mylist`, `${TEST_KEY_PREFIX}myotherlist`, 'LEFT', 'RIGHT', 2), null); + + }); + +}); + +test.after(async () => { + + await cmdCli.close(); +}); \ No newline at end of file