Skip to content

Commit ed3e48a

Browse files
committed
feat: add basic commands for sorted sets
1 parent 2bd1229 commit ed3e48a

File tree

3 files changed

+122
-0
lines changed

3 files changed

+122
-0
lines changed

src/commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export * from "./commands/hash";
44
export * from "./commands/pubsub";
55
export * from "./commands/read";
66
export * from "./commands/set";
7+
export * from "./commands/sorted-set";
78
export * from "./commands/stream";
89
export * from "./commands/write";
910

src/commands/sorted-set.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { StaticEncode, TSchema } from "@sinclair/typebox";
2+
import { RedisCommand, RedisValue } from "../command";
3+
import { RedisSortedSet, RedisSortedSetEntry } from "../key";
4+
5+
/**
6+
* Adds entries to a sorted set.
7+
*
8+
* If the no entries are provided, an error is thrown.
9+
*
10+
* @see https://redis.io/commands/zadd
11+
* @returns The number of elements added to the sorted set (excluding score
12+
* updates).
13+
*/
14+
export function ZADD<T extends TSchema>(
15+
key: RedisSortedSet<T>,
16+
entries: [score: number, value: StaticEncode<T>][],
17+
) {
18+
if (entries.length === 0) {
19+
throw new Error("At least one entry is required");
20+
}
21+
const flatEntries: RedisValue[] = new Array(entries.length * 2);
22+
for (const [score, value] of entries) {
23+
flatEntries.push(score);
24+
flatEntries.push(key.encode(value));
25+
}
26+
return new RedisCommand<number>(["ZADD", key.name, ...flatEntries]);
27+
}
28+
29+
function parseSortedSetEntry<T extends TSchema>(
30+
reply: [key: string, data: string, score: string],
31+
keys: RedisSortedSet<T>[],
32+
) {
33+
const key = keys.find((key) => key.name === reply[0])!;
34+
return new RedisSortedSetEntry(
35+
key,
36+
key.decode(reply[1]),
37+
parseFloat(reply[2]),
38+
);
39+
}
40+
41+
/**
42+
* Removes and returns the entry with the highest score from the first
43+
* non-empty sorted set, blocking until one is available or the timeout is
44+
* reached.
45+
*
46+
* @see https://redis.io/commands/bzpopmax
47+
*/
48+
export function BZPOPMAX<T extends TSchema>(
49+
keys: RedisSortedSet<T>[],
50+
timeout: number,
51+
) {
52+
if (keys.length === 0) {
53+
throw new Error("At least one key is required");
54+
}
55+
return new RedisCommand(
56+
["BZPOPMAX", ...keys.map((key) => key.name), timeout],
57+
(reply) => (reply !== null ? parseSortedSetEntry(reply, keys) : null),
58+
);
59+
}
60+
61+
/**
62+
* Removes and returns the entry with the lowest score from the first
63+
* non-empty sorted set, blocking until one is available or the timeout is
64+
* reached.
65+
*
66+
* @see https://redis.io/commands/bzpopmin
67+
*/
68+
export function BZPOPMIN<T extends TSchema>(
69+
keys: RedisSortedSet<T>[],
70+
timeout: number,
71+
) {
72+
if (keys.length === 0) {
73+
throw new Error("At least one key is required");
74+
}
75+
return new RedisCommand(
76+
["BZPOPMIN", ...keys.map((key) => key.name), timeout],
77+
(reply) => (reply !== null ? parseSortedSetEntry(reply, keys) : null),
78+
);
79+
}
80+
81+
/**
82+
* Removes the specified values from the sorted set stored at key.
83+
* Non-existing values are ignored.
84+
*
85+
* @see https://redis.io/commands/zrem
86+
*/
87+
export function ZREM<T extends TSchema>(
88+
key: RedisSortedSet<T>,
89+
...values: [StaticEncode<T>, ...StaticEncode<T>[]]
90+
) {
91+
return new RedisCommand<number>([
92+
"ZREM",
93+
key.name,
94+
...values.map((value) => key.encode(value)),
95+
]);
96+
}

src/key.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,31 @@ export class RedisHash<
172172
}
173173
}
174174

175+
export class RedisSortedSet<
176+
T extends TSchema = TSchema,
177+
K extends string | RedisKeyspace = string,
178+
> extends RedisKey<T, K> {
179+
declare $$typeof: "RedisKey" & { subtype: "RedisSortedSet" };
180+
declare qualify: {
181+
(
182+
name: K extends RedisKeyspace<infer KeyType> ? KeyType : string | number,
183+
): RedisSortedSet<T, string>;
184+
<T extends TSchema>(
185+
name: K extends RedisKeyspace<infer KeyType> ? KeyType : string | number,
186+
schema: T,
187+
): RedisSortedSet<T, string>;
188+
};
189+
}
190+
191+
export class RedisSortedSetEntry<T extends TSchema> {
192+
declare $$typeof: "RedisSortedSetEntry";
193+
constructor(
194+
public readonly key: RedisSortedSet<T>,
195+
public readonly data: StaticEncode<T>,
196+
public readonly score: number,
197+
) {}
198+
}
199+
175200
export class RedisIndex {
176201
declare $$typeof: "RedisIndex";
177202
constructor(readonly name: string) {}

0 commit comments

Comments
 (0)