ts-server-lib 0.0.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +8 -0
  3. package/db/TSJournal.d.ts +108 -0
  4. package/db/TSJournal.js +229 -0
  5. package/db/TSMongo.d.ts +103 -0
  6. package/db/TSMongo.js +516 -0
  7. package/db/TSRQW.d.ts +625 -0
  8. package/db/TSRQW.js +1204 -0
  9. package/db/TSRedis.d.ts +530 -0
  10. package/db/TSRedis.js +1368 -0
  11. package/db/TSRedisTB.d.ts +80 -0
  12. package/db/TSRedisTB.js +178 -0
  13. package/package.json +85 -0
  14. package/ussd/TSUssdMenu.d.ts +139 -0
  15. package/ussd/TSUssdMenu.js +368 -0
  16. package/ussd/TSUssdScreen.d.ts +58 -0
  17. package/ussd/TSUssdScreen.js +218 -0
  18. package/ussd/index.d.ts +3 -0
  19. package/ussd/index.js +19 -0
  20. package/ussd/providers/AfricasTalking.d.ts +3 -0
  21. package/ussd/providers/AfricasTalking.js +17 -0
  22. package/ussd/providers/AirtelDRC.d.ts +9 -0
  23. package/ussd/providers/AirtelDRC.js +31 -0
  24. package/ussd/providers/OrangeDRC.d.ts +5 -0
  25. package/ussd/providers/OrangeDRC.js +213 -0
  26. package/ussd/providers/VodacomDRC.d.ts +9 -0
  27. package/ussd/providers/VodacomDRC.js +48 -0
  28. package/ussd/providers/_.d.ts +55 -0
  29. package/ussd/providers/_.js +83 -0
  30. package/ussd/providers/index.d.ts +13 -0
  31. package/ussd/providers/index.js +56 -0
  32. package/utils/TSFifo.d.ts +109 -0
  33. package/utils/TSFifo.js +145 -0
  34. package/utils/TSFile.d.ts +36 -0
  35. package/utils/TSFile.js +244 -0
  36. package/utils/TSHash.d.ts +19 -0
  37. package/utils/TSHash.js +71 -0
  38. package/utils/TSRequest.d.ts +248 -0
  39. package/utils/TSRequest.js +689 -0
  40. package/utils/TSStub.d.ts +159 -0
  41. package/utils/TSStub.js +296 -0
  42. package/utils/abort.d.ts +18 -0
  43. package/utils/abort.js +97 -0
  44. package/utils/mime.json +11358 -0
  45. package/utils/object-keys.d.ts +39 -0
  46. package/utils/object-keys.js +52 -0
