diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index f8f8ddba3d..327b9fa335 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -134,6 +134,7 @@ enum RequestType { Zrank = 90; Rename = 91; DBSize = 92; + Brpop = 93; } message Command { diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index 0fcc39f98a..232ee3089c 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -364,6 +364,7 @@ fn get_command(request: &Command) -> Option { RequestType::Zrank => Some(cmd("ZRANK")), RequestType::Rename => Some(cmd("RENAME")), RequestType::DBSize => Some(cmd("DBSIZE")), + RequestType::Brpop => Some(cmd("BRPOP")), } } diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 35b9cbe2f5..2a5aac3cf0 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -18,6 +18,7 @@ import { StreamReadOptions, StreamTrimOptions, ZaddOptions, + createBrpop, createDecr, createDecrBy, createDel, @@ -1373,6 +1374,30 @@ export class BaseClient { return this.createWritePromise(createRename(key, newKey)); } + /** Blocking list pop primitive. + * Pop an element from the tail of the first list that is non-empty, + * with the given keys being checked in the order that they are given. + * Blocks the connection when there are no elements to pop from any of the given lists. + * See https://redis.io/commands/brpop/ for more details. + * Note: BRPOP is a blocking command, + * see [Blocking Commands](https://github.com/aws/glide-for-redis/wiki/General-Concepts#blocking-commands) for more details and best practices. + * + * @param keys - The `keys` of the lists to pop from. + * @param timeout - The `timeout` in seconds. + * @returns - An `array` containing the `key` from which the element was popped and the value of the popped element, + * formatted as [key, value]. If no element could be popped and the timeout expired, returns Null. + * + * @example + * await client.brpop(["list1", "list2"], 5); + * ["list1", "element"] + */ + public brpop( + keys: string[], + timeout: number, + ): Promise<[string, string] | null> { + return this.createWritePromise(createBrpop(keys, timeout)); + } + /** * @internal */ diff --git a/node/src/Commands.ts b/node/src/Commands.ts index 320d2295c9..ee2ecb6898 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -1063,6 +1063,17 @@ export function createTime(): redis_request.Command { return createCommand(RequestType.Time, []); } +/** + * @internal + */ +export function createBrpop( + keys: string[], + timeout: number, +): redis_request.Command { + const args = [...keys, timeout.toString()]; + return createCommand(RequestType.Brpop, args); +} + export type StreamReadOptions = { /** * If set, the read request will block for the set amount of milliseconds or until the server has the required number of entries. diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index f12dc4a452..c7eb34b153 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -11,6 +11,7 @@ import { StreamReadOptions, StreamTrimOptions, ZaddOptions, + createBrpop, createClientGetName, createClientId, createConfigGet, @@ -1156,6 +1157,23 @@ export class BaseTransaction> { public rename(key: string, newKey: string): T { return this.addAndReturn(createRename(key, newKey)); } + + /** Blocking list pop primitive. + * Pop an element from the tail of the first list that is non-empty, + * with the given keys being checked in the order that they are given. + * Blocks the connection when there are no elements to pop from any of the given lists. + * See https://redis.io/commands/brpop/ for more details. + * Note: BRPOP is a blocking command, + * see [Blocking Commands](https://github.com/aws/glide-for-redis/wiki/General-Concepts#blocking-commands) for more details and best practices. + * + * @param keys - The `keys` of the lists to pop from. + * @param timeout - The `timeout` in seconds. + * Command Response - An `array` containing the `key` from which the element was popped and the value of the popped element, + * formatted as [key, value]. If no element could be popped and the timeout expired, returns Null. + */ + public brpop(keys: string[], timeout: number): T { + return this.addAndReturn(createBrpop(keys, timeout)); + } } /** diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index afde1a7841..efcb42c09a 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -1797,6 +1797,26 @@ export function runBaseTests(config: { await expect(client.zrank(key2, "member")).rejects.toThrow(); }, protocol); }, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `test brpop test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + expect( + await client.rpush("brpop-test", ["foo", "bar", "baz"]), + ).toEqual(3); + // Test basic usage + expect(await client.brpop(["brpop-test"], 0.1)).toEqual([ + "brpop-test", + "baz", + ]); + // Delete all values from list + expect(await client.del(["brpop-test"])).toEqual(1); + // Test null return when key doesn't exist + expect(await client.brpop(["brpop-test"], 0.1)).toEqual(null); + }, protocol); + }, config.timeout, ); diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index 0501e623eb..435fb641a6 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -205,6 +205,10 @@ export async function transactionTest( args.push("OK"); baseTransaction.exists([key10]); args.push(1); + baseTransaction.rpush(key6, [field + "1", field + "2", field + "3"]); + args.push(3); + baseTransaction.brpop([key6], 0.1); + args.push([key6, field + "3"]); return args; }