ioredis-toolkit 0.0.10 → 0.5.0
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.
- package/CHANGELOG.md +67 -0
- package/LICENSE +1 -1
- package/README.md +68 -1613
- package/dist/cache/cache.d.ts +30 -0
- package/dist/cache/cache.d.ts.map +1 -0
- package/dist/cache/cache.js +59 -0
- package/dist/cache/cache.js.map +1 -0
- package/dist/cache/config.d.ts +12 -0
- package/dist/cache/config.d.ts.map +1 -0
- package/dist/cache/config.js +13 -0
- package/dist/cache/config.js.map +1 -0
- package/dist/cache/types.d.ts +32 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +5 -0
- package/dist/cache/types.js.map +1 -0
- package/dist/index.d.ts +43 -51
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -44
- package/dist/index.js.map +1 -0
- package/dist/lock/config.d.ts +12 -0
- package/dist/lock/config.d.ts.map +1 -0
- package/dist/lock/config.js +8 -0
- package/dist/lock/config.js.map +1 -0
- package/dist/lock/lock.d.ts +20 -0
- package/dist/lock/lock.d.ts.map +1 -0
- package/dist/lock/lock.js +44 -0
- package/dist/lock/lock.js.map +1 -0
- package/dist/lock/types.d.ts +19 -0
- package/dist/lock/types.d.ts.map +1 -0
- package/dist/lock/types.js +2 -0
- package/dist/lock/types.js.map +1 -0
- package/dist/modules-config.d.ts +3 -0
- package/dist/modules-config.d.ts.map +1 -0
- package/dist/modules-config.js +2 -0
- package/dist/modules-config.js.map +1 -0
- package/dist/pubsub/config.d.ts +11 -0
- package/dist/pubsub/config.d.ts.map +1 -0
- package/dist/pubsub/config.js +6 -0
- package/dist/pubsub/config.js.map +1 -0
- package/dist/pubsub/pubsub.d.ts +20 -0
- package/dist/pubsub/pubsub.d.ts.map +1 -0
- package/dist/pubsub/pubsub.js +54 -0
- package/dist/pubsub/pubsub.js.map +1 -0
- package/dist/pubsub/types.d.ts +24 -0
- package/dist/pubsub/types.d.ts.map +1 -0
- package/dist/pubsub/types.js +2 -0
- package/dist/pubsub/types.js.map +1 -0
- package/dist/rate-limit/config.d.ts +12 -0
- package/dist/rate-limit/config.d.ts.map +1 -0
- package/dist/rate-limit/config.js +6 -0
- package/dist/rate-limit/config.js.map +1 -0
- package/dist/rate-limit/rate-limiter.d.ts +18 -0
- package/dist/rate-limit/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limit/rate-limiter.js +37 -0
- package/dist/rate-limit/rate-limiter.js.map +1 -0
- package/dist/rate-limit/types.d.ts +27 -0
- package/dist/rate-limit/types.d.ts.map +1 -0
- package/dist/rate-limit/types.js +2 -0
- package/dist/rate-limit/types.js.map +1 -0
- package/dist/redis/client-facade.d.ts +77 -0
- package/dist/redis/client-facade.d.ts.map +1 -0
- package/dist/redis/client-facade.js +102 -0
- package/dist/redis/client-facade.js.map +1 -0
- package/dist/redis/client.d.ts +10 -0
- package/dist/redis/client.d.ts.map +1 -0
- package/dist/redis/client.js +29 -0
- package/dist/redis/client.js.map +1 -0
- package/dist/redis/cluster.d.ts +7 -0
- package/dist/redis/cluster.d.ts.map +1 -0
- package/dist/redis/cluster.js +47 -0
- package/dist/redis/cluster.js.map +1 -0
- package/dist/redis/config.d.ts +39 -0
- package/dist/redis/config.d.ts.map +1 -0
- package/dist/redis/config.js +52 -0
- package/dist/redis/config.js.map +1 -0
- package/dist/redis/errors.d.ts +5 -0
- package/dist/redis/errors.d.ts.map +1 -0
- package/dist/redis/errors.js +5 -0
- package/dist/redis/errors.js.map +1 -0
- package/dist/redis/types.d.ts +135 -0
- package/dist/redis/types.d.ts.map +1 -0
- package/dist/redis/types.js +2 -0
- package/dist/redis/types.js.map +1 -0
- package/dist/redis/wrapper.d.ts +88 -0
- package/dist/redis/wrapper.d.ts.map +1 -0
- package/dist/redis/wrapper.js +206 -0
- package/dist/redis/wrapper.js.map +1 -0
- package/dist/session/config.d.ts +47 -0
- package/dist/session/config.d.ts.map +1 -0
- package/dist/session/config.js +101 -0
- package/dist/session/config.js.map +1 -0
- package/dist/session/cookie.d.ts +16 -0
- package/dist/session/cookie.d.ts.map +1 -0
- package/dist/session/cookie.js +28 -0
- package/dist/session/cookie.js.map +1 -0
- package/dist/session/errors.d.ts +56 -0
- package/dist/session/errors.d.ts.map +1 -0
- package/dist/session/errors.js +58 -0
- package/dist/session/errors.js.map +1 -0
- package/dist/session/factory.d.ts +21 -0
- package/dist/session/factory.d.ts.map +1 -0
- package/dist/session/factory.js +30 -0
- package/dist/session/factory.js.map +1 -0
- package/dist/session/health.d.ts +12 -0
- package/dist/session/health.d.ts.map +1 -0
- package/dist/session/health.js +23 -0
- package/dist/session/health.js.map +1 -0
- package/dist/session/keys.d.ts +23 -0
- package/dist/session/keys.d.ts.map +1 -0
- package/dist/session/keys.js +27 -0
- package/dist/session/keys.js.map +1 -0
- package/dist/session/manager.d.ts +34 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +31 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/session/metrics.d.ts +11 -0
- package/dist/session/metrics.d.ts.map +1 -0
- package/dist/session/metrics.js +10 -0
- package/dist/session/metrics.js.map +1 -0
- package/dist/session/repository.d.ts +49 -0
- package/dist/session/repository.d.ts.map +1 -0
- package/dist/session/repository.js +203 -0
- package/dist/session/repository.js.map +1 -0
- package/dist/session/revocation.d.ts +22 -0
- package/dist/session/revocation.d.ts.map +1 -0
- package/dist/session/revocation.js +41 -0
- package/dist/session/revocation.js.map +1 -0
- package/dist/session/script-sources.d.ts +11 -0
- package/dist/session/script-sources.d.ts.map +1 -0
- package/dist/session/script-sources.js +140 -0
- package/dist/session/script-sources.js.map +1 -0
- package/dist/session/scripts.d.ts +15 -0
- package/dist/session/scripts.d.ts.map +1 -0
- package/dist/session/scripts.js +41 -0
- package/dist/session/scripts.js.map +1 -0
- package/dist/session/serializer.d.ts +12 -0
- package/dist/session/serializer.d.ts.map +1 -0
- package/dist/session/serializer.js +77 -0
- package/dist/session/serializer.js.map +1 -0
- package/dist/session/service.d.ts +48 -0
- package/dist/session/service.d.ts.map +1 -0
- package/dist/session/service.js +235 -0
- package/dist/session/service.js.map +1 -0
- package/dist/session/token.d.ts +16 -0
- package/dist/session/token.d.ts.map +1 -0
- package/dist/session/token.js +32 -0
- package/dist/session/token.js.map +1 -0
- package/dist/session/types.d.ts +134 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/session/types.js +2 -0
- package/dist/session/types.js.map +1 -0
- package/dist/streams/config.d.ts +12 -0
- package/dist/streams/config.d.ts.map +1 -0
- package/dist/streams/config.js +6 -0
- package/dist/streams/config.js.map +1 -0
- package/dist/streams/streams.d.ts +24 -0
- package/dist/streams/streams.d.ts.map +1 -0
- package/dist/streams/streams.js +55 -0
- package/dist/streams/streams.js.map +1 -0
- package/dist/streams/types.d.ts +32 -0
- package/dist/streams/types.d.ts.map +1 -0
- package/dist/streams/types.js +2 -0
- package/dist/streams/types.js.map +1 -0
- package/docs/ACCEPTANCE-REPORT.md +70 -0
- package/docs/ARCHITECTURE.md +61 -0
- package/docs/CAPACITY.md +33 -0
- package/docs/DEPLOYMENT.md +22 -0
- package/docs/README-API.md +15 -0
- package/docs/STATE-MACHINE.md +38 -0
- package/docs/TESTING.md +37 -0
- package/docs/THREAT-MODEL.md +23 -0
- package/docs/TYPE-SAFETY.md +34 -0
- package/docs/modules/cache/README.md +7 -0
- package/docs/modules/cache/usage.md +156 -0
- package/docs/modules/lock/README.md +7 -0
- package/docs/modules/lock/usage.md +105 -0
- package/docs/modules/pubsub/README.md +7 -0
- package/docs/modules/pubsub/usage.md +106 -0
- package/docs/modules/rate-limit/README.md +7 -0
- package/docs/modules/rate-limit/usage.md +100 -0
- package/docs/modules/sessions/README.md +7 -0
- package/docs/modules/sessions/usage.md +262 -0
- package/docs/modules/streams/README.md +7 -0
- package/docs/modules/streams/usage.md +141 -0
- package/package.json +50 -60
- package/src/scripts/cleanup-index.lua +4 -0
- package/src/scripts/conditional-update.lua +21 -0
- package/src/scripts/consume-session.lua +21 -0
- package/src/scripts/create-session.lua +28 -0
- package/src/scripts/delete.lua +2 -0
- package/src/scripts/destroy-user.lua +13 -0
- package/src/scripts/enforce-limit.lua +17 -0
- package/src/scripts/revoke-session.lua +13 -0
- package/src/scripts/rotate.lua +24 -0
- package/src/scripts/touch-session.lua +28 -0
- package/src/scripts/update-session.lua +18 -0
- package/dist/cache.d.ts +0 -797
- package/dist/cache.js +0 -1115
- package/dist/client.d.ts +0 -287
- package/dist/client.js +0 -1113
- package/dist/cluster-slot.d.ts +0 -4
- package/dist/cluster-slot.js +0 -31
- package/dist/cluster.d.ts +0 -79
- package/dist/cluster.js +0 -156
- package/dist/errors.d.ts +0 -30
- package/dist/errors.js +0 -63
- package/dist/health.d.ts +0 -180
- package/dist/health.js +0 -239
- package/dist/lock.d.ts +0 -233
- package/dist/lock.js +0 -440
- package/dist/logger.d.ts +0 -12
- package/dist/logger.js +0 -40
- package/dist/pubsub.d.ts +0 -423
- package/dist/pubsub.js +0 -537
- package/dist/ratelimiter.d.ts +0 -441
- package/dist/ratelimiter.js +0 -539
- package/dist/session/index.d.ts +0 -23
- package/dist/session/index.js +0 -16
- package/dist/session/revocation-store.d.ts +0 -176
- package/dist/session/revocation-store.js +0 -318
- package/dist/session/scripts/cleanup-index.lua +0 -21
- package/dist/session/scripts/conditional-update-encrypted.lua +0 -60
- package/dist/session/scripts/conditional-update.lua +0 -63
- package/dist/session/scripts/create.lua +0 -83
- package/dist/session/scripts/delete-by-user.lua +0 -29
- package/dist/session/scripts/delete.lua +0 -15
- package/dist/session/scripts/enforce-limit.lua +0 -38
- package/dist/session/scripts/revoke.lua +0 -61
- package/dist/session/scripts/rotate-encrypted.lua +0 -149
- package/dist/session/scripts/rotate.lua +0 -167
- package/dist/session/scripts/touch-encrypted.lua +0 -89
- package/dist/session/scripts/touch.lua +0 -72
- package/dist/session/scripts/validate.lua +0 -90
- package/dist/session/session-circuit-breaker.d.ts +0 -42
- package/dist/session/session-circuit-breaker.js +0 -129
- package/dist/session/session-config.d.ts +0 -355
- package/dist/session/session-config.js +0 -171
- package/dist/session/session-cookie.d.ts +0 -72
- package/dist/session/session-cookie.js +0 -101
- package/dist/session/session-encryption.d.ts +0 -87
- package/dist/session/session-encryption.js +0 -139
- package/dist/session/session-errors.d.ts +0 -85
- package/dist/session/session-errors.js +0 -145
- package/dist/session/session-health.d.ts +0 -38
- package/dist/session/session-health.js +0 -60
- package/dist/session/session-keys.d.ts +0 -64
- package/dist/session/session-keys.js +0 -128
- package/dist/session/session-manager.d.ts +0 -73
- package/dist/session/session-manager.js +0 -94
- package/dist/session/session-metrics.d.ts +0 -41
- package/dist/session/session-metrics.js +0 -135
- package/dist/session/session-repository.d.ts +0 -184
- package/dist/session/session-repository.js +0 -763
- package/dist/session/session-scripts.d.ts +0 -36
- package/dist/session/session-scripts.js +0 -130
- package/dist/session/session-serializer.d.ts +0 -42
- package/dist/session/session-serializer.js +0 -267
- package/dist/session/session-service.d.ts +0 -123
- package/dist/session/session-service.js +0 -670
- package/dist/session/session-token.d.ts +0 -38
- package/dist/session/session-token.js +0 -86
- package/dist/session/session-types.d.ts +0 -281
- package/dist/session/session-types.js +0 -16
- package/dist/types.d.ts +0 -924
- package/dist/types.js +0 -151
- package/dist/utils/deepmerge.d.ts +0 -9
- package/dist/utils/deepmerge.js +0 -61
package/README.md
CHANGED
|
@@ -1,1656 +1,111 @@
|
|
|
1
1
|
# ioredis-toolkit
|
|
2
2
|
|
|
3
|
-
Production-
|
|
3
|
+
Production-oriented Redis infrastructure for Node.js 22+ and TypeScript, built around one shared Redis connection boundary.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
## Installation
|
|
7
|
-
|
|
8
|
-
```bash
|
|
9
|
-
npm install ioredis-toolkit ioredis zod
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
<a name="quick-start"></a>
|
|
13
|
-
## Quick Start
|
|
14
|
-
|
|
15
|
-
```ts
|
|
16
|
-
import { RedisClientWrapper, Cache, RateLimiter } from 'ioredis-toolkit';
|
|
17
|
-
|
|
18
|
-
// 1. Create the Redis client (standalone by default)
|
|
19
|
-
const client = new RedisClientWrapper({
|
|
20
|
-
mode: 'standalone',
|
|
21
|
-
host: 'localhost',
|
|
22
|
-
port: 6379,
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// 2. Cache — JSON serialization, TTL, namespaces, compression
|
|
26
|
-
const cache = new Cache(client, { defaultTTL: 3600, compressionThreshold: 1024 });
|
|
27
|
-
await cache.set('user:1', { name: 'alice' });
|
|
28
|
-
const user = await cache.get('user:1');
|
|
29
|
-
|
|
30
|
-
// 3. Rate limiting — per-route, per-IP, per-user
|
|
31
|
-
const limiter = new RateLimiter(client, { limit: 100, duration: 60 });
|
|
32
|
-
const result = await limiter.consume('/api/login', 'ip-10.0.0.1');
|
|
33
|
-
if (!result.allowed) {
|
|
34
|
-
// HTTP 429, set Retry-After: result.retryAfter
|
|
35
|
-
}
|
|
5
|
+
## Modules
|
|
36
6
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
Choose the Redis topology via the `mode` config field. All features behave identically across modes; only the underlying connection changes.
|
|
45
|
-
|
|
46
|
-
| Option | Type | Default | Description |
|
|
47
|
-
|---|---|---|---|
|
|
48
|
-
| `mode` | `'standalone' \| 'sentinel' \| 'cluster'` | `'standalone'` | Redis topology |
|
|
49
|
-
| `host` | `string` | `'localhost'` | Standalone host |
|
|
50
|
-
| `port` | `number` | `6379` | Standalone port |
|
|
51
|
-
| `url` | `string` | — | Full Redis URL (e.g. `redis://:pass@host:6379/0`) |
|
|
52
|
-
| `password` | `string` | — | Authentication password |
|
|
53
|
-
| `username` | `string` | — | Redis ACL username |
|
|
54
|
-
| `database` | `number` | `0` | Database index (standalone/sentinel only) |
|
|
55
|
-
| `sentinelNodes` | `Array<{host, port}>` | — | Sentinel nodes |
|
|
56
|
-
| `sentinelMasterName` | `string` | — | Sentinel master name |
|
|
57
|
-
| `clusterNodes` | `Array<{host, port}>` | — | Cluster nodes |
|
|
58
|
-
| `maxRetries` | `number` | `3` | Max reconnect attempts |
|
|
59
|
-
| `retryDelay` | `number` | `1000` | Base reconnect delay (ms) |
|
|
60
|
-
| `connectionTimeout` | `number` | `5000` | Connect timeout (ms) |
|
|
61
|
-
| `defaultTTL` | `number` | `3600` | Default cache TTL (seconds) |
|
|
62
|
-
| `compressionThreshold` | `number` | `1024` | Cache compression threshold (bytes) |
|
|
63
|
-
| `slowCommandThreshold` | `number` | `1000` | Log commands slower than this (ms) |
|
|
64
|
-
| `tls` | `boolean` | `false` | Enable TLS (`tlsOptions` for CA/cert/key) |
|
|
65
|
-
| `maxFanOutConcurrency` | `number` | `8` | Max concurrent fan-out operations in cluster |
|
|
66
|
-
| `maxBatchSize` | `number` | `500` | Max batch size for SCAN operations |
|
|
7
|
+
- Sessions — opaque credentials, rotation, revocation, idle/absolute expiry.
|
|
8
|
+
- Cache — JSON cache with TTL and conditional writes.
|
|
9
|
+
- Lock — ownership-token distributed leases.
|
|
10
|
+
- Rate limiting — atomic fixed-window counters.
|
|
11
|
+
- Pub/Sub — JSON fan-out over a dedicated subscriber connection.
|
|
12
|
+
- Streams — append, consumer groups, reads and acknowledgements.
|
|
67
13
|
|
|
68
|
-
|
|
14
|
+
## One client, all configuration
|
|
69
15
|
|
|
70
|
-
|
|
16
|
+
Every module can be configured in the same `createRedisClient()` call. Module configuration is optional and normalized independently.
|
|
71
17
|
|
|
72
|
-
|
|
18
|
+
Each module defaults to `enabled: false`. Accessing a module's getter on the client (`redis.cache`, `redis.lock`, `redis.rateLimiter`, `redis.pubsub`, `redis.streams`, `redis.sessions`) throws while it is disabled — set `enabled: true` in that module's configuration to use it.
|
|
73
19
|
|
|
74
20
|
```ts
|
|
75
|
-
|
|
21
|
+
import { createRedisClient } from 'ioredis-toolkit';
|
|
22
|
+
|
|
23
|
+
const redis = createRedisClient({
|
|
76
24
|
mode: 'standalone',
|
|
77
|
-
host: '
|
|
25
|
+
host: '127.0.0.1',
|
|
78
26
|
port: 6379,
|
|
79
|
-
password: 'secret',
|
|
80
|
-
database: 0,
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// Or with a URL:
|
|
84
|
-
const client = new RedisClientWrapper({ mode: 'standalone', url: 'redis://:secret@localhost:6379/0' });
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
#### Sentinel
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
const client = new RedisClientWrapper({
|
|
91
|
-
mode: 'sentinel',
|
|
92
|
-
sentinelNodes: [
|
|
93
|
-
{ host: 'sentinel1', port: 26379 },
|
|
94
|
-
{ host: 'sentinel2', port: 26380 },
|
|
95
|
-
],
|
|
96
|
-
sentinelMasterName: 'mymaster',
|
|
97
|
-
password: 'secret',
|
|
98
|
-
});
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
#### Cluster
|
|
102
27
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
{ host: 'redis3', port: 7002 },
|
|
110
|
-
],
|
|
111
|
-
password: 'secret',
|
|
28
|
+
cache: { enabled: true, namespace: 'app:cache', defaultTtl: 300 },
|
|
29
|
+
lock: { enabled: true, namespace: 'app:lock', defaultTtl: 30, maxTtl: 300 },
|
|
30
|
+
rateLimit: { enabled: true, namespace: 'app:limit', windowSeconds: 60, maxRequests: 100 },
|
|
31
|
+
pubsub: { enabled: true, channelPrefix: 'app:event' },
|
|
32
|
+
streams: { enabled: true, keyPrefix: 'app:stream', maxEntries: 100_000 },
|
|
33
|
+
sessions: { enabled: true, namespace: 'app:session' },
|
|
112
34
|
});
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
<a name="redisclient"></a>
|
|
116
|
-
## RedisClientWrapper — The Unified Client
|
|
117
|
-
|
|
118
|
-
The `RedisClientWrapper` is the core of the library. It automatically adapts to the configured topology (standalone, sentinel, or cluster) and lazily creates and shares sub-components: `cache`, `pubsub`, `lock`, `rateLimiter`, and `session`.
|
|
119
|
-
|
|
120
|
-
### Flow Diagram
|
|
121
|
-
|
|
122
|
-
```text
|
|
123
|
-
+--------------------+ +----------------------+ +---------------------+
|
|
124
|
-
| RedisClientWrapper| --- | Sub-components | --- | Redis (underlying) |
|
|
125
|
-
| (config mode) | | cache/pubsub/lock | | ioredis client |
|
|
126
|
-
+--------------------+ +----------------------+ +---------------------+
|
|
127
|
-
^ ^ |
|
|
128
|
-
| | |
|
|
129
|
-
lazy init lazy init lazy init
|
|
130
|
-
| | |
|
|
131
|
-
+-----v------+ +-----v-------+ +-----v-------+
|
|
132
|
-
| get cache | | get lock | | get rateLimiter|
|
|
133
|
-
+------------+ +-------------+ +---------------+
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Convenience Accessors
|
|
137
|
-
|
|
138
|
-
`RedisClientWrapper` lazily creates and shares one instance of each sub-component. Access them as properties — no manual wiring needed:
|
|
139
|
-
|
|
140
|
-
```ts
|
|
141
|
-
// Shared instances (created on first access)
|
|
142
|
-
client.cache; // Cache
|
|
143
|
-
client.pubsub; // PubSub
|
|
144
|
-
client.lock; // DistributedLock
|
|
145
|
-
client.rateLimiter; // RateLimiter
|
|
146
|
-
|
|
147
|
-
// Use them directly:
|
|
148
|
-
await client.cache.set('user:1', { name: 'alice' });
|
|
149
|
-
const ok = await client.lock.acquire('order:42');
|
|
150
|
-
const { allowed } = await client.rateLimiter.consume('/api', 'ip-1', { limit: 5, duration: 60 });
|
|
151
|
-
|
|
152
|
-
// Replace with custom instances:
|
|
153
|
-
client.cache = new Cache(client, { defaultTTL: 600 });
|
|
154
|
-
client.rateLimiter = new RateLimiter(client, { limit: 50, duration: 10 });
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### Configuration
|
|
158
|
-
|
|
159
|
-
```ts
|
|
160
|
-
interface RedisClientOptions {
|
|
161
|
-
config: RedisConfigInput;
|
|
162
|
-
logger?: LoggerLike;
|
|
163
|
-
}
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### Basic Commands
|
|
167
|
-
|
|
168
|
-
#### Strings & Keys
|
|
169
|
-
|
|
170
|
-
```ts
|
|
171
|
-
await client.set('name', 'alice'); // SET
|
|
172
|
-
await client.set('session', 'x', 3600); // SET ... EX
|
|
173
|
-
await client.setexnx('job:1', 'w', 60); // SET ... EX NX (only if missing)
|
|
174
|
-
await client.setnx('lock:1', 'owner', 30); // SETNX + EXPIRE, returns 1|0
|
|
175
|
-
await client.get('name'); // 'alice' | null
|
|
176
|
-
await client.getdel('queue:job'); // GETDEL
|
|
177
|
-
await client.exists('name'); // 1 | 0
|
|
178
|
-
await client.del('a', 'b'); // number deleted
|
|
179
|
-
await client.expire('session', 3600); // set TTL
|
|
180
|
-
await client.ttl('session'); // seconds left
|
|
181
|
-
await client.incr('visits'); // counters
|
|
182
|
-
await client.decr('stock:sku-1');
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
#### Batch Operations (Cluster-Safe)
|
|
186
|
-
|
|
187
|
-
```ts
|
|
188
|
-
await client.mset(['user:1', 'alice'], ['user:2', 'bob']); // grouped by hash slot
|
|
189
|
-
const [a, b] = await client.mget('user:1', 'user:2'); // routed per slot
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
#### Hashes, Sets, Sorted Sets
|
|
193
|
-
|
|
194
|
-
```ts
|
|
195
|
-
await client.hset('user:1', 'name', 'alice');
|
|
196
|
-
await client.hget('user:1', 'name');
|
|
197
|
-
await client.hgetall('user:1');
|
|
198
|
-
|
|
199
|
-
await client.sadd('tags:1', 'redis', 'typescript');
|
|
200
|
-
await client.smembers('tags:1');
|
|
201
|
-
await client.sismember('tags:1', 'redis');
|
|
202
|
-
await client.srem('tags:1', 'redis');
|
|
203
|
-
|
|
204
|
-
await client.zadd('leaderboard', 100, 'p1');
|
|
205
|
-
await client.zrange('leaderboard', 0, 9);
|
|
206
|
-
await client.zrem('leaderboard', 'p1');
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
#### Scanning & Pipelines
|
|
210
|
-
|
|
211
|
-
```ts
|
|
212
|
-
// Scans every node in cluster mode
|
|
213
|
-
for await (const key of client.scanIterator('session:*')) {
|
|
214
|
-
console.log(key);
|
|
215
|
-
}
|
|
216
|
-
await client.deletePattern('temp:*'); // delete by glob, cluster-safe
|
|
217
|
-
|
|
218
|
-
// Pipelines (cluster mode: keys must share a hash slot)
|
|
219
|
-
const pipeline = client.pipeline();
|
|
220
|
-
pipeline.set('a', '1');
|
|
221
|
-
pipeline.incr('b');
|
|
222
|
-
const results = await pipeline.exec();
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
#### Cluster Helpers
|
|
226
|
-
|
|
227
|
-
```ts
|
|
228
|
-
client.isCluster(); // boolean
|
|
229
|
-
client.getClusterNodes(); // raw node clients
|
|
230
|
-
client.getClusterSlots(); // raw slot map
|
|
231
|
-
client.getSlotRanges(); // Map<slot, host:port[]>
|
|
232
|
-
client.calculateSlot('{user}:a'); // CRC16 slot, honors hash tags
|
|
233
|
-
await client.getNodeForKey('user:1');// node serving a key
|
|
234
|
-
await client.isKeyServed('user:1'); // slot is served
|
|
235
|
-
await client.executeOnNode('user:1', 'get', 'user:1'); // run on owning node
|
|
236
|
-
await client.mgetClusterAware([...]); // slot-grouped multi-get
|
|
237
|
-
client.getClusterInfo(); // topology snapshot
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
#### Lua Scripts (Atomic Server-Side Logic)
|
|
241
|
-
|
|
242
|
-
```ts
|
|
243
|
-
// EVAL: the first numKeys arguments are KEYS, everything else is ARGV.
|
|
244
|
-
// Cluster mode: every key touched inside the script must be declared in KEYS and share one hash slot.
|
|
245
|
-
const script = `
|
|
246
|
-
if redis.call('GET', KEYS[1]) == ARGV[1] then
|
|
247
|
-
return redis.call('DEL', KEYS[1])
|
|
248
|
-
end
|
|
249
|
-
return 0
|
|
250
|
-
`;
|
|
251
|
-
await client.set('lock:job', 'owner-1');
|
|
252
|
-
await client.eval(script, 1, 'lock:job', 'owner-1'); // 1 (deleted)
|
|
253
|
-
|
|
254
|
-
// SCRIPT LOAD + EVALSHA: avoid re-sending the script body on every call.
|
|
255
|
-
// evalsha falls back to EVAL automatically when the server cache was flushed.
|
|
256
|
-
const sha = await client.scriptLoad(script);
|
|
257
|
-
await client.evalsha(sha, script, 1, 'lock:job', 'owner-2'); // 0 (not owner)
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
#### Convenience Accessors (Session, PubSub, Lock, Cache)
|
|
261
|
-
|
|
262
|
-
```ts
|
|
263
|
-
client.cache; // shared Cache (created on first access)
|
|
264
|
-
client.pubsub; // shared PubSub
|
|
265
|
-
client.lock; // shared DistributedLock
|
|
266
|
-
client.rateLimiter; // shared RateLimiter
|
|
267
|
-
|
|
268
|
-
// Session subsystem:
|
|
269
|
-
const { token, session } = await manager.service.create({ userId: 'user-42' });
|
|
270
|
-
const result = await manager.service.validate(token, { userId: 'user-42' });
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
### Lifecycle
|
|
274
|
-
|
|
275
|
-
```ts
|
|
276
|
-
await client.ping(); // boolean
|
|
277
|
-
await client.close(); // graceful QUIT
|
|
278
|
-
client.raw; // raw ioredis client
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
<a name="cache"></a>
|
|
282
|
-
## Cache
|
|
283
|
-
|
|
284
|
-
The `Cache` layer provides JSON serialization, optional gzip compression, namespaces, TTLs, hash helpers, and pattern-based cleanup. Works in all three modes.
|
|
285
|
-
|
|
286
|
-
### Class: Cache
|
|
287
|
-
|
|
288
|
-
```ts
|
|
289
|
-
constructor(client: RedisClientWrapper, config: CacheInputConfig, logger: LoggerLike = defaultLogger)
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
| Param | Type | Description |
|
|
293
|
-
|---|---|---|
|
|
294
|
-
| `client` | `RedisClientWrapper` | The underlying Redis client |
|
|
295
|
-
| `config` | `CacheInputConfig` | `defaultTTL` (seconds) and `compressionThreshold` (bytes) |
|
|
296
|
-
| `logger` | `LoggerLike` | Optional pino-compatible logger; defaults to `console` |
|
|
297
|
-
|
|
298
|
-
#### basic usage
|
|
299
|
-
|
|
300
|
-
```ts
|
|
301
|
-
const cache = new Cache(client, { defaultTTL: 3600, compressionThreshold: 1024 });
|
|
302
|
-
|
|
303
|
-
await cache.set('user:1', { name: 'alice' }, { ttl: 300 });
|
|
304
|
-
const user = await cache.get('user:1');
|
|
305
|
-
|
|
306
|
-
await cache.delete('user:1');
|
|
307
|
-
await cache.exists('user:1');
|
|
308
|
-
await cache.expire('user:1', 60);
|
|
309
|
-
await cache.ttl('user:1');
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
#### Namespaces
|
|
313
|
-
|
|
314
|
-
Values stored under a namespace are prefixed with `namespace:`, keeping keys isolated.
|
|
315
|
-
|
|
316
|
-
```ts
|
|
317
|
-
await cache.set('token', 'abc', { namespace: 'auth' });
|
|
318
|
-
await cache.get('token', 'auth'); // 'abc'
|
|
319
|
-
await cache.get('token'); // null (no namespace)
|
|
320
|
-
|
|
321
|
-
await cache.clearNamespace('sessions'); // delete every 'sessions:*' key
|
|
322
|
-
await cache.keys('session:*'); // list keys (cluster-safe)
|
|
323
|
-
await cache.deletePattern('temp:*'); // delete by pattern
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
#### Atomic & Batch Operations
|
|
327
|
-
|
|
328
|
-
```ts
|
|
329
|
-
await cache.setNX('job:1', 'worker-1', { ttl: 60 }); // only if missing
|
|
330
|
-
await cache.setEXNX('lock:1', 'txn', { ttl: 30 }); // atomic with TTL
|
|
331
|
-
|
|
332
|
-
await cache.increment('stats:visits'); // 1, 2, 3, ...
|
|
333
|
-
await cache.decrement('stock:sku-1');
|
|
334
|
-
|
|
335
|
-
await cache.mset({ 'user:1': alice, 'user:2': bob }, { ttl: 300 }); // slot-grouped
|
|
336
|
-
const [a, b] = await cache.mget(['user:1', 'user:2']);
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
#### Hash Helpers
|
|
340
|
-
|
|
341
|
-
```ts
|
|
342
|
-
await cache.hset('user:1', 'age', 30);
|
|
343
|
-
await cache.hget('user:1', 'age'); // 30
|
|
344
|
-
await cache.hgetall('user:1'); // { age: 30, ... }
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
#### Compression
|
|
348
|
-
|
|
349
|
-
Values larger than `compressionThreshold` bytes are gzip-compressed transparently. Set `compress: false` to disable compression for a single write.
|
|
350
|
-
|
|
351
|
-
```ts
|
|
352
|
-
await cache.set('large-data', bigBufferOrObject, { compress: false });
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
### Cache Interface — Method Documentation
|
|
356
|
-
|
|
357
|
-
| Method | Description | Args | Returns |
|
|
358
|
-
|---|---|---|---|
|
|
359
|
-
| `set(key, value, options?)` | Store a value in cache | `key: string`, `value: T`, `options: CacheOptions = {}` | `Promise<boolean>` — `true` when stored |
|
|
360
|
-
| `get(key, namespace?)` | Read a cached value | `key: string`, `namespace?: string` | `Promise<T \| null>` — parsed value or `null` |
|
|
361
|
-
| `setNX(key, value, options?)` | Store only if key does not exist | `key: string`, `value: T`, `options: CacheOptions = {}` | `Promise<boolean>` — `true` only when stored |
|
|
362
|
-
| `setEXNX(key, value, options?)` | Store atomically with TTL (SET ... EX NX) | `key: string`, `value: T`, `options: CacheOptions = {}` | `Promise<boolean>` — `true` only when stored |
|
|
363
|
-
| `mget(keys, namespace?)` | Read multiple keys (cluster-safe) | `keys: string[]`, `namespace?: string` | `Promise<(T \| null)[]>` — values in input order |
|
|
364
|
-
| `mset(entries, options?)` | Store multiple entries (cluster-safe, one pipeline per slot) | `entries: Record<string, T>`, `options: CacheOptions = {}` | `Promise<boolean>` — `true` when all stored |
|
|
365
|
-
| `delete(key, namespace?)` | Delete a cache key | `key: string`, `namespace?: string` | `Promise<boolean>` — `true` if key existed |
|
|
366
|
-
| `exists(key, namespace?)` | Check if key exists | `key: string`, `namespace?: string` | `Promise<boolean>` — `true` if key exists |
|
|
367
|
-
| `expire(key, ttl, namespace?)` | Set TTL on existing key | `key: string`, `ttl: number`, `namespace?: string` | `Promise<boolean>` — `true` if TTL applied |
|
|
368
|
-
| `ttl(key, namespace?)` | Get remaining TTL in seconds | `key: string`, `namespace?: string` | `Promise<number>` — seconds left (`-2` if missing, `-1` if no TTL) |
|
|
369
|
-
| `increment(key, by?, namespace?)` | Atomically increment counter | `key: string`, `by?: number` (default `1`), `namespace?: string` | `Promise<number>` — new counter value |
|
|
370
|
-
| `decrement(key, by?, namespace?)` | Atomically decrement counter | `key: string`, `by?: number` (default `1`), `namespace?: string` | `Promise<number>` — new counter value |
|
|
371
|
-
| `hget(key, field, namespace?)` | Read a hash field | `key: string`, `field: string`, `namespace?: string` | `Promise<T \| null>` — field value JSON-parsed or raw string |
|
|
372
|
-
| `hset(key, field, value, namespace?)` | Write a hash field | `key: string`, `field: string`, `value: any`, `namespace?: string` | `Promise<boolean>` — `true` if new field created |
|
|
373
|
-
| `hgetall(key, namespace?)` | Read all hash fields | `key: string`, `namespace?: string` | `Promise<Record<string, T>>` — field-value map JSON-parsed |
|
|
374
|
-
| `deletePattern(pattern, namespace?)` | Delete keys matching glob pattern (cluster-safe) | `pattern: string`, `namespace?: string` | `Promise<number>` — number of deleted keys |
|
|
375
|
-
| `keys(pattern, namespace?)` | List keys matching glob pattern (cluster-safe) | `pattern: string`, `namespace?: string` | `Promise<string[]>` — matching keys |
|
|
376
|
-
| `clearNamespace(namespace?)` | Delete every key inside a namespace | `namespace: string` | `Promise<number>` — number of deleted keys |
|
|
377
|
-
|
|
378
|
-
<a name="ratelimiter"></a>
|
|
379
|
-
## RateLimiter
|
|
380
|
-
|
|
381
|
-
Generic rate limiting for any resource — routes, API endpoints, users, IPs, API keys, database writes, email sends, webhooks...
|
|
382
|
-
|
|
383
|
-
### Algorithm Selection
|
|
384
|
-
|
|
385
|
-
| Algorithm | Key Type | Characteristics |
|
|
386
|
-
|---|---|---|
|
|
387
|
-
| `sliding` (default) | sorted set + atomic Lua | Smoothest; precise rolling window |
|
|
388
|
-
| `fixed` | counter (`INCR`/`EXPIRE`) | Cheapest; window resets at fixed boundaries |
|
|
389
|
-
|
|
390
|
-
```ts
|
|
391
|
-
// Sliding window (default)
|
|
392
|
-
const limiter = new RateLimiter(client, { limit: 100, duration: 60 });
|
|
393
|
-
|
|
394
|
-
// Fixed window
|
|
395
|
-
const fixed = new RateLimiter(client, { limit: 10, duration: 1, algorithm: 'fixed' });
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
### RateLimiter — Type Documentation
|
|
399
|
-
|
|
400
|
-
#### Class: RateLimiter
|
|
401
|
-
|
|
402
|
-
```ts
|
|
403
|
-
constructor(client: RedisClientWrapper, options: RateLimitOptionsInput = {}, logger: LoggerLike = defaultLogger)
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
| Param | Type | Default | Description |
|
|
407
|
-
|---|---|---|---|
|
|
408
|
-
| `client` | `RedisClientWrapper` | — | The underlying Redis client |
|
|
409
|
-
| `options.limit` | `number` | `100` | Maximum allowed requests within `duration` |
|
|
410
|
-
| `options.duration` | `number` | `60` | Window length in seconds |
|
|
411
|
-
| `options.algorithm` | `'fixed' \| 'sliding'` | `'sliding'` | Window algorithm |
|
|
412
|
-
| `options.namespace` | `string` | `'ratelimit'` | Redis key prefix |
|
|
413
|
-
|
|
414
|
-
#### RateLimitOptions type
|
|
415
|
-
|
|
416
|
-
```ts
|
|
417
|
-
interface RateLimitOptions {
|
|
418
|
-
limit?: number; // Max allowed requests within duration
|
|
419
|
-
duration?: number; // Window length in seconds
|
|
420
|
-
algorithm?: RateLimitAlgorithm; // 'fixed' | 'sliding'
|
|
421
|
-
namespace?: string; // Key prefix
|
|
422
|
-
}
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
#### RateLimitResult type
|
|
426
|
-
|
|
427
|
-
```ts
|
|
428
|
-
interface RateLimitResult {
|
|
429
|
-
allowed: boolean; // request may proceed
|
|
430
|
-
limit: number; // configured max
|
|
431
|
-
used: number; // requests in current window
|
|
432
|
-
remaining: number; // left in the window (limit - used, floored at 0)
|
|
433
|
-
resetAt: number; // epoch ms when the window resets
|
|
434
|
-
retryAfter: number; // seconds to wait (0 when allowed)
|
|
435
|
-
}
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
#### RateLimitAlgorithm type
|
|
439
|
-
|
|
440
|
-
```ts
|
|
441
|
-
type RateLimitAlgorithm = 'fixed' | 'sliding';
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
### RateLimiter Methods
|
|
445
|
-
|
|
446
|
-
| Method | Description | Args | Returns |
|
|
447
|
-
|---|---|---|---|
|
|
448
|
-
| `consume(resource, identifier, options?)` | Consume one unit of capacity | `resource: string`, `identifier: string`, `options: RateLimitOptions = {}` | `Promise<RateLimitResult>` — limit state |
|
|
449
|
-
| `check(resource, identifier, options?)` | Peek at current limit state (no consumption) | `resource: string`, `identifier: string`, `options: RateLimitOptions = {}` | `Promise<RateLimitResult>` — current limit state |
|
|
450
|
-
| `reset(resource, identifier, namespace?)` | Reset counter, grant full capacity | `resource: string`, `identifier: string`, `namespace?: string` | `Promise<boolean>` — `true` if counter existed and was removed |
|
|
451
|
-
| `makeKey(resource, identifier, namespace?)` | Build the Redis key for a resource + identifier | `resource: string`, `identifier: string`, `namespace?: string` | `string` — e.g. `'ratelimit:/api/login:ip-10.0.0.1'` |
|
|
452
|
-
|
|
453
|
-
#### Private Methods (algorithmic)
|
|
454
|
-
|
|
455
|
-
| Method | Description |
|
|
456
|
-
|---|---|
|
|
457
|
-
| `consumeFixed(key, limit, duration)` | Fixed-window: `INCR`/`EXPIRE` based |
|
|
458
|
-
| `consumeSliding(key, limit, duration)` | Sliding-window: atomic Lua over sorted set |
|
|
459
|
-
| `checkFixed(key, limit, duration)` | Fixed-window peek |
|
|
460
|
-
| `checkSliding(key, limit, duration)` | Sliding-window peek |
|
|
461
|
-
|
|
462
|
-
<a name="lock"></a>
|
|
463
|
-
## DistributedLock
|
|
464
|
-
|
|
465
|
-
Atomic distributed mutual-exclusion lock backed by Redis. Works in standalone, sentinel, and cluster modes.
|
|
466
|
-
|
|
467
|
-
### Class: DistributedLock
|
|
468
|
-
|
|
469
|
-
```ts
|
|
470
|
-
constructor(client: RedisClientWrapper, logger: LoggerLike = defaultLogger, options: Partial<DistributedLockOptions> = {})
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
| Param | Type | Default | Description |
|
|
474
|
-
|---|---|---|---|
|
|
475
|
-
| `client` | `RedisClientWrapper` | — | The underlying Redis client |
|
|
476
|
-
| `logger` | `LoggerLike` | `defaultLogger` | Optional pino-compatible logger |
|
|
477
|
-
| `options.ttl` | `number` | `30000` | Lock TTL in milliseconds |
|
|
478
|
-
| `options.retryCount` | `number` | `3` | Number of acquisition attempts |
|
|
479
|
-
| `options.retryDelay` | `number` | `200` | Base delay between retries (ms), grows exponentially |
|
|
480
|
-
|
|
481
|
-
#### DistributedLockOptions type
|
|
482
|
-
|
|
483
|
-
```ts
|
|
484
|
-
type DistributedLockOptions = {
|
|
485
|
-
ttl?: number; // Lock TTL in milliseconds. Default: `30000`.
|
|
486
|
-
retryCount?: number; // Number of acquisition attempts. Default: `3`.
|
|
487
|
-
retryDelay?: number; // Base delay between retries in ms (grows exponentially). Default: `200`.
|
|
488
|
-
};
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
#### LockInfo type
|
|
492
|
-
|
|
493
|
-
```ts
|
|
494
|
-
type LockInfo = {
|
|
495
|
-
locked: boolean; // Whether the lock is currently held
|
|
496
|
-
ttl?: number; // Remaining TTL in seconds (when held and TTL set)
|
|
497
|
-
lockId?: string; // Unique owner id of the lock
|
|
498
|
-
};
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
### DistributedLock Methods
|
|
502
|
-
|
|
503
|
-
| Method | Description | Args | Returns |
|
|
504
|
-
|---|---|---|---|
|
|
505
|
-
| `acquire(key, ttl?)` | Attempt to acquire the lock | `key: string`, `ttl?: number` (ms) | `Promise<boolean>` — `true` when acquired |
|
|
506
|
-
| `release(key)` | Release the lock (owner-checked) | `key: string` | `Promise<boolean>` — `true` if released, `false` if not owned or missing |
|
|
507
|
-
| `releaseForce(key)` | Force-release without ownership check | `key: string` | `Promise<boolean>` — `true` if a lock existed and was deleted |
|
|
508
|
-
| `extend(key, ttl?)` | Extend the lock TTL (owner-checked) | `key: string`, `ttl?: number` (ms) | `Promise<boolean>` — `true` if extended |
|
|
509
|
-
| `isLocked(key)` | Check if lock is held | `key: string` | `Promise<boolean>` — `true` if lock exists |
|
|
510
|
-
| `getLockInfo(key)` | Get lock details | `key: string` | `Promise<LockInfo>` — `{ locked, ttl, lockId }` |
|
|
511
|
-
| `getLockOwner(key)` | Get the lock owner ID | `key: string` | `Promise<string \| null>` — lock id or `null` |
|
|
512
|
-
| `getLockTTL(key)` | Get remaining TTL in seconds | `key: string` | `Promise<number>` — seconds left (`0` when not held or expired) |
|
|
513
|
-
| `withLock(key, fn, options?)` | Acquire lock, run critical section, auto-extend, always release | `key: string`, `fn: () => Promise<T>`, `options: DistributedLockOptions = {}` | `Promise<T>` — return value of `fn` |
|
|
514
|
-
| `cleanupAll()` | Delete every lock key (`lock:*`) from Redis | — | `Promise<number>` — number of deleted locks |
|
|
515
|
-
|
|
516
|
-
<a name="pubsub"></a>
|
|
517
|
-
## Pub/Sub
|
|
518
|
-
|
|
519
|
-
Redis Pub/Sub with a dedicated publisher and subscriber connection. Messages are JSON-serialized on publish and auto-parsed on delivery. Extends `EventEmitter` and emits `'error'` on subscriber failures.
|
|
520
|
-
|
|
521
|
-
### Class: PubSub
|
|
522
|
-
|
|
523
|
-
```ts
|
|
524
|
-
constructor(publisher: RedisClientWrapper, logger: LoggerLike = defaultLogger)
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
| Param | Type | Description |
|
|
528
|
-
|---|---|---|
|
|
529
|
-
| `publisher` | `RedisClientWrapper` | A Redis client used for publishing |
|
|
530
|
-
| `logger` | `LoggerLike` | Optional pino-compatible logger; defaults to `console` |
|
|
531
|
-
|
|
532
|
-
### PubSub — Type Documentation
|
|
533
|
-
|
|
534
|
-
#### Event Map
|
|
535
|
-
|
|
536
|
-
| Event | Payload |
|
|
537
|
-
|---|---|
|
|
538
|
-
| `message` | `{ channel: string, message: string }` |
|
|
539
|
-
| `pmessage` | `{ pattern: string, channel: string, message: string }` |
|
|
540
|
-
| `subscribe` | `{ channel: string, count: number }` |
|
|
541
|
-
| `unsubscribe` | `{ channel: string, count: number }` |
|
|
542
|
-
| `psubscribe` | `{ pattern: string, count: number }` |
|
|
543
|
-
| `punsubscribe` | `{ pattern: string, count: number }` |
|
|
544
|
-
| `error` | `Error` |
|
|
545
|
-
|
|
546
|
-
### PubSub Methods
|
|
547
|
-
|
|
548
|
-
| Method | Description | Args | Returns |
|
|
549
|
-
|---|---|---|---|
|
|
550
|
-
| `connectSubscriber(config)` | Open a dedicated subscriber connection (idempotent) | `config: RedisConfig` | `Promise<void>` |
|
|
551
|
-
| `publish(channel, message)` | Publish a message to a channel | `channel: string`, `message: T` (string or JSON-serializable) | `Promise<number>` — number of subscribers that received the message |
|
|
552
|
-
| `subscribe(channel, handler)` | Subscribe a handler to a channel | `channel: string`, `handler: (data: T) => void` | `Promise<void>` |
|
|
553
|
-
| `unsubscribe(channel, handler?)` | Remove a handler (or all handlers) from a channel | `channel: string`, `handler?: (data: T) => void` | `Promise<void>` |
|
|
554
|
-
| `psubscribe(pattern, handler)` | Subscribe to all channels matching a glob pattern | `pattern: string`, `handler: (data: { channel: string; message: T }) => void` | `Promise<void>` |
|
|
555
|
-
| `punsubscribe(pattern, handler?)` | Remove a handler from a pattern subscription | `pattern: string`, `handler?: (data: any) => void` | `Promise<void>` |
|
|
556
|
-
| `close()` | Close the subscriber connection and clear all subscriptions | — | `Promise<void>` — closes subscriber only; publisher is not closed |
|
|
557
|
-
| `getStats()` | Return subscription statistics | — | `PubSubStats` — `{ subscriptions, patternSubscriptions, connected }` |
|
|
558
|
-
|
|
559
|
-
#### PubSubStats type
|
|
560
|
-
|
|
561
|
-
```ts
|
|
562
|
-
type PubSubStats = {
|
|
563
|
-
subscriptions: number;
|
|
564
|
-
patternSubscriptions: number;
|
|
565
|
-
connected: boolean;
|
|
566
|
-
};
|
|
567
|
-
```
|
|
568
|
-
|
|
569
|
-
#### PubSubMessage type
|
|
570
|
-
|
|
571
|
-
```ts
|
|
572
|
-
type PubSubMessage<T = unknown> = {
|
|
573
|
-
channel: string;
|
|
574
|
-
message: T;
|
|
575
|
-
};
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
<a name="health"></a>
|
|
579
|
-
## HealthChecker
|
|
580
|
-
|
|
581
|
-
Periodic health monitoring with callbacks.
|
|
582
|
-
|
|
583
|
-
### Class: HealthChecker
|
|
584
|
-
|
|
585
|
-
```ts
|
|
586
|
-
constructor(client: RedisClientWrapper, logger: LoggerLike = defaultLogger)
|
|
587
|
-
```
|
|
588
|
-
|
|
589
|
-
| Param | Type | Description |
|
|
590
|
-
|---|---|---|
|
|
591
|
-
| `client` | `RedisClientWrapper` | The underlying Redis client |
|
|
592
|
-
| `logger` | `LoggerLike` | Optional pino-compatible logger; defaults to `console` |
|
|
593
|
-
|
|
594
|
-
### HealthChecker Methods
|
|
595
|
-
|
|
596
|
-
| Method | Description | Args | Returns |
|
|
597
|
-
|---|---|---|---|
|
|
598
|
-
| `start(interval?)` | Start periodic health checks | `interval?: number` (ms, default `10000`) | `void` |
|
|
599
|
-
| `stop()` | Stop the health checker | — | `void` |
|
|
600
|
-
| `check()` | Run a single health check (ping + latency) | — | `Promise<HealthStatus>` — current health status |
|
|
601
|
-
| `getStatus()` | Get the most recent health check result | — | `HealthStatus \| null` — last result (null before first check) |
|
|
602
|
-
| `onChange(callback)` | Register a callback for status changes | `callback: (status: HealthStatus) => void` | `void` |
|
|
603
|
-
| `waitForHealthy(timeout?)` | Wait until healthy (polling) | `timeout?: number` (ms, default `30000`) | `Promise<boolean>` — `true` if became healthy within timeout |
|
|
604
|
-
|
|
605
|
-
#### HealthStatus type
|
|
606
|
-
|
|
607
|
-
```ts
|
|
608
|
-
type HealthStatus = {
|
|
609
|
-
healthy: boolean;
|
|
610
|
-
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
611
|
-
latency: number; // ms
|
|
612
|
-
timestamp: Date;
|
|
613
|
-
details: {
|
|
614
|
-
ping: boolean;
|
|
615
|
-
connections?: number;
|
|
616
|
-
memory?: string;
|
|
617
|
-
};
|
|
618
|
-
};
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
<a name="session"></a>
|
|
622
|
-
## Session Subsystem
|
|
623
|
-
|
|
624
|
-
The production session stack (`createSessionManager`): validation with fail-closed semantics, rotation with retry-safe idempotency, throttled touches, idle/absolute expiry, per-user eviction ceilings, security versioning, optional AES-256-GCM encryption at rest, fail-closed circuit breaker, metrics and health. Cluster-safe by construction.
|
|
625
|
-
|
|
626
|
-
### Function: createSessionManager
|
|
627
|
-
|
|
628
|
-
```ts
|
|
629
|
-
createSessionManager(options: SessionManagerOptions): SessionManager
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
| Param | Type | Description |
|
|
633
|
-
|---|---|---|
|
|
634
|
-
| `options.client` | `RedisClientWrapper` | The underlying Redis client |
|
|
635
|
-
| `options.config` | `PartialSessionConfig` | Session configuration (see SessionConfig type) |
|
|
636
|
-
| `options.encryptionKeyProvider?` | `SessionKeyProvider` | REQUIRED when `config.encryption.enabled` is true |
|
|
637
|
-
| `options.revocationStore?` | `RevocationStore` | External revocation store (JWT jti denylists etc.) |
|
|
638
|
-
| `options.metricsAdapter?` | `SessionMetricsAdapter` | Metrics adapter (no-op without it) |
|
|
639
|
-
| `options.circuitBreaker?` | `SessionCircuitBreaker` | Optional circuit breaker |
|
|
640
|
-
| `options.now?` | `() => number` | Injectable clock for tests |
|
|
641
|
-
|
|
642
|
-
#### SessionManagerOptions type
|
|
643
|
-
|
|
644
|
-
```ts
|
|
645
|
-
type SessionManagerOptions = {
|
|
646
|
-
client: RedisClientWrapper;
|
|
647
|
-
config?: PartialSessionConfig;
|
|
648
|
-
encryptionKeyProvider?: SessionKeyProvider;
|
|
649
|
-
revocationStore?: RevocationStore;
|
|
650
|
-
metricsAdapter?: SessionMetricsAdapter;
|
|
651
|
-
circuitBreaker?: SessionCircuitBreaker;
|
|
652
|
-
now?: () => number;
|
|
653
|
-
}
|
|
654
|
-
```
|
|
655
|
-
|
|
656
|
-
#### WithSessionManagerOptions type
|
|
657
|
-
|
|
658
|
-
```ts
|
|
659
|
-
type WithSessionManagerOptions = {
|
|
660
|
-
config?: PartialSessionConfig;
|
|
661
|
-
encryptionKeyProvider?: SessionKeyProvider;
|
|
662
|
-
metricsAdapter?: SessionMetricsAdapter;
|
|
663
|
-
now?: () => number;
|
|
664
|
-
}
|
|
665
|
-
```
|
|
666
|
-
|
|
667
|
-
### SessionManager class
|
|
668
|
-
|
|
669
|
-
```ts
|
|
670
|
-
new SessionManager(options: SessionManagerOptions)
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
Properties:
|
|
674
|
-
- `config: SessionConfig` — normalized configuration
|
|
675
|
-
- `service: SessionService` — the application-facing API
|
|
676
|
-
- `repository: SessionRepository` — low-level data access
|
|
677
|
-
- `metrics: SessionMetrics` — metrics tracking
|
|
678
|
-
- `circuitBreaker: SessionCircuitBreaker \| null` — circuit breaker (or null)
|
|
679
|
-
- `health: SessionHealthChecker` — health checking
|
|
680
|
-
- `cookies: SessionCookieManager` — cookie helpers
|
|
681
|
-
- `token: SessionTokenManager` — token generation/hashing
|
|
682
|
-
- `keys: SessionKeyStrategy` — key strategy for userId mapping
|
|
683
|
-
|
|
684
|
-
### SessionConfig type documentation
|
|
685
|
-
|
|
686
|
-
```ts
|
|
687
|
-
type SessionConfig = {
|
|
688
|
-
enabled: boolean; // Explicit opt-in; manager refuses to construct otherwise
|
|
689
|
-
namespace: string; // Key prefix (e.g. 'authcore')
|
|
690
|
-
ttl: number; // Absolute session lifetime in seconds (default: 2592000 = 30d)
|
|
691
|
-
idleTimeout: number | null; // Rolling idle timeout in seconds (default: 86400 = 24h, null disables)
|
|
692
|
-
rolling: boolean; // touch extends the idle boundary (default: true)
|
|
693
|
-
touchInterval: number; // Minimum seconds between touch writes (default: 300)
|
|
694
|
-
maxSessionsPerUser: number; // Eviction ceiling, enforced atomically (default: 20)
|
|
695
|
-
securityVersion: { // Global per-user version; bump invalidates older sessions
|
|
696
|
-
enabled: boolean;
|
|
697
|
-
};
|
|
698
|
-
encryption: { // AES-256-GCM envelopes
|
|
699
|
-
enabled: boolean;
|
|
700
|
-
encryptionKeyProvider: SessionKeyProvider;
|
|
701
|
-
};
|
|
702
|
-
jtiIndex: { // jti -> userId map so validate/touch/rotate work without userId
|
|
703
|
-
enabled: boolean;
|
|
704
|
-
};
|
|
705
|
-
checkRevocationStore: boolean; // Consult revocation store during validation (default: false)
|
|
706
|
-
bindingPolicy: 'disabled' | 'strict' | 'advisory'; // strict rejects on mismatch, advisory reports it
|
|
707
|
-
circuitBreaker: { // Circuit breaker config
|
|
708
|
-
failureThreshold: number; // default: 10
|
|
709
|
-
resetTimeoutMs: number; // default: 30_000
|
|
710
|
-
halfOpenMaxRequests: number; // default: 5
|
|
711
|
-
};
|
|
712
|
-
enableCreateIdempotency: boolean; // Idempotent create() via idempotencyKey (default: false)
|
|
713
|
-
retainConsumedTombstones: boolean; // Keep consumed records (TTL-bounded) for replay detection (default: true)
|
|
714
|
-
limits: { // Hard limits
|
|
715
|
-
maxMetadataSize: number; // max size of metadata in bytes
|
|
716
|
-
maxSessionsPerUserHardCap: number; // hard cap for listing sessions
|
|
717
|
-
};
|
|
718
|
-
health: { // Health check config
|
|
719
|
-
// ...
|
|
720
|
-
};
|
|
721
|
-
};
|
|
722
|
-
```
|
|
723
|
-
|
|
724
|
-
### Session Service Methods
|
|
725
|
-
|
|
726
|
-
| Method | Description | Args | Returns |
|
|
727
|
-
|---|---|---|---|
|
|
728
|
-
| `create(input)` | Create a session | `SessionCreateInput` | `Promise<CreatedSession>` — `{ token, session, replayed? }` |
|
|
729
|
-
| `validate(token, options?)` | Validate a session token | `token: string`, `options: ValidateOptions = {}` | `Promise<SessionValidationResult>` — `{ valid: true, session? }` or `{ valid: false, reason }` |
|
|
730
|
-
| `touch(token, options?)` | Refresh activity (throttled) | `token: string`, `options: TouchOptions = {}` | `Promise<TouchOutcome>` — outcome code |
|
|
731
|
-
| `rotate(token, options?)` | Rotate to a new session (idempotent via nonce) | `token: string`, `options: RotateOptions = {}` | `Promise<RotatedSession>` — `{ token?, session, replayed }` |
|
|
732
|
-
| `update(token, patch, options?)` | Patch update (optimistic concurrency) | `token: string`, `patch: SessionUpdatePatch`, `options: UpdateOptions = {}` | `Promise<SessionRecord>` |
|
|
733
|
-
| `destroy(token, options?)` | Physically delete a session (idempotent) | `token: string`, `options: { userId?: string } = {}` | `Promise<boolean>` |
|
|
734
|
-
| `revoke(token, options?)` | Logically revoke a session | `token: string`, `options: { userId?: string } = {}` | `Promise<string>` — outcome (`'revoked' \| 'already_revoked' \|\| 'not_found'`) |
|
|
735
|
-
| `revokeAll(userId)` | Revoke every session of a user | `userId: string` | `Promise<number>` — number revoked |
|
|
736
|
-
| `deleteByUser(userId)` | Delete every session of a user (physical) | `userId: string` | `Promise<string[]>` — deleted JTIs |
|
|
737
|
-
| `findByUser(userId, options?)` | List a user's sessions (oldest first) | `userId: string`, `options: ListOptions = {}` | `Promise<SessionRecord[]>` |
|
|
738
|
-
| `list(userId, options?)` | Alias of findByUser | `userId: string`, `options: ListOptions = {}` | `Promise<SessionRecord[]>` |
|
|
739
|
-
| `setSecurityVersion(userId, version?)` | Bump security version, invalidate old sessions | `userId: string`, `version?: number` | `Promise<number>` — new version |
|
|
740
|
-
| `getSecurityVersion(userId)` | Get current security version | `userId: string` | `Promise<number \| null>` |
|
|
741
|
-
| `health()` | Dependency health check | — | `Promise<ReturnType<SessionHealthChecker['check']>>` |
|
|
742
|
-
|
|
743
|
-
#### SessionCreateInput type
|
|
744
|
-
|
|
745
|
-
```ts
|
|
746
|
-
type SessionCreateInput = {
|
|
747
|
-
userId: string;
|
|
748
|
-
deviceId?: string; // stored when config.storeDeviceId is true
|
|
749
|
-
ipAddress?: string; // stored when config.storeIpAddress is true
|
|
750
|
-
userAgent?: string; // stored when config.storeUserAgent is true
|
|
751
|
-
metadata?: Record<string, unknown>; // bounded by config.maxMetadataSize
|
|
752
|
-
idempotencyKey?: string; // when provided + enableCreateIdempotency, enables idempotent create
|
|
753
|
-
};
|
|
754
|
-
```
|
|
755
|
-
|
|
756
|
-
#### CreatedSession type
|
|
757
|
-
|
|
758
|
-
```ts
|
|
759
|
-
type CreatedSession = {
|
|
760
|
-
token: string; // The raw session token. Give to client; store nowhere.
|
|
761
|
-
session: SessionRecord; // The persisted session record (contains only jti, never the token)
|
|
762
|
-
replayed?: boolean; // True when create was an idempotent replay
|
|
763
|
-
};
|
|
764
|
-
```
|
|
765
|
-
|
|
766
|
-
#### SessionValidationResult type
|
|
767
|
-
|
|
768
|
-
```ts
|
|
769
|
-
type SessionValidationResult =
|
|
770
|
-
| { valid: true; session: SessionRecord; binding?: BindingMismatch }
|
|
771
|
-
| { valid: false; reason: SessionInvalidReason; session?: never };
|
|
772
|
-
```
|
|
773
|
-
|
|
774
|
-
#### SessionInvalidReason type
|
|
775
|
-
|
|
776
|
-
```ts
|
|
777
|
-
type SessionInvalidReason =
|
|
778
|
-
| 'not_found'
|
|
779
|
-
| 'expired'
|
|
780
|
-
| 'idle_timeout'
|
|
781
|
-
| 'absolute_timeout'
|
|
782
|
-
| 'revoked'
|
|
783
|
-
| 'invalid'
|
|
784
|
-
| 'binding_mismatch';
|
|
785
|
-
```
|
|
786
|
-
|
|
787
|
-
#### TouchOutcome type
|
|
788
|
-
|
|
789
|
-
```ts
|
|
790
|
-
type TouchOutcome =
|
|
791
|
-
| 'touched'
|
|
792
|
-
| 'skipped_throttled'
|
|
793
|
-
| 'skipped_stale'
|
|
794
|
-
| 'not_found'
|
|
795
|
-
| 'consumed'
|
|
796
|
-
| 'expired'
|
|
797
|
-
| 'idle_expired';
|
|
798
|
-
```
|
|
799
|
-
|
|
800
|
-
#### TouchOptions type
|
|
801
|
-
|
|
802
|
-
```ts
|
|
803
|
-
type TouchOptions = {
|
|
804
|
-
force?: boolean; // Force a write regardless of touchInterval
|
|
805
|
-
userId?: string; // When known, avoids JTI lookup index round trip
|
|
806
|
-
};
|
|
807
|
-
```
|
|
808
|
-
|
|
809
|
-
#### RotateOptions type
|
|
810
|
-
|
|
811
|
-
```ts
|
|
812
|
-
type RotateOptions = {
|
|
813
|
-
rotationNonce?: string; // Client-supplied random nonce for retry-safe rotation
|
|
814
|
-
userId?: string; // Skip pre-flight GET, let Lua script be authoritative
|
|
815
|
-
expectedVersion?: number; // Optimistic concurrency: only rotate when version matches
|
|
816
|
-
};
|
|
817
|
-
```
|
|
818
|
-
|
|
819
|
-
#### UpdateOptions type
|
|
820
|
-
|
|
821
|
-
```ts
|
|
822
|
-
type UpdateOptions = {
|
|
823
|
-
expectedVersion?: number; // Optimistic concurrency: only update when version matches
|
|
824
|
-
userId?: string; // When known, avoids JTI lookup index round trip
|
|
825
|
-
};
|
|
826
|
-
```
|
|
827
|
-
|
|
828
|
-
#### ListOptions type
|
|
829
|
-
|
|
830
|
-
```ts
|
|
831
|
-
type ListOptions = {
|
|
832
|
-
limit?: number; // Max sessions to return (default: 100)
|
|
833
|
-
offset?: number; // Skip first N sessions (oldest first)
|
|
834
|
-
includeInactive?: boolean; // Include consumed/revoked records (default: false)
|
|
835
|
-
};
|
|
836
|
-
```
|
|
837
|
-
|
|
838
|
-
#### BindingMismatch type
|
|
839
|
-
|
|
840
|
-
```ts
|
|
841
|
-
type BindingMismatch = {
|
|
842
|
-
ipAddress: boolean; // IP address mismatch
|
|
843
|
-
userAgent: boolean; // User agent mismatch
|
|
844
|
-
deviceId: boolean; // Device ID mismatch
|
|
845
|
-
};
|
|
846
|
-
```
|
|
847
|
-
|
|
848
|
-
<a name="revocation"></a>
|
|
849
|
-
## RedisRevocationStore
|
|
850
|
-
|
|
851
|
-
Supports refresh-token revocation workflows: short-lived entries (`revoked:{jti}`) so rotated/logged-out tokens are rejected for their remaining lifetime. Framework-independent, works identically on standalone, Sentinel and Cluster, never stores raw tokens — only ids (`jti`).
|
|
852
|
-
|
|
853
|
-
### Class: RedisRevocationStore
|
|
854
|
-
|
|
855
|
-
```ts
|
|
856
|
-
new RedisRevocationStore(options: RedisRevocationStoreOptions)
|
|
857
|
-
```
|
|
858
|
-
|
|
859
|
-
| Param | Type | Description |
|
|
860
|
-
|---|---|---|
|
|
861
|
-
| `options.client` | `RedisClientWrapper` | The underlying Redis client |
|
|
862
|
-
| `options.keyPrefix` | `string` | Key prefix (e.g. `'authcore:revoked:'`) |
|
|
863
|
-
|
|
864
|
-
#### RedisRevocationStoreOptions type
|
|
865
|
-
|
|
866
|
-
```ts
|
|
867
|
-
interface RedisRevocationStoreOptions {
|
|
868
|
-
client: RedisClientWrapper;
|
|
869
|
-
keyPrefix: string;
|
|
870
|
-
}
|
|
871
|
-
```
|
|
872
|
-
|
|
873
|
-
### RedisRevocationStore Methods
|
|
874
|
-
|
|
875
|
-
| Method | Description | Args | Returns |
|
|
876
|
-
|---|---|---|---|
|
|
877
|
-
| `revoke(record)` | Revoke a single jti | `record: RevocationRecord` | `Promise<void>` |
|
|
878
|
-
| `revokeMany(records)` | Revoke multiple jtis (batch) | `records: RevocationRecord[]` | `Promise<void>` — throws `RevocationBatchError` on failure |
|
|
879
|
-
| `isRevoked(jti)` | Check if a single jti is revoked | `jti: string` | `Promise<boolean>` |
|
|
880
|
-
| `isRevokedMany(jtis)` | Check multiple jtis (batch) | `jtis: string[]` | `Promise<Set<string>>` — set of revoked jtis that were found; throws `RevocationBatchError` on partial failure |
|
|
881
35
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
reason?: string; // e.g. 'logout', 'logout-all', 'password-change'
|
|
889
|
-
};
|
|
36
|
+
await redis.cache.set('user:42', { id: '42' });
|
|
37
|
+
const lock = await redis.lock.acquire('job:42');
|
|
38
|
+
const decision = await redis.rateLimiter.consume('user:42');
|
|
39
|
+
await redis.pubsub.publish('orders.created', { orderId: '42' });
|
|
40
|
+
await redis.streams.add('orders', { orderId: '42', type: 'created' });
|
|
41
|
+
const session = await redis.sessions.create({ userId: '42' });
|
|
890
42
|
```
|
|
891
43
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
Thrown when a batched command fails (Redis error, timeout, ...). Carries the exact jtis that failed — a check can never silently treat a token as "not revoked" when its status is unknown.
|
|
895
|
-
|
|
896
|
-
<a name="lua-scripts"></a>
|
|
897
|
-
## Lua Scripts (`eval` / `evalsha`)
|
|
44
|
+
## Fluent module overrides
|
|
898
45
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
### Usage
|
|
46
|
+
Each module can inherit the global configuration and override selected values:
|
|
902
47
|
|
|
903
48
|
```ts
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
await client.set('lock:job', 'owner-1');
|
|
912
|
-
await client.eval(script, 1, 'lock:job', 'owner-1'); // 1 (deleted)
|
|
913
|
-
|
|
914
|
-
// SCRIPT LOAD + EVALSHA
|
|
915
|
-
const sha = await client.scriptLoad(script);
|
|
916
|
-
await client.evalsha(sha, script, 1, 'lock:job', 'owner-2'); // 0 (not owner)
|
|
49
|
+
redis
|
|
50
|
+
.withCache({ defaultTtl: 60 })
|
|
51
|
+
.withLock({ defaultTtl: 15 })
|
|
52
|
+
.withRateLimit({ maxRequests: 500 })
|
|
53
|
+
.withPubSub({ channelPrefix: 'critical-events' })
|
|
54
|
+
.withStreams({ maxEntries: 500_000 })
|
|
55
|
+
.withSessions({ idleTimeout: 60 * 60 });
|
|
917
56
|
```
|
|
918
57
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
- **EVAL**: The first `numKeys` arguments are `KEYS`, everything else is `ARGV`.
|
|
922
|
-
- **Cluster mode**: Every key touched inside the script must be declared in `KEYS` and share one hash slot (honored via hash tags `{tag}:...`).
|
|
923
|
-
- **evalsha fallback**: If the script cache was flushed, `evalsha` automatically falls back to `EVAL`.
|
|
924
|
-
|
|
925
|
-
<a name="errors"></a>
|
|
926
|
-
## Errors
|
|
927
|
-
|
|
928
|
-
### RedisError
|
|
58
|
+
Use `'replace'` when the module should start again from its defaults:
|
|
929
59
|
|
|
930
60
|
```ts
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
try {
|
|
934
|
-
await client.select(1);
|
|
935
|
-
} catch (error) {
|
|
936
|
-
if (error instanceof RedisError && error.code === 'CLUSTER_MODE') {
|
|
937
|
-
// SELECT is not available in cluster mode
|
|
938
|
-
}
|
|
939
|
-
}
|
|
61
|
+
redis.withCache({ namespace: 'temporary' }, 'replace');
|
|
62
|
+
redis.withLock({ namespace: 'maintenance' }, 'replace');
|
|
940
63
|
```
|
|
941
64
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
| Code | When It Occurs |
|
|
945
|
-
|---|---|
|
|
946
|
-
| `CLUSTER_MODE` | Operations unavailable in cluster mode (e.g. `SELECT`) |
|
|
947
|
-
| `LOCK_ACQUISITION_FAILED` | Lock could not be acquired after retries |
|
|
948
|
-
| `LOCK_LOST` | Lock was lost during `withLock` execution |
|
|
949
|
-
| `SessionStorageError` | Infra failure (503) — only one that trips the circuit breaker |
|
|
950
|
-
| `SessionNotFoundError` | Session not found |
|
|
951
|
-
| `SessionExpiredError` | Session absolute TTL passed |
|
|
952
|
-
| `SessionInvalidError` | Session invalid (corrupt, cyclic metadata, etc.) |
|
|
953
|
-
| `SessionRevokedError` | Session explicitly revoked |
|
|
954
|
-
| `SessionRotationError` | Rotation failed (version conflict, successor collision, etc.) |
|
|
955
|
-
| `SessionConcurrencyError` | Optimistic concurrency violation (version mismatch) |
|
|
956
|
-
| `SessionSerializationError` | Session deserialization failed |
|
|
957
|
-
| `SessionConfigurationError` | Invalid session configuration |
|
|
958
|
-
| `SessionBindingError` | Binding policy mismatch (when strict) |
|
|
65
|
+
`withLock()` is canonical; `withlock()` is provided as an alias.
|
|
959
66
|
|
|
960
|
-
|
|
961
|
-
## Logging
|
|
67
|
+
## Documentation
|
|
962
68
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
```ts
|
|
966
|
-
import { createLogger } from 'pino';
|
|
967
|
-
const logger = createLogger();
|
|
968
|
-
const client = new RedisClientWrapper(config, logger);
|
|
969
|
-
```
|
|
69
|
+
Each module has a complete usage guide with configuration tables, types, method arguments, return values, semantics, edge cases, and multiple examples:
|
|
970
70
|
|
|
971
|
-
|
|
972
|
-
|
|
71
|
+
- [Cache usage](./docs/modules/cache/usage.md)
|
|
72
|
+
- [Lock usage](./docs/modules/lock/usage.md)
|
|
73
|
+
- [Rate limiting usage](./docs/modules/rate-limit/usage.md)
|
|
74
|
+
- [Pub/Sub usage](./docs/modules/pubsub/usage.md)
|
|
75
|
+
- [Streams usage](./docs/modules/streams/usage.md)
|
|
76
|
+
- [Sessions usage](./docs/modules/sessions/usage.md)
|
|
973
77
|
|
|
974
|
-
|
|
975
|
-
|---|---|---|---|
|
|
976
|
-
| Single-key commands (get/set/hash/set/zset/incr/...) | ✅ | ✅ | ✅ |
|
|
977
|
-
| `mget` / `mset` / `mgetClusterAware` | ✅ | ✅ | ✅ slot-grouped |
|
|
978
|
-
| `scanIterator` / `deletePattern` / `keys` | ✅ | ✅ | ✅ all nodes scanned |
|
|
979
|
-
| Pipelines | ✅ | ✅ | ✅ (same-slot keys per pipeline) |
|
|
980
|
-
| Lua scripts (`eval` / `evalsha` / `scriptLoad`) | ✅ | ✅ | ✅ (keys declared in `KEYS`, one slot) |
|
|
981
|
-
| `RedisRevocationStore` | ✅ | ✅ | ✅ batch ops slot-grouped |
|
|
982
|
-
| `createSessionManager` | ✅ | ✅ | ✅ hash-tagged Lua scripts, slot-grouped fan-out |
|
|
983
|
-
| `select(database)` | ✅ | ✅ | ❌ (Redis limitation) |
|
|
984
|
-
| Hash-tag keys `{tag}:...` | ✅ | ✅ | ✅ same slot |
|
|
78
|
+
Additional architecture and operations documentation is in `docs/`.
|
|
985
79
|
|
|
986
|
-
|
|
987
|
-
## Development
|
|
80
|
+
## Installation
|
|
988
81
|
|
|
989
82
|
```bash
|
|
990
|
-
npm install
|
|
991
|
-
npm run build # tsc + asset copy (Lua scripts land in dist/session/scripts)
|
|
992
|
-
npm run typecheck # src + test + scripts (tsconfig.test.json)
|
|
993
|
-
npm test # vitest
|
|
994
|
-
npm run test:watch # vitest watch mode
|
|
995
|
-
npm run format # prettier --write 'src/**/*.ts'
|
|
996
|
-
```
|
|
997
|
-
|
|
998
|
-
The test suite covers the client, cache and rate limiter (including cluster-mode behavior) using in-memory fakes — no Redis server required. The session suites are gated: they run against real Redis (`localhost:6379`, or `REDIS_MODE=cluster` / `REDIS_MODE=sentinel` with the compose topologies in `test/infra/`) and skip cleanly when it is unreachable.
|
|
999
|
-
|
|
1000
|
-
<a name="types"></a>
|
|
1001
|
-
## Type Exports
|
|
1002
|
-
|
|
1003
|
-
The package exports comprehensive types for all modules. Key type exports:
|
|
1004
|
-
|
|
1005
|
-
### Core Client Types
|
|
1006
|
-
|
|
1007
|
-
| Type | Description |
|
|
1008
|
-
|---|---|
|
|
1009
|
-
| `RedisClientWrapper` | The unified client wrapper (standalone/sentinel/cluster) |
|
|
1010
|
-
| `createRedisClient` | Factory function: `createRedisClient(config)` — creates client for specified mode |
|
|
1011
|
-
| `RedisConfig` | Normalized Redis configuration (after Zod validation) |
|
|
1012
|
-
| `RedisConfigInput` | User-facing configuration input (validated with Zod) |
|
|
1013
|
-
| `RedisMode` | `'standalone' \| 'sentinel' \| 'cluster'` |
|
|
1014
|
-
| `RedisConfigForMode<M>` | Mode-specific config type |
|
|
1015
|
-
|
|
1016
|
-
### Cache Types
|
|
1017
|
-
|
|
1018
|
-
| Type | Description |
|
|
1019
|
-
|---|---|
|
|
1020
|
-
| `Cache` | Cache layer with JSON serialization, TTL, namespaces, compression |
|
|
1021
|
-
| `CacheOptions` | Options for cache operations (`ttl`, `compress`, `namespace`) |
|
|
1022
|
-
| `CacheInputConfig` | Constructor config (`defaultTTL`, `compressionThreshold`) |
|
|
1023
|
-
|
|
1024
|
-
### Rate Limiting Types
|
|
1025
|
-
|
|
1026
|
-
| Type | Description |
|
|
1027
|
-
|---|---|
|
|
1028
|
-
| `RateLimiter` | Rate limiter instance |
|
|
1029
|
-
| `RateLimitAlgorithm` | `'fixed' \| 'sliding'` |
|
|
1030
|
-
| `RateLimitOptions` | Options with defaults materialized (limit, duration, algorithm, namespace) |
|
|
1031
|
-
| `RateLimitResult` | Result of consume/check: allowed, limit, used, remaining, resetAt, retryAfter |
|
|
1032
|
-
|
|
1033
|
-
### Distributed Lock Types
|
|
1034
|
-
|
|
1035
|
-
| Type | Description |
|
|
1036
|
-
|---|---|
|
|
1037
|
-
| `DistributedLock` | Distributed lock instance |
|
|
1038
|
-
| `DistributedLockOptions` | Constructor options (ttl, retryCount, retryDelay) |
|
|
1039
|
-
| `LockInfo` | Lock info: `{ locked, ttl?, lockId? }` |
|
|
1040
|
-
|
|
1041
|
-
### Pub/Sub Types
|
|
1042
|
-
|
|
1043
|
-
| Type | Description |
|
|
1044
|
-
|---|---|
|
|
1045
|
-
| `PubSub` | Pub/sub instance |
|
|
1046
|
-
| `PubSubMessage<T>` | `{ channel: string; message: T }` |
|
|
1047
|
-
| `PubSubStats` | `{ subscriptions, patternSubscriptions, connected }` |
|
|
1048
|
-
|
|
1049
|
-
### Health Types
|
|
1050
|
-
|
|
1051
|
-
| Type | Description |
|
|
1052
|
-
|---|---|
|
|
1053
|
-
| `HealthStatus` | `{ healthy, status, latency, timestamp, details }` |
|
|
1054
|
-
|
|
1055
|
-
### Session Types
|
|
1056
|
-
|
|
1057
|
-
| Type | Description |
|
|
1058
|
-
|---|---|
|
|
1059
|
-
| `SessionManager` | Session manager composition root |
|
|
1060
|
-
| `SessionService` | Application-facing session API |
|
|
1061
|
-
| `SessionRecord` | Persisted session record |
|
|
1062
|
-
| `SessionCreateInput` | Input for `create()` |
|
|
1063
|
-
| `CreatedSession` | Result of `create()`: `{ token, session, replayed? }` |
|
|
1064
|
-
| `SessionValidationResult` | Result of `validate()` |
|
|
1065
|
-
| `SessionInvalidReason` | Invalid reason discriminant |
|
|
1066
|
-
| `TouchOutcome` | Touch outcome codes |
|
|
1067
|
-
| `RotateOptions` | Options for `rotate()` |
|
|
1068
|
-
| `UpdateOptions` | Options for `update()` |
|
|
1069
|
-
| `ListOptions` | Options for `findByUser`/`list()` |
|
|
1070
|
-
| `BindingMismatch` | Binding mismatch details |
|
|
1071
|
-
| `SessionStatus` | `'active' \| 'consumed' \| 'revoked'` |
|
|
1072
|
-
| `SessionStatus` | Session lifecycle state |
|
|
1073
|
-
| `RevocationRecord` | Revocation store record |
|
|
1074
|
-
| `RevocationStore` | Storage-agnostic revocation interface |
|
|
1075
|
-
|
|
1076
|
-
### Configuration Types
|
|
1077
|
-
|
|
1078
|
-
| Type | Description |
|
|
1079
|
-
|---|---|
|
|
1080
|
-
| `RedisCommonConfig` | Common config shared by all topologies |
|
|
1081
|
-
| `StandaloneRedisConfig` | Standalone-specific config |
|
|
1082
|
-
| `SentinelRedisConfig` | Sentinel-specific config |
|
|
1083
|
-
| `ClusterRedisConfig` | Cluster-specific config |
|
|
1084
|
-
| `RedisConfigInputSchema` | Zod schema for config validation |
|
|
1085
|
-
| `BaseRedisConfigSchema` | Base config schema (password, username, database, tls, etc.) |
|
|
1086
|
-
|
|
1087
|
-
### Utility Types
|
|
1088
|
-
|
|
1089
|
-
| Type | Description |
|
|
1090
|
-
|---|---|
|
|
1091
|
-
| `RedisError` | Base Redis error type |
|
|
1092
|
-
| `calculateRedisClusterSlot` | CRC16 slot calculation for cluster keys |
|
|
1093
|
-
| `hashTag` | Extract hash tag from key (`{tag}:key`) |
|
|
1094
|
-
|
|
1095
|
-
<a name="api-reference"></a>
|
|
1096
|
-
## Complete API Reference
|
|
1097
|
-
|
|
1098
|
-
This section provides JSDoc-style documentation for every public method, class, and type. Use it as a quick lookup for signatures, parameters, return values, and behaviors.
|
|
1099
|
-
|
|
1100
|
-
### `RedisClientWrapper`
|
|
1101
|
-
|
|
1102
|
-
#### Constructor
|
|
1103
|
-
|
|
1104
|
-
```ts
|
|
1105
|
-
new RedisClientWrapper(config: RedisConfigInput, logger?: LoggerLike)
|
|
1106
|
-
```
|
|
1107
|
-
|
|
1108
|
-
| Param | Type | Description |
|
|
1109
|
-
|---|---|---|
|
|
1110
|
-
| `config` | `RedisConfigInput` | Redis configuration (validated with Zod `RedisConfigSchema`). Must include `mode` (standalone/sentinel/cluster), and topology-specific fields. |
|
|
1111
|
-
| `logger` | `LoggerLike` | Optional pino-compatible logger; defaults to `console`. |
|
|
1112
|
-
|
|
1113
|
-
Throws `ConfigurationError` when the config fails Zod validation.
|
|
1114
|
-
|
|
1115
|
-
#### Properties
|
|
1116
|
-
|
|
1117
|
-
| Property | Type | Description |
|
|
1118
|
-
|---|---|---|
|
|
1119
|
-
| `mode` | `RedisMode` | The Redis topology mode (`'standalone'`, `'sentinel'`, or `'cluster'`). |
|
|
1120
|
-
| `cache` | `Cache` | Shared Cache instance (lazily created on first access). |
|
|
1121
|
-
| `pubsub` | `PubSub` | Shared Pub/Sub instance (lazily created on first access). |
|
|
1122
|
-
| `lock` | `DistributedLock` | Shared DistributedLock instance (lazily created on first access). |
|
|
1123
|
-
| `rateLimiter` | `RateLimiter` | Shared RateLimiter instance (lazily created on first access). |
|
|
1124
|
-
| `session` | `SessionManager` | Shared SessionManager instance (lazily created on first access). |
|
|
1125
|
-
| `revocationStore` | `RedisRevocationStore` | Shared revocation store (lazily created on first access). |
|
|
1126
|
-
| `raw` | `RedisClient \| Cluster` | The raw underlying ioredis client. |
|
|
1127
|
-
|
|
1128
|
-
#### Builder-style configurators
|
|
1129
|
-
|
|
1130
|
-
These methods let you swap configuration after construction. The corresponding sub-component is reset so it picks up the new configuration on next access.
|
|
1131
|
-
|
|
1132
|
-
| Method | Description | Args | Returns |
|
|
1133
|
-
|---|---|---|---|
|
|
1134
|
-
| `withCache(value)` | Set cache configuration and reset the cache. | `value: CacheInputConfig` | `this` for chaining |
|
|
1135
|
-
| `withLock(value)` | Set lock options and reset the lock. | `value: DistributedLockInputOptions` | `this` for chaining |
|
|
1136
|
-
| `withRateLimiter(value)` | Set rate limit options and reset the limiter. | `value: RateLimitOptionsInput` | `this` for chaining |
|
|
1137
|
-
| `withSession(value)` | Merge session configuration and reset the session manager. | `value: WithSessionManagerOptions` | `this` for chaining |
|
|
1138
|
-
|
|
1139
|
-
#### Raw client access
|
|
1140
|
-
|
|
1141
|
-
| Method | Description | Args | Returns |
|
|
1142
|
-
|---|---|---|---|
|
|
1143
|
-
| `getRawClient<T>()` | Typed access to the raw ioredis client/cluster. | generic `T` defaults to `RedisClient \| Cluster` | `T` |
|
|
1144
|
-
| `get raw` | The raw underlying client (`RedisClient` or `Cluster`). | — | `RedisClient \| Cluster` |
|
|
1145
|
-
|
|
1146
|
-
#### Lifecycle
|
|
1147
|
-
|
|
1148
|
-
| Method | Description | Args | Returns |
|
|
1149
|
-
|---|---|---|---|
|
|
1150
|
-
| `ping()` | Verify Redis connectivity with PING. | — | `Promise<boolean>` — `true` when the server replied `PONG`, `false` on any error. |
|
|
1151
|
-
| `close()` | Gracefully `QUIT` the underlying client and mark the wrapper as not ready. | — | `Promise<void>` |
|
|
1152
|
-
| `getConnectionStatus()` | Snapshot of the connection state. | — | `ConnectionStatus` |
|
|
1153
|
-
| `defineCommand(...args)` | Forward to `ioredis#defineCommand` to register a custom command on the underlying client. | mirrors `ioredis` | mirrors `ioredis` |
|
|
1154
|
-
|
|
1155
|
-
#### Basic commands (string, key, counter)
|
|
1156
|
-
|
|
1157
|
-
| Method | Description | Args | Returns |
|
|
1158
|
-
|---|---|---|---|
|
|
1159
|
-
| `get(key)` | `GET` a key. | `key: string` | `Promise<string \| null>` |
|
|
1160
|
-
| `set(key, value, ttl?)` | `SET` with optional `EX` TTL. | `key: string`, `value: string \| Buffer`, `ttl?: number` (seconds) | `Promise<"OK" \| null>` |
|
|
1161
|
-
| `setexnx(key, value, ttl?)` | `SET ... EX NX` (optional TTL). | as above | `Promise<"OK" \| null>` |
|
|
1162
|
-
| `setnx(key, value, ttl?)` | `SETNX` with optional `EX` — returns `1` on success, `0` on conflict. | as above | `Promise<number>` (1 or 0) |
|
|
1163
|
-
| `getdel(key)` | `GETDEL` — return and remove. | `key: string` | `Promise<string \| null>` |
|
|
1164
|
-
| `exists(key)` | `EXISTS` — count of existing keys (0 or 1 here). | `key: string` | `Promise<number>` |
|
|
1165
|
-
| `del(...keys)` | `DEL` one or more keys. | `...keys: string[]` | `Promise<number>` — number deleted |
|
|
1166
|
-
| `expire(key, ttl)` | `EXPIRE` — set a TTL in seconds. | `key: string`, `ttl: number` | `Promise<number>` — 1 if applied, 0 if not |
|
|
1167
|
-
| `ttl(key)` | `TTL` — remaining seconds (`-2` missing, `-1` no TTL). | `key: string` | `Promise<number>` |
|
|
1168
|
-
| `incr(key)` | `INCR` — counter by 1. | `key: string` | `Promise<number>` |
|
|
1169
|
-
| `decr(key)` | `DECR` — counter by 1. | `key: string` | `Promise<number>` |
|
|
1170
|
-
| `incrby(key, amount)` | `INCRBY` — counter by `amount` (must be a positive safe integer; throws `RedisError 'INVALID_AMOUNT'` otherwise). | `key: string`, `amount: number` | `Promise<number>` |
|
|
1171
|
-
| `decrby(key, amount)` | `DECRBY` — counter by `amount` (same validation as `incrby`). | `key: string`, `amount: number` | `Promise<number>` |
|
|
1172
|
-
| `time()` | `TIME` — Redis server time (seconds portion). | — | `Promise<number>` — Unix seconds |
|
|
1173
|
-
|
|
1174
|
-
#### Multi-key commands (cluster-safe)
|
|
1175
|
-
|
|
1176
|
-
| Method | Description | Args | Returns |
|
|
1177
|
-
|---|---|---|---|
|
|
1178
|
-
| `mget(...keys)` | `MGET`; routes by hash slot in cluster mode. | `...keys: string[]` | `Promise<(string \| null)[]>` |
|
|
1179
|
-
| `mset(...pairs)` | `MSET`; grouped by hash slot in cluster mode with bounded fan-out (`maxFanOutConcurrency`). | `...pairs: Array<[string, string \| Buffer]>` | `Promise<"OK">` |
|
|
1180
|
-
| `mgetClusterAware(keys)` | Same as `mget`, but the cluster grouping is always applied (useful when the cluster branch is selected by mode narrowing). | `keys: string[]` | `Promise<(string \| null)[]>` |
|
|
1181
|
-
|
|
1182
|
-
#### Hash commands
|
|
1183
|
-
|
|
1184
|
-
| Method | Description | Args | Returns |
|
|
1185
|
-
|---|---|---|---|
|
|
1186
|
-
| `hget(key, field)` | `HGET`. | `key: string`, `field: string` | `Promise<string \| null>` |
|
|
1187
|
-
| `hset(key, field, value)` | `HSET`. | `key: string`, `field: string`, `value: string \| Buffer` | `Promise<number>` — 1 if new, 0 if updated |
|
|
1188
|
-
| `hgetall(key)` | `HGETALL`. | `key: string` | `Promise<Record<string, string>>` |
|
|
1189
|
-
| `hdel(key, ...fields)` | `HDEL`. | `key: string`, `...fields: string[]` | `Promise<number>` — number deleted |
|
|
1190
|
-
|
|
1191
|
-
#### Set commands
|
|
1192
|
-
|
|
1193
|
-
| Method | Description | Args | Returns |
|
|
1194
|
-
|---|---|---|---|
|
|
1195
|
-
| `sadd(key, ...members)` | `SADD`. | `key: string`, `...members: string[]` | `Promise<number>` — added count |
|
|
1196
|
-
| `srem(key, ...members)` | `SREM`. | `key: string`, `...members: string[]` | `Promise<number>` — removed count |
|
|
1197
|
-
| `smembers(key)` | `SMEMBERS`. | `key: string` | `Promise<string[]>` |
|
|
1198
|
-
| `sismember(key, member)` | `SISMEMBER`. | `key: string`, `member: string` | `Promise<number>` — 0 or 1 |
|
|
1199
|
-
|
|
1200
|
-
#### Sorted set commands
|
|
1201
|
-
|
|
1202
|
-
| Method | Description | Args | Returns |
|
|
1203
|
-
|---|---|---|---|
|
|
1204
|
-
| `zadd(key, score, member)` | `ZADD`. | `key: string`, `score: number`, `member: string` | `Promise<number>` — added count |
|
|
1205
|
-
| `zrange(key, start, stop)` | `ZRANGE` (legacy form, ascending). | `key: string`, `start: number`, `stop: number` | `Promise<string[]>` |
|
|
1206
|
-
| `zcard(key)` | `ZCARD`. | `key: string` | `Promise<number>` |
|
|
1207
|
-
| `zrem(key, ...members)` | `ZREM`. | `key: string`, `...members: string[]` | `Promise<number>` — removed count |
|
|
1208
|
-
|
|
1209
|
-
#### Pipelines and scanning
|
|
1210
|
-
|
|
1211
|
-
| Method | Description | Args | Returns |
|
|
1212
|
-
|---|---|---|---|
|
|
1213
|
-
| `pipeline()` | Returns a new ioredis pipeline (keys in a pipeline must share one hash slot in cluster mode). | — | `Pipeline` |
|
|
1214
|
-
| `scanIterator(pattern, count?)` | Cluster-safe async iterator over keys matching `pattern`. | `pattern: string`, `count?: number` (default 100) | `AsyncIterable<string>` |
|
|
1215
|
-
| `scanCluster(pattern, options?)` | Cluster-safe async iterator yielding batches of matching keys. | `pattern: string`, `options?: { count?, batchSize? }` | `AsyncIterable<string[]>` |
|
|
1216
|
-
| `deletePattern(pattern, options?)` | SCAN every node, then `DEL` matches in slot-grouped pipelines. | `pattern: string`, `options?: { batchSize?, scanCount? }` | `Promise<number>` — number deleted |
|
|
1217
|
-
| `clearNamespace(prefix, options?)` | Alias of `deletePattern(${prefix}*)`. | `prefix: string`, `options?: DeletePatternOptions` | `Promise<number>` |
|
|
1218
|
-
| `clearNamespaceClusterAware(prefix, options?)` | Cluster-aware clear (internally calls `clearNamespace`). | as above | `Promise<number>` |
|
|
1219
|
-
|
|
1220
|
-
#### Lua scripts
|
|
1221
|
-
|
|
1222
|
-
| Method | Description | Args | Returns |
|
|
1223
|
-
|---|---|---|---|
|
|
1224
|
-
| `eval(script, numKeys, ...args)` | `EVAL` — first `numKeys` are `KEYS`, the rest are `ARGV`. | `script: string`, `numKeys: number`, `...args: RedisCommandArgument[]` | `Promise<unknown>` |
|
|
1225
|
-
| `evalsha(sha, script, numKeys, ...args)` | `EVALSHA` with automatic `EVAL` fallback on `NOSCRIPT`. | `sha: string`, `script: string`, `numKeys: number`, `...args: RedisCommandArgument[]` | `Promise<unknown>` |
|
|
1226
|
-
| `scriptLoad(script)` | `SCRIPT LOAD`. | `script: string` | `Promise<string>` — the SHA1 |
|
|
1227
|
-
|
|
1228
|
-
#### Cluster helpers (cluster mode only)
|
|
1229
|
-
|
|
1230
|
-
| Method | Description | Args | Returns |
|
|
1231
|
-
|---|---|---|---|
|
|
1232
|
-
| `isCluster()` | Is the underlying client a Cluster? | — | `boolean` |
|
|
1233
|
-
| `getClusterNodes()` | Raw master node clients. | — | `RedisClient[]` (empty for non-cluster) |
|
|
1234
|
-
| `getClusterSlots()` | `CLUSTER SLOTS` parsed into structured slot ranges. | — | `Promise<ClusterSlotRange[]>` |
|
|
1235
|
-
| `getSlotRanges()` | `Map<slot, host:port[]>` for every slot. | — | `Promise<Map<number, string[]>>` |
|
|
1236
|
-
| `calculateSlot(key)` | CRC16 hash slot for a key (honors `{hash tags}`). | `key: string` | `number` (0..16383) |
|
|
1237
|
-
| `getNodeForKey(key)` | The Redis client owning the key's slot, or `null`. | `key: string` | `Promise<RedisClient \| null>` |
|
|
1238
|
-
| `isKeyServed(key)` | Whether the cluster currently serves the key's slot. | `key: string` | `Promise<boolean>` — `true` outside cluster mode |
|
|
1239
|
-
| `executeOnNode(key, command, ...args)` | Run an arbitrary command on the node owning `key` (or the single client outside cluster mode). | `key: string`, `command: string`, `...args: unknown[]` | `Promise<T>` |
|
|
1240
|
-
| `mgetClusterAware(keys)` | Slot-grouped multi-get (always applies the grouping). | `keys: string[]` | `Promise<(string \| null)[]>` |
|
|
1241
|
-
| `clearNamespaceClusterAware(prefix, options?)` | Cluster-aware namespace wipe. | `prefix: string`, `options?: DeletePatternOptions` | `Promise<number>` |
|
|
1242
|
-
| `getClusterInfo()` | Snapshot of the topology and node list. | — | `ClusterInfo` |
|
|
1243
|
-
|
|
1244
|
-
#### Server info and database selection
|
|
1245
|
-
|
|
1246
|
-
| Method | Description | Args | Returns |
|
|
1247
|
-
|---|---|---|---|
|
|
1248
|
-
| `info(section?)` | `INFO` (full or per-section). | `section?: string` | `Promise<string>` |
|
|
1249
|
-
| `select(database)` | `SELECT`. Throws `RedisError 'CLUSTER_MODE'` in cluster mode; throws `RedisError 'INVALID_DATABASE'` when `database` is not an integer in `[0, 15]`. | `database: number` | `Promise<"OK">` |
|
|
1250
|
-
|
|
1251
|
-
#### Internal helpers (private)
|
|
1252
|
-
|
|
1253
|
-
| Method | Description |
|
|
1254
|
-
|---|---|
|
|
1255
|
-
| `exec<T>(command, args, operation)` | Wraps a Redis call with timing/logging. Logs slow commands above `slowCommandThreshold`. |
|
|
1256
|
-
| `isClusterClient(client)` | Type guard for `Cluster`. |
|
|
1257
|
-
| `runWithConcurrency(items, concurrency, worker)` | Bounded-fan-out helper. |
|
|
1258
|
-
| `executeCommandOnClient<T>(client, command, args)` | Reflectively invoke a method on a client. |
|
|
1259
|
-
|
|
1260
|
-
### `Cache`
|
|
1261
|
-
|
|
1262
|
-
#### Constructor
|
|
1263
|
-
|
|
1264
|
-
```ts
|
|
1265
|
-
new Cache(client: RedisClientWrapper, config: CacheInputConfig, logger?: LoggerLike)
|
|
1266
|
-
```
|
|
1267
|
-
|
|
1268
|
-
| Param | Type | Description |
|
|
1269
|
-
|---|---|---|
|
|
1270
|
-
| `client` | `RedisClientWrapper` | The underlying client. |
|
|
1271
|
-
| `config` | `CacheInputConfig` | `{ defaultTTL?, compressionThreshold?, namespace? }`. |
|
|
1272
|
-
| `logger` | `LoggerLike` | Optional pino-compatible logger; defaults to `console`. |
|
|
1273
|
-
|
|
1274
|
-
#### Internal helpers (private)
|
|
1275
|
-
|
|
1276
|
-
| Method | Description |
|
|
1277
|
-
|---|---|
|
|
1278
|
-
| `serialize<T>(value)` | Buffer-ify, then gzip if larger than `compressionThreshold`. Returns `{ data, compressed }`. |
|
|
1279
|
-
| `deserialize<T>(data, compressed)` | Inverse of `serialize` — gunzip if needed, JSON-parse when possible. |
|
|
1280
|
-
| `getKey(key, namespace?)` | Build the final Redis key with the configured/per-call namespace prefix. |
|
|
1281
|
-
| `get getNamespace` | Get the configured namespace prefix with trailing `:`. |
|
|
1282
|
-
|
|
1283
|
-
#### Methods
|
|
1284
|
-
|
|
1285
|
-
See the [Cache](#cache-1) section above for the full method table.
|
|
1286
|
-
|
|
1287
|
-
### `PubSub`
|
|
1288
|
-
|
|
1289
|
-
#### Constructor
|
|
1290
|
-
|
|
1291
|
-
```ts
|
|
1292
|
-
new PubSub(publisher: RedisClientWrapper, logger?: LoggerLike)
|
|
1293
|
-
```
|
|
1294
|
-
|
|
1295
|
-
| Param | Type | Description |
|
|
1296
|
-
|---|---|---|
|
|
1297
|
-
| `publisher` | `RedisClientWrapper` | A client used for publishing. |
|
|
1298
|
-
| `logger` | `LoggerLike` | Optional pino-compatible logger; defaults to `console`. |
|
|
1299
|
-
|
|
1300
|
-
#### Internal helpers (private)
|
|
1301
|
-
|
|
1302
|
-
| Method | Description |
|
|
1303
|
-
|---|---|
|
|
1304
|
-
| `setupSubscriber()` | Wires `message`/`pmessage`/`error` listeners on the subscriber client. |
|
|
1305
|
-
| `handleMessage(channel, message)` | Dispatches a standard message to registered handlers (with JSON parse + per-handler try/catch). |
|
|
1306
|
-
| `handlePatternMessage(pattern, channel, message)` | Same as `handleMessage` for pattern subscriptions. |
|
|
1307
|
-
|
|
1308
|
-
#### Methods
|
|
1309
|
-
|
|
1310
|
-
See the [Pub/Sub](#pubsub-1) section above for the full method table.
|
|
1311
|
-
|
|
1312
|
-
### `DistributedLock`
|
|
1313
|
-
|
|
1314
|
-
#### Constructor
|
|
1315
|
-
|
|
1316
|
-
```ts
|
|
1317
|
-
new DistributedLock(client: RedisClientWrapper, logger?: LoggerLike, options?: Partial<DistributedLockOptions>)
|
|
1318
|
-
```
|
|
1319
|
-
|
|
1320
|
-
| Param | Type | Default | Description |
|
|
1321
|
-
|---|---|---|---|
|
|
1322
|
-
| `client` | `RedisClientWrapper` | — | The underlying client. |
|
|
1323
|
-
| `logger` | `LoggerLike` | `defaultLogger` | Optional pino-compatible logger. |
|
|
1324
|
-
| `options.ttl` | `number` | `30000` | Lock TTL in milliseconds. |
|
|
1325
|
-
| `options.retryCount` | `number` | `3` | Acquisition attempts. |
|
|
1326
|
-
| `options.retryDelay` | `number` | `200` | Base delay (exponential, with jitter) between attempts. |
|
|
1327
|
-
|
|
1328
|
-
#### Internal helpers (private)
|
|
1329
|
-
|
|
1330
|
-
| Method | Description |
|
|
1331
|
-
|---|---|
|
|
1332
|
-
| `getLockKey(key)` | Build the Redis key (`lock:{key}`). |
|
|
1333
|
-
| `generateLockId()` | 16 random bytes as hex. |
|
|
1334
|
-
| `executeWithRetry(fn, retryCount, retryDelay)` | Exponential backoff with 0.5–1.0 jitter. |
|
|
1335
|
-
|
|
1336
|
-
#### Methods
|
|
1337
|
-
|
|
1338
|
-
See the [DistributedLock](#distributedlock) section above for the full method table.
|
|
1339
|
-
|
|
1340
|
-
### `RateLimiter`
|
|
1341
|
-
|
|
1342
|
-
#### Constructor
|
|
1343
|
-
|
|
1344
|
-
```ts
|
|
1345
|
-
new RateLimiter(client: RedisClientWrapper, options?: RateLimitOptionsInput, logger?: LoggerLike)
|
|
1346
|
-
```
|
|
1347
|
-
|
|
1348
|
-
| Param | Type | Default | Description |
|
|
1349
|
-
|---|---|---|---|
|
|
1350
|
-
| `client` | `RedisClientWrapper` | — | The underlying client. |
|
|
1351
|
-
| `options.limit` | `number` | `100` | Max requests per window. |
|
|
1352
|
-
| `options.duration` | `number` | `60` | Window length in seconds. |
|
|
1353
|
-
| `options.algorithm` | `'fixed' \| 'sliding'` | `'sliding'` | Window algorithm. |
|
|
1354
|
-
| `options.namespace` | `string` | `'ratelimit'` | Key prefix. |
|
|
1355
|
-
| `logger` | `LoggerLike` | `defaultLogger` | Optional pino-compatible logger. |
|
|
1356
|
-
|
|
1357
|
-
#### Internal helpers (private)
|
|
1358
|
-
|
|
1359
|
-
| Method | Description |
|
|
1360
|
-
|---|---|
|
|
1361
|
-
| `consumeFixed(key, limit, duration)` | Fixed-window: `INCR` + `EXPIRE` on first hit. |
|
|
1362
|
-
| `consumeSliding(key, limit, duration)` | Sliding-window via atomic Lua over a sorted set. |
|
|
1363
|
-
| `checkFixed(key, limit, duration)` | Fixed-window peek. |
|
|
1364
|
-
| `checkSliding(key, limit, duration)` | Sliding-window peek. |
|
|
1365
|
-
|
|
1366
|
-
#### Methods
|
|
1367
|
-
|
|
1368
|
-
See the [RateLimiter](#ratelimiter-1) section above for the full method table.
|
|
1369
|
-
|
|
1370
|
-
### `HealthChecker`
|
|
1371
|
-
|
|
1372
|
-
#### Constructor
|
|
1373
|
-
|
|
1374
|
-
```ts
|
|
1375
|
-
new HealthChecker(client: RedisClientWrapper, logger?: LoggerLike)
|
|
83
|
+
npm install ioredis-toolkit ioredis zod
|
|
1376
84
|
```
|
|
1377
85
|
|
|
1378
|
-
|
|
1379
|
-
|---|---|---|
|
|
1380
|
-
| `client` | `RedisClientWrapper` | The underlying client. |
|
|
1381
|
-
| `logger` | `LoggerLike` | Optional pino-compatible logger; defaults to `console`. |
|
|
1382
|
-
|
|
1383
|
-
#### Internal helpers (private)
|
|
1384
|
-
|
|
1385
|
-
| Method | Description |
|
|
1386
|
-
|---|---|
|
|
1387
|
-
| `notifyCallbacks(status)` | Fire every registered callback with try/catch isolation. |
|
|
86
|
+
The package is ESM-only and requires Node.js 22 or newer.
|
|
1388
87
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
See the [HealthChecker](#healthchecker) section above for the full method table.
|
|
1392
|
-
|
|
1393
|
-
### `RedisRevocationStore`
|
|
88
|
+
## Development
|
|
1394
89
|
|
|
1395
|
-
|
|
90
|
+
This repo uses [Bun](https://bun.sh) for dependency installation and scripts; the committed `bun.lock` is the source of truth.
|
|
1396
91
|
|
|
1397
|
-
```
|
|
1398
|
-
|
|
92
|
+
```bash
|
|
93
|
+
bun install
|
|
94
|
+
bun run typecheck
|
|
95
|
+
bun run lint
|
|
96
|
+
bun run test
|
|
97
|
+
bun run test:integration
|
|
98
|
+
bun run build
|
|
1399
99
|
```
|
|
1400
100
|
|
|
1401
|
-
|
|
1402
|
-
|---|---|---|
|
|
1403
|
-
| `options.client` | `RedisClientWrapper` | The underlying client. |
|
|
1404
|
-
| `options.keyPrefix` | `string` | Key prefix (default `'cache:revoked:'`). |
|
|
1405
|
-
|
|
1406
|
-
#### Internal helpers (private)
|
|
1407
|
-
|
|
1408
|
-
| Method | Description |
|
|
1409
|
-
|---|---|
|
|
1410
|
-
| `key(jti)` | Build the full Redis key (`{prefix}{jti}`). |
|
|
1411
|
-
|
|
1412
|
-
#### Methods
|
|
1413
|
-
|
|
1414
|
-
See the [RedisRevocationStore](#redisrevocationstore) section above for the full method table.
|
|
1415
|
-
|
|
1416
|
-
### Slot / cluster-slot helpers (`src/cluster-slot.ts`)
|
|
1417
|
-
|
|
1418
|
-
| Export | Signature | Description |
|
|
1419
|
-
|---|---|---|
|
|
1420
|
-
| `hashTag(key)` | `(key: string) => string` | Extract the `{...}` hash tag from a key, or return the key when no tag is present. |
|
|
1421
|
-
| `calculateRedisClusterSlot(key)` | `(key: string) => number` | CRC16-CCITT/XMODEM over the hash-tagged key bytes, `crc % 16384`. Honors `{hash tags}`. |
|
|
1422
|
-
|
|
1423
|
-
### Cluster fan-out helpers (`src/cluster.ts`)
|
|
1424
|
-
|
|
1425
|
-
| Export | Signature | Description |
|
|
1426
|
-
|---|---|---|
|
|
1427
|
-
| `PipelineCommand` | interface `{ command, args, slot }` | A single queued pipeline command with its slot precomputed. |
|
|
1428
|
-
| `PipelineCommandResult` | type `[Error \| null, unknown]` | A pipeline result. |
|
|
1429
|
-
| `ExecuteBySlotOptions` | interface `{ concurrency?, retry? }` | Tuning for `executeBySlot`. |
|
|
1430
|
-
| `executeBySlot(client, commands, options?)` | `(client, commands: PipelineCommand[], options?) => Promise<PipelineCommandResult[]>` | Group commands by slot, run one pipeline per slot with bounded concurrency, retry network-level slot-pipeline failures. Result order matches the input order. |
|
|
1431
|
-
| `assertPipelineOk(results, describe?)` | `(results, describe?) => unknown[]` | Throws a structured `Error` listing failed command indexes when any pipeline command returned an error; returns the values otherwise. |
|
|
1432
|
-
| `mapWithConcurrency(items, limit, fn)` | `<T,R>(items, limit, fn) => Promise<R[]>` | Bounded-concurrency async map. |
|
|
1433
|
-
| `chunk(items, size)` | `<T>(items, size) => Generator<T[]>` | Bounded batch chunker. |
|
|
1434
|
-
|
|
1435
|
-
### Logger types (`src/logger.ts`)
|
|
1436
|
-
|
|
1437
|
-
| Export | Description |
|
|
1438
|
-
|---|---|
|
|
1439
|
-
| `LogMeta` | `Record<string, unknown>` — structured log metadata. |
|
|
1440
|
-
| `LoggerLike` | Interface matching pino's `trace/debug/info/warn/error/fatal` + `child()`. |
|
|
1441
|
-
| `defaultLogger` | A `ConsoleLogger` using `console.*` methods. |
|
|
1442
|
-
| `createConsoleLogger(bindings?)` | Factory for a `ConsoleLogger` with pre-bound metadata. |
|
|
1443
|
-
|
|
1444
|
-
### Error types (`src/errors.ts`)
|
|
1445
|
-
|
|
1446
|
-
| Class | Code | When |
|
|
1447
|
-
|---|---|---|
|
|
1448
|
-
| `RedisError` | configurable | Base error for the toolkit. |
|
|
1449
|
-
| `ConnectionError` | `CONNECTION_ERROR` | Underlying connection issues. |
|
|
1450
|
-
| `TimeoutError` | `TIMEOUT_ERROR` | Operation timeout. |
|
|
1451
|
-
| `SessionExpiredError` | `AUTH_SESSION_EXPIRED` | Session referenced by a token no longer exists or has expired. |
|
|
1452
|
-
| `LockError` | `LOCK_ERROR` | Distributed lock error. |
|
|
1453
|
-
| `SerializationError` | `SERIALIZATION_ERROR` | Cache / payload serialization error. |
|
|
1454
|
-
| `CompressionError` | `COMPRESSION_ERROR` | Cache compression error. |
|
|
1455
|
-
| `ConfigurationError` | `CONFIGURATION_ERROR` | Invalid config (e.g. Zod failure). |
|
|
1456
|
-
| `ClusterError` | `CLUSTER_ERROR` | Cluster topology error. |
|
|
1457
|
-
|
|
1458
|
-
### Session types (`src/session/`)
|
|
1459
|
-
|
|
1460
|
-
See [`src/session/README.md`](src/session/README.md) for the complete session reference. Highlights:
|
|
1461
|
-
|
|
1462
|
-
| Export | Description |
|
|
1463
|
-
|---|---|
|
|
1464
|
-
| `createSessionManager` | Build a `SessionManager` (synchronous). |
|
|
1465
|
-
| `SessionManager` | Composition root; exposes `service`, `repository`, `metrics`, `health`, `circuitBreaker`, `cookies`, `token`, `keys`, `init()`, `close()`. |
|
|
1466
|
-
| `SessionService` | Application-facing API: `create`, `validate`, `touch`, `rotate`, `update`, `destroy`, `revoke`, `revokeAll`, `deleteByUser`, `findByUser`, `list`, `setSecurityVersion`, `getSecurityVersion`, `reconcileUser`, `health`. |
|
|
1467
|
-
| `SessionRepository` | Low-level Redis I/O. |
|
|
1468
|
-
| `SessionTokenManager` | Token + jti management (`generate`, `generateNonce`, `hash`, `validateFormat`, `safeEquals`, `tokenToJti`). |
|
|
1469
|
-
| `SessionKeyStrategy` | Cluster-safe key layout (`sessionKey`, `userIndexKey`, `securityVersionKey`, `createClaimKey`, `jtiIndexKey`, `revokedKey`, `sessionKeyPrefix`, `familyHeadKeyPrefix`, `namespacePrefix`). |
|
|
1470
|
-
| `SessionMetrics` | Internal metrics facade (`operation`, `latency`, `breakerState`, `revocationMiss`, `encryptionError`, `jtiIndexWriteFailure`, `reconcileUser`). |
|
|
1471
|
-
| `SessionMetricsAdapter` | Application-provided metrics sink. |
|
|
1472
|
-
| `SessionCircuitBreaker` | Fail-closed breaker (`state`, `run`, `tryAcquire`, `recordSuccess`, `recordFailure`, `reset`). |
|
|
1473
|
-
| `SessionHealthChecker` | PING + sliding-window error rate (`recordOp`, `check`). |
|
|
1474
|
-
| `SessionCookieManager` | `name`, `serialize`, `serializeWithAttributes`, `clear`, `parse`. |
|
|
1475
|
-
| `SessionKeyProvider` | `getCurrentKey` / `getKey` for encryption. |
|
|
1476
|
-
| `StaticSessionKeyProvider` | In-memory map of versions to 32-byte keys. |
|
|
1477
|
-
| `createRandomSessionKeyProvider` | Convenience: a single random 32-byte key. |
|
|
1478
|
-
| `serializeSession` / `serializeEncryptedSession` / `deserializeSession` / `validateSessionRecord` | (De)serialization helpers. |
|
|
1479
|
-
| `parseSessionConfig` / `redactSessionConfig` | Config helpers. |
|
|
1480
|
-
| `TTL` / `IDLE_TIMEOUT` / `TOUCH_INTERVAL` | Default values (7d, 24h, 5m). |
|
|
1481
|
-
| Session error classes | `SessionError`, `SessionNotFoundError`, `SessionExpiredError`, `SessionRevokedError`, `SessionInvalidError`, `SessionRotationError`, `SessionReplayError`, `SessionStorageError`, `SessionSerializationError`, `SessionConfigurationError`, `SessionConcurrencyError`, `RevocationError`, `RevocationBatchError`, `CircuitBreakerOpenError`. |
|
|
1482
|
-
| `redactIdentifier(value)` | Safe-to-log identifier redaction. |
|
|
1483
|
-
|
|
1484
|
-
### Public types catalog
|
|
1485
|
-
|
|
1486
|
-
#### Core client types
|
|
1487
|
-
|
|
1488
|
-
| Type | Description |
|
|
1489
|
-
|---|---|
|
|
1490
|
-
| `RedisClientWrapper` | The unified client wrapper. |
|
|
1491
|
-
| `RedisClient` (alias `RedisClientForMode`) | The unified client, narrowed per topology. |
|
|
1492
|
-
| `createRedisClient` | Factory function: `createRedisClient(config)` — creates a client for the specified mode. |
|
|
1493
|
-
| `ClusterCapabilities` | Cluster-only methods available on cluster clients. |
|
|
1494
|
-
| `RedisConfig` | Normalized configuration (after Zod validation). |
|
|
1495
|
-
| `RedisConfigInput` | User-facing configuration input (validated with Zod). |
|
|
1496
|
-
| `RedisMode` | `'standalone' \| 'sentinel' \| 'cluster'`. |
|
|
1497
|
-
| `RedisConfigForMode<M>` | Mode-specific config type. |
|
|
1498
|
-
| `RedisCommonConfig` | Common config shared by all topologies. |
|
|
1499
|
-
| `RedisCommonConfigInput` | User-input shape for common config. |
|
|
1500
|
-
| `StandaloneRedisConfig` / `StandaloneRedisConfigInput` | Standalone-specific config. |
|
|
1501
|
-
| `SentinelRedisConfig` / `SentinelRedisConfigInput` | Sentinel-specific config. |
|
|
1502
|
-
| `ClusterRedisConfig` / `ClusterRedisConfigInput` | Cluster-specific config. |
|
|
1503
|
-
| `RedisConfigInputSchema` | Zod union schema for input config. |
|
|
1504
|
-
| `BaseRedisConfigSchema` | Base Zod schema (password, username, database, tls, etc.). |
|
|
1505
|
-
| `RedisTlsOptions` / `RedisTlsOptionsInput` | TLS settings (CA / cert / key / `rejectUnauthorized`). |
|
|
1506
|
-
| `RedisNode` / `RedisNodeSchema` | `{ host, port, nodeId? }` for cluster/sentinel nodes. |
|
|
1507
|
-
| `Redis` | Alias for the raw `ioredis` `Redis` type. |
|
|
1508
|
-
|
|
1509
|
-
#### Cache types
|
|
1510
|
-
|
|
1511
|
-
| Type | Description |
|
|
1512
|
-
|---|---|
|
|
1513
|
-
| `Cache` | Cache class. |
|
|
1514
|
-
| `CacheOptions` | `{ ttl?, compress?, namespace? }` per-call options. |
|
|
1515
|
-
| `CacheInputConfig` | `{ defaultTTL?, compressionThreshold?, namespace? }` constructor config. |
|
|
1516
|
-
| `CacheStats` | `{ namespace, connectionStatus }`. |
|
|
1517
|
-
| `CacheOptionsSchema` | Zod schema for cache options. |
|
|
1518
|
-
|
|
1519
|
-
#### Lock types
|
|
1520
|
-
|
|
1521
|
-
| Type | Description |
|
|
1522
|
-
|---|---|
|
|
1523
|
-
| `DistributedLock` | Lock class. |
|
|
1524
|
-
| `DistributedLockOptions` | `{ ttl?, retryCount?, retryDelay? }`. |
|
|
1525
|
-
| `DistributedLockOptionsSchema` | Zod schema for lock options. |
|
|
1526
|
-
| `DistributedLockInputOptions` | Pre-default Zod input type. |
|
|
1527
|
-
| `LockInfo` | `{ locked, ttl?, lockId? }`. |
|
|
1528
|
-
|
|
1529
|
-
#### Rate limit types
|
|
1530
|
-
|
|
1531
|
-
| Type | Description |
|
|
1532
|
-
|---|---|
|
|
1533
|
-
| `RateLimiter` | Limiter class. |
|
|
1534
|
-
| `RateLimitAlgorithm` | `'fixed' \| 'sliding'`. |
|
|
1535
|
-
| `RateLimitOptions` | `{ limit?, duration?, algorithm?, namespace? }` for instance/call. |
|
|
1536
|
-
| `RateLimitOptionsInput` | Pre-default Zod input type. |
|
|
1537
|
-
| `RateLimitOptionsSchema` | Zod schema for rate-limit options. |
|
|
1538
|
-
| `RateLimitResult` | `{ allowed, limit, used, remaining, resetAt, retryAfter }`. |
|
|
1539
|
-
| `RateLimitAlgorithmSchema` | Zod schema for the algorithm literal. |
|
|
1540
|
-
|
|
1541
|
-
#### Pub/Sub types
|
|
1542
|
-
|
|
1543
|
-
| Type | Description |
|
|
1544
|
-
|---|---|
|
|
1545
|
-
| `PubSub` | Pub/Sub class. |
|
|
1546
|
-
| `PubSubMessage<T>` | `{ channel, message }`. |
|
|
1547
|
-
| `PubSubStats` | `{ subscriptions, patternSubscriptions, connected }`. |
|
|
1548
|
-
| `PubSubEventMap` | Event payload map for `EventEmitter` typing. |
|
|
1549
|
-
|
|
1550
|
-
#### Health types
|
|
1551
|
-
|
|
1552
|
-
| Type | Description |
|
|
1553
|
-
|---|---|
|
|
1554
|
-
| `HealthStatus` | `{ healthy, status, latency, timestamp, details }`. |
|
|
1555
|
-
| `HealthChecker` | Checker class. |
|
|
1556
|
-
|
|
1557
|
-
#### Cluster types
|
|
1558
|
-
|
|
1559
|
-
| Type | Description |
|
|
1560
|
-
|---|---|
|
|
1561
|
-
| `ClusterInfo` | `{ mode, status, nodeCount?, slotCount?, nodes?, host?, port?, error? }`. |
|
|
1562
|
-
| `ClusterSlotRange` | `{ start, end, master, replicas }`. |
|
|
1563
|
-
| `ClusterSlotNode` | `{ host, port, nodeId? }`. |
|
|
1564
|
-
|
|
1565
|
-
#### Connection types
|
|
1566
|
-
|
|
1567
|
-
| Type | Description |
|
|
1568
|
-
|---|---|
|
|
1569
|
-
| `ConnectionState` | `'disconnected' \| 'connecting' \| 'connected' \| 'error' \| 'closed'`. |
|
|
1570
|
-
| `ConnectionStatus` | `{ state, connected, ready, lastError?, reconnectAttempts, uptime }`. |
|
|
1571
|
-
| `RedisEventMap` | Event map for the underlying client. |
|
|
1572
|
-
|
|
1573
|
-
#### Session types (exported at root)
|
|
1574
|
-
|
|
1575
|
-
| Type | Description |
|
|
1576
|
-
|---|---|
|
|
1577
|
-
| `SessionManager` | Composition root. |
|
|
1578
|
-
| `SessionManagerOptions` | `{ client, config?, encryptionKeyProvider?, revocationStore?, metricsAdapter?, circuitBreaker?, now? }`. |
|
|
1579
|
-
| `WithSessionManagerOptions` | `{ config?, encryptionKeyProvider?, metricsAdapter?, now? }` (for `withSession`). |
|
|
1580
|
-
| `SessionService` | Application-facing session API. |
|
|
1581
|
-
| `SessionServiceDeps` | Service dependencies. |
|
|
1582
|
-
| `SessionRepository` | Low-level Redis I/O. |
|
|
1583
|
-
| `SessionKeyStrategy` | Key layout. |
|
|
1584
|
-
| `SessionTokenManager` | Token + jti. |
|
|
1585
|
-
| `SessionMetrics` | Internal metrics. |
|
|
1586
|
-
| `SessionMetricsAdapter` | Application-provided metrics sink. |
|
|
1587
|
-
| `SessionCircuitBreaker` | Fail-closed breaker. |
|
|
1588
|
-
| `CircuitBreakerState` | `'closed' \| 'open' \| 'half_open'`. |
|
|
1589
|
-
| `SessionHealthChecker` | Health check. |
|
|
1590
|
-
| `SessionHealthStatus` | `{ healthy, latencyMs, errorRate, reachable, checkedAt }`. |
|
|
1591
|
-
| `SessionCookieManager` | Cookie helper. |
|
|
1592
|
-
| `SerializeCookieOptions` | `serialize` options. |
|
|
1593
|
-
| `SerializedCookie` | `{ header, name, value, attributes }`. |
|
|
1594
|
-
| `SerializedCookieAttributes` | `{ path, domain?, httpOnly, secure, sameSite, maxAge? }`. |
|
|
1595
|
-
| `SessionConfig` | Normalized session configuration. |
|
|
1596
|
-
| `SessionConfigInput` | Pre-default Zod input type. |
|
|
1597
|
-
| `PartialSessionConfig` | Partial pre-default config. |
|
|
1598
|
-
| `SessionRecord` | Persisted session record. |
|
|
1599
|
-
| `SessionCreateInput` | Input for `create()`. |
|
|
1600
|
-
| `SessionUpdatePatch` | Mutable fields for `update()`. |
|
|
1601
|
-
| `CreatedSession` | Result of `create()`. |
|
|
1602
|
-
| `RotatedSession` | Result of `rotate()`. |
|
|
1603
|
-
| `SessionValidationResult` | Result of `validate()`. |
|
|
1604
|
-
| `SessionInvalidReason` | Invalid reason discriminant. |
|
|
1605
|
-
| `TouchOutcome` | Touch outcome codes. |
|
|
1606
|
-
| `SessionEnvelope` | `PlainSessionEnvelope \| EncryptedSessionEnvelope`. |
|
|
1607
|
-
| `EncryptedSessionEnvelope` | AES-256-GCM envelope. |
|
|
1608
|
-
| `PlainSessionEnvelope` | JSON envelope (`v: 1`). |
|
|
1609
|
-
| `ListOptions` | `findByUser`/`list` options. |
|
|
1610
|
-
| `RotateOptions` | `rotate()` options. |
|
|
1611
|
-
| `TouchOptions` | `touch()` options. |
|
|
1612
|
-
| `UpdateOptions` | `update()` options. |
|
|
1613
|
-
| `ValidateOptions` | `validate()` options. |
|
|
1614
|
-
| `BindingMismatch` | Binding mismatch details. |
|
|
1615
|
-
| `SessionStatus` | `'active' \| 'consumed' \| 'revoked'`. |
|
|
1616
|
-
| `RevocationRecord` | Revocation record. |
|
|
1617
|
-
| `RevocationStore` | Storage-agnostic revocation interface. |
|
|
1618
|
-
| `SessionKeyProvider` | Encryption key provider. |
|
|
1619
|
-
| `SessionKeyProvider` | Re-exported from `session-encryption.ts`. |
|
|
1620
|
-
|
|
1621
|
-
#### Revocation store types
|
|
1622
|
-
|
|
1623
|
-
| Type | Description |
|
|
1624
|
-
|---|---|
|
|
1625
|
-
| `RedisRevocationStore` | Redis-backed revocation store. |
|
|
1626
|
-
| `RedisRevocationStoreOptions` | `{ client, keyPrefix? }`. |
|
|
1627
|
-
| `RedisRevocationStoreOptionsSchema` | Zod schema. |
|
|
1628
|
-
| `RedisRevocationStoreOptionsInput` | Pre-default Zod input type. |
|
|
1629
|
-
|
|
1630
|
-
#### Configuration exports
|
|
1631
|
-
|
|
1632
|
-
| Export | Description |
|
|
1633
|
-
|---|---|
|
|
1634
|
-
| `RedisConfigSchema` | Full Zod schema for `RedisConfig` (with `.transform` for `mode: 'standalone'` default). |
|
|
1635
|
-
| `parseSessionConfig` | Validate + apply defaults to a `PartialSessionConfig`. |
|
|
1636
|
-
| `redactSessionConfig` | Returns a redacted copy of a `SessionConfig`. |
|
|
1637
|
-
| `TTL` / `IDLE_TIMEOUT` / `TOUCH_INTERVAL` | Session config defaults. |
|
|
1638
|
-
|
|
1639
|
-
#### Utility exports
|
|
101
|
+
Real Redis integration tests require a Redis deployment. Docker definitions are provided under `docker/`.
|
|
1640
102
|
|
|
1641
|
-
|
|
1642
|
-
|---|---|
|
|
1643
|
-
| `RedisError` | Base Redis error. |
|
|
1644
|
-
| `calculateRedisClusterSlot` | CRC16 slot calculation. |
|
|
1645
|
-
| `hashTag` | Hash-tag extraction. |
|
|
1646
|
-
| `RedisConfiguration` | Alias of `RedisConfig`. |
|
|
1647
|
-
| `Redis` | The raw `ioredis` type. |
|
|
1648
|
-
| `default` | Default export: `{ RedisClient, createRedisClient, Cache, PubSub, DistributedLock, HealthChecker, RateLimiter }`. |
|
|
103
|
+
## Design principle
|
|
1649
104
|
|
|
1650
|
-
|
|
1651
|
-
## Changelog
|
|
105
|
+
The session layer and other modules use the shared Redis abstraction rather than creating competing clients. Redis Cluster is treated as a distributed keyspace: same-slot atomicity is used only where Redis can actually provide it, while cross-slot work uses explicit bounded fan-out.
|
|
1652
106
|
|
|
1653
|
-
|
|
107
|
+
## Links
|
|
1654
108
|
|
|
1655
|
-
|
|
1656
|
-
|
|
109
|
+
- [Changelog](./CHANGELOG.md)
|
|
110
|
+
- [Issues](https://github.com/org-utils/ioredis-toolkit/issues)
|
|
111
|
+
- [Repository](https://github.com/org-utils/ioredis-toolkit)
|