ioredis-toolkit 0.0.5 → 0.0.7
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/README.md +772 -316
- package/dist/cache.d.ts +502 -6
- package/dist/cache.js +501 -5
- package/dist/client.d.ts +105 -0
- package/dist/client.js +105 -0
- package/dist/health.d.ts +141 -0
- package/dist/health.js +133 -0
- package/dist/lock.d.ts +55 -1
- package/dist/lock.js +12 -22
- package/dist/pubsub.d.ts +259 -7
- package/dist/pubsub.js +259 -7
- package/dist/ratelimiter.d.ts +279 -1
- package/dist/ratelimiter.js +250 -0
- package/dist/session/scripts/create.lua +18 -3
- package/dist/session/scripts/rotate-encrypted.lua +5 -2
- package/dist/session/scripts/rotate.lua +5 -2
- package/dist/types.js +0 -2
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -75,15 +75,120 @@ export declare class RedisClientWrapper {
|
|
|
75
75
|
private readonly logger;
|
|
76
76
|
private isReady;
|
|
77
77
|
constructor(config: RedisConfigInput, logger?: LoggerLike);
|
|
78
|
+
/**
|
|
79
|
+
* Creates a new Redis client wrapper supporting standalone, sentinel, and cluster topologies.
|
|
80
|
+
*
|
|
81
|
+
* The client automatically adapts to the configured Redis topology and lazily creates
|
|
82
|
+
* and shares sub-components: `cache`, `pubsub`, `lock`, `rateLimiter`, and `session`.
|
|
83
|
+
*
|
|
84
|
+
* @param config - Redis configuration (validated with Zod `RedisConfigSchema`).
|
|
85
|
+
* Must include `mode` (standalone/sentinel/cluster), and topology-specific fields.
|
|
86
|
+
* @param logger - Optional pino-compatible logger; defaults to `console`.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const client = new RedisClientWrapper({
|
|
91
|
+
* mode: 'standalone',
|
|
92
|
+
* host: 'localhost',
|
|
93
|
+
* port: 6379,
|
|
94
|
+
* });
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
/**
|
|
98
|
+
* Returns the Redis topology mode of this client.
|
|
99
|
+
*
|
|
100
|
+
* @returns `'standalone'`, `'sentinel'`, or `'cluster'`
|
|
101
|
+
*/
|
|
78
102
|
get mode(): RedisMode;
|
|
103
|
+
/**
|
|
104
|
+
* Returns the shared Cache instance (lazily created on first access).
|
|
105
|
+
*
|
|
106
|
+
* The Cache provides JSON serialization, optional gzip compression, namespace
|
|
107
|
+
* support, TTLs, hash helpers, and pattern-based cleanup. All multi-key
|
|
108
|
+
* operations are slot-aware and cluster-safe.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* const cache = client.cache;
|
|
113
|
+
* await cache.set('user:1', { name: 'alice' });
|
|
114
|
+
* const user = await cache.get('user:1');
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
79
117
|
get cache(): Cache;
|
|
80
118
|
withCache(value: CacheInputConfig): RedisClientWrapper;
|
|
119
|
+
/**
|
|
120
|
+
* Returns the shared Pub/Sub instance (lazily created on first access).
|
|
121
|
+
*
|
|
122
|
+
* Publishing works immediately; subscribing requires calling {@link connectSubscriber}
|
|
123
|
+
* first. Messages are JSON-serialized on publish and auto-parsed on delivery.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* const pubsub = client.pubsub;
|
|
128
|
+
* await pubsub.connectSubscriber({ mode: 'standalone', host: 'localhost', port: 6379 });
|
|
129
|
+
* await pubsub.subscribe('orders:created', (message) => {
|
|
130
|
+
* console.log(message); // { id: 1 }
|
|
131
|
+
* });
|
|
132
|
+
* await pubsub.publish('orders:created', { id: 1 });
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
81
135
|
get pubsub(): PubSub;
|
|
136
|
+
/**
|
|
137
|
+
* Returns the shared DistributedLock instance (lazily created on first access).
|
|
138
|
+
*
|
|
139
|
+
* Provides atomic distributed mutual-exclusion locks backed by Redis. Works in
|
|
140
|
+
* standalone, sentinel, and cluster modes. Acquisition uses atomic `SET ... PX NX`;
|
|
141
|
+
* release and extension use Lua scripts so only the lock owner can release or extend.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* const lock = client.lock;
|
|
146
|
+
* const acquired = await lock.acquire('order:42');
|
|
147
|
+
* if (acquired) {
|
|
148
|
+
* try {
|
|
149
|
+
* // critical section
|
|
150
|
+
* } finally {
|
|
151
|
+
* await lock.release('order:42');
|
|
152
|
+
* }
|
|
153
|
+
* }
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
82
156
|
get lock(): DistributedLock;
|
|
83
157
|
withLock(value: DistributedLockInputOptions): RedisClientWrapper;
|
|
158
|
+
/**
|
|
159
|
+
* Returns the shared RateLimiter instance (lazily created on first access).
|
|
160
|
+
*
|
|
161
|
+
* Generic rate limiting for any resource — routes, API endpoints, users, IPs,
|
|
162
|
+
* API keys, database writes, email sends, webhooks. Supports fixed and sliding
|
|
163
|
+
* window algorithms. Fails open when Redis is unavailable.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```ts
|
|
167
|
+
* const limiter = client.rateLimiter;
|
|
168
|
+
* const result = await limiter.consume('/api/login', 'ip-10.0.0.1');
|
|
169
|
+
* if (!result.allowed) {
|
|
170
|
+
* // HTTP 429, set Retry-After: result.retryAfter
|
|
171
|
+
* }
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
84
174
|
get rateLimiter(): RateLimiter;
|
|
85
175
|
withRateLimiter(value: RateLimitOptionsInput): RedisClientWrapper;
|
|
86
176
|
get revocationStore(): RedisRevocationStore;
|
|
177
|
+
/**
|
|
178
|
+
* Returns the shared SessionManager instance (lazily created on first access).
|
|
179
|
+
*
|
|
180
|
+
* The production session stack: validation with fail-closed semantics, rotation
|
|
181
|
+
* with retry-safe idempotency, throttled touches, idle/absolute expiry, per-user
|
|
182
|
+
* eviction ceilings, security versioning, optional AES-256-GCM encryption at rest,
|
|
183
|
+
* fail-closed circuit breaker, metrics and health. Cluster-safe by construction.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```ts
|
|
187
|
+
* const manager = client.session;
|
|
188
|
+
* const { token, session } = await manager.service.create({ userId: 'user-42' });
|
|
189
|
+
* const result = await manager.service.validate(token, { userId: 'user-42' });
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
87
192
|
get session(): SessionManager;
|
|
88
193
|
withSession(value: WithSessionManagerOptions): RedisClientWrapper;
|
|
89
194
|
private createClient;
|
package/dist/client.js
CHANGED
|
@@ -81,15 +81,53 @@ export class RedisClientWrapper {
|
|
|
81
81
|
this.client = this.createClient();
|
|
82
82
|
this.setupEventHandlers();
|
|
83
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Creates a new Redis client wrapper supporting standalone, sentinel, and cluster topologies.
|
|
86
|
+
*
|
|
87
|
+
* The client automatically adapts to the configured Redis topology and lazily creates
|
|
88
|
+
* and shares sub-components: `cache`, `pubsub`, `lock`, `rateLimiter`, and `session`.
|
|
89
|
+
*
|
|
90
|
+
* @param config - Redis configuration (validated with Zod `RedisConfigSchema`).
|
|
91
|
+
* Must include `mode` (standalone/sentinel/cluster), and topology-specific fields.
|
|
92
|
+
* @param logger - Optional pino-compatible logger; defaults to `console`.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* const client = new RedisClientWrapper({
|
|
97
|
+
* mode: 'standalone',
|
|
98
|
+
* host: 'localhost',
|
|
99
|
+
* port: 6379,
|
|
100
|
+
* });
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
84
103
|
// ========================================================================
|
|
85
104
|
// Configuration
|
|
86
105
|
// ========================================================================
|
|
106
|
+
/**
|
|
107
|
+
* Returns the Redis topology mode of this client.
|
|
108
|
+
*
|
|
109
|
+
* @returns `'standalone'`, `'sentinel'`, or `'cluster'`
|
|
110
|
+
*/
|
|
87
111
|
get mode() {
|
|
88
112
|
return this.config.mode;
|
|
89
113
|
}
|
|
90
114
|
// ========================================================================
|
|
91
115
|
// Cache
|
|
92
116
|
// ========================================================================
|
|
117
|
+
/**
|
|
118
|
+
* Returns the shared Cache instance (lazily created on first access).
|
|
119
|
+
*
|
|
120
|
+
* The Cache provides JSON serialization, optional gzip compression, namespace
|
|
121
|
+
* support, TTLs, hash helpers, and pattern-based cleanup. All multi-key
|
|
122
|
+
* operations are slot-aware and cluster-safe.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* const cache = client.cache;
|
|
127
|
+
* await cache.set('user:1', { name: 'alice' });
|
|
128
|
+
* const user = await cache.get('user:1');
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
93
131
|
get cache() {
|
|
94
132
|
if (!this._cache) {
|
|
95
133
|
this._cache = new Cache(this, this.config.cacheOptions ?? {}, this.logger);
|
|
@@ -114,6 +152,22 @@ export class RedisClientWrapper {
|
|
|
114
152
|
// ========================================================================
|
|
115
153
|
// Pub/Sub
|
|
116
154
|
// ========================================================================
|
|
155
|
+
/**
|
|
156
|
+
* Returns the shared Pub/Sub instance (lazily created on first access).
|
|
157
|
+
*
|
|
158
|
+
* Publishing works immediately; subscribing requires calling {@link connectSubscriber}
|
|
159
|
+
* first. Messages are JSON-serialized on publish and auto-parsed on delivery.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```ts
|
|
163
|
+
* const pubsub = client.pubsub;
|
|
164
|
+
* await pubsub.connectSubscriber({ mode: 'standalone', host: 'localhost', port: 6379 });
|
|
165
|
+
* await pubsub.subscribe('orders:created', (message) => {
|
|
166
|
+
* console.log(message); // { id: 1 }
|
|
167
|
+
* });
|
|
168
|
+
* await pubsub.publish('orders:created', { id: 1 });
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
117
171
|
get pubsub() {
|
|
118
172
|
if (!this._pubsub) {
|
|
119
173
|
this._pubsub = new PubSub(this, this.logger);
|
|
@@ -123,6 +177,26 @@ export class RedisClientWrapper {
|
|
|
123
177
|
// ========================================================================
|
|
124
178
|
// Distributed Lock
|
|
125
179
|
// ========================================================================
|
|
180
|
+
/**
|
|
181
|
+
* Returns the shared DistributedLock instance (lazily created on first access).
|
|
182
|
+
*
|
|
183
|
+
* Provides atomic distributed mutual-exclusion locks backed by Redis. Works in
|
|
184
|
+
* standalone, sentinel, and cluster modes. Acquisition uses atomic `SET ... PX NX`;
|
|
185
|
+
* release and extension use Lua scripts so only the lock owner can release or extend.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```ts
|
|
189
|
+
* const lock = client.lock;
|
|
190
|
+
* const acquired = await lock.acquire('order:42');
|
|
191
|
+
* if (acquired) {
|
|
192
|
+
* try {
|
|
193
|
+
* // critical section
|
|
194
|
+
* } finally {
|
|
195
|
+
* await lock.release('order:42');
|
|
196
|
+
* }
|
|
197
|
+
* }
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
126
200
|
get lock() {
|
|
127
201
|
if (!this._lock) {
|
|
128
202
|
this._lock = new DistributedLock(this, this.logger, this.config.lockOptions);
|
|
@@ -147,6 +221,22 @@ export class RedisClientWrapper {
|
|
|
147
221
|
// ========================================================================
|
|
148
222
|
// Rate Limiter
|
|
149
223
|
// ========================================================================
|
|
224
|
+
/**
|
|
225
|
+
* Returns the shared RateLimiter instance (lazily created on first access).
|
|
226
|
+
*
|
|
227
|
+
* Generic rate limiting for any resource — routes, API endpoints, users, IPs,
|
|
228
|
+
* API keys, database writes, email sends, webhooks. Supports fixed and sliding
|
|
229
|
+
* window algorithms. Fails open when Redis is unavailable.
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```ts
|
|
233
|
+
* const limiter = client.rateLimiter;
|
|
234
|
+
* const result = await limiter.consume('/api/login', 'ip-10.0.0.1');
|
|
235
|
+
* if (!result.allowed) {
|
|
236
|
+
* // HTTP 429, set Retry-After: result.retryAfter
|
|
237
|
+
* }
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
150
240
|
get rateLimiter() {
|
|
151
241
|
if (!this._rateLimiter) {
|
|
152
242
|
this._rateLimiter = new RateLimiter(this, this.config.rateLimit);
|
|
@@ -181,6 +271,21 @@ export class RedisClientWrapper {
|
|
|
181
271
|
// ========================================================================
|
|
182
272
|
// Session
|
|
183
273
|
// ========================================================================
|
|
274
|
+
/**
|
|
275
|
+
* Returns the shared SessionManager instance (lazily created on first access).
|
|
276
|
+
*
|
|
277
|
+
* The production session stack: validation with fail-closed semantics, rotation
|
|
278
|
+
* with retry-safe idempotency, throttled touches, idle/absolute expiry, per-user
|
|
279
|
+
* eviction ceilings, security versioning, optional AES-256-GCM encryption at rest,
|
|
280
|
+
* fail-closed circuit breaker, metrics and health. Cluster-safe by construction.
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```ts
|
|
284
|
+
* const manager = client.session;
|
|
285
|
+
* const { token, session } = await manager.service.create({ userId: 'user-42' });
|
|
286
|
+
* const result = await manager.service.validate(token, { userId: 'user-42' });
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
184
289
|
get session() {
|
|
185
290
|
if (!this._session) {
|
|
186
291
|
const options = this.config.sessionOptions ?? {};
|
package/dist/health.d.ts
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { RedisClientWrapper } from './client.js';
|
|
2
2
|
import { LoggerLike } from './logger.js';
|
|
3
3
|
export interface HealthStatus {
|
|
4
|
+
/** Whether the system is healthy. */
|
|
4
5
|
healthy: boolean;
|
|
6
|
+
/** Current status label. */
|
|
5
7
|
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
8
|
+
/** Latency of the last ping check in milliseconds. */
|
|
6
9
|
latency: number;
|
|
10
|
+
/** Timestamp of when the status was recorded. */
|
|
7
11
|
timestamp: Date;
|
|
12
|
+
/** Additional details about the health check. */
|
|
8
13
|
details: {
|
|
14
|
+
/** Whether a PING command succeeded. */
|
|
9
15
|
ping: boolean;
|
|
16
|
+
/** Number of connected clients (when available). */
|
|
10
17
|
connections?: number;
|
|
18
|
+
/** Memory usage information (when available). */
|
|
11
19
|
memory?: string;
|
|
12
20
|
};
|
|
13
21
|
}
|
|
@@ -17,9 +25,82 @@ export declare class HealthChecker {
|
|
|
17
25
|
private timer;
|
|
18
26
|
private callbacks;
|
|
19
27
|
private lastStatus;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a health checker instance.
|
|
30
|
+
*
|
|
31
|
+
* @param client - The underlying {@link RedisClientWrapper}.
|
|
32
|
+
* @param logger - Optional pino-compatible logger; defaults to `console`.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* const health = new HealthChecker(client);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
20
39
|
constructor(client: RedisClientWrapper, logger?: LoggerLike);
|
|
40
|
+
/**
|
|
41
|
+
* Starts periodic health checks.
|
|
42
|
+
*
|
|
43
|
+
* **Behavior:**
|
|
44
|
+
* - If a timer is already running, it is cleared and replaced with the new interval.
|
|
45
|
+
* - Health checks run at the specified `interval` in milliseconds.
|
|
46
|
+
* - Each check runs asynchronously; errors are logged but do not stop the interval.
|
|
47
|
+
* - The first check runs immediately when `start()` is called (depending on setInterval timing).
|
|
48
|
+
*
|
|
49
|
+
* **Parameters:**
|
|
50
|
+
* - `interval` - Check interval in milliseconds. Default: `10000` (10 seconds).
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* // Check every 5 seconds
|
|
55
|
+
* health.start(5000);
|
|
56
|
+
*
|
|
57
|
+
* // Check every 30 seconds (default)
|
|
58
|
+
* health.start();
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* @returns `void`
|
|
62
|
+
*/
|
|
21
63
|
start(interval?: number): void;
|
|
64
|
+
/**
|
|
65
|
+
* Stops the health checker.
|
|
66
|
+
*
|
|
67
|
+
* **Behavior:**
|
|
68
|
+
* - Clears the internal timer, stopping further health checks.
|
|
69
|
+
* - Logs a warning if no timer was active.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* health.stop();
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @returns `void`
|
|
77
|
+
*/
|
|
22
78
|
stop(): void;
|
|
79
|
+
/**
|
|
80
|
+
* Runs a single health check.
|
|
81
|
+
*
|
|
82
|
+
* **Behavior:**
|
|
83
|
+
* - Performs a PING command to verify Redis connectivity.
|
|
84
|
+
* - Attempts to fetch Redis INFO for additional details (connections, memory).
|
|
85
|
+
* In cluster mode, INFO may not be available and is silently ignored.
|
|
86
|
+
* - Measures latency of the PING command.
|
|
87
|
+
* - Updates the internal `lastStatus` and notifies all registered callbacks.
|
|
88
|
+
*
|
|
89
|
+
* **Returns:**
|
|
90
|
+
* - A {@link HealthStatus} object with the current health state.
|
|
91
|
+
*
|
|
92
|
+
* **Example:**
|
|
93
|
+
* ```ts
|
|
94
|
+
* const status = await health.check();
|
|
95
|
+
* console.log(status.healthy, status.latency);
|
|
96
|
+
* // healthy === true, latency === 1.2 (ms)
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* **Parameters:**
|
|
100
|
+
* - None
|
|
101
|
+
*
|
|
102
|
+
* @returns Current health status.
|
|
103
|
+
*/
|
|
23
104
|
check(): Promise<HealthStatus>;
|
|
24
105
|
/**
|
|
25
106
|
* Returns the most recent health check result.
|
|
@@ -32,8 +113,68 @@ export declare class HealthChecker {
|
|
|
32
113
|
* console.log(status?.healthy, status?.latency);
|
|
33
114
|
* ```
|
|
34
115
|
*/
|
|
116
|
+
/**
|
|
117
|
+
* Returns the most recent health check result.
|
|
118
|
+
*
|
|
119
|
+
* **Returns:**
|
|
120
|
+
* - The last {@link HealthStatus}, or `null` before the first check.
|
|
121
|
+
*
|
|
122
|
+
* **Example:**
|
|
123
|
+
* ```ts
|
|
124
|
+
* const status = health.getStatus();
|
|
125
|
+
* console.log(status?.healthy, status?.latency);
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* **Parameters:**
|
|
129
|
+
* - None
|
|
130
|
+
*
|
|
131
|
+
* @returns The last result, or `null`.
|
|
132
|
+
*/
|
|
35
133
|
getStatus(): HealthStatus | null;
|
|
134
|
+
/**
|
|
135
|
+
* Registers a callback for health status changes.
|
|
136
|
+
*
|
|
137
|
+
* **Behavior:**
|
|
138
|
+
* - The callback is invoked whenever a health check runs and the status changes.
|
|
139
|
+
* - Callbacks are invoked synchronously within the `check()` method.
|
|
140
|
+
* - Multiple callbacks can be registered; they are invoked in registration order.
|
|
141
|
+
*
|
|
142
|
+
* **Parameters:**
|
|
143
|
+
* - `callback` - A function receiving a {@link HealthStatus} object.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* health.onChange((status) => {
|
|
148
|
+
* console.log(`Health status: ${status.status}, latency: ${status.latency}ms`);
|
|
149
|
+
* });
|
|
150
|
+
* ```
|
|
151
|
+
*
|
|
152
|
+
* @returns `void`
|
|
153
|
+
*/
|
|
36
154
|
onChange(callback: (status: HealthStatus) => void): void;
|
|
37
155
|
private notifyCallbacks;
|
|
156
|
+
/**
|
|
157
|
+
* Waits until the Redis connection is healthy.
|
|
158
|
+
*
|
|
159
|
+
* **Behavior:**
|
|
160
|
+
* - Polls {@link check} at 1-second intervals.
|
|
161
|
+
* - Returns `true` as soon as `status.healthy` is `true`.
|
|
162
|
+
* - Returns `false` if the timeout is reached without becoming healthy.
|
|
163
|
+
*
|
|
164
|
+
* **Parameters:**
|
|
165
|
+
* - `timeout` - Maximum time to wait in milliseconds. Default: `30000` (30 seconds).
|
|
166
|
+
*
|
|
167
|
+
* **Returns:**
|
|
168
|
+
* - `true` if the connection became healthy within the timeout.
|
|
169
|
+
* - `false` if the timeout was reached without the connection becoming healthy.
|
|
170
|
+
*
|
|
171
|
+
* **Example:**
|
|
172
|
+
* ```ts
|
|
173
|
+
* const healthy = await health.waitForHealthy(10000);
|
|
174
|
+
* // healthy === true if Redis became healthy within 10 seconds
|
|
175
|
+
* ```
|
|
176
|
+
*
|
|
177
|
+
* @returns `true` if became healthy within timeout.
|
|
178
|
+
*/
|
|
38
179
|
waitForHealthy(timeout?: number): Promise<boolean>;
|
|
39
180
|
}
|
package/dist/health.js
CHANGED
|
@@ -5,10 +5,44 @@ export class HealthChecker {
|
|
|
5
5
|
timer = null;
|
|
6
6
|
callbacks = [];
|
|
7
7
|
lastStatus = null;
|
|
8
|
+
/**
|
|
9
|
+
* Creates a health checker instance.
|
|
10
|
+
*
|
|
11
|
+
* @param client - The underlying {@link RedisClientWrapper}.
|
|
12
|
+
* @param logger - Optional pino-compatible logger; defaults to `console`.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const health = new HealthChecker(client);
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
8
19
|
constructor(client, logger = defaultLogger) {
|
|
9
20
|
this.client = client;
|
|
10
21
|
this.logger = logger.child({ component: 'HealthChecker' });
|
|
11
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Starts periodic health checks.
|
|
25
|
+
*
|
|
26
|
+
* **Behavior:**
|
|
27
|
+
* - If a timer is already running, it is cleared and replaced with the new interval.
|
|
28
|
+
* - Health checks run at the specified `interval` in milliseconds.
|
|
29
|
+
* - Each check runs asynchronously; errors are logged but do not stop the interval.
|
|
30
|
+
* - The first check runs immediately when `start()` is called (depending on setInterval timing).
|
|
31
|
+
*
|
|
32
|
+
* **Parameters:**
|
|
33
|
+
* - `interval` - Check interval in milliseconds. Default: `10000` (10 seconds).
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* // Check every 5 seconds
|
|
38
|
+
* health.start(5000);
|
|
39
|
+
*
|
|
40
|
+
* // Check every 30 seconds (default)
|
|
41
|
+
* health.start();
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @returns `void`
|
|
45
|
+
*/
|
|
12
46
|
start(interval = 10000) {
|
|
13
47
|
if (this.timer) {
|
|
14
48
|
clearInterval(this.timer);
|
|
@@ -20,6 +54,20 @@ export class HealthChecker {
|
|
|
20
54
|
}, interval);
|
|
21
55
|
this.logger.info(`Health checker started (interval: ${interval}ms)`);
|
|
22
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Stops the health checker.
|
|
59
|
+
*
|
|
60
|
+
* **Behavior:**
|
|
61
|
+
* - Clears the internal timer, stopping further health checks.
|
|
62
|
+
* - Logs a warning if no timer was active.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* health.stop();
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @returns `void`
|
|
70
|
+
*/
|
|
23
71
|
stop() {
|
|
24
72
|
if (this.timer) {
|
|
25
73
|
clearInterval(this.timer);
|
|
@@ -27,6 +75,31 @@ export class HealthChecker {
|
|
|
27
75
|
this.logger.info('Health checker stopped');
|
|
28
76
|
}
|
|
29
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Runs a single health check.
|
|
80
|
+
*
|
|
81
|
+
* **Behavior:**
|
|
82
|
+
* - Performs a PING command to verify Redis connectivity.
|
|
83
|
+
* - Attempts to fetch Redis INFO for additional details (connections, memory).
|
|
84
|
+
* In cluster mode, INFO may not be available and is silently ignored.
|
|
85
|
+
* - Measures latency of the PING command.
|
|
86
|
+
* - Updates the internal `lastStatus` and notifies all registered callbacks.
|
|
87
|
+
*
|
|
88
|
+
* **Returns:**
|
|
89
|
+
* - A {@link HealthStatus} object with the current health state.
|
|
90
|
+
*
|
|
91
|
+
* **Example:**
|
|
92
|
+
* ```ts
|
|
93
|
+
* const status = await health.check();
|
|
94
|
+
* console.log(status.healthy, status.latency);
|
|
95
|
+
* // healthy === true, latency === 1.2 (ms)
|
|
96
|
+
* ```
|
|
97
|
+
*
|
|
98
|
+
* **Parameters:**
|
|
99
|
+
* - None
|
|
100
|
+
*
|
|
101
|
+
* @returns Current health status.
|
|
102
|
+
*/
|
|
30
103
|
async check() {
|
|
31
104
|
const start = Date.now();
|
|
32
105
|
const details = {
|
|
@@ -77,9 +150,46 @@ export class HealthChecker {
|
|
|
77
150
|
* console.log(status?.healthy, status?.latency);
|
|
78
151
|
* ```
|
|
79
152
|
*/
|
|
153
|
+
/**
|
|
154
|
+
* Returns the most recent health check result.
|
|
155
|
+
*
|
|
156
|
+
* **Returns:**
|
|
157
|
+
* - The last {@link HealthStatus}, or `null` before the first check.
|
|
158
|
+
*
|
|
159
|
+
* **Example:**
|
|
160
|
+
* ```ts
|
|
161
|
+
* const status = health.getStatus();
|
|
162
|
+
* console.log(status?.healthy, status?.latency);
|
|
163
|
+
* ```
|
|
164
|
+
*
|
|
165
|
+
* **Parameters:**
|
|
166
|
+
* - None
|
|
167
|
+
*
|
|
168
|
+
* @returns The last result, or `null`.
|
|
169
|
+
*/
|
|
80
170
|
getStatus() {
|
|
81
171
|
return this.lastStatus;
|
|
82
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Registers a callback for health status changes.
|
|
175
|
+
*
|
|
176
|
+
* **Behavior:**
|
|
177
|
+
* - The callback is invoked whenever a health check runs and the status changes.
|
|
178
|
+
* - Callbacks are invoked synchronously within the `check()` method.
|
|
179
|
+
* - Multiple callbacks can be registered; they are invoked in registration order.
|
|
180
|
+
*
|
|
181
|
+
* **Parameters:**
|
|
182
|
+
* - `callback` - A function receiving a {@link HealthStatus} object.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* health.onChange((status) => {
|
|
187
|
+
* console.log(`Health status: ${status.status}, latency: ${status.latency}ms`);
|
|
188
|
+
* });
|
|
189
|
+
* ```
|
|
190
|
+
*
|
|
191
|
+
* @returns `void`
|
|
192
|
+
*/
|
|
83
193
|
onChange(callback) {
|
|
84
194
|
this.callbacks.push(callback);
|
|
85
195
|
}
|
|
@@ -93,6 +203,29 @@ export class HealthChecker {
|
|
|
93
203
|
}
|
|
94
204
|
}
|
|
95
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Waits until the Redis connection is healthy.
|
|
208
|
+
*
|
|
209
|
+
* **Behavior:**
|
|
210
|
+
* - Polls {@link check} at 1-second intervals.
|
|
211
|
+
* - Returns `true` as soon as `status.healthy` is `true`.
|
|
212
|
+
* - Returns `false` if the timeout is reached without becoming healthy.
|
|
213
|
+
*
|
|
214
|
+
* **Parameters:**
|
|
215
|
+
* - `timeout` - Maximum time to wait in milliseconds. Default: `30000` (30 seconds).
|
|
216
|
+
*
|
|
217
|
+
* **Returns:**
|
|
218
|
+
* - `true` if the connection became healthy within the timeout.
|
|
219
|
+
* - `false` if the timeout was reached without the connection becoming healthy.
|
|
220
|
+
*
|
|
221
|
+
* **Example:**
|
|
222
|
+
* ```ts
|
|
223
|
+
* const healthy = await health.waitForHealthy(10000);
|
|
224
|
+
* // healthy === true if Redis became healthy within 10 seconds
|
|
225
|
+
* ```
|
|
226
|
+
*
|
|
227
|
+
* @returns `true` if became healthy within timeout.
|
|
228
|
+
*/
|
|
96
229
|
async waitForHealthy(timeout = 30000) {
|
|
97
230
|
const start = Date.now();
|
|
98
231
|
while (Date.now() - start < timeout) {
|