Skip to content

Commit a08f1ca

Browse files
Added command: getex (#1358)
* Added command: getex * Updated other modules regarding GetExCommand. Also added an extra test: redis_getex * fix: tests * fix: lint --------- Co-authored-by: CahidArda <[email protected]>
1 parent 71f0bae commit a08f1ca

9 files changed

+182
-17
lines changed

pkg/auto-pipeline.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ describe("Auto pipeline", () => {
4444
redis.get(newKey()),
4545
redis.getbit(newKey(), 0),
4646
redis.getdel(newKey()),
47+
redis.getex(newKey()),
4748
redis.getset(newKey(), "hello"),
4849
redis.hdel(newKey(), "field"),
4950
redis.hexists(newKey(), "field"),
@@ -148,7 +149,7 @@ describe("Auto pipeline", () => {
148149
redis.json.arrappend(persistentKey3, "$.log", '"three"'),
149150
]);
150151
expect(result).toBeTruthy();
151-
expect(result.length).toBe(121); // returns
152+
expect(result.length).toBe(122); // returns
152153
// @ts-expect-error pipelineCounter is not in type but accessible120 results
153154
expect(redis.pipelineCounter).toBe(1);
154155
});

pkg/commands/exec.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
import { type CommandOptions, Command } from "./command";
1+
import { type CommandOptions, Command } from "./command";
22

33
/**
44
* Generic exec command for executing arbitrary Redis commands
55
* Allows executing Redis commands that might not be directly supported by the SDK
6-
*
6+
*
77
* @example
88
* // Execute MEMORY USAGE command
99
* await redis.exec<number>("MEMORY", "USAGE", "myKey")
10-
*
10+
*
1111
* // Execute GET command
1212
* await redis.exec<string>("GET", "foo")
1313
*/
1414

1515
export class ExecCommand<TResult> extends Command<TResult, TResult> {
16-
constructor(
17-
cmd: [command: string, ...args: (string | number | boolean)[]],
18-
opts?: CommandOptions<TResult, TResult>
19-
){
20-
const normalizedCmd = cmd.map(arg => typeof arg === "string" ? arg : String(arg));
21-
super(normalizedCmd, opts);
22-
}
23-
}
16+
constructor(
17+
cmd: [command: string, ...args: (string | number | boolean)[]],
18+
opts?: CommandOptions<TResult, TResult>
19+
) {
20+
const normalizedCmd = cmd.map((arg) => (typeof arg === "string" ? arg : String(arg)));
21+
super(normalizedCmd, opts);
22+
}
23+
}

