Files
skills/docs/persistence/cache/cache-redis/src/redis-like/redis-like.md
2026-05-29 08:20:56 +01:00

99 lines
4.3 KiB
Markdown

# RedisLike
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Declares the minimal Redis client surface `RedisCache` consumes. Any client conforming to this interface — `ioredis`, `node-redis`, a custom adapter over the official RESP3 protocol, or even a non-Redis store like KeyDB or Dragonfly that exposes the same commands — is a valid argument to the cache. Constraining the surface to seven primitives (`set`, `get`, `del`, `exists`, `ttl`, `expire`, `scan`) plus optional `mget` keeps the contract small enough that an in-memory `FakeRedis` for tests is straightforward to write and audit.
## Features
- Seven required methods plus optional `mget` — chosen to match the exact set of commands `RedisCache` needs at runtime.
- `set` accepts `{ EX, PX }` options matching the canonical Redis protocol; the cache only emits `EX`.
- `scan` returns the cursor + matched keys as a single normalised shape (`RedisScanResult`), letting adapters bridge over both the `ioredis` tuple shape `[cursor, keys]` and the `node-redis` object shape `{ cursor, keys }`.
- No transactional, pub/sub, or scripting primitives — those concerns belong to the application, not the cache.
- Pure type module — zero runtime code, no `instanceof` requirement.
## Usage: Basic Example
```ts
import type { RedisLike } from "@suaveplan/cache-redis/redis-like";
import { Redis } from "ioredis";
// `ioredis` v5 conforms to RedisLike out of the box.
const client: RedisLike = new Redis({ host: "redis.example.com" });
```
## Usage: Advanced Example
```ts
import type {
RedisLike,
RedisScanResult,
} from "@suaveplan/cache-redis/redis-like";
import { createClient } from "redis";
// `node-redis` returns scan results in `{ cursor, keys }` shape and uses
// `pExpire` for millisecond TTLs — a thin adapter normalises both.
class NodeRedisAdapter implements RedisLike {
readonly #client = createClient();
async set(key: string, value: string, opts?: { EX?: number }) {
return opts?.EX !== undefined
? this.#client.set(key, value, { EX: opts.EX })
: this.#client.set(key, value);
}
async get(k: string) {
return this.#client.get(k);
}
async del(...keys: string[]) {
return this.#client.del(keys);
}
async exists(...keys: string[]) {
return this.#client.exists(keys);
}
async ttl(k: string) {
return this.#client.ttl(k);
}
async expire(k: string, s: number) {
const ok = await this.#client.expire(k, s);
return ok ? 1 : 0;
}
async scan(
cursor: string,
opts?: { MATCH?: string; COUNT?: number },
): Promise<RedisScanResult> {
const result = await this.#client.scan(Number(cursor), {
MATCH: opts?.MATCH,
COUNT: opts?.COUNT,
});
return { cursor: String(result.cursor), keys: result.keys };
}
}
```
## API Reference
| Symbol | Kind | Description |
| --- | --- | --- |
| `RedisLike` | interface | Required surface: `set`, `get`, `del`, `exists`, `ttl`, `expire`, `scan`, optional `mget`. |
| `RedisSetOptions` | interface | `{ EX?: number; PX?: number }`. |
| `RedisScanOptions` | interface | `{ MATCH?: string; COUNT?: number }`. |
| `RedisScanResult` | interface | `{ cursor: string; keys: string[] }`. |
### Method semantics
- `set(key, value, opts?)` — `SET key value [EX seconds]`. Returns the server's response (typically `"OK"`); the cache ignores it.
- `get(key)` — returns the value or `null` on miss.
- `del(...keys)` — returns the number of keys actually deleted.
- `exists(...keys)` — returns the number of matching keys.
- `ttl(key)` — returns seconds, `-1` for "no expiry", `-2` for "missing key".
- `expire(key, seconds)` — returns `1` on success, `0` on missing key.
- `scan(cursor, opts?)` — returns `{ cursor, keys }` with `cursor === "0"` signalling end of iteration.
- `mget(...keys)` — optional; if absent, `getMultiple` falls back to per-key `get`.
## Implementation Notes
The interface uses `string` for both keys and values because the canonical RESP protocol speaks strings; `RedisCache` runs UTF-8 / base64 conversion at the boundary. Adapters that expose `Buffer` (`ioredis` in `Buffer`-mode, `node-redis` with `returnBuffers`) work as long as the adapter normalises back to `string` before returning. A future binary-safe variant of the cache may add a parallel `RedisBinaryLike` contract; until then keeping `RedisLike` string-only keeps the implementation universal compatible.