# RedisLike _(no submodules)_ ## 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 { 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.