first commit
This commit is contained in:
98
docs/persistence/cache/cache-redis/src/redis-like/redis-like.md
vendored
Normal file
98
docs/persistence/cache/cache-redis/src/redis-like/redis-like.md
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user