99 lines
4.3 KiB
Markdown
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.
|