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
text/typescript
/**
* @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 };