pkg/commands/getex.test.ts

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { keygen, newHttpClient, randomID } from "../test-utils";
2+
3+
import { afterAll, describe, expect, test } from "bun:test";
4+
import { GetExCommand } from "./getex";
5+
import { GetCommand } from "./get";
6+
import { SetCommand } from "./set";
7+
8+
const client = newHttpClient();
9+
10+
const { newKey, cleanup } = keygen();
11+
afterAll(cleanup);
12+
13+
describe("without options", () => {
14+
test("gets value", async () => {
15+
const key = newKey();
16+
const value = randomID();
17+
18+
const res = await new SetCommand([key, value]).exec(client);
19+
expect(res).toEqual("OK");
20+
const res2 = await new GetExCommand([key]).exec(client);
21+
expect(res2).toEqual(value);
22+
});
23+
});
24+
25+
describe("ex", () => {
26+
test("gets value and sets expiry in seconds", async () => {
27+
const key = newKey();
28+
const value = randomID();
29+
30+
const res = await new SetCommand([key, value]).exec(client);
31+
expect(res).toEqual("OK");
32+
const res2 = await new GetExCommand([key, { ex: 1 }]).exec(client);
33+
expect(res2).toEqual(value);
34+
await new Promise((res) => setTimeout(res, 2000));
35+
36+
const res3 = await new GetCommand([key]).exec(client);
37+
expect(res3).toEqual(null);
38+
});
39+
});
40+
41+
describe("px", () => {
42+
test("gets value and sets expiry in milliseconds", async () => {
43+
const key = newKey();
44+
const value = randomID();
45+
46+
const res = await new SetCommand([key, value]).exec(client);
47+
expect(res).toEqual("OK");
48+
const res2 = await new GetExCommand([key, { px: 1000 }]).exec(client);
49+
expect(res2).toEqual(value);
50+
await new Promise((res) => setTimeout(res, 2000));
51+
52+
const res3 = await new GetCommand([key]).exec(client);
53+
54+
expect(res3).toEqual(null);
55+
});
56+
});
57+
58+
describe("exat", () => {
59+
test("gets value and sets expiry in Unix time (seconds)", async () => {
60+
const key = newKey();
61+
const value = randomID();
62+
63+
const res = await new SetCommand([key, value]).exec(client);
64+
expect(res).toEqual("OK");
65+
const res2 = await new GetExCommand([
66+
key,
67+
{
68+
exat: Math.floor(Date.now() / 1000) + 2,
69+
},
70+
]).exec(client);
71+
expect(res2).toEqual(value);
72+
await new Promise((res) => setTimeout(res, 3000));
73+
74+
const res3 = await new GetCommand([key]).exec(client);
75+
76+
expect(res3).toEqual(null);
77+
});
78+
});
79+
80+
describe("pxat", () => {
81+
test("gets value and sets expiry in Unix time (milliseconds)", async () => {
82+
const key = newKey();
83+
const value = randomID();
84+
85+
const res = await new SetCommand([key, value]).exec(client);
86+
expect(res).toEqual("OK");
87+
const res2 = await new GetExCommand([key, { pxat: Date.now() + 2000 }]).exec(client);
88+
expect(res2).toEqual(value);
89+
await new Promise((res) => setTimeout(res, 3000));
90+
91+
const res3 = await new GetCommand([key]).exec(client);
92+
93+
expect(res3).toEqual(null);
94+
});
95+
});
96+
97+
describe("persist", () => {
98+
test("gets value and removes expiry", async () => {
99+
const key = newKey();
100+
const value = randomID();
101+
102+
const res = await new SetCommand([key, value, { ex: 1 }]).exec(client);
103+
expect(res).toEqual("OK");
104+
const res2 = await new GetExCommand([key, { persist: true }]).exec(client);
105+
expect(res2).toEqual(value);
106+
await new Promise((res) => setTimeout(res, 2000));
107+
108+
const res3 = await new GetCommand([key]).exec(client);
109+
110+
expect(res3).toEqual(value);
111+
});
112+
});

pkg/commands/getex.ts

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { CommandOptions } from "./command";
2+
import { Command } from "./command";
3+
4+
type GetExCommandOptions =
5+
| { ex: number; px?: never; exat?: never; pxat?: never; persist?: never }
6+
| { ex?: never; px: number; exat?: never; pxat?: never; persist?: never }
7+
| { ex?: never; px?: never; exat: number; pxat?: never; persist?: never }
8+
| { ex?: never; px?: never; exat?: never; pxat: number; persist?: never }
9+
| { ex?: never; px?: never; exat?: never; pxat?: never; persist: true }
10+
| { ex?: never; px?: never; exat?: never; pxat?: never; persist?: never };
11+
12+
/**
13+
* @see https://redis.io/commands/getex
14+
*/
15+
export class GetExCommand<TData = string> extends Command<unknown | null, TData | null> {
16+
constructor(
17+
[key, opts]: [key: string, opts?: GetExCommandOptions],
18+
cmdOpts?: CommandOptions<unknown | null, TData | null>
19+
) {
20+
const command: unknown[] = ["getex", key];
21+
if (opts) {
22+
if ("ex" in opts && typeof opts.ex === "number") {
23+
command.push("ex", opts.ex);
24+
} else if ("px" in opts && typeof opts.px === "number") {
25+
command.push("px", opts.px);
26+
} else if ("exat" in opts && typeof opts.exat === "number") {
27+
command.push("exat", opts.exat);
28+
} else if ("pxat" in opts && typeof opts.pxat === "number") {
29+
command.push("pxat", opts.pxat);
30+
} else if ("persist" in opts && opts.persist) {
31+
command.push("persist");
32+
}
33+
}
34+
super(command, cmdOpts);
35+
}
36+
}

pkg/commands/mod.ts

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export * from "./geo_search_store";
2727
export * from "./get";
2828
export * from "./getbit";
2929
export * from "./getdel";
30+
export * from "./getex";
3031
export * from "./getrange";
3132
export * from "./getset";
3233
export * from "./hdel";

pkg/commands/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export { type GeoSearchStoreCommand } from "./geo_search_store";
2424
export { type GetCommand } from "./get";
2525
export { type GetBitCommand } from "./getbit";
2626
export { type GetDelCommand } from "./getdel";
27+
export { type GetExCommand } from "./getex";
2728
export { type GetRangeCommand } from "./getrange";
2829
export { type GetSetCommand } from "./getset";
2930
export { type HDelCommand } from "./hdel";

pkg/pipeline.test.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ describe("use all the things", () => {
146146
.get(newKey())
147147
.getbit(newKey(), 0)
148148
.getdel(newKey())
149+
.getex(newKey())
149150
.getset(newKey(), "hello")
150151
.hdel(newKey(), "field")
151152
.hexists(newKey(), "field")
@@ -249,15 +250,15 @@ describe("use all the things", () => {
249250
.json.set(newKey(), "$", { hello: "world" });
250251

251252
const res = await p.exec();
252-
expect(res.length).toEqual(121);
253+
expect(res.length).toEqual(122);
253254
});
254255
});
255256
describe("keep errors", () => {
256257
test("should return results in case of success", async () => {
257258
const p = new Pipeline({ client });
258259
p.set("foo", "1");
259260
p.set("bar", "2");
260-
p.get("foo");
261+
p.getex("foo", { ex: 1 });
261262
p.get("bar");
262263
const results = await p.exec({ keepErrors: true });
263264

@@ -286,18 +287,18 @@ describe("keep errors", () => {
286287
p.set("foo", "1");
287288
p.set("bar", "2");
288289
p.evalsha("wrong-sha1", [], []);
289-
p.get("foo");
290+
p.getex("foo", { exat: 123 });
290291
p.get("bar");
291292
const results = await p.exec<[string, string, string, number, number]>({ keepErrors: true });
292293

293294
expect(results[0].error).toBeUndefined();
294295
expect(results[1].error).toBeUndefined();
295296
expect(results[2].error).toBe("NOSCRIPT No matching script. Please use EVAL.");
296-
expect(results[3].error).toBeUndefined();
297+
expect(results[3].error).toBe("ERR invalid expire time");
297298
expect(results[4].error).toBeUndefined();
298299

299300
expect(results[2].result).toBeUndefined();
300-
expect(results[3].result).toBe(1);
301+
expect(results[3].result).toBeUndefined();
301302
expect(results[4].result).toBe(2);
302303
});
303304
});

pkg/pipeline.ts

+6
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
GetBitCommand,
3535
GetCommand,
3636
GetDelCommand,
37+
GetExCommand,
3738
GetRangeCommand,
3839
GetSetCommand,
3940
HDelCommand,
@@ -544,6 +545,11 @@ export class Pipeline<TCommands extends Command<any, any>[] = []> {
544545
*/
545546
getdel = <TData>(...args: CommandArgs<typeof GetDelCommand>) =>
546547
this.chain(new GetDelCommand<TData>(args, this.commandOptions));
548+
/**
549+
* @see https://redis.io/commands/getex
550+
*/
551+
getex = <TData>(...args: CommandArgs<typeof GetExCommand>) =>
552+
this.chain(new GetExCommand<TData>(args, this.commandOptions));
547553
/**
548554
* @see https://redis.io/commands/getrange
549555
*/

pkg/redis.ts

+7
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
GetBitCommand,
3636
GetCommand,
3737
GetDelCommand,
38+
GetExCommand,
3839
GetRangeCommand,
3940
GetSetCommand,
4041
HDelCommand,
@@ -621,6 +622,12 @@ export class Redis {
621622
getdel = <TData>(...args: CommandArgs<typeof GetDelCommand>) =>
622623
new GetDelCommand<TData>(args, this.opts).exec(this.client);
623624

625+
/**
626+
* @see https://redis.io/commands/getex
627+
*/
628+
getex = <TData>(...args: CommandArgs<typeof GetExCommand>) =>
629+
new GetExCommand<TData>(args, this.opts).exec(this.client);
630+
624631
/**
625632
* @see https://redis.io/commands/getrange
626633
*/

0 commit comments

Comments
 (0)