From ac00647e8761be08ec314cb93a6be6c5c9168d2e Mon Sep 17 00:00:00 2001 From: TJ Zhang Date: Wed, 7 Aug 2024 17:31:15 -0700 Subject: [PATCH] Node: Add command GETEX Signed-off-by: TJ Zhang --- CHANGELOG.md | 1 + node/npm/glide/index.ts | 2 ++ node/src/BaseClient.ts | 30 +++++++++++++++++++++ node/src/Commands.ts | 53 +++++++++++++++++++++++++++++++++++++ node/src/Transaction.ts | 25 +++++++++++++++++ node/tests/SharedTests.ts | 46 ++++++++++++++++++++++++++++++++ node/tests/TestUtilities.ts | 12 +++++++++ 7 files changed, 169 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4531e039ad..3bcbf37092 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ * Node: Added XGROUP CREATE & XGROUP DESTROY commands ([#2084](https://github.com/valkey-io/valkey-glide/pull/2084)) * Node: Added BZPOPMAX & BZPOPMIN command ([#2077]((https://github.com/valkey-io/valkey-glide/pull/2077)) * Node: Added XGROUP CREATECONSUMER & XGROUP DELCONSUMER commands ([#2088](https://github.com/valkey-io/valkey-glide/pull/2088)) +* Node: Added GETEX command ([#2107]((https://github.com/valkey-io/valkey-glide/pull/2107)) #### Breaking Changes * Node: (Refactor) Convert classes to types ([#2005](https://github.com/valkey-io/valkey-glide/pull/2005)) diff --git a/node/npm/glide/index.ts b/node/npm/glide/index.ts index a4686e774b..d7070015ff 100644 --- a/node/npm/glide/index.ts +++ b/node/npm/glide/index.ts @@ -109,6 +109,7 @@ function initialize() { FunctionStatsResponse, SlotIdTypes, SlotKeyTypes, + TimeUnit, RouteByAddress, Routes, SingleNodeRoute, @@ -203,6 +204,7 @@ function initialize() { SlotIdTypes, SlotKeyTypes, StreamEntries, + TimeUnit, ReturnTypeXinfoStream, RouteByAddress, Routes, diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 549f81ea37..fe4d143ee5 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -82,6 +82,7 @@ import { createGet, createGetBit, createGetDel, + createGetEx, createGetRange, createHDel, createHExists, @@ -201,6 +202,9 @@ import { createZRevRankWithScore, createZScan, createZScore, + StreamClaimOptions, + createXClaim, + TimeUnit, } from "./Commands"; import { ClosingError, @@ -844,6 +848,32 @@ export class BaseClient { return this.createWritePromise(createGet(key)); } + /** + * Get the value of `key` and optionally set its expiration. `GETEX` is similar to {@link get} + * + * See https://valkey.io/commands/getex for more details. + * + * @param key - The key to retrieve from the database. + * @param options - (Optional) set expiriation to the given key. + * "persist" will retain the time to live associated with the key. Equivalent to `PERSIST` in the VALKEY API. + * Otherwise, a {@link TimeUnit} and duration of the expire time should be specified. + * @returns If `key` exists, returns the value of `key` as a `string`. Otherwise, return `null`. + * + * since - Valkey 6.2.0 and above. + * + * @example + * ```typescript + * const result = await client.getex("key", {expiry: { type: "seconds", count: 5 }}); + * console.log(result); // Output: 'value' + * ``` + */ + public async getex( + key: string, + options?: "persist" | { unit: TimeUnit; duration: number }, + ): Promise { + return this.createWritePromise(createGetEx(key, options)); + } + /** * Gets a string value associated with the given `key`and deletes the key. * diff --git a/node/src/Commands.ts b/node/src/Commands.ts index ca00417ac9..0939cb02a3 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -3573,3 +3573,56 @@ export function createBZPopMin( ): command_request.Command { return createCommand(RequestType.BZPopMin, [...keys, timeout.toString()]); } + +/** + * Optional arguments to getex command. + */ +export enum TimeUnit { + /* Set the specified expire time, in seconds. Equivalent to + * `EX` in the VALKEY API. + */ + seconds = "EX", + /** + * Set the specified expire time, in milliseconds. Equivalent + * to `PX` in the VALKEY API. + */ + milliseconds = "PX", + /** + * Set the specified Unix time at which the key will expire, + * in seconds. Equivalent to `EXAT` in the VALKEY API. + */ + unixSeconds = "EXAT", + /** + * Set the specified Unix time at which the key will expire, + * in milliseconds. Equivalent to `PXAT` in the VALKEY API. + */ + unixMilliseconds = "PXAT", +} + +/** + * @internal + */ +export function createGetEx( + key: string, + options?: "persist" | { unit: TimeUnit; duration: number }, +): command_request.Command { + const args = [key]; + + if (options) { + if (options !== "persist" && !Number.isInteger(options.duration)) { + throw new Error( + `Received expiry '${JSON.stringify( + options.duration, + )}'. Count must be an integer`, + ); + } + + if (options === "persist") { + args.push("PERSIST"); + } else { + args.push(options.unit, options.duration.toString()); + } + } + + return createCommand(RequestType.GetEx, args); +} diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index e4fe1f5674..d41ba2de92 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -105,6 +105,7 @@ import { createGet, createGetBit, createGetDel, + createGetEx, createGetRange, createHDel, createHExists, @@ -236,6 +237,9 @@ import { createZRevRankWithScore, createZScan, createZScore, + createGeoSearchStore, + GeoSearchStoreResultOptions, + TimeUnit, } from "./Commands"; import { command_request } from "./ProtobufMessage"; @@ -289,6 +293,7 @@ export class BaseTransaction> { } /** Get the value associated with the given key, or null if no such value exists. + * * See https://valkey.io/commands/get/ for details. * * @param key - The key to retrieve from the database. @@ -299,6 +304,26 @@ export class BaseTransaction> { return this.addAndReturn(createGet(key)); } + /** + * Get the value of `key` and optionally set its expiration. `GETEX` is similar to {@link get}. + * See https://valkey.io/commands/getex for more details. + * + * @param key - The key to retrieve from the database. + * @param options - (Optional) set expiriation to the given key. + * "persist" will retain the time to live associated with the key. Equivalent to `PERSIST` in the VALKEY API. + * Otherwise, a {@link TimeUnit} and duration of the expire time should be specified. + * + * since - Valkey 6.2.0 and above. + * + * Command Response - If `key` exists, returns the value of `key` as a `string`. Otherwise, return `null`. + */ + public getex( + key: string, + options?: "persist" | { unit: TimeUnit; duration: number }, + ): T { + return this.addAndReturn(createGetEx(key, options)); + } + /** * Gets a string value associated with the given `key`and deletes the key. * diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 47c6e1cbb6..333c4e5d8a 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -38,6 +38,7 @@ import { Script, SignedEncoding, SortOrder, + TimeUnit, Transaction, UnsignedEncoding, UpdateByScore, @@ -8199,6 +8200,51 @@ export function runBaseTests(config: { }, config.timeout, ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `getex test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient, cluster: RedisCluster) => { + if (cluster.checkIfServerVersionLessThan("6.2.0")) { + return; + } + + const key1 = "{key}" + uuidv4(); + const key2 = "{key}" + uuidv4(); + const value = uuidv4(); + + expect(await client.set(key1, value)).toBe("OK"); + expect(await client.getex(key1)).toEqual(value); + expect(await client.ttl(key1)).toBe(-1); + + expect( + await client.getex(key1, { + unit: TimeUnit.seconds, + duration: 15, + }), + ).toEqual(value); + expect(await client.ttl(key1)).toBeGreaterThan(0); + expect(await client.getex(key1, "persist")).toEqual(value); + expect(await client.ttl(key1)).toBe(-1); + + // non existent key + expect(await client.getex(key2)).toBeNull(); + + // invalid time measurement + await expect( + client.getex(key1, { + unit: TimeUnit.seconds, + duration: -10, + }), + ).rejects.toThrow(RequestError); + + // Key exists, but is not a string + expect(await client.sadd(key2, ["a"])).toBe(1); + await expect(client.getex(key2)).rejects.toThrow(RequestError); + }, protocol); + }, + config.timeout, + ); } export function runCommonTests(config: { diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index 9660d06809..803c807f30 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -33,6 +33,7 @@ import { ScoreFilter, SignedEncoding, SortOrder, + TimeUnit, Transaction, UnsignedEncoding, } from ".."; @@ -540,6 +541,17 @@ export async function transactionTest( responseData.push(["dbsize()", 0]); baseTransaction.set(key1, "bar"); responseData.push(['set(key1, "bar")', "OK"]); + + if (gte(version, "6.2.0")) { + baseTransaction.getex(key1); + responseData.push(["getex(key1)", "bar"]); + baseTransaction.getex(key1, { unit: TimeUnit.seconds, duration: 1 }); + responseData.push([ + 'getex(key1, {expiry: { type: "seconds", count: 1 }})', + "bar", + ]); + } + baseTransaction.randomKey(); responseData.push(["randomKey()", key1]); baseTransaction.getrange(key1, 0, -1);