@@ -0,0 +1,530 @@
1
+ /**
2
+ * TSRedis – Single file for Redis: connection (standalone/Sentinel), events (pub/sub), operations.
3
+ */
4
+ import { EventEmitter } from 'events';
5
+ import { type RedisClientType, type RedisClusterType, type RedisSentinelType } from 'redis';
6
+ export type TSRedisClient = RedisClientType | RedisClusterType | RedisSentinelType;
7
+ export type TSRedisPattern = {
8
+ match?: string;
9
+ text?: string;
10
+ } | string;
11
+ export type TSRedisScan = {
12
+ pattern?: TSRedisPattern;
13
+ COUNT?: number;
14
+ /** Hard ceiling on keys materialised. Defaults to `COUNT * 500`. */
15
+ maxKeys?: number;
16
+ /**
17
+ * What to do when the ceiling is reached.
18
+ *
19
+ * `'throw'` (DEFAULT) — a scan that could not see the whole match set is an error, because the caller asked
20
+ * for "the keys matching this pattern" and a prefix of that set is a WRONG ANSWER THAT LOOKS RIGHT. Measured
21
+ * 2026-08-10: `loadLists('*')` returned 100,000 of 477,377 keys with no signal, and the only symptom was two
22
+ * assertions failing on a key that was present in Redis the whole time.
23
+ *
24
+ * `'truncate'` — return the first `maxKeys`. Legitimate for sampling or a bounded probe, but the caller has
25
+ * to say so, which is the entire point: silence is no longer a possible outcome.
26
+ */
27
+ onLimit?: 'throw' | 'truncate';
28
+ };
29
+ export type TSRedisCallback = (data: unknown, k?: string) => unknown;
30
+ export interface RedisSentinelConfig {
31
+ hosts: Array<{
32
+ host: string;
33
+ port: number;
34
+ }>;
35
+ name: string;
36
+ /** Auth password for the sentinel nodes themselves (rare — only when sentinels have requirepass). */
37
+ password?: string;
38
+ /**
39
+ * Remap master/replica addresses announced by sentinel to client-reachable addresses.
40
+ * Useful when sentinel runs in Docker/k8s on a different network than the client
41
+ * (e.g. sentinel announces 172.x.x.x:6379 but client must connect via 127.0.0.1:6381).
42
+ */
43
+ nodeAddressMap?: RedisClusterConfig['nodeAddressMap'];
44
+ }
45
+ export interface RedisReplicaConfig {
46
+ enabled: boolean;
47
+ urls?: string[];
48
+ strategy?: 'round-robin' | 'random' | 'first-available';
49
+ }
50
+ /**
51
+ * Redis Cluster connection config. When set on `RedisConfig`, `TSRedis.connect()` creates
52
+ * a `createCluster()` client instead of `createClient()` and stores it as the active client.
53
+ *
54
+ * Multi-key constraints: commands that touch >1 key (`mGet`, `mSet`, `multi().chain`, custom
55
+ * Lua) require all keys to hash to the same slot. Use {tag} hash tags
56
+ * (e.g. `wallet:{tenantId:userId}:bal`) so co-located keys land on the same node.
57
+ *
58
+ * Caveat for queue consumers: TSRQW is built for the standalone client API. In cluster
59
+ * deployments, pass a standalone client to TSRQW (or a dedicated connection to one node)
60
+ * — see TSRedis.md §"Tuning and deployment".
61
+ */
62
+ export interface RedisClusterConfig {
63
+ /** Seed nodes — node-redis discovers the rest via CLUSTER SLOTS after connect. */
64
+ nodes: Array<{
65
+ host: string;
66
+ port: number;
67
+ }>;
68
+ /** When true, route read-only commands (GET, MGET, …) to replicas. Default false (read from masters). */
69
+ useReplicas?: boolean;
70
+ /** Max MOVED/ASK hops per command (node-redis default 16). Raised to 32 for resharding tolerance. */
71
+ maxCommandRedirections?: number;
72
+ /** Cluster-wide password (most clusters share one). */
73
+ password?: string;
74
+ /**
75
+ * Optional remap of cluster-announced addresses. Cluster nodes typically announce docker
76
+ * hostnames (e.g. `ms-redis-3`) which aren't resolvable from a host-machine validator;
77
+ * supply this map to rewrite each discovered address to a client-reachable one
78
+ * (e.g. `127.0.0.1:7003`). Form: object map keyed by `host:port`, OR a function returning
79
+ * `{ host, port }` per discovered address string.
80
+ */
81
+ nodeAddressMap?: Record<string, {
82
+ host: string;
83
+ port: number;
84
+ }> | ((address: string) => {
85
+ host: string;
86
+ port: number;
87
+ } | undefined);
88
+ }
89
+ export interface RedisConfig {
90
+ url: string;
91
+ connectTimeout?: number;
92
+ socketTimeout?: number;
93
+ autoReconnect?: boolean;
94
+ maxReconnectRetries?: number;
95
+ reconnectDelay?: number;
96
+ clientName?: string;
97
+ pingInterval?: number;
98
+ commandsQueueMaxLength?: number;
99
+ disableOfflineQueue?: boolean;
100
+ sentinel?: RedisSentinelConfig;
101
+ /**
102
+ * Redis Cluster mode. Mutually exclusive with `sentinel` and `readReplicas` (the cluster
103
+ * client handles replica routing internally via `useReplicas`).
104
+ * When set, `TSRedis.connect()` creates a cluster client; `TSRedis.getCluster()` returns it.
105
+ * `TSRedis.getClient()` returns null in cluster mode (singleton client variable stays null);
106
+ * callers needing the cluster client should use `TSRedis.getCluster()` or `TSRedis.getActiveClient()`.
107
+ */
108
+ cluster?: RedisClusterConfig;
109
+ readReplicas?: RedisReplicaConfig;
110
+ }
111
+ export interface ScanOptions {
112
+ pattern?: string;
113
+ count?: number;
114
+ maxKeys?: number;
115
+ batchSize?: number;
116
+ }
117
+ export interface RedisConnectionStats {
118
+ connected: boolean;
119
+ mode: 'standalone' | 'sentinel' | 'cluster';
120
+ hasReadReplica: boolean;
121
+ master: {
122
+ connected: boolean;
123
+ };
124
+ replica: {
125
+ connected: boolean;
126
+ count: number;
127
+ };
128
+ }
129
+ /**
130
+ * Flat topology descriptor — the single input shape for TSRedis.connectFromTopology().
131
+ *
132
+ * All values are explicit. The library never reads process.env inside this path —
133
+ * password, nodes, and mode come entirely from the caller's config snapshot.
134
+ *
135
+ * Usage pattern:
136
+ * const descriptor = buildRedisDescriptor(myConfig);
137
+ * await TSRedis.connectFromTopology(descriptor);
138
+ * const config = TSRedis.buildConfigFromTopology(descriptor); // if another API needs RedisConfig
139
+ */
140
+ export interface RedisTopologyDescriptor {
141
+ mode: 'single' | 'sentinel' | 'cluster';
142
+ /** Redis URL (redis://[:password@]host:port). Used directly for single mode; provides the
143
+ * seed URL for cluster; ignored for sentinel (connection is driven by sentinel nodes). */
144
+ url?: string;
145
+ /** Auth password for the Redis master / standalone server. No env-var fallback. */
146
+ password?: string;
147
+ /**
148
+ * Sentinel: auth password for the sentinel nodes themselves.
149
+ * Only needed when sentinels are configured with 'requirepass'.
150
+ * Most setups only password-protect the master — in that case leave this unset.
151
+ */
152
+ sentinelPassword?: string;
153
+ /** Sentinel: quorum-monitor node addresses. Required when mode === 'sentinel'. */
154
+ sentinelNodes?: Array<{
155
+ host: string;
156
+ port: number;
157
+ }>;
158
+ /** Sentinel master set name (default 'mymaster'). */
159
+ sentinelMaster?: string;
160
+ /**
161
+ * Sentinel: remap master/replica addresses announced by sentinel to client-reachable addresses.
162
+ * Useful when sentinel runs in Docker/k8s on a different network than the client.
163
+ * Also readable from env via REDIS_SENTINEL_MASTER_HOST + REDIS_SENTINEL_MASTER_PORT
164
+ * (sets a static remap for all announced addresses).
165
+ */
166
+ sentinelNodeAddressMap?: RedisClusterConfig['nodeAddressMap'];
167
+ /** Cluster: seed nodes — node-redis discovers the rest via CLUSTER SLOTS. Required when mode === 'cluster'. */
168
+ clusterNodes?: Array<{
169
+ host: string;
170
+ port: number;
171
+ }>;
172
+ /**
173
+ * Cluster: optional address rewriter for MOVED redirect handling.
174
+ * Used by test runners behind Docker/k8s port-forwards where cluster-announced
175
+ * hostnames are not directly reachable from the host.
176
+ */
177
+ clusterNodeAddressMap?: RedisClusterConfig['nodeAddressMap'];
178
+ maxReconnectRetries?: number;
179
+ reconnectDelay?: number;
180
+ connectTimeout?: number;
181
+ /** Per-command socket timeout (ms) — maps to node-redis socketTimeout on all topologies. */
182
+ commandTimeoutMs?: number;
183
+ }
184
+ export declare function errMsg(e: unknown): string;
185
+ /** True when a node-redis client handle is open/ready (false after max-reconnect exhaustion). */
186
+ export declare function isClientOpen(c: TSRedisClient | null | undefined): boolean;
187
+ /**
188
+ * True when an error indicates the caller is using a closed or half-open Redis / cluster client.
189
+ * Used by queue/cron recovery paths after max-reconnect exhaustion or cluster slot churn.
190
+ */
191
+ export declare function isStaleConnectionError(err: unknown): boolean;
192
+ declare function connectRedisImpl(urlOrConfig: string | RedisConfig): Promise<RedisClientType>;
193
+ declare function getRedisImpl(): RedisClientType | null;
194
+ /**
195
+ /** Returns the active cluster client when `connectRedisImpl` was called with `config.cluster`, else null. */
196
+ declare function getRedisClusterImpl(): RedisClusterType | null;
197
+ /**
198
+ * Returns whichever client backs the active singleton: standalone, sentinel, or cluster.
199
+ * Use this when the code path is topology-agnostic (e.g. health checks, GET/SET on a single key).
200
+ * For multi-key ops you still need to know cluster mode to enforce hash-tag co-location.
201
+ */
202
+ declare function getActiveClientImpl(): TSRedisClient | null;
203
+ /** True when TSRedis holds an active singleton client that is open (not post-max-reconnect closed). */
204
+ export declare function isActiveClientConnected(): boolean;
205
+ declare function getRedisForReadImpl(): RedisClientType | null;
206
+ declare function hasReadReplicaImpl(): boolean;
207
+ declare function checkRedisHealthImpl(): Promise<{
208
+ healthy: boolean;
209
+ latencyMs: number;
210
+ }>;
211
+ declare function publishImpl(channel: string, message: string): Promise<boolean>;
212
+ declare function subscribeImpl(channel: string, handler: (message: string) => void): Promise<() => Promise<void>>;
213
+ declare function scanKeysIteratorImpl(options?: ScanOptions): AsyncGenerator<string, void, unknown>;
214
+ declare function batchGetValuesImpl(keys: string[]): Promise<Record<string, string | null>>;
215
+ /** One live entry resolved through {@link keyIndexEnumerateImpl}. */
216
+ export interface KeyIndexEntry {
217
+ member: string;
218
+ /** Redis key the member resolves to (via `keyOf`). */
219
+ key: string;
220
+ /** Raw value (`GET key`) — only populated when `withValues` (default true). */
221
+ value: string | null;
222
+ /** TTL of the value key in seconds — only populated when `withValues` (default true). */
223
+ ttlSeconds: number;
224
+ }
225
+ /** Register a member in an index set (idempotent). Refreshes the index's safety TTL. */
226
+ declare function keyIndexAddImpl(indexKey: string, member: string, opts?: {
227
+ indexTtlSeconds?: number;
228
+ }): Promise<void>;
229
+ /** Remove a member from an index set (lazy pruning in enumerate covers a missed remove). */
230
+ declare function keyIndexRemoveImpl(indexKey: string, member: string): Promise<void>;
231
+ /** All members of an index set. */
232
+ declare function keyIndexMembersImpl(indexKey: string): Promise<string[]>;
233
+ /** Member count (SCARD). */
234
+ declare function keyIndexCountImpl(indexKey: string): Promise<number>;
235
+ /**
236
+ * Enumerate an index: resolve each member to its value key via `keyOf`, GET it, and lazily
237
+ * `SREM` members whose key no longer exists. Returns only LIVE entries — O(members), never
238
+ * a keyspace SCAN. `withValues: false` skips GET/TTL (EXISTS-only liveness) for callers that
239
+ * only need the live member list.
240
+ */
241
+ declare function keyIndexEnumerateImpl(indexKey: string, keyOf: (member: string) => string, opts?: {
242
+ limit?: number;
243
+ withValues?: boolean;
244
+ }): Promise<{
245
+ entries: KeyIndexEntry[];
246
+ prunedCount: number;
247
+ }>;
248
+ /** True when `client` is a Redis Cluster client (has a `masters` array). */
249
+ declare function isClusterImpl(client: TSRedisClient): client is RedisClusterType;
250
+ /** True when Redis returns a transient cluster error that should be retried (TRYAGAIN, CLUSTERDOWN, LOADING). */
251
+ export declare function isTransientClusterError(err: unknown): boolean;
252
+ /**
253
+ * Execute a Redis command with bounded retry on transient cluster errors (TRYAGAIN / CLUSTERDOWN / LOADING).
254
+ * MOVED/ASK are handled by node-redis per-command — no retry needed here.
255
+ */
256
+ export declare function executeWithClusterRetry<T>(fn: () => Promise<T>, opts?: {
257
+ maxRetries?: number;
258
+ delayMs?: number;
259
+ }): Promise<T>;
260
+ /** True when a Redis command failed due to client timeout or stale connection. */
261
+ export declare function isCommandTimeoutError(err: unknown): boolean;
262
+ /**
263
+ * Retry wrapper for feed-hot paths: command timeout / stale connection + cluster TRYAGAIN
264
+ * (via nested executeWithClusterRetry).
265
+ */
266
+ export declare function executeWithCommandRetry<T>(fn: () => Promise<T>, opts?: {
267
+ maxRetries?: number;
268
+ delayMs?: number;
269
+ jitterMs?: number;
270
+ }): Promise<T>;
271
+ export interface EvalTaggedScriptOptions {
272
+ keys: string[];
273
+ argv?: string[];
274
+ source: string;
275
+ /** Validates all KEYS share one `{tag}` — catches CROSSSLOT mistakes before runtime. */
276
+ validateSameTag?: boolean;
277
+ }
278
+ /**
279
+ * EVAL a Lua script whose KEYS[] share a hash tag (cluster-safe multi-key atomicity).
280
+ *
281
+ * Sends **EVALSHA** (40-byte digest), falling back to EVAL exactly once per node on `NOSCRIPT`.
282
+ *
283
+ * Why this matters: this used to call `eval(source, …)` unconditionally, which transmits the ENTIRE Lua
284
+ * body and makes the server SHA1 it to find the cached script — on every single invocation. Measured on
285
+ * the 2026-07-31 sports-service replay, `eval` accounted for **385,732 calls and 588 s of server CPU**,
286
+ * the single largest Redis cost in the cluster, and every one of those calls re-shipped a ~1 KB script
287
+ * that Redis already had compiled. The digest is constant-size, so the wire and hashing cost stop scaling
288
+ * with body length — which also means script sources may be commented normally again.
289
+ *
290
+ * The `NOSCRIPT` fallback is what makes this safe on cluster and across failover, and why there is no
291
+ * `SCRIPT LOAD` step: a node that has never seen the script (fresh node, restart, `SCRIPT FLUSH`, a
292
+ * MOVED/ASK redirect to a different master, a promoted replica) answers NOSCRIPT, and the EVAL both runs
293
+ * the script and caches it there. Self-healing per node, no coordination, no startup ordering.
294
+ */
295
+ export declare function evalTaggedScript(client: TSRedisClient, opts: EvalTaggedScriptOptions): Promise<unknown>;
296
+ /**
297
+ * Max commands in flight on the per-key cluster fallback paths.
298
+ *
299
+ * These paths issue one command PER KEY, so an unbounded `Promise.all` puts the caller's whole key
300
+ * list on the connection at once. Redis serves each of them quickly — a slowlog stays clean — but
301
+ * any latency-critical command issued by another caller on the same client queues behind every
302
+ * pending reply. Observed in a sports feed replay (2026-07-28): a 400-key `mSet` from a background
303
+ * schedule sweep pushed a live odds-book `hmGet` to 1435 ms of wait while the event loop was idle
304
+ * (worst stall 15 ms), GC was negligible (28 ms) and the slowlog's worst entry was 15 ms.
305
+ *
306
+ * A cap converts that from "whole batch, then the read" into "at most this many, then the read".
307
+ * Chosen small because it is a queue-depth budget shared with latency-sensitive traffic, not a
308
+ * throughput knob — node-redis still auto-pipelines each chunk per target node.
309
+ */
310
+ export declare const PER_KEY_BATCH_CONCURRENCY = 25;
311
+ /**
312
+ * Max keys per batched command on the standalone/sentinel fast paths (`MGET`, `MULTI/EXEC`).
313
+ *
314
+ * Separate budget from {@link PER_KEY_BATCH_CONCURRENCY} because the failure mode differs: there the
315
+ * risk is queue depth (many small commands), here it is a single command whose payload and
316
+ * server-side service time scale with the key count. Redis executes one command at a time, so an
317
+ * unbounded `MGET k1 … k10000` blocks every other client on that node for its full duration.
318
+ *
319
+ * 500 matches the chunk convention used by callers for the same reason.
320
+ */
321
+ export declare const BATCH_COMMAND_CHUNK = 500;
322
+ /**
323
+ * Topology-aware MGET — standalone/sentinel fast path, cluster per-key path.
324
+ *
325
+ * Cluster: always per-key GET via `Promise.all` — Redis requires same slot for MGET; node-redis
326
+ * auto-pipelines per target node and follows MOVED/ASK per command.
327
+ * Standalone/sentinel: single `mGet` round-trip; falls back to per-key on unexpected batch errors.
328
+ */
329
+ declare function mGetImpl(client: TSRedisClient, keys: string[]): Promise<(string | null)[]>;
330
+ /**
331
+ * Topology-aware multi-key SET — standalone/sentinel pipeline, cluster per-key path.
332
+ *
333
+ * Cluster: per-key SETEX/SET via `Promise.all` — avoids CROSSSLOT/MOVED from MULTI/EXEC pipelines.
334
+ * Standalone/sentinel: `multi().exec()` fast path; falls back to per-key on batch errors.
335
+ *
336
+ * entries.value must already be serialised to a string (JSON.stringify beforehand).
337
+ */
338
+ declare function mSetImpl(client: TSRedisClient, entries: Array<{
339
+ key: string;
340
+ value: string;
341
+ ttl?: number;
342
+ }>): Promise<void>;
343
+ /**
344
+ * Topology-aware multi-key DELETE — the single canonical implementation.
345
+ *
346
+ * Standalone / sentinel: single `DEL k1 k2 …` round-trip.
347
+ * Cluster: per-key DEL loop — avoids CROSSSLOT when keys span different hash slots.
348
+ *
349
+ * All callers should use this instead of `client.del(array)` directly.
350
+ */
351
+ declare function mDelImpl(client: TSRedisClient, keys: string[]): Promise<number>;
352
+ /**
353
+ * Topology-aware FLUSHDB — flushes every data store.
354
+ *
355
+ * Standalone / sentinel: single `flushDb()` call.
356
+ * Cluster: `flushDb()` on the cluster client only flushes the node owning the command's
357
+ * hash slot — i.e. one master. This calls `flushDb()` on every master client directly.
358
+ *
359
+ * Use only in tests and dev tooling — never on production data.
360
+ */
361
+ declare function flushAllImpl(client: TSRedisClient): Promise<void>;
362
+ declare function closeRedisImpl(): Promise<void>;
363
+ declare function getRedisConnectionStatsImpl(): RedisConnectionStats;
364
+ /**
365
+ * Translate a RedisTopologyDescriptor into the internal RedisConfig shape.
366
+ * Exported as TSRedis.buildConfigFromTopology for callers that need a RedisConfig
367
+ * alongside connectFromTopology (e.g. passing defaultConfig to configureRedisStrategy).
368
+ */
369
+ declare function buildConfigFromDescriptor(d: RedisTopologyDescriptor): RedisConfig;
370
+ /**
371
+ * Connect using a flat topology descriptor — the preferred entry point for all callers.
372
+ *
373
+ * Handles all three topologies (single / sentinel / cluster) from one call.
374
+ * No environment-variable reads; every value comes explicitly from the descriptor.
375
+ * Idempotent: if a client for this topology is already connected, this is a no-op.
376
+ * Stale closed clients (max-reconnect exhausted) are discarded and reconnected.
377
+ */
378
+ declare function connectFromTopologyImpl(descriptor: RedisTopologyDescriptor): Promise<void>;
379
+ /**
380
+ * Build a RedisTopologyDescriptor from standard Redis environment variables.
381
+ *
382
+ * Detection priority:
383
+ * 1. REDIS_SENTINEL_NODES (comma-separated host:port) → sentinel
384
+ * 2. REDIS_CLUSTER_NODES (comma-separated host:port) → cluster
385
+ * 3. REDIS_URL → single
386
+ * 4. Default → cluster on 127.0.0.1:7000-7005 (local dev default)
387
+ *
388
+ * Env vars consumed:
389
+ * REDIS_SENTINEL_NODES e.g. "127.0.0.1:26380,127.0.0.1:26381,127.0.0.1:26382"
390
+ * REDIS_SENTINEL_MASTER sentinel master name (default: "mymaster")
391
+ * REDIS_CLUSTER_NODES e.g. "127.0.0.1:7000,...,127.0.0.1:7005"
392
+ * REDIS_CLUSTER_HOST_REWRITE host to rewrite cluster-announced addresses to (default: "127.0.0.1")
393
+ * REDIS_URL e.g. "redis://localhost:6379"
394
+ * REDIS_PASSWORD auth password (applied to all modes)
395
+ *
396
+ * For cluster mode the nodeAddressMap handles three redirect scenarios automatically:
397
+ * - Port-matched rewrites (Docker host-port-forward)
398
+ * - k8s FQDN MOVED redirects (redis-cluster-N.* → localhost:startPort+N)
399
+ * - Dynamic Docker pod-IP MOVED redirect mapping
400
+ * When all cluster nodes are non-loopback hosts (direct k8s pod routing), no rewrite is applied.
401
+ */
402
+ declare function buildTopologyDescriptorFromEnvImpl(): RedisTopologyDescriptor;
403
+ export declare class TSRedis extends EventEmitter {
404
+ redis: TSRedisClient;
405
+ constructor(instance: TSRedisClient);
406
+ /**
407
+ * Connect from a flat topology descriptor — the preferred entry point.
408
+ * Handles single / sentinel / cluster from one call; no env-var reads inside.
409
+ * Idempotent: already-connected clients are reused.
410
+ * Stale closed clients (max-reconnect exhausted) are discarded and reconnected.
411
+ */
412
+ static connectFromTopology: typeof connectFromTopologyImpl;
413
+ /**
414
+ * Build a RedisTopologyDescriptor from standard Redis env vars.
415
+ * Use with connectFromTopology for zero-config topology-aware connections:
416
+ * await TSRedis.connectFromTopology(TSRedis.buildTopologyDescriptorFromEnv())
417
+ */
418
+ static buildTopologyDescriptorFromEnv: typeof buildTopologyDescriptorFromEnvImpl;
419
+ /**
420
+ * Translate a RedisTopologyDescriptor into a RedisConfig.
421
+ * Use when another API (e.g. configureRedisStrategy) still takes RedisConfig directly.
422
+ */
423
+ static buildConfigFromTopology: typeof buildConfigFromDescriptor;
424
+ /** Connect (standalone, Sentinel, Cluster, or with read replicas). Cluster is selected
425
+ * when the supplied `RedisConfig.cluster` is set; otherwise sentinel / standalone per
426
+ * the existing precedence rules. */
427
+ static connect: typeof connectRedisImpl;
428
+ /** Standalone / sentinel client singleton. Returns null in cluster mode — use {@link getCluster}. */
429
+ static getClient: typeof getRedisImpl;
430
+ /** Active cluster client. Returns null when not in cluster mode. */
431
+ static getCluster: typeof getRedisClusterImpl;
432
+ /** Topology-agnostic accessor — returns the active client (standalone / sentinel / cluster). */
433
+ static getActiveClient: typeof getActiveClientImpl;
434
+ static getClientForRead: typeof getRedisForReadImpl;
435
+ static hasReadReplica: typeof hasReadReplicaImpl;
436
+ static close: typeof closeRedisImpl;
437
+ static checkHealth: typeof checkRedisHealthImpl;
438
+ static getConnectionStats: typeof getRedisConnectionStatsImpl;
439
+ static publish: typeof publishImpl;
440
+ static subscribe: typeof subscribeImpl;
441
+ /**
442
+ * STREAMING key scan — the primitive to reach for whenever the match-set size is not already known.
443
+ *
444
+ * Preferred over {@link TSRedis.scanr} for one structural reason: an iterator cannot misreport completeness.
445
+ * The caller drains it or does not, and either way there is no single value that claims to be "all the keys".
446
+ * `scanr` materialises, so it needs a ceiling and a policy for hitting it — see `TSRedisScan.onLimit`.
447
+ *
448
+ * `maxKeys` still bounds it (default 10,000) and reaching that bound ends iteration SILENTLY, so if
449
+ * completeness matters here too, pass a `maxKeys` you are prepared to treat as an error and check the count
450
+ * yourself. Best of all: do not scan on a hot path at all — keep an index set (ms repo: CIG-4).
451
+ */
452
+ static scanKeysIterator: typeof scanKeysIteratorImpl;
453
+ static batchGetValues: typeof batchGetValuesImpl;
454
+ /**
455
+ * Key-family index — the canonical SCAN replacement for enumerating a family of keys.
456
+ * O(members) SMEMBERS + per-member GET (CROSSSLOT-safe), lazy pruning of dead members.
457
+ * Prefer these over scanKeysIterator for ANY latency-sensitive or repeated enumeration.
458
+ */
459
+ static keyIndexAdd: typeof keyIndexAddImpl;
460
+ static keyIndexRemove: typeof keyIndexRemoveImpl;
461
+ static keyIndexMembers: typeof keyIndexMembersImpl;
462
+ static keyIndexCount: typeof keyIndexCountImpl;
463
+ static keyIndexEnumerate: typeof keyIndexEnumerateImpl;
464
+ /**
465
+ * True when `client` is a Redis Cluster client.
466
+ * Prefer the topology-aware helpers (mGet, mSet, mDel, flushAll) over manual branching.
467
+ */
468
+ static isCluster: typeof isClusterImpl;
469
+ /**
470
+ * Topology-aware MGET: tries a single mGet round-trip; on cluster CROSSSLOT/MOVED/ASK falls back
471
+ * to per-key GETs via Promise.all. Safe for keys that span different hash slots.
472
+ */
473
+ static mGet: typeof mGetImpl;
474
+ /**
475
+ * Topology-aware multi-key SET: tries a multi().exec() pipeline; on cluster CROSSSLOT/MOVED/ASK
476
+ * falls back to per-key SETEX/SET via Promise.all.
477
+ * entries.value must be pre-serialised (string).
478
+ */
479
+ static mSet: typeof mSetImpl;
480
+ /**
481
+ * Topology-aware multi-key DELETE: per-key loop in cluster (avoids CROSSSLOT),
482
+ * batch DEL in standalone/sentinel (single round-trip).
483
+ */
484
+ static mDel: typeof mDelImpl;
485
+ static executeWithCommandRetry: typeof executeWithCommandRetry;
486
+ static evalTaggedScript: typeof evalTaggedScript;
487
+ static isCommandTimeoutError: typeof isCommandTimeoutError;
488
+ /**
489
+ * Topology-aware FLUSHDB: flushes every master in cluster mode, single flushDb()
490
+ * otherwise. Test/dev tooling only — never call on production data.
491
+ */
492
+ static flushAll: typeof flushAllImpl;
493
+ cachedRequest(options: Record<string, unknown>, defaultValue?: unknown, debug?: unknown): Promise<any>;
494
+ pathToStore(keys: string[] | undefined, object: unknown): unknown;
495
+ cached: (path: string, options?: {
496
+ value?: string;
497
+ expire?: number;
498
+ }) => Promise<string | undefined>;
499
+ load: (entity: string) => Promise<unknown[]>;
500
+ get: (key: string, cb?: (r: string | null) => unknown) => Promise<unknown>;
501
+ set: (key: string, data: unknown) => Promise<boolean>;
502
+ hget: (key: string, field: string) => Promise<any>;
503
+ getM: (entity: string, keys: string[]) => Promise<(string | null)[]>;
504
+ hgetall: (key: string, cb?: (r: Record<string, string>) => unknown) => Promise<unknown>;
505
+ hset: (key: string, field: string, data: unknown) => Promise<boolean>;
506
+ hdel: (key: string, fields: string[] | string) => Promise<boolean>;
507
+ del: (keys: string[] | string) => Promise<boolean>;
508
+ readonly loadLists: (pattern?: TSRedisPattern) => Promise<Record<string, Record<string, string>>>;
509
+ readonly hscanr: (entity: string, { pattern, COUNT }: TSRedisScan, cb?: TSRedisCallback) => Promise<Record<string, unknown>>;
510
+ readonly zscanr: (entity: string, { pattern, COUNT }: TSRedisScan, cb?: TSRedisCallback) => Promise<unknown[]>;
511
+ /**
512
+ * Materialise every key matching `pattern`, across all shards.
513
+ *
514
+ * ⚠ This BUILDS A LIST. For anything whose size you do not already know, prefer
515
+ * {@link TSRedis.scanKeysIterator} — an iterator cannot misreport completeness, because the caller either
516
+ * drains it or does not. This wrapper exists for bounded, known-small patterns.
517
+ *
518
+ * Exceeding `maxKeys` THROWS by default; pass `onLimit: 'truncate'` to opt into a prefix. See
519
+ * {@link TSRedisScan.onLimit} for why the default is loud.
520
+ *
521
+ * And note what this costs even when it succeeds: `SCAN` walks the entire keyspace regardless of how few keys
522
+ * match, so a sparse pattern over a large instance is expensive. On any latency-sensitive path, do not scan —
523
+ * address exact keys or maintain an index set (ms repo: cache-invalidation-governance CIG-4).
524
+ */
525
+ readonly scanr: ({ pattern, COUNT, maxKeys, onLimit }: TSRedisScan, cb?: TSRedisCallback) => Promise<string[]>;
526
+ private static readonly _regexCache;
527
+ private check;
528
+ private match;
529
+ }
530
+ export {};