From 39bc77893253272b46e6429cea0a8321280c2ac8 Mon Sep 17 00:00:00 2001 From: Aaron <69273634+aaron-congo@users.noreply.github.com> Date: Thu, 13 Jun 2024 13:00:22 -0700 Subject: [PATCH] Node: add OBJECT IDLETIME command (#1567) --- CHANGELOG.md | 1 + node/src/BaseClient.ts | 19 +++++++++++ node/src/Commands.ts | 7 +++++ node/src/Transaction.ts | 14 +++++++++ node/tests/RedisClient.test.ts | 45 +++++++++++++++++++++++++++ node/tests/RedisClusterClient.test.ts | 45 +++++++++++++++++++++++++++ node/tests/SharedTests.ts | 43 +++++++++++++++++++++++++ 7 files changed, 174 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e543ab12d9..c77ccb0666 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ * Node: Added XLEN command ([#1555](https://github.com/aws/glide-for-redis/pull/1555)) * Node: Added ZINTERCARD command ([#1553](https://github.com/aws/glide-for-redis/pull/1553)) * Python: Added LMPOP and BLMPOP commands ([#1547](https://github.com/aws/glide-for-redis/pull/1547)) +* Node: Added OBJECT IDLETIME command ([#1567](https://github.com/aws/glide-for-redis/pull/1567)) ### Breaking Changes * Node: Update XREAD to return a Map of Map ([#1494](https://github.com/aws/glide-for-redis/pull/1494)) diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 7987b0bc19..cf4b0919e2 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -101,6 +101,7 @@ import { createSUnionStore, createXLen, createZInterCard, + createObjectIdletime, } from "./Commands"; import { ClosingError, @@ -2574,6 +2575,24 @@ export class BaseClient { return this.createWritePromise(createObjectFreq(key)); } + /** + * Returns the time in seconds since the last access to the value stored at `key`. + * + * See https://valkey.io/commands/object-idletime/ for more details. + * + * @param key - The key of the object to get the idle time of. + * @returns If `key` exists, returns the idle time in seconds. Otherwise, returns `null`. + * + * @example + * ```typescript + * const result = await client.objectIdletime("my_hash"); + * console.log(result); // Output: 13 - "my_hash" was last accessed 13 seconds ago. + * ``` + */ + public objectIdletime(key: string): Promise { + return this.createWritePromise(createObjectIdletime(key)); + } + /** * @internal */ diff --git a/node/src/Commands.ts b/node/src/Commands.ts index 7be993ab62..f507555dae 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -1493,3 +1493,10 @@ export function createObjectEncoding(key: string): redis_request.Command { export function createObjectFreq(key: string): redis_request.Command { return createCommand(RequestType.ObjectFreq, [key]); } + +/** + * @internal + */ +export function createObjectIdletime(key: string): redis_request.Command { + return createCommand(RequestType.ObjectIdleTime, [key]); +} diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index f112174806..fbb1889d22 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -106,6 +106,7 @@ import { createSUnionStore, createXLen, createZInterCard, + createObjectIdletime, } from "./Commands"; import { redis_request } from "./ProtobufMessage"; @@ -1493,6 +1494,19 @@ export class BaseTransaction> { public objectFreq(key: string): T { return this.addAndReturn(createObjectFreq(key)); } + + /** + * Returns the time in seconds since the last access to the value stored at `key`. + * + * See https://valkey.io/commands/object-idletime/ for more details. + * + * @param key - The key of the object to get the idle time of. + * + * Command Response - If `key` exists, returns the idle time in seconds. Otherwise, returns `null`. + */ + public objectIdletime(key: string): T { + return this.addAndReturn(createObjectIdletime(key)); + } } /** diff --git a/node/tests/RedisClient.test.ts b/node/tests/RedisClient.test.ts index da4418134a..97a1f6e0d7 100644 --- a/node/tests/RedisClient.test.ts +++ b/node/tests/RedisClient.test.ts @@ -221,6 +221,51 @@ describe("RedisClient", () => { }, ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "object idletime transaction test_%p", + async (protocol) => { + const client = await RedisClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const key = uuidv4(); + const maxmemoryPolicyKey = "maxmemory-policy"; + const config = await client.configGet([maxmemoryPolicyKey]); + const maxmemoryPolicy = String(config[maxmemoryPolicyKey]); + + try { + const transaction = new Transaction(); + transaction.configSet({ + // OBJECT IDLETIME requires a non-LFU maxmemory-policy + [maxmemoryPolicyKey]: "allkeys-random", + }); + transaction.set(key, "foo"); + transaction.objectIdletime(key); + + const response = await client.exec(transaction); + expect(response).not.toBeNull(); + + if (response != null) { + expect(response.length).toEqual(3); + // transaction.configSet({[maxmemoryPolicyKey]: "allkeys-random"}); + expect(response[0]).toEqual("OK"); + // transaction.set(key, "foo"); + expect(response[1]).toEqual("OK"); + // transaction.objectIdletime(key); + expect(response[2]).toBeGreaterThanOrEqual(0); + } + } finally { + expect( + await client.configSet({ + [maxmemoryPolicyKey]: maxmemoryPolicy, + }), + ).toEqual("OK"); + } + + client.close(); + }, + ); + runBaseTests({ init: async (protocol, clientName?) => { const options = getClientConfigurationOption( diff --git a/node/tests/RedisClusterClient.test.ts b/node/tests/RedisClusterClient.test.ts index ad3e4190ae..967069597e 100644 --- a/node/tests/RedisClusterClient.test.ts +++ b/node/tests/RedisClusterClient.test.ts @@ -372,4 +372,49 @@ describe("RedisClusterClient", () => { client.close(); }, ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "object idletime transaction test_%p", + async (protocol) => { + const client = await RedisClusterClient.createClient( + getClientConfigurationOption(cluster.getAddresses(), protocol), + ); + + const key = uuidv4(); + const maxmemoryPolicyKey = "maxmemory-policy"; + const config = await client.configGet([maxmemoryPolicyKey]); + const maxmemoryPolicy = String(config[maxmemoryPolicyKey]); + + try { + const transaction = new ClusterTransaction(); + transaction.configSet({ + // OBJECT IDLETIME requires a non-LFU maxmemory-policy + [maxmemoryPolicyKey]: "allkeys-random", + }); + transaction.set(key, "foo"); + transaction.objectIdletime(key); + + const response = await client.exec(transaction); + expect(response).not.toBeNull(); + + if (response != null) { + expect(response.length).toEqual(3); + // transaction.configSet({[maxmemoryPolicyKey]: "allkeys-random"}); + expect(response[0]).toEqual("OK"); + // transaction.set(key, "foo"); + expect(response[1]).toEqual("OK"); + // transaction.objectIdletime(key); + expect(response[2]).toBeGreaterThanOrEqual(0); + } + } finally { + expect( + await client.configSet({ + [maxmemoryPolicyKey]: maxmemoryPolicy, + }), + ).toEqual("OK"); + } + + client.close(); + }, + ); }); diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index cc0403fd57..6e544e4602 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -3254,6 +3254,49 @@ export function runBaseTests(config: { }, config.timeout, ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "object idletime test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const nonExistingKey = uuidv4(); + const maxmemoryPolicyKey = "maxmemory-policy"; + const config = await client.configGet([maxmemoryPolicyKey]); + const maxmemoryPolicy = String(config[maxmemoryPolicyKey]); + + try { + expect( + await client.configSet({ + // OBJECT IDLETIME requires a non-LFU maxmemory-policy + [maxmemoryPolicyKey]: "allkeys-random", + }), + ).toEqual("OK"); + expect(await client.objectIdletime(nonExistingKey)).toEqual( + null, + ); + expect(await client.set(key, "foobar")).toEqual("OK"); + + await wait(2000); + + expect(await client.objectIdletime(key)).toBeGreaterThan(0); + } finally { + expect( + await client.configSet({ + [maxmemoryPolicyKey]: maxmemoryPolicy, + }), + ).toEqual("OK"); + } + }, protocol); + }, + config.timeout, + ); + + function wait(numMilliseconds: number) { + return new Promise((resolve) => { + setTimeout(resolve, numMilliseconds); + }); + } } export function runCommonTests(config: {