import type { Redis } from "ioredis"; import { RedisKVMarker } from "../keys.js"; import type { createPinboard } from "../server.js"; type RedisKV = createPinboard.RedisKV; type SetOptions = createPinboard.SetOptions; export class IoredisKV implements RedisKV { readonly [RedisKVMarker] = true; private readonly client: Redis; constructor(client: Redis) { if (typeof client?.call !== "function") { throw new Error("IoredisKV expects an ioredis instance"); } this.client = client; } get(key: string): Promise { return this.client.get(key); } async set( key: string, value: string, opts?: SetOptions, ): Promise<"OK" | null> { const args: (string | number)[] = []; if (opts?.pxMs !== undefined) args.push("PX", opts.pxMs); if (opts?.nx) args.push("NX"); const result = await this.client.call("SET", key, value, ...args); return result === "OK" ? "OK" : null; } del(key: string): Promise { return this.client.del(key); } pexpire(key: string, ms: number): Promise { return this.client.pexpire(key, ms); } hget(key: string, field: string): Promise { return this.client.hget(key, field); } hset(key: string, field: string, value: string): Promise { return this.client.hset(key, field, value); } hsetnx(key: string, field: string, value: string): Promise { return this.client.hsetnx(key, field, value); } hdel(key: string, field: string): Promise { return this.client.hdel(key, field); } hgetall(key: string): Promise> { return this.client.hgetall(key); } async hscan( key: string, cursor: string, count: number, ): Promise<{ cursor: string; entries: [string, string][] }> { const [next, flat] = await this.client.hscan(key, cursor, "COUNT", count); const entries: [string, string][] = []; for (let i = 0; i < flat.length; i += 2) { entries.push([flat[i]!, flat[i + 1]!]); } return { cursor: next, entries }; } rpush(key: string, value: string): Promise { return this.client.rpush(key, value); } lrange(key: string, start: number, stop: number): Promise { return this.client.lrange(key, start, stop); } lrem(key: string, count: number, value: string): Promise { return this.client.lrem(key, count, value); } async scriptLoad(script: string): Promise { // EVALSHA routes by key, so a cluster needs the script on every master. const client = this.client as Redis & { nodes?: (role: "master") => Redis[]; }; const nodes = typeof client.nodes === "function" ? client.nodes("master") : [client]; const shas = await Promise.all( nodes.map((node) => node.script("LOAD", script)), ); return shas[0] as string; } evalsha(sha: string, keys: string[], argv: string[]): Promise { return this.client.evalsha(sha, keys.length, ...keys, ...argv); } async quit(): Promise { await this.client.quit().catch(() => {}); } }