Skip to content

Commit

Permalink
Node: Add command GETEX
Browse files Browse the repository at this point in the history
Signed-off-by: TJ Zhang <[email protected]>
  • Loading branch information
TJ Zhang committed Aug 9, 2024
1 parent ce69945 commit ac00647
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
* Node: Added XGROUP CREATE & XGROUP DESTROY commands ([#2084](https:/valkey-io/valkey-glide/pull/2084))
* Node: Added BZPOPMAX & BZPOPMIN command ([#2077]((https:/valkey-io/valkey-glide/pull/2077))
* Node: Added XGROUP CREATECONSUMER & XGROUP DELCONSUMER commands ([#2088](https:/valkey-io/valkey-glide/pull/2088))
* Node: Added GETEX command ([#2107]((https:/valkey-io/valkey-glide/pull/2107))

#### Breaking Changes
* Node: (Refactor) Convert classes to types ([#2005](https:/valkey-io/valkey-glide/pull/2005))
Expand Down
2 changes: 2 additions & 0 deletions node/npm/glide/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ function initialize() {
FunctionStatsResponse,
SlotIdTypes,
SlotKeyTypes,
TimeUnit,
RouteByAddress,
Routes,
SingleNodeRoute,
Expand Down Expand Up @@ -203,6 +204,7 @@ function initialize() {
SlotIdTypes,
SlotKeyTypes,
StreamEntries,
TimeUnit,
ReturnTypeXinfoStream,
RouteByAddress,
Routes,
Expand Down
30 changes: 30 additions & 0 deletions node/src/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import {
createGet,
createGetBit,
createGetDel,
createGetEx,
createGetRange,
createHDel,
createHExists,
Expand Down Expand Up @@ -201,6 +202,9 @@ import {
createZRevRankWithScore,
createZScan,
createZScore,
StreamClaimOptions,
createXClaim,
TimeUnit,
} from "./Commands";
import {
ClosingError,
Expand Down Expand Up @@ -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<string | null> {
return this.createWritePromise(createGetEx(key, options));
}

/**
* Gets a string value associated with the given `key`and deletes the key.
*
Expand Down
53 changes: 53 additions & 0 deletions node/src/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
25 changes: 25 additions & 0 deletions node/src/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ import {
createGet,
createGetBit,
createGetDel,
createGetEx,
createGetRange,
createHDel,
createHExists,
Expand Down Expand Up @@ -236,6 +237,9 @@ import {
createZRevRankWithScore,
createZScan,
createZScore,
createGeoSearchStore,
GeoSearchStoreResultOptions,
TimeUnit,
} from "./Commands";
import { command_request } from "./ProtobufMessage";

Expand Down Expand Up @@ -289,6 +293,7 @@ export class BaseTransaction<T extends BaseTransaction<T>> {
}

/** 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.
Expand All @@ -299,6 +304,26 @@ export class BaseTransaction<T extends BaseTransaction<T>> {
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.
*
Expand Down
46 changes: 46 additions & 0 deletions node/tests/SharedTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
Script,
SignedEncoding,
SortOrder,
TimeUnit,
Transaction,
UnsignedEncoding,
UpdateByScore,
Expand Down Expand Up @@ -8199,6 +8200,51 @@ export function runBaseTests<Context>(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<Context>(config: {
Expand Down
12 changes: 12 additions & 0 deletions node/tests/TestUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
ScoreFilter,
SignedEncoding,
SortOrder,
TimeUnit,
Transaction,
UnsignedEncoding,
} from "..";
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit ac00647

Please sign in to comment.