UNPKG

power-redis

Version:

Type-safe Redis wrapper for Node.js providing predictable key schema, safe JSON serialization, atomic list ops, and chunked SCAN/MGET utilities.

1,071 lines (1,068 loc) 38.5 kB
/** * @public * @packageDocumentation * Utility type aliases and Redis client façade interfaces used by the * `full-utils` Redis helpers and queue primitives. * * @remarks * These definitions are intentionally **library-agnostic** and cover the * smallest common subset of features present in popular Redis clients such as * `ioredis` and `node-redis`. They let you write reusable logic that can be * typed once and executed against different client implementations—handy for * testing and for swapping clients without refactoring the rest of your code. * * @since 1.0.0 */ /** * Primitive JSON values as defined by RFC 7159. * * @remarks * This type is useful when you want to model values that can be safely * serialized with `JSON.stringify` and deserialized with `JSON.parse` * **without lossy conversions**. * * @example * ```ts * const value: JsonPrimitive = 'hello'; * const other: JsonPrimitive = 42; * const flag: JsonPrimitive = false; * const empty: JsonPrimitive = null; * ``` * * @see {@link Jsonish} for a recursive JSON-like structure. * @since 1.0.0 */ type JsonPrimitive = string | number | boolean | null; /** * A recursive, JSON-serializable structure composed of primitives, arrays, * and plain objects with string keys. * * @remarks * - Values conforming to `Jsonish` can be round-tripped with * `JSON.stringify` / `JSON.parse`. * - Keys are restricted to strings to match how JSON objects are encoded. * - This is useful for defining payloads you intend to store in Redis as * strings (for example, via `SET`, `RPUSH`, or in sorted set members). * * @example * ```ts * const payload: Jsonish = { * id: 'u_123', * balance: 19.95, * tags: ['new', 'trial'], * meta: { newsletter: false } * }; * ``` * * @since 1.0.0 */ type Jsonish = JsonPrimitive | { [key: string]: Jsonish; } | Jsonish[]; /** * A **minimal, chainable** interface describing a Redis MULTI transaction. * * @remarks * Instances of this interface are created by calling {@link IORedisLike.multi}. * Each method enqueues a command in the transaction and returns `this` so that * you can fluently chain multiple operations before calling {@link exec}. * * The implementation is expected to mirror Redis semantics: * - Commands are queued and **not executed** until `exec()` is called. * - `exec()` resolves to an array of `[error, reply]` tuples, one per command, * preserving order. * * @example * ```ts * const tx = client.multi() * .set('user:1', JSON.stringify({ id: 1 })) * .expire('user:1', 3600) * .rpush('queue:jobs', 'job-1', 'job-2'); * * const results = await tx.exec(); * // results: Array<[Error | null, any]> * ``` * * @see {@link IORedisLike} for the corresponding client surface. * @since 1.0.0 */ interface RedisMultiLike { /** * Queue `SET key value` into the transaction. * * @param key - Redis key to set. * @param value - Raw string value to store (serialize your objects beforehand). * @returns The same transaction instance for chaining. * * @remarks * Use this overload when no TTL is needed. * * @example * ```ts * multi.set('flags:site-enabled', '1'); * ``` * * @since 1.0.0 */ set(key: string, value: string): this; /** * Queue `SET key value EX ttlSec` into the transaction. * * @param key - Redis key to set. * @param value - Raw string value to store. * @param ex - The literal string `'EX'`; included for stricter typing. * @param ttlSec - Time-to-live in seconds. * @returns The same transaction instance for chaining. * * @remarks * This overload configures an **expiry** alongside the write. * * @example * ```ts * multi.set('session:abc', token, 'EX', 1800); * ``` * * @since 1.0.0 */ set(key: string, value: string, ex: 'EX', ttlSec: number): this; /** * Queue `RPUSH key ...values`. * * @param key - Target list key. * @param values - One or more string items to append (right push). * @returns The same transaction instance for chaining. * * @remarks * Using lists for queues? Consider pairing with `LMOVE`/`RPOPLPUSH` for * **at-least-once** processing patterns. * * @example * ```ts * multi.rpush('queue:mail', JSON.stringify({ to: 'a@b' })); * ``` * * @since 1.0.0 */ rpush(key: string, ...values: string[]): this; /** * Queue `LPUSH key ...values`. * * @param key - Target list key. * @param values - One or more string items to prepend (left push). * @returns The same transaction instance for chaining. * * @since 1.0.0 */ lpush(key: string, ...values: string[]): this; /** * Queue `LRANGE key start stop`. * * @param key - List key. * @param start - Start index (0-based; `0` is the head). * @param stop - Stop index (inclusive; `-1` for the tail). * @returns The same transaction instance for chaining. * * @since 1.0.0 */ lrange(key: string, start: number, stop: number): this; /** * Queue `LTRIM key start stop` to keep only a sub-range of a list. * * @param key - List key. * @param start - Start index to keep. * @param stop - Stop index to keep (inclusive). * @returns The same transaction instance for chaining. * * @example * ```ts * // Keep the most recent 100 items * multi.ltrim('logs:app', -100, -1); * ``` * * @since 1.0.0 */ ltrim(key: string, start: number, stop: number): this; /** * Queue `LREM key count value` to remove occurrences from a list. * * @param key - List key. * @param count - Removal mode: * `>0` remove from head, `<0` remove from tail, `0` remove all. * @param value - The string to match. * @returns The same transaction instance for chaining. * * @since 1.0.0 */ lrem(key: string, count: number, value: string): this; /** * Queue `ZREM key ...members` to remove members from a sorted set. * * @param key - Sorted set key. * @param members - One or more member strings to remove. * @returns The same transaction instance for chaining. * * @since 1.0.0 */ zrem(key: string, ...members: string[]): this; /** * Queue `ZADD key score member` to add or update a single member. * * @param key - Sorted set key. * @param score - Numerical score used for ordering. * @param member - Member value (string). * @returns The same transaction instance for chaining. * * @remarks * For bulk `ZADD`, prefer the client method on {@link IORedisLike.zadd}. * * @since 1.0.0 */ zadd(key: string, score: number, member: string): this; /** * Queue `EXPIRE key ttlSec` to set an expiry on a key. * * @param key - Any Redis key. * @param ttlSec - Time-to-live in seconds. * @returns The same transaction instance for chaining. * * @since 1.0.0 */ expire(key: string, ttlSec: number): this; /** * Execute the queued transaction (`EXEC`) and resolve results. * * @returns A promise resolving to an array of tuples `[error, reply]`, * one per queued command, in the same order. * * @remarks * If the transaction is discarded or aborted by the server, behavior depends * on the underlying client. Many clients reject the promise with an error. * * @example * ```ts * const results = await multi.exec(); * for (const [err, reply] of results) { * if (err) console.error('Command failed:', err.message); * } * ``` * * @throws {Error} If the underlying client fails to execute `EXEC`. * @since 1.0.0 */ exec(): Promise<Array<[Error | null, any]>>; } /** * A **lightweight, promise-based** interface describing the subset of methods * used by this library from a Redis client such as `ioredis`. * * @remarks * - All methods return promises and are expected to be **single-command** calls * unless otherwise stated. * - Optional methods (marked with `?`) may not be present in older servers or * client versions; you should feature-detect before using them. * - Keys and values are typed as strings to keep the surface area minimal. * * @example * ```ts * async function pushJob(client: IORedisLike, queue: string, job: Jsonish) { * await client.rpush(queue, JSON.stringify(job)); * } * ``` * * @since 1.0.0 */ interface IORedisLike { /** * Current client status string (e.g. `'ready'`, `'connecting'`). * * @remarks * Intended for simple health checks or readiness gates. * * @since 1.0.0 */ status: 'ready' | 'connecting' | 'reconnecting' | string; /** * Cursor-based key scan: `SCAN cursor MATCH pattern COUNT count`. * * @param cursor - The cursor from the previous call, or `'0'` to start. * @param matchKeyword - The literal `'MATCH'` (typed for clarity). * @param pattern - Glob-style pattern (e.g. `logs:*`). * @param countKeyword - The literal `'COUNT'` (typed for clarity). * @param count - Hint for how many keys to return per step. * @returns A tuple `[nextCursor, keys]`. Iteration ends when `nextCursor === '0'`. * * @example * ```ts * let cursor = '0'; * do { * const [next, keys] = await client.scan(cursor, 'MATCH', 'queue:*', 'COUNT', 100); * // process keys... * cursor = next; * } while (cursor !== '0'); * ``` * * @since 1.0.0 */ scan(cursor: string, matchKeyword: 'MATCH', pattern: string, countKeyword: 'COUNT', count: number): Promise<[nextCursor: string, keys: string[]]>; /** * Get the string value of a key: `GET key`. * * @param key - Key to read. * @returns The stored string or `null` if the key does not exist. * @since 1.0.0 */ get(key: string): Promise<string | null>; /** * Get multiple keys at once: `MGET key [key ...]`. * * @param keys - One or more keys. * @returns Array of string values or `null` for missing keys, matching the * input order. * @since 1.0.0 */ mget(...keys: string[]): Promise<Array<string | null>>; /** * Set a key to a string value: `SET key value`. * * @param key - Key to write. * @param value - Raw string value (serialize complex objects yourself). * @returns `'OK'` if successful. * @since 1.0.0 */ set(key: string, value: string): Promise<'OK'>; /** * Set a key with expiry: `SET key value EX ttlSec`. * * @param key - Key to write. * @param value - Raw string value. * @param ex - The literal `'EX'` (seconds). * @param ttlSec - Time-to-live in seconds. * @returns `'OK'` if successful. * @since 1.0.0 */ set(key: string, value: string, ex: 'EX', ttlSec: number): Promise<'OK'>; /** * Set multiple keys in one call: `MSET key value [key value ...]`. * * @param keyValues - An even list of alternating keys and values. * @returns `'OK'` if successful. * @since 1.0.0 */ mset(...keyValues: string[]): Promise<'OK'>; /** * Atomically increment a key: `INCR key`. * * @param key - Counter key (created as `0` if it does not exist). * @returns The new value after increment. * @since 1.0.0 */ incr(key: string): Promise<number>; /** * Length of a list: `LLEN key`. * * @param key - List key. * @returns Number of elements in the list. * @since 1.0.0 */ llen(key: string): Promise<number>; /** * Get a range from a list: `LRANGE key start stop`. * * @param key - List key. * @param start - Start index (0-based). * @param stop - Stop index (inclusive; `-1` for tail). * @returns Array of string elements. * @since 1.0.0 */ lrange(key: string, start: number, stop: number): Promise<string[]>; /** * Pop from the left: `LPOP key [count]`. * * @param key - List key. * @param count - Optional number of elements to pop (server ≥ 6.2). * @returns A single string, an array of strings (when `count` is provided), * or `null` if the list is empty. * @since 1.0.0 */ lpop(key: string, count?: number): Promise<string[] | string | null>; /** * Push to the right: `RPUSH key ...values`. * * @param key - List key. * @param values - One or more items to append. * @returns New length of the list. * @since 1.0.0 */ rpush(key: string, ...values: string[]): Promise<number>; /** * Push to the left: `LPUSH key ...values`. * * @param key - List key. * @param values - One or more items to prepend. * @returns New length of the list. * @since 1.0.0 */ lpush(key: string, ...values: string[]): Promise<number>; /** * Trim a list to a sub-range: `LTRIM key start stop`. * * @param key - List key. * @param start - Start index to keep. * @param stop - Stop index to keep (inclusive). * @returns `'OK'` on success. * @since 1.0.0 */ ltrim(key: string, start: number, stop: number): Promise<'OK'>; /** * Remove occurrences from a list: `LREM key count value`. * * @param key - List key. * @param count - `>0` from head, `<0` from tail, `0` remove all. * @param value - String to match. * @returns Number of removed elements. * @since 1.0.0 */ lrem(key: string, count: number, value: string): Promise<number>; /** * Move one element atomically between lists: `LMOVE`. * * @param source - Source list key. * @param destination - Destination list key. * @param whereFrom - `'LEFT'` or `'RIGHT'` side of the source. * @param whereTo - `'LEFT'` or `'RIGHT'` side of the destination. * @returns The moved element, or `null` if the source was empty. * * @remarks * Optional: may be unavailable in older Redis versions (< 6.2). * Feature-detect before using. * * @since 1.0.0 */ lmove?(source: string, destination: string, whereFrom: 'LEFT' | 'RIGHT', whereTo: 'LEFT' | 'RIGHT'): Promise<string | null>; /** * Fallback atomic move: `RPOPLPUSH source destination`. * * @param source - Source list key (pop from right). * @param destination - Destination list key (push to left). * @returns The moved element or `null` if the source was empty. * * @remarks * Optional: older pattern used before `LMOVE` existed. * * @since 1.0.0 */ rpoplpush?(source: string, destination: string): Promise<string | null>; /** * Add elements to a sorted set: `ZADD key ...args`. * * @param args - A sequence of options and/or `score member` pairs. * Common patterns: * - `score1, member1, score2, member2, ...` * - with modifiers like `'NX' | 'XX' | 'CH' | 'INCR'` depending on the client. * @returns The number of new elements added (not including updated ones). * * @example * ```ts * await client.zadd('schedule', 1690000000, 'job-1', 1690000100, 'job-2'); * ``` * * @since 1.0.0 */ zadd(key: string, ...args: (string | number)[]): Promise<number>; /** * Remove members from a sorted set: `ZREM key ...members`. * * @param members - One or more member strings to remove. * @returns The number of removed elements. * @since 1.0.0 */ zrem(key: string, ...members: string[]): Promise<number>; /** * Range query by score: `ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]`. * * @param key - Sorted set key. * @param min - Minimum score (number or string like `'(42'` for exclusive). * @param max - Maximum score (number or string like `'(100'` for exclusive). * @param args - Optional flags such as `'WITHSCORES'`, `'LIMIT'`, etc. * @returns Array of members (and optionally scores, depending on flags). * * @example * ```ts * // Get due jobs * const now = Date.now(); * const jobs = await client.zrangebyscore('schedule', 0, now, 'LIMIT', 0, 100); * ``` * * @since 1.0.0 */ zrangebyscore(key: string, min: number | string, max: number | string, ...args: (string | number)[]): Promise<string[]>; /** * Set a key expiry in seconds: `EXPIRE key ttlSec`. * * @param key - Any key. * @param ttlSec - Time-to-live in seconds. * @returns `1` if the timeout was set, `0` if the key does not exist. * @since 1.0.0 */ expire(key: string, ttlSec: number): Promise<number>; /** * Asynchronously unlink (free) keys: `UNLINK ...keys`. * * @param keys - One or more keys to unlink. * @returns Number of keys unlinked. * * @remarks * Optional: Some clients expose `UNLINK`; use `DEL` as a fallback. * * @since 1.0.0 */ unlink?(...keys: string[]): Promise<number>; /** * Synchronously delete keys: `DEL ...keys`. * * @param keys - One or more keys to delete. * @returns Number of keys removed. * @since 1.0.0 */ del(...keys: string[]): Promise<number>; /** * Start a transaction: `MULTI`. * * @returns A chainable transaction object; call {@link RedisMultiLike.exec} * to actually run the queued commands. * * @since 1.0.0 */ multi(): RedisMultiLike; /** * Load a Lua script into the script cache: `SCRIPT LOAD script`. * * @param subcommand - Must be the literal `'LOAD'`. * @param script - The Lua source code. * @returns The SHA1 digest string of the script. * * @remarks * Optional: Not all client builds expose `script()`. Use `eval` as a fallback. * * @since 1.0.0 */ script?(subcommand: 'LOAD', script: string): Promise<string>; /** * Execute a cached script by SHA1: `EVALSHA sha1 numKeys ...args`. * * @param sha1 - SHA1 of a previously loaded script. * @param numKeys - Number of key arguments that follow. * @param args - Keys first, then other arguments, as strings. * @returns Script result (type depends on the script). * * @remarks * Optional: Make sure the script is loaded (or catch `NOSCRIPT` and fall back * to {@link eval}). * * @since 1.0.0 */ evalsha?(sha1: string, numKeys: number, ...args: string[]): Promise<any>; /** * Execute a Lua script directly: `EVAL script numKeys ...args`. * * @param script - Lua source code. * @param numKeys - Number of key arguments that follow. * @param args - Keys first, then other arguments, as strings. * @returns Script result (type depends on the script). * * @since 1.0.0 */ eval?(script: string, numKeys: number, ...args: string[]): Promise<any>; } /** * PowerRedis — a lightweight abstract wrapper around a Redis client that * standardizes: * - safe key and pattern construction (strict segment validation), * - (de)serialization of JSON-like payloads, * - bulk reads (SCAN + MGET with chunking), * - LPOP with `count` compatibility (MULTI-based emulation fallback), * - list operations (iterators, safe batched reading), * - grouped writes (MSET / MULTI SET EX) and pattern-based deletion (UNLINK/DEL). * * The class is not tied to a specific Redis client implementation: it expects * an object compatible with {@link IORedisLike}. A subclass must provide the * concrete client via the abstract {@link redis} field. * * ## Core guarantees & invariants * - Key and pattern segments are strictly validated: colons, whitespace, * and (for keys) glob characters are forbidden in segments. This helps keep * a clean and predictable key schema. * - Serialization: objects/arrays are stored as JSON; primitives are stringified. * Conversely, {@link fromPayload} can distinguish JSON, boolean-like strings, * and numbers. * - List reading via {@link getList}/{@link getListIterator}: when `remove=true` * it uses LPOP with count (or its atomic emulation); when `remove=false` it * uses index windows (not isolated from concurrent mutations). * * ## Connection readiness model * - {@link checkConnection} treats "ready" as healthy. * If the REDIS_STRICT_CHECK_CONNECTION environment variable is not set, * "connecting"/"reconnecting" are considered "conditionally healthy". * For critical paths, a stricter "ready-only" check is recommended. * * ## Performance * - SCAN and deletion are chunked to avoid blocking Redis and to stay within * argument limits. MGET is chunked as well. * - Pattern-based deletion prefers UNLINK (asynchronous), falling back to DEL. * * ## Exceptions * - All public methods validate input parameters and connection state. * On errors they throw human-readable `Error`s with context. * * Inheritance: * ```ts * class MyRedis extends PowerRedis { * public redis: IORedisLike; * constructor(redis: IORedisLike) { * super(); * this.redis = redis; * } * } * ``` */ declare abstract class PowerRedis { /** * When `true`, only `status === "ready"` is considered a healthy connection. * When `false`, transient states like `'connecting'`/`'reconnecting'` are tolerated. * * Parsed from `process.env.REDIS_STRICT_CHECK_CONNECTION` with common truthy * forms: `true|on|yes|y|1` (case/space-insensitive). */ readonly isStrictCheckConnection: boolean; /** * Concrete Redis client instance. Subclasses must provide an implementation * compatible with {@link IORedisLike}. */ abstract redis: IORedisLike; /** * Lightweight health check for the underlying Redis client. * * @returns `true` if the client is considered healthy; otherwise `false`. * * @remarks * - When the environment variable `REDIS_STRICT_CHECK_CONNECTION` is **truthy**, * only `status === 'ready'` is treated as healthy. * - Otherwise, transient states `'connecting'`/`'reconnecting'` are tolerated. * - For critical code paths prefer an explicit readiness guard (e.g., throw unless `'ready'`). */ checkConnection(): boolean; /** * Builds a strict, colon-separated pattern **base** for SCAN MATCH usage. * Each segment must be non-empty and must not contain `:` or whitespace. * * @param parts - Key/pattern segments to join (validated). * @returns The validated base string joined by `:` (without wildcards). * * @remarks * - This method does **not** append wildcards; add your own (`*`, `?`, etc.) outside. * - Use this to reduce accidental broad scans and keep a predictable namespace. * * @throws Error * - If any segment is empty or contains `:` or whitespace. * * @example * ```ts * const base = pr.toPatternString('queue', 'orders'); * const pattern = `${base}:*`; // queue:orders:* * ``` */ toPatternString(...parts: Array<string | number>): string; /** * Builds a strict Redis key by joining validated segments with `:`. * Disallows wildcards (`* ? [ ]`), spaces, and nested `:` inside segments. * * @param parts - Key segments to join (validated). * @returns The validated key string. * * @remarks * - Helps enforce a clean, searchable key schema and prevents accidental globbing. * * @throws Error * - If any segment is empty or contains `:`, whitespace, or glob characters. * * @example * ```ts * const key = pr.toKeyString('user', 'profile', userId); // user:profile:42 * ``` */ toKeyString(...parts: Array<string | number>): string; /** * Splits a Redis key by `:` into non-empty segments. * * @param key - A full Redis key (e.g., "user:profile:42"). * @returns An array of key segments (empty segments are filtered out). * * @example * ```ts * pr.fromKeyString('a::b:c') // -> ['a', 'b', 'c'] * ``` */ fromKeyString(key: string): Array<string>; /** * Decodes a stored payload string into a JSON-like value. * * @param value - Raw string returned by Redis (or `null`). * @returns Decoded value: * - `null` if input is not a string (e.g., `null` from Redis), * - `''` for empty string, * - JSON-parsed value (object/array/number/string/boolean) when valid, * - boolean for boolean-like strings (`'true'`, `'false'`, `'yes'`, `'no'`), * - otherwise the original string. * * @remarks * - Use together with {@link toPayload} to round-trip values. * - Only safe JSON types are returned when parsing succeeds. */ fromPayload(value: string | null): Jsonish; /** * Serializes a JSON-like value to a string suitable for Redis storage. * * @param value - Any JSON-like value. * @returns String representation: * - objects/arrays → JSON string, * - primitives → stringified, * - `null`/`undefined` → `''`. * * @example * ```ts * pr.toPayload({a:1}) // -> '{"a":1}' * pr.toPayload(true) // -> "true" * ``` * * @see fromPayload */ toPayload(value: Jsonish): string; /** * Compatibility wrapper for `LPOP key count`. * * If the client supports the multi-arity form (`LPOP key count`), it is used. * Otherwise, performs an atomic emulation via `MULTI`: * `LRANGE key 0 count-1` + `LTRIM key count -1`. * * @param key - List key to pop from. * @param count - Maximum number of items to pop (≥ 1). * @returns An array of raw strings popped in the original order (may be empty). * * @remarks * - Emulation guarantees atomicity within a single Redis transaction. * - Returns `[]` when the list is empty. * * @throws Error * - On invalid parameters or client errors. * * @example * ```ts * const rawItems = await pr.lpopCountCompat('queue:jobs', 100); * ``` */ lpopCountCompat(key: string, count: number): Promise<string[]>; /** * Iteratively scans for keys matching a SCAN/MATCH pattern and returns * up to `limit` unique keys (using `COUNT = scanSize` hints). * * @param pattern - A SCAN MATCH pattern (may include wildcards). * @param limit - Max number of unique keys to return (default `100`). * @param scanSize - COUNT hint for SCAN (default `1000`). * @returns An array of unique keys up to `limit`. * * @remarks * - Use {@link toPatternString} to build the stable base, then append wildcards manually. * - Stops early once `limit` is reached; not guaranteed to be a snapshot. * * @throws Error * - If parameters are invalid. * - If Redis connection is not considered healthy. * * @example * ```ts * const keys = await pr.keys('user:profile:*', 5000, 2000); * ``` * * @see getMany * @see dropMany */ keys(pattern: string, limit?: number, scanSize?: number): Promise<string[]>; /** * Reads a single key via `GET` and decodes it with {@link fromPayload}. * * @param key - Exact key to read. * @returns Decoded value or `null` when the key does not exist. * * @remarks * - Distinguishes between "missing" (`null`) and "existing but empty" (`''`). * * @throws Error * - If `key` is invalid. * - If Redis connection is not considered healthy. * * @example * ```ts * const user = await pr.getOne(pr.toKeyString('user','profile',42)); * ``` */ getOne(key: string): Promise<Jsonish | null>; /** * Batch MGET over keys discovered by SCAN, with chunking to avoid * argument explosion. Each value is decoded via {@link fromPayload}. * * @param pattern - SCAN MATCH pattern used to discover keys. * @param limit - Max number of keys to read (default `100`). * @param scanSize - COUNT hint for SCAN (default `1000`). * @param chunkSize - Max keys per `MGET` batch (default `1000`). * @returns A map `{ key: decodedValue }`. * * @remarks * - Designed for large keyspaces; keeps individual commands reasonably sized. * - If no keys match, returns `{}`. * * @throws Error * - If arguments are invalid. * - If Redis connection is not considered healthy. * * @example * ```ts * const map = await pr.getMany('session:*', 10_000, 1000, 500); * ``` */ getMany(pattern: string, limit?: number, scanSize?: number, chunkSize?: number): Promise<Record<string, Jsonish>>; /** * Collects up to `limit` items from a Redis list. * * @param key - List key. * @param limit - Maximum number of items to collect (default `100`). * @param remove - Whether to delete items as they are read (default `false`). * @returns An array of decoded items. * * @remarks * - When `remove=true`, performs destructive reads in batches using * {@link getListIterator} → {@link lpopCountCompat}. * - When `remove=false`, reads by index windows (`LLEN` + `LRANGE`), which is * **not snapshot-isolated**; concurrent list changes may affect paging. * * @throws Error * - If parameters are invalid. * - If Redis connection is not considered healthy. * * @example * ```ts * // Non-destructive peek at up to 500 items: * const items = await pr.getList('logs:ingest', 500, false); * * // Destructive drain of 1k items: * const drained = await pr.getList('queue:jobs', 1000, true); * ``` */ getList(key: string, limit?: number, remove?: boolean): Promise<Jsonish[]>; /** * Async generator that pages through a Redis list. * * @param key - List key. * @param limit - Batch size per page (default `100`). * @param remove - Destructive read toggle (default `false`). * @yields Arrays of decoded items for each page. * * @remarks * - `remove=true`: repeatedly pops up to `limit` items via {@link lpopCountCompat} * until the list is drained (or a short batch is read). Atomic per batch. * - `remove=false`: pages by index windows (`LLEN`/`LRANGE`), which may observe * concurrent mutations (not snapshot-isolated). * * @throws Error * - If parameters are invalid. * - If Redis connection is not considered healthy. * * @example * ```ts * for await (const batch of pr.getListIterator('queue:jobs', 256, true)) { * await processBatch(batch); * } * ``` */ getListIterator(key: string, limit?: number, remove?: boolean): AsyncGenerator<Jsonish[], void, unknown>; /** * Sets a single key via `SET`, optionally with TTL (`EX`). * * @param key - Exact key to write. * @param value - JSON-like value to store (serialized via {@link toPayload}). * @param ttlSec - Optional TTL in seconds. * @returns `'OK'` on success. * * @remarks * - TTL applies to the key as a whole (not to elements of list/hash/etc.). * - Overwrites the previous value if the key exists. * * @throws Error * - If `key` is invalid. * - If Redis connection is not considered healthy. * * @example * ```ts * await pr.setOne('config:featureX', { enabled: true }, 3600); * ``` */ setOne(key: string, value: any, ttlSec?: number): Promise<'OK'>; /** * Sets multiple keys in one shot. * - Without TTL uses `MSET`. * - With TTL uses a `MULTI` block of individual `SET EX` operations. * * @param values - Array of `{ key, value }` pairs. * @param ttlSec - Optional TTL in seconds to apply uniformly to all keys. * @returns Number of successful writes (`values.length` on full success). * * @remarks * - With TTL, each `SET` and the shared `EXPIRE` per key are executed in a transaction. * - Values are serialized via {@link toPayload}. * * @throws Error * - If arguments are invalid (e.g., bad keys). * - If Redis connection is not considered healthy. * * @example * ```ts * await pr.setMany([ * { key: 'cfg:a', value: 1 }, * { key: 'cfg:b', value: { x: true } }, * ], 600); * ``` */ setMany(values: Array<{ key: string; value: any; }>, ttlSec?: number): Promise<number>; /** * Pushes a single item to the tail of a list (`RPUSH`). * If `ttlSec` is provided, wraps `RPUSH` and `EXPIRE` in a `MULTI` block. * * @param key - List key. * @param value - JSON-like value to append (serialized via {@link toPayload}). * @param ttlSec - Optional TTL for the **list key** in seconds. * @returns The new list length on success, `0` if the MULTI branch fails validation. * * @remarks * - TTL applies to the **list key**, not to individual list items. * - When `ttlSec` is omitted, a single `RPUSH` call is issued. * * @throws Error * - If `key` or `value` are invalid. * - If Redis connection is not considered healthy. * * @example * ```ts * await pr.pushOne('logs:ingest', { msg: 'hello' }, 86400); * ``` * * @see pushMany * @see expire */ pushOne(key: string, value: any, ttlSec?: number): Promise<number>; /** * Pushes multiple items to the tail of a list (`RPUSH ...values`). * If `ttlSec` is provided, wraps `RPUSH` and `EXPIRE` in a `MULTI` block. * * @param key - List key. * @param values - Array of JSON-like values to append (serialized via {@link toPayload}). * @param ttlSec - Optional TTL for the **list key** in seconds. * @returns The new list length on success, `0` if the MULTI branch fails validation. * * @remarks * - TTL applies to the **list key**, not to individual list items. * - Uses a single `RPUSH` with many arguments for efficiency. * * @throws Error * - If `key` is invalid or `values` is empty/invalid. * - If Redis connection is not considered healthy. * * @example * ```ts * await pr.pushMany('queue:jobs', [{id:1},{id:2},{id:3}], 3600); * ``` */ pushMany(key: string, values: Array<any>, ttlSec?: number): Promise<number>; /** * Deletes keys matching a SCAN/MATCH pattern by iterating with `SCAN` and * removing in chunks via `UNLINK` (if available) or `DEL` (fallback). * * @param pattern - SCAN MATCH pattern to select keys. * @param size - Chunk size for deletion and the SCAN COUNT hint (default `1000`). * @returns Approximate number of keys matched (attempted for deletion). * * @remarks * - Prefers `UNLINK` for asynchronous deletion to reduce blocking. * - Operates in chunks to avoid large command payloads and blocking scans. * * @throws Error * - If Redis connection is not considered healthy. * - On unexpected errors during scanning/deletion (re-thrown with context). * * @example * ```ts * const n = await pr.dropMany('tmp:*', 2000); * ``` */ dropMany(pattern: string, size?: number): Promise<number>; /** * Atomically increments an integer value stored at the given key. * * If the key does not exist, Redis initializes it to `0` before performing the * increment, so the first call returns `1`. If the key exists but contains a * non-integer (e.g., JSON/string), Redis will throw a type error. * * @param key - Exact Redis key to increment. * @returns A promise that resolves to the new integer value after increment. * * @remarks * - This is a thin wrapper over Redis `INCR`. * - Operation is atomic on the Redis side. * - Use TTL (`expire`) separately if you need the counter to auto-expire. * * @throws Error * - If the key format is invalid in upstream validation (not enforced here). * - If the Redis connection is down (depending on the client configuration). * - If the key holds a non-integer value (Redis type error). * * @example * ```ts * const counterKey = pr.toKeyString('rate', 'ip', '203.0.113.7'); * const n1 = await pr.incr(counterKey); // -> 1 (if no key before) * const n2 = await pr.incr(counterKey); // -> 2 * ``` */ incr(key: string): Promise<number>; /** * Sets a time-to-live (TTL) for a key in seconds. * * After the TTL elapses, the key is automatically removed by Redis. If the key * does not exist at the moment of the call, Redis returns `0` and does nothing. * * @param key - Exact Redis key to expire. * @param ttl - Time-to-live in seconds (must be a positive integer). * @returns A promise that resolves to: * - `1` if the timeout was set, * - `0` if the key does not exist or the timeout could not be set. * * @remarks * - This is a thin wrapper over Redis `EXPIRE`. * - Repeated calls update the remaining TTL to the new value. * - To remove expiration (make persistent), use `PERSIST` (не реализовано здесь). * - Expiration applies to the **key as a whole**, not to individual list items. * * @throws Error * - If the Redis connection is down (depending on the client configuration). * - If the client rejects invalid arguments (e.g., negative TTL). * * @example * ```ts * const listKey = pr.toKeyString('logs', 'ingest'); * await pr.pushMany(listKey, [{a:1}, {a:2}], 0); // no TTL at push time * const applied = await pr.expire(listKey, 86400); // -> 1 (TTL set to 24h) * ``` */ expire(key: string, ttl: number): Promise<number>; } export { type IORedisLike, type JsonPrimitive, type Jsonish, PowerRedis, type RedisMultiLike };