@keyv/redis 4.4.1 → 4.6.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/README.md CHANGED
@@ -26,9 +26,10 @@ Redis storage adapter for [Keyv](https://github.com/jaredwray/keyv).
26
26
  # Table of Contents
27
27
  * [Usage](#usage)
28
28
  * [Namespaces](#namespaces)
29
- * [Typescript](#typescript)
29
+ * [Using Generic Types](#using-generic-types)
30
30
  * [Performance Considerations](#performance-considerations)
31
31
  * [High Memory Usage on Redis Server](#high-memory-usage-on-redis-server)
32
+ * [Gracefully Handling Errors and Timeouts](#gracefully-handling-errors-and-timeouts)
32
33
  * [Using Cacheable with Redis](#using-cacheable-with-redis)
33
34
  * [Clustering and TLS Support](#clustering-and-tls-support)
34
35
  * [API](#api)
@@ -99,6 +100,87 @@ const keyvRedis = new KeyvRedis(redis);
99
100
  const keyv = new Keyv({ store: keyvRedis});
100
101
  ```
101
102
 
103
+ # Keyv Redis Options
104
+
105
+ You can pass in options to the `KeyvRedis` constructor. Here are the available options:
106
+
107
+ ```typescript
108
+ export type KeyvRedisOptions = {
109
+ /**
110
+ * Namespace for the current instance.
111
+ */
112
+ namespace?: string;
113
+ /**
114
+ * Separator to use between namespace and key.
115
+ */
116
+ keyPrefixSeparator?: string;
117
+ /**
118
+ * Number of keys to delete in a single batch.
119
+ */
120
+ clearBatchSize?: number;
121
+ /**
122
+ * Enable Unlink instead of using Del for clearing keys. This is more performant but may not be supported by all Redis versions.
123
+ */
124
+ useUnlink?: boolean;
125
+
126
+ /**
127
+ * Whether to allow clearing all keys when no namespace is set.
128
+ * If set to true and no namespace is set, iterate() will return all keys.
129
+ * Defaults to `false`.
130
+ */
131
+ noNamespaceAffectsAll?: boolean;
132
+
133
+ /**
134
+ * This is used to throw an error if the client is not connected when trying to connect. By default, this is
135
+ * set to true so that it throws an error when trying to connect to the Redis server fails.
136
+ */
137
+ throwOnConnectError?: boolean;
138
+
139
+ /**
140
+ * This is used to throw an error if at any point there is a failure. Use this if you want to
141
+ * ensure that all operations are successful and you want to handle errors. By default, this is
142
+ * set to false so that it does not throw an error on every operation and instead emits an error event
143
+ * and returns no-op responses.
144
+ * @default false
145
+ */
146
+ throwErrors?: boolean;
147
+
148
+ /**
149
+ * Timeout in milliseconds for the connection. Default is undefined, which uses the default timeout of the Redis client.
150
+ * If set, it will throw an error if the connection does not succeed within the specified time.
151
+ * @default undefined
152
+ */
153
+ connectionTimeout?: number;
154
+ };
155
+ ```
156
+ You can pass these options when creating a new `KeyvRedis` instance:
157
+
158
+ ```js
159
+ import Keyv from 'keyv';
160
+ import KeyvRedis from '@keyv/redis';
161
+
162
+ const keyvRedis = new KeyvRedis({
163
+ namespace: 'my-namespace',
164
+ keyPrefixSeparator: ':',
165
+ clearBatchSize: 1000,
166
+ useUnlink: true,
167
+ noNamespaceAffectsAll: false,
168
+ connectTimeout: 200
169
+ });
170
+
171
+ const keyv = new Keyv({ store: keyvRedis });
172
+ ```
173
+
174
+ You can also set these options after the fact by using the `KeyvRedis` instance properties:
175
+
176
+ ```js
177
+ import {createKeyv} from '@keyv/redis';
178
+
179
+ const keyv = createKeyv('redis://user:pass@localhost:6379');
180
+ keyv.store.namespace = 'my-namespace';
181
+ ```
182
+
183
+
102
184
  # Namespaces
103
185
 
104
186
  You can set a namespace for your keys. This is useful if you want to manage your keys in a more organized way. Here is an example of how to set a `namespace` with the `store` option:
@@ -120,7 +202,7 @@ keyv.namespace = 'my-namespace';
120
202
 
121
203
  NOTE: If you plan to do many clears or deletes, it is recommended to read the [Performance Considerations](#performance-considerations) section.
122
204
 
123
- ## Typescript
205
+ ## Using Generic Types
124
206
 
125
207
  When initializing `KeyvRedis`, you can specify the type of the values you are storing and you can also specify types when calling methods:
126
208
 
@@ -129,7 +211,7 @@ import Keyv from 'keyv';
129
211
  import KeyvRedis, { createClient } from '@keyv/redis';
130
212
 
131
213
 
132
- interface User {
214
+ type User {
133
215
  id: number
134
216
  name: string
135
217
  }
@@ -175,6 +257,40 @@ const keyv = new Keyv(new KeyvRedis('redis://user:pass@localhost:6379', { useUnl
175
257
  keyv.useUnlink = false;
176
258
  ```
177
259
 
260
+ # Gracefully Handling Errors and Timeouts
261
+
262
+ When using `@keyv/redis`, it is important to handle connection errors gracefully. You can do this by listening to the `error` event on the `KeyvRedis` instance. Here is an example of how to do that:
263
+
264
+ ```js
265
+ import Keyv from 'keyv';
266
+ import KeyvRedis from '@keyv/redis';
267
+ const keyv = new Keyv(new KeyvRedis('redis://user:pass@localhost:6379'));
268
+ keyv.on('error', (error) => {
269
+ console.error('error', error);
270
+ });
271
+ ```
272
+
273
+ By default, the `KeyvRedis` instance will `throw an error` if the connection fails to connect. You can disable this behavior by setting the `throwOnConnectError` option to `false` when creating the `KeyvRedis` instance:
274
+
275
+ On `get`, `getMany`, `set`, `setMany`, `delete`, and `deleteMany`, if the connection is lost, it will emit an error and return a no-op value. You can catch this error and handle it accordingly. This is important to ensure that your application does not crash due to a lost connection to Redis.
276
+
277
+ If you want to handle connection errors, retries, and timeouts more gracefully, you can use the `throwErrors` option. This will throw an error if any operation fails, allowing you to catch it and handle it accordingly:
278
+
279
+ There is a default `Reconnect Strategy` if you pass in just a `uri` connection string we will automatically create a Redis client for you with the following reconnect strategy:
280
+
281
+ ```typescript
282
+ export const defaultReconnectStrategy = (attempts: number): number | Error => {
283
+ // Exponential backoff base: double each time, capped at 2s.
284
+ // Parentheses make it clear we do (2 ** attempts) first, then * 100
285
+ const backoff = Math.min((2 ** attempts) * 100, 2000);
286
+
287
+ // Add random jitter of up to ±50ms to avoid thundering herds:
288
+ const jitter = (Math.random() - 0.5) * 100;
289
+
290
+ return backoff + jitter;
291
+ };
292
+ ```
293
+
178
294
  # Using Cacheable with Redis
179
295
 
180
296
  If you are wanting to see even better performance with Redis, you can use [Cacheable](https://npmjs.org/package/cacheable) which is a multi-layered cache library that has in-memory primary caching and non-blocking secondary caching. Here is an example of how to use it with Redis:
package/dist/index.cjs CHANGED
@@ -31,25 +31,39 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  Keyv: () => import_keyv2.Keyv,
34
+ RedisErrorMessages: () => RedisErrorMessages,
34
35
  createClient: () => import_client2.createClient,
35
36
  createCluster: () => import_client2.createCluster,
36
37
  createKeyv: () => createKeyv,
37
- default: () => KeyvRedis
38
+ default: () => KeyvRedis,
39
+ defaultReconnectStrategy: () => defaultReconnectStrategy
38
40
  });
39
41
  module.exports = __toCommonJS(index_exports);
40
- var import_node_events = __toESM(require("events"), 1);
41
42
  var import_client = require("@redis/client");
43
+ var import_hookified = require("hookified");
42
44
  var import_keyv = require("keyv");
43
45
  var import_cluster_key_slot = __toESM(require("cluster-key-slot"), 1);
44
46
  var import_client2 = require("@redis/client");
45
47
  var import_keyv2 = require("keyv");
46
- var KeyvRedis = class extends import_node_events.default {
48
+ var RedisErrorMessages = /* @__PURE__ */ ((RedisErrorMessages2) => {
49
+ RedisErrorMessages2["RedisClientNotConnectedThrown"] = "Redis client is not connected or has failed to connect. This is thrown because throwOnConnectError is set to true.";
50
+ return RedisErrorMessages2;
51
+ })(RedisErrorMessages || {});
52
+ var defaultReconnectStrategy = (attempts) => {
53
+ const backoff = Math.min(2 ** attempts * 100, 2e3);
54
+ const jitter = (Math.random() - 0.5) * 100;
55
+ return backoff + jitter;
56
+ };
57
+ var KeyvRedis = class extends import_hookified.Hookified {
47
58
  _client = (0, import_client.createClient)();
48
59
  _namespace;
49
60
  _keyPrefixSeparator = "::";
50
61
  _clearBatchSize = 1e3;
51
62
  _useUnlink = true;
52
63
  _noNamespaceAffectsAll = false;
64
+ _throwOnConnectError = true;
65
+ _throwErrors = false;
66
+ _connectionTimeout;
53
67
  /**
54
68
  * KeyvRedis constructor.
55
69
  * @param {string | RedisClientOptions | RedisClientType} [connect] How to connect to the Redis server. If string pass in the url, if object pass in the options, if RedisClient pass in the client.
@@ -57,9 +71,13 @@ var KeyvRedis = class extends import_node_events.default {
57
71
  */
58
72
  constructor(connect, options) {
59
73
  super();
74
+ const socket = {
75
+ reconnectStrategy: defaultReconnectStrategy
76
+ // Default timeout for the connection
77
+ };
60
78
  if (connect) {
61
79
  if (typeof connect === "string") {
62
- this._client = (0, import_client.createClient)({ url: connect });
80
+ this._client = (0, import_client.createClient)({ url: connect, socket });
63
81
  } else if (connect.connect !== void 0) {
64
82
  this._client = this.isClientCluster(connect) ? connect : connect;
65
83
  } else if (connect instanceof Object) {
@@ -98,6 +116,10 @@ var KeyvRedis = class extends import_node_events.default {
98
116
  keyPrefixSeparator: this._keyPrefixSeparator,
99
117
  clearBatchSize: this._clearBatchSize,
100
118
  noNamespaceAffectsAll: this._noNamespaceAffectsAll,
119
+ useUnlink: this._useUnlink,
120
+ throwOnConnectError: this._throwOnConnectError,
121
+ throwErrors: this._throwErrors,
122
+ connectionTimeout: this._connectionTimeout,
101
123
  dialect: "redis",
102
124
  url
103
125
  };
@@ -180,17 +202,82 @@ var KeyvRedis = class extends import_node_events.default {
180
202
  set noNamespaceAffectsAll(value) {
181
203
  this._noNamespaceAffectsAll = value;
182
204
  }
205
+ /**
206
+ * Get if throwOnConnectError is set to true.
207
+ * This is used to throw an error if the client is not connected when trying to connect. By default, this is
208
+ * set to true so that it throws an error when trying to connect to the Redis server fails.
209
+ * @default true
210
+ */
211
+ get throwOnConnectError() {
212
+ return this._throwOnConnectError;
213
+ }
214
+ /**
215
+ * Set if throwOnConnectError is set to true.
216
+ * This is used to throw an error if the client is not connected when trying to connect. By default, this is
217
+ * set to true so that it throws an error when trying to connect to the Redis server fails.
218
+ */
219
+ set throwOnConnectError(value) {
220
+ this._throwOnConnectError = value;
221
+ }
222
+ /**
223
+ * Get if throwErrors is set to true.
224
+ * This is used to throw an error if at any point there is a failure. Use this if you want to
225
+ * ensure that all operations are successful and you want to handle errors. By default, this is
226
+ * set to false so that it does not throw an error on every operation and instead emits an error event
227
+ * and returns no-op responses.
228
+ * @default false
229
+ */
230
+ get throwErrors() {
231
+ return this._throwErrors;
232
+ }
233
+ /**
234
+ * Set if throwErrors is set to true.
235
+ * This is used to throw an error if at any point there is a failure. Use this if you want to
236
+ * ensure that all operations are successful and you want to handle errors. By default, this is
237
+ * set to false so that it does not throw an error on every operation and instead emits an error event
238
+ * and returns no-op responses.
239
+ */
240
+ set throwErrors(value) {
241
+ this._throwErrors = value;
242
+ }
243
+ /**
244
+ * Get the connection timeout in milliseconds such as 5000 (5 seconds). Default is undefined. If undefined, it will use the default.
245
+ * @default undefined
246
+ */
247
+ get connectionTimeout() {
248
+ return this._connectionTimeout;
249
+ }
250
+ /**
251
+ * Set the connection timeout in milliseconds such as 5000 (5 seconds). Default is undefined. If undefined, it will use the default.
252
+ * @default undefined
253
+ */
254
+ set connectionTimeout(value) {
255
+ this._connectionTimeout = value;
256
+ }
183
257
  /**
184
258
  * Get the Redis URL used to connect to the server. This is used to get a connected client.
185
259
  */
186
260
  async getClient() {
261
+ if (this._client.isOpen) {
262
+ return this._client;
263
+ }
187
264
  try {
188
- if (!this._client.isOpen) {
265
+ if (this._connectionTimeout === void 0) {
189
266
  await this._client.connect();
267
+ } else {
268
+ await Promise.race([
269
+ this._client.connect(),
270
+ this.createTimeoutPromise(this._connectionTimeout)
271
+ ]);
190
272
  }
191
273
  } catch (error) {
192
274
  this.emit("error", error);
275
+ if (this._throwOnConnectError) {
276
+ throw new Error("Redis client is not connected or has failed to connect. This is thrown because throwOnConnectError is set to true." /* RedisClientNotConnectedThrown */);
277
+ }
278
+ await this.disconnect(true);
193
279
  }
280
+ this.initClient();
194
281
  return this._client;
195
282
  }
196
283
  /**
@@ -201,11 +288,18 @@ var KeyvRedis = class extends import_node_events.default {
201
288
  */
202
289
  async set(key, value, ttl) {
203
290
  const client = await this.getClient();
204
- key = this.createKeyPrefix(key, this._namespace);
205
- if (ttl) {
206
- await client.set(key, value, { PX: ttl });
207
- } else {
208
- await client.set(key, value);
291
+ try {
292
+ key = this.createKeyPrefix(key, this._namespace);
293
+ if (ttl) {
294
+ await client.set(key, value, { PX: ttl });
295
+ } else {
296
+ await client.set(key, value);
297
+ }
298
+ } catch (error) {
299
+ this.emit("error", error);
300
+ if (this._throwErrors) {
301
+ throw error;
302
+ }
209
303
  }
210
304
  }
211
305
  /**
@@ -214,16 +308,23 @@ var KeyvRedis = class extends import_node_events.default {
214
308
  */
215
309
  async setMany(entries) {
216
310
  const client = await this.getClient();
217
- const multi = client.multi();
218
- for (const { key, value, ttl } of entries) {
219
- const prefixedKey = this.createKeyPrefix(key, this._namespace);
220
- if (ttl) {
221
- multi.set(prefixedKey, value, { PX: ttl });
222
- } else {
223
- multi.set(prefixedKey, value);
311
+ try {
312
+ const multi = client.multi();
313
+ for (const { key, value, ttl } of entries) {
314
+ const prefixedKey = this.createKeyPrefix(key, this._namespace);
315
+ if (ttl) {
316
+ multi.set(prefixedKey, value, { PX: ttl });
317
+ } else {
318
+ multi.set(prefixedKey, value);
319
+ }
320
+ }
321
+ await multi.exec();
322
+ } catch (error) {
323
+ this.emit("error", error);
324
+ if (this._throwErrors) {
325
+ throw error;
224
326
  }
225
327
  }
226
- await multi.exec();
227
328
  }
228
329
  /**
229
330
  * Check if a key exists in the store.
@@ -232,9 +333,17 @@ var KeyvRedis = class extends import_node_events.default {
232
333
  */
233
334
  async has(key) {
234
335
  const client = await this.getClient();
235
- key = this.createKeyPrefix(key, this._namespace);
236
- const exists = await client.exists(key);
237
- return exists === 1;
336
+ try {
337
+ key = this.createKeyPrefix(key, this._namespace);
338
+ const exists = await client.exists(key);
339
+ return exists === 1;
340
+ } catch (error) {
341
+ this.emit("error", error);
342
+ if (this._throwErrors) {
343
+ throw error;
344
+ }
345
+ return false;
346
+ }
238
347
  }
239
348
  /**
240
349
  * Check if many keys exist in the store. This will be done as a single transaction.
@@ -243,13 +352,21 @@ var KeyvRedis = class extends import_node_events.default {
243
352
  */
244
353
  async hasMany(keys) {
245
354
  const client = await this.getClient();
246
- const multi = client.multi();
247
- for (const key of keys) {
248
- const prefixedKey = this.createKeyPrefix(key, this._namespace);
249
- multi.exists(prefixedKey);
355
+ try {
356
+ const multi = client.multi();
357
+ for (const key of keys) {
358
+ const prefixedKey = this.createKeyPrefix(key, this._namespace);
359
+ multi.exists(prefixedKey);
360
+ }
361
+ const results = await multi.exec();
362
+ return results.map((result) => result === 1);
363
+ } catch (error) {
364
+ this.emit("error", error);
365
+ if (this._throwErrors) {
366
+ throw error;
367
+ }
368
+ return Array.from({ length: keys.length }).fill(false);
250
369
  }
251
- const results = await multi.exec();
252
- return results.map((result) => result === 1);
253
370
  }
254
371
  /**
255
372
  * Get a value from the store. If the key does not exist, it will return undefined.
@@ -258,12 +375,20 @@ var KeyvRedis = class extends import_node_events.default {
258
375
  */
259
376
  async get(key) {
260
377
  const client = await this.getClient();
261
- key = this.createKeyPrefix(key, this._namespace);
262
- const value = await client.get(key);
263
- if (value === null) {
378
+ try {
379
+ key = this.createKeyPrefix(key, this._namespace);
380
+ const value = await client.get(key);
381
+ if (value === null) {
382
+ return void 0;
383
+ }
384
+ return value;
385
+ } catch (error) {
386
+ this.emit("error", error);
387
+ if (this._throwErrors) {
388
+ throw error;
389
+ }
264
390
  return void 0;
265
391
  }
266
- return value;
267
392
  }
268
393
  /**
269
394
  * Get many values from the store. If a key does not exist, it will return undefined.
@@ -275,8 +400,16 @@ var KeyvRedis = class extends import_node_events.default {
275
400
  return [];
276
401
  }
277
402
  keys = keys.map((key) => this.createKeyPrefix(key, this._namespace));
278
- const values = await this.mget(keys);
279
- return values;
403
+ try {
404
+ const values = await this.mget(keys);
405
+ return values;
406
+ } catch (error) {
407
+ this.emit("error", error);
408
+ if (this._throwErrors) {
409
+ throw error;
410
+ }
411
+ return Array.from({ length: keys.length }).fill(void 0);
412
+ }
280
413
  }
281
414
  /**
282
415
  * Delete a key from the store.
@@ -285,10 +418,18 @@ var KeyvRedis = class extends import_node_events.default {
285
418
  */
286
419
  async delete(key) {
287
420
  const client = await this.getClient();
288
- key = this.createKeyPrefix(key, this._namespace);
289
- let deleted = 0;
290
- deleted = await (this._useUnlink ? client.unlink(key) : client.del(key));
291
- return deleted > 0;
421
+ try {
422
+ key = this.createKeyPrefix(key, this._namespace);
423
+ let deleted = 0;
424
+ deleted = await (this._useUnlink ? client.unlink(key) : client.del(key));
425
+ return deleted > 0;
426
+ } catch (error) {
427
+ this.emit("error", error);
428
+ if (this._throwErrors) {
429
+ throw error;
430
+ }
431
+ return false;
432
+ }
292
433
  }
293
434
  /**
294
435
  * Delete many keys from the store. This will be done as a single transaction.
@@ -298,19 +439,26 @@ var KeyvRedis = class extends import_node_events.default {
298
439
  async deleteMany(keys) {
299
440
  let result = false;
300
441
  const client = await this.getClient();
301
- const multi = client.multi();
302
- for (const key of keys) {
303
- const prefixedKey = this.createKeyPrefix(key, this._namespace);
304
- if (this._useUnlink) {
305
- multi.unlink(prefixedKey);
306
- } else {
307
- multi.del(prefixedKey);
442
+ try {
443
+ const multi = client.multi();
444
+ for (const key of keys) {
445
+ const prefixedKey = this.createKeyPrefix(key, this._namespace);
446
+ if (this._useUnlink) {
447
+ multi.unlink(prefixedKey);
448
+ } else {
449
+ multi.del(prefixedKey);
450
+ }
308
451
  }
309
- }
310
- const results = await multi.exec();
311
- for (const deleted of results) {
312
- if (typeof deleted === "number" && deleted > 0) {
313
- result = true;
452
+ const results = await multi.exec();
453
+ for (const deleted of results) {
454
+ if (typeof deleted === "number" && deleted > 0) {
455
+ result = true;
456
+ }
457
+ }
458
+ } catch (error) {
459
+ this.emit("error", error);
460
+ if (this._throwErrors) {
461
+ throw error;
314
462
  }
315
463
  }
316
464
  return result;
@@ -520,11 +668,37 @@ var KeyvRedis = class extends import_node_events.default {
520
668
  if (options.noNamespaceAffectsAll !== void 0) {
521
669
  this._noNamespaceAffectsAll = options.noNamespaceAffectsAll;
522
670
  }
671
+ if (options.throwOnConnectError !== void 0) {
672
+ this._throwOnConnectError = options.throwOnConnectError;
673
+ }
674
+ if (options.throwErrors !== void 0) {
675
+ this._throwErrors = options.throwErrors;
676
+ }
677
+ if (options.connectionTimeout !== void 0) {
678
+ this._connectionTimeout = options.connectionTimeout;
679
+ }
523
680
  }
524
681
  initClient() {
525
- this._client.on("error", (error) => {
526
- this.emit("error", error);
682
+ this._client.on("connect", () => {
683
+ this.emit("connect", this._client);
684
+ });
685
+ this._client.on("disconnect", () => {
686
+ this.emit("disconnect", this._client);
527
687
  });
688
+ this._client.on("reconnecting", (reconnectInfo) => {
689
+ this.emit("reconnecting", reconnectInfo);
690
+ });
691
+ }
692
+ async createTimeoutPromise(timeoutMs) {
693
+ return new Promise((_, reject) => (
694
+ // eslint-disable-next-line no-promise-executor-return
695
+ setTimeout(
696
+ () => {
697
+ reject(new Error(`Redis timed out after ${timeoutMs}ms`));
698
+ },
699
+ timeoutMs
700
+ )
701
+ ));
528
702
  }
529
703
  };
530
704
  function createKeyv(connect, options) {
@@ -536,7 +710,9 @@ function createKeyv(connect, options) {
536
710
  // Annotate the CommonJS export names for ESM import in node:
537
711
  0 && (module.exports = {
538
712
  Keyv,
713
+ RedisErrorMessages,
539
714
  createClient,
540
715
  createCluster,
541
- createKeyv
716
+ createKeyv,
717
+ defaultReconnectStrategy
542
718
  });
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
- import EventEmitter from 'node:events';
2
1
  import { RedisClientOptions, RedisClusterOptions, RedisClientType, RedisClusterType, RedisModules, RedisFunctions, RedisScripts } from '@redis/client';
3
2
  export { RedisClientOptions, RedisClientType, RedisClusterOptions, RedisClusterType, createClient, createCluster } from '@redis/client';
3
+ import { Hookified } from 'hookified';
4
4
  import { KeyvStoreAdapter, KeyvEntry, Keyv } from 'keyv';
5
5
  export { Keyv } from 'keyv';
6
6
 
@@ -27,6 +27,25 @@ type KeyvRedisOptions = {
27
27
  * Defaults to `false`.
28
28
  */
29
29
  noNamespaceAffectsAll?: boolean;
30
+ /**
31
+ * This is used to throw an error if the client is not connected when trying to connect. By default, this is
32
+ * set to true so that it throws an error when trying to connect to the Redis server fails.
33
+ */
34
+ throwOnConnectError?: boolean;
35
+ /**
36
+ * This is used to throw an error if at any point there is a failure. Use this if you want to
37
+ * ensure that all operations are successful and you want to handle errors. By default, this is
38
+ * set to false so that it does not throw an error on every operation and instead emits an error event
39
+ * and returns no-op responses.
40
+ * @default false
41
+ */
42
+ throwErrors?: boolean;
43
+ /**
44
+ * Timeout in milliseconds for the connection. Default is undefined, which uses the default timeout of the Redis client.
45
+ * If set, it will throw an error if the connection does not succeed within the specified time.
46
+ * @default undefined
47
+ */
48
+ connectionTimeout?: number;
30
49
  };
31
50
  type KeyvRedisPropertyOptions = KeyvRedisOptions & {
32
51
  /**
@@ -52,14 +71,24 @@ type KeyvRedisEntry<T> = {
52
71
  */
53
72
  ttl?: number;
54
73
  };
74
+ declare enum RedisErrorMessages {
75
+ /**
76
+ * Error message when the Redis client is not connected and throwOnConnectError is set to true.
77
+ */
78
+ RedisClientNotConnectedThrown = "Redis client is not connected or has failed to connect. This is thrown because throwOnConnectError is set to true."
79
+ }
80
+ declare const defaultReconnectStrategy: (attempts: number) => number | Error;
55
81
  type RedisClientConnectionType = RedisClientType | RedisClusterType<RedisModules, RedisFunctions, RedisScripts>;
56
- declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
82
+ declare class KeyvRedis<T> extends Hookified implements KeyvStoreAdapter {
57
83
  private _client;
58
84
  private _namespace;
59
85
  private _keyPrefixSeparator;
60
86
  private _clearBatchSize;
61
87
  private _useUnlink;
62
88
  private _noNamespaceAffectsAll;
89
+ private _throwOnConnectError;
90
+ private _throwErrors;
91
+ private _connectionTimeout;
63
92
  /**
64
93
  * KeyvRedis constructor.
65
94
  * @param {string | RedisClientOptions | RedisClientType} [connect] How to connect to the Redis server. If string pass in the url, if object pass in the options, if RedisClient pass in the client.
@@ -129,6 +158,46 @@ declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
129
158
  * Set if not namespace affects all keys.
130
159
  */
131
160
  set noNamespaceAffectsAll(value: boolean);
161
+ /**
162
+ * Get if throwOnConnectError is set to true.
163
+ * This is used to throw an error if the client is not connected when trying to connect. By default, this is
164
+ * set to true so that it throws an error when trying to connect to the Redis server fails.
165
+ * @default true
166
+ */
167
+ get throwOnConnectError(): boolean;
168
+ /**
169
+ * Set if throwOnConnectError is set to true.
170
+ * This is used to throw an error if the client is not connected when trying to connect. By default, this is
171
+ * set to true so that it throws an error when trying to connect to the Redis server fails.
172
+ */
173
+ set throwOnConnectError(value: boolean);
174
+ /**
175
+ * Get if throwErrors is set to true.
176
+ * This is used to throw an error if at any point there is a failure. Use this if you want to
177
+ * ensure that all operations are successful and you want to handle errors. By default, this is
178
+ * set to false so that it does not throw an error on every operation and instead emits an error event
179
+ * and returns no-op responses.
180
+ * @default false
181
+ */
182
+ get throwErrors(): boolean;
183
+ /**
184
+ * Set if throwErrors is set to true.
185
+ * This is used to throw an error if at any point there is a failure. Use this if you want to
186
+ * ensure that all operations are successful and you want to handle errors. By default, this is
187
+ * set to false so that it does not throw an error on every operation and instead emits an error event
188
+ * and returns no-op responses.
189
+ */
190
+ set throwErrors(value: boolean);
191
+ /**
192
+ * Get the connection timeout in milliseconds such as 5000 (5 seconds). Default is undefined. If undefined, it will use the default.
193
+ * @default undefined
194
+ */
195
+ get connectionTimeout(): number | undefined;
196
+ /**
197
+ * Set the connection timeout in milliseconds such as 5000 (5 seconds). Default is undefined. If undefined, it will use the default.
198
+ * @default undefined
199
+ */
200
+ set connectionTimeout(value: number | undefined);
132
201
  /**
133
202
  * Get the Redis URL used to connect to the server. This is used to get a connected client.
134
203
  */
@@ -251,6 +320,7 @@ declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
251
320
  private isClientCluster;
252
321
  private setOptions;
253
322
  private initClient;
323
+ private createTimeoutPromise;
254
324
  }
255
325
  /**
256
326
  * Will create a Keyv instance with the Redis adapter. This will also set the namespace and useKeyPrefix to false.
@@ -260,4 +330,4 @@ declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
260
330
  */
261
331
  declare function createKeyv(connect?: string | RedisClientOptions | RedisClientType, options?: KeyvRedisOptions): Keyv;
262
332
 
263
- export { type KeyvRedisEntry, type KeyvRedisOptions, type KeyvRedisPropertyOptions, type RedisClientConnectionType, createKeyv, KeyvRedis as default };
333
+ export { type KeyvRedisEntry, type KeyvRedisOptions, type KeyvRedisPropertyOptions, type RedisClientConnectionType, RedisErrorMessages, createKeyv, KeyvRedis as default, defaultReconnectStrategy };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import EventEmitter from 'node:events';
2
1
  import { RedisClientOptions, RedisClusterOptions, RedisClientType, RedisClusterType, RedisModules, RedisFunctions, RedisScripts } from '@redis/client';
3
2
  export { RedisClientOptions, RedisClientType, RedisClusterOptions, RedisClusterType, createClient, createCluster } from '@redis/client';
3
+ import { Hookified } from 'hookified';
4
4
  import { KeyvStoreAdapter, KeyvEntry, Keyv } from 'keyv';
5
5
  export { Keyv } from 'keyv';
6
6
 
@@ -27,6 +27,25 @@ type KeyvRedisOptions = {
27
27
  * Defaults to `false`.
28
28
  */
29
29
  noNamespaceAffectsAll?: boolean;
30
+ /**
31
+ * This is used to throw an error if the client is not connected when trying to connect. By default, this is
32
+ * set to true so that it throws an error when trying to connect to the Redis server fails.
33
+ */
34
+ throwOnConnectError?: boolean;
35
+ /**
36
+ * This is used to throw an error if at any point there is a failure. Use this if you want to
37
+ * ensure that all operations are successful and you want to handle errors. By default, this is
38
+ * set to false so that it does not throw an error on every operation and instead emits an error event
39
+ * and returns no-op responses.
40
+ * @default false
41
+ */
42
+ throwErrors?: boolean;
43
+ /**
44
+ * Timeout in milliseconds for the connection. Default is undefined, which uses the default timeout of the Redis client.
45
+ * If set, it will throw an error if the connection does not succeed within the specified time.
46
+ * @default undefined
47
+ */
48
+ connectionTimeout?: number;
30
49
  };
31
50
  type KeyvRedisPropertyOptions = KeyvRedisOptions & {
32
51
  /**
@@ -52,14 +71,24 @@ type KeyvRedisEntry<T> = {
52
71
  */
53
72
  ttl?: number;
54
73
  };
74
+ declare enum RedisErrorMessages {
75
+ /**
76
+ * Error message when the Redis client is not connected and throwOnConnectError is set to true.
77
+ */
78
+ RedisClientNotConnectedThrown = "Redis client is not connected or has failed to connect. This is thrown because throwOnConnectError is set to true."
79
+ }
80
+ declare const defaultReconnectStrategy: (attempts: number) => number | Error;
55
81
  type RedisClientConnectionType = RedisClientType | RedisClusterType<RedisModules, RedisFunctions, RedisScripts>;
56
- declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
82
+ declare class KeyvRedis<T> extends Hookified implements KeyvStoreAdapter {
57
83
  private _client;
58
84
  private _namespace;
59
85
  private _keyPrefixSeparator;
60
86
  private _clearBatchSize;
61
87
  private _useUnlink;
62
88
  private _noNamespaceAffectsAll;
89
+ private _throwOnConnectError;
90
+ private _throwErrors;
91
+ private _connectionTimeout;
63
92
  /**
64
93
  * KeyvRedis constructor.
65
94
  * @param {string | RedisClientOptions | RedisClientType} [connect] How to connect to the Redis server. If string pass in the url, if object pass in the options, if RedisClient pass in the client.
@@ -129,6 +158,46 @@ declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
129
158
  * Set if not namespace affects all keys.
130
159
  */
131
160
  set noNamespaceAffectsAll(value: boolean);
161
+ /**
162
+ * Get if throwOnConnectError is set to true.
163
+ * This is used to throw an error if the client is not connected when trying to connect. By default, this is
164
+ * set to true so that it throws an error when trying to connect to the Redis server fails.
165
+ * @default true
166
+ */
167
+ get throwOnConnectError(): boolean;
168
+ /**
169
+ * Set if throwOnConnectError is set to true.
170
+ * This is used to throw an error if the client is not connected when trying to connect. By default, this is
171
+ * set to true so that it throws an error when trying to connect to the Redis server fails.
172
+ */
173
+ set throwOnConnectError(value: boolean);
174
+ /**
175
+ * Get if throwErrors is set to true.
176
+ * This is used to throw an error if at any point there is a failure. Use this if you want to
177
+ * ensure that all operations are successful and you want to handle errors. By default, this is
178
+ * set to false so that it does not throw an error on every operation and instead emits an error event
179
+ * and returns no-op responses.
180
+ * @default false
181
+ */
182
+ get throwErrors(): boolean;
183
+ /**
184
+ * Set if throwErrors is set to true.
185
+ * This is used to throw an error if at any point there is a failure. Use this if you want to
186
+ * ensure that all operations are successful and you want to handle errors. By default, this is
187
+ * set to false so that it does not throw an error on every operation and instead emits an error event
188
+ * and returns no-op responses.
189
+ */
190
+ set throwErrors(value: boolean);
191
+ /**
192
+ * Get the connection timeout in milliseconds such as 5000 (5 seconds). Default is undefined. If undefined, it will use the default.
193
+ * @default undefined
194
+ */
195
+ get connectionTimeout(): number | undefined;
196
+ /**
197
+ * Set the connection timeout in milliseconds such as 5000 (5 seconds). Default is undefined. If undefined, it will use the default.
198
+ * @default undefined
199
+ */
200
+ set connectionTimeout(value: number | undefined);
132
201
  /**
133
202
  * Get the Redis URL used to connect to the server. This is used to get a connected client.
134
203
  */
@@ -251,6 +320,7 @@ declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
251
320
  private isClientCluster;
252
321
  private setOptions;
253
322
  private initClient;
323
+ private createTimeoutPromise;
254
324
  }
255
325
  /**
256
326
  * Will create a Keyv instance with the Redis adapter. This will also set the namespace and useKeyPrefix to false.
@@ -260,4 +330,4 @@ declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
260
330
  */
261
331
  declare function createKeyv(connect?: string | RedisClientOptions | RedisClientType, options?: KeyvRedisOptions): Keyv;
262
332
 
263
- export { type KeyvRedisEntry, type KeyvRedisOptions, type KeyvRedisPropertyOptions, type RedisClientConnectionType, createKeyv, KeyvRedis as default };
333
+ export { type KeyvRedisEntry, type KeyvRedisOptions, type KeyvRedisPropertyOptions, type RedisClientConnectionType, RedisErrorMessages, createKeyv, KeyvRedis as default, defaultReconnectStrategy };
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // src/index.ts
2
- import EventEmitter from "events";
3
2
  import {
4
3
  createClient,
5
4
  createCluster
6
5
  } from "@redis/client";
6
+ import { Hookified } from "hookified";
7
7
  import { Keyv } from "keyv";
8
8
  import calculateSlot from "cluster-key-slot";
9
9
  import {
@@ -13,13 +13,25 @@ import {
13
13
  import {
14
14
  Keyv as Keyv2
15
15
  } from "keyv";
16
- var KeyvRedis = class extends EventEmitter {
16
+ var RedisErrorMessages = /* @__PURE__ */ ((RedisErrorMessages2) => {
17
+ RedisErrorMessages2["RedisClientNotConnectedThrown"] = "Redis client is not connected or has failed to connect. This is thrown because throwOnConnectError is set to true.";
18
+ return RedisErrorMessages2;
19
+ })(RedisErrorMessages || {});
20
+ var defaultReconnectStrategy = (attempts) => {
21
+ const backoff = Math.min(2 ** attempts * 100, 2e3);
22
+ const jitter = (Math.random() - 0.5) * 100;
23
+ return backoff + jitter;
24
+ };
25
+ var KeyvRedis = class extends Hookified {
17
26
  _client = createClient();
18
27
  _namespace;
19
28
  _keyPrefixSeparator = "::";
20
29
  _clearBatchSize = 1e3;
21
30
  _useUnlink = true;
22
31
  _noNamespaceAffectsAll = false;
32
+ _throwOnConnectError = true;
33
+ _throwErrors = false;
34
+ _connectionTimeout;
23
35
  /**
24
36
  * KeyvRedis constructor.
25
37
  * @param {string | RedisClientOptions | RedisClientType} [connect] How to connect to the Redis server. If string pass in the url, if object pass in the options, if RedisClient pass in the client.
@@ -27,9 +39,13 @@ var KeyvRedis = class extends EventEmitter {
27
39
  */
28
40
  constructor(connect, options) {
29
41
  super();
42
+ const socket = {
43
+ reconnectStrategy: defaultReconnectStrategy
44
+ // Default timeout for the connection
45
+ };
30
46
  if (connect) {
31
47
  if (typeof connect === "string") {
32
- this._client = createClient({ url: connect });
48
+ this._client = createClient({ url: connect, socket });
33
49
  } else if (connect.connect !== void 0) {
34
50
  this._client = this.isClientCluster(connect) ? connect : connect;
35
51
  } else if (connect instanceof Object) {
@@ -68,6 +84,10 @@ var KeyvRedis = class extends EventEmitter {
68
84
  keyPrefixSeparator: this._keyPrefixSeparator,
69
85
  clearBatchSize: this._clearBatchSize,
70
86
  noNamespaceAffectsAll: this._noNamespaceAffectsAll,
87
+ useUnlink: this._useUnlink,
88
+ throwOnConnectError: this._throwOnConnectError,
89
+ throwErrors: this._throwErrors,
90
+ connectionTimeout: this._connectionTimeout,
71
91
  dialect: "redis",
72
92
  url
73
93
  };
@@ -150,17 +170,82 @@ var KeyvRedis = class extends EventEmitter {
150
170
  set noNamespaceAffectsAll(value) {
151
171
  this._noNamespaceAffectsAll = value;
152
172
  }
173
+ /**
174
+ * Get if throwOnConnectError is set to true.
175
+ * This is used to throw an error if the client is not connected when trying to connect. By default, this is
176
+ * set to true so that it throws an error when trying to connect to the Redis server fails.
177
+ * @default true
178
+ */
179
+ get throwOnConnectError() {
180
+ return this._throwOnConnectError;
181
+ }
182
+ /**
183
+ * Set if throwOnConnectError is set to true.
184
+ * This is used to throw an error if the client is not connected when trying to connect. By default, this is
185
+ * set to true so that it throws an error when trying to connect to the Redis server fails.
186
+ */
187
+ set throwOnConnectError(value) {
188
+ this._throwOnConnectError = value;
189
+ }
190
+ /**
191
+ * Get if throwErrors is set to true.
192
+ * This is used to throw an error if at any point there is a failure. Use this if you want to
193
+ * ensure that all operations are successful and you want to handle errors. By default, this is
194
+ * set to false so that it does not throw an error on every operation and instead emits an error event
195
+ * and returns no-op responses.
196
+ * @default false
197
+ */
198
+ get throwErrors() {
199
+ return this._throwErrors;
200
+ }
201
+ /**
202
+ * Set if throwErrors is set to true.
203
+ * This is used to throw an error if at any point there is a failure. Use this if you want to
204
+ * ensure that all operations are successful and you want to handle errors. By default, this is
205
+ * set to false so that it does not throw an error on every operation and instead emits an error event
206
+ * and returns no-op responses.
207
+ */
208
+ set throwErrors(value) {
209
+ this._throwErrors = value;
210
+ }
211
+ /**
212
+ * Get the connection timeout in milliseconds such as 5000 (5 seconds). Default is undefined. If undefined, it will use the default.
213
+ * @default undefined
214
+ */
215
+ get connectionTimeout() {
216
+ return this._connectionTimeout;
217
+ }
218
+ /**
219
+ * Set the connection timeout in milliseconds such as 5000 (5 seconds). Default is undefined. If undefined, it will use the default.
220
+ * @default undefined
221
+ */
222
+ set connectionTimeout(value) {
223
+ this._connectionTimeout = value;
224
+ }
153
225
  /**
154
226
  * Get the Redis URL used to connect to the server. This is used to get a connected client.
155
227
  */
156
228
  async getClient() {
229
+ if (this._client.isOpen) {
230
+ return this._client;
231
+ }
157
232
  try {
158
- if (!this._client.isOpen) {
233
+ if (this._connectionTimeout === void 0) {
159
234
  await this._client.connect();
235
+ } else {
236
+ await Promise.race([
237
+ this._client.connect(),
238
+ this.createTimeoutPromise(this._connectionTimeout)
239
+ ]);
160
240
  }
161
241
  } catch (error) {
162
242
  this.emit("error", error);
243
+ if (this._throwOnConnectError) {
244
+ throw new Error("Redis client is not connected or has failed to connect. This is thrown because throwOnConnectError is set to true." /* RedisClientNotConnectedThrown */);
245
+ }
246
+ await this.disconnect(true);
163
247
  }
248
+ this.initClient();
164
249
  return this._client;
165
250
  }
166
251
  /**
@@ -171,11 +256,18 @@ var KeyvRedis = class extends EventEmitter {
171
256
  */
172
257
  async set(key, value, ttl) {
173
258
  const client = await this.getClient();
174
- key = this.createKeyPrefix(key, this._namespace);
175
- if (ttl) {
176
- await client.set(key, value, { PX: ttl });
177
- } else {
178
- await client.set(key, value);
259
+ try {
260
+ key = this.createKeyPrefix(key, this._namespace);
261
+ if (ttl) {
262
+ await client.set(key, value, { PX: ttl });
263
+ } else {
264
+ await client.set(key, value);
265
+ }
266
+ } catch (error) {
267
+ this.emit("error", error);
268
+ if (this._throwErrors) {
269
+ throw error;
270
+ }
179
271
  }
180
272
  }
181
273
  /**
@@ -184,16 +276,23 @@ var KeyvRedis = class extends EventEmitter {
184
276
  */
185
277
  async setMany(entries) {
186
278
  const client = await this.getClient();
187
- const multi = client.multi();
188
- for (const { key, value, ttl } of entries) {
189
- const prefixedKey = this.createKeyPrefix(key, this._namespace);
190
- if (ttl) {
191
- multi.set(prefixedKey, value, { PX: ttl });
192
- } else {
193
- multi.set(prefixedKey, value);
279
+ try {
280
+ const multi = client.multi();
281
+ for (const { key, value, ttl } of entries) {
282
+ const prefixedKey = this.createKeyPrefix(key, this._namespace);
283
+ if (ttl) {
284
+ multi.set(prefixedKey, value, { PX: ttl });
285
+ } else {
286
+ multi.set(prefixedKey, value);
287
+ }
288
+ }
289
+ await multi.exec();
290
+ } catch (error) {
291
+ this.emit("error", error);
292
+ if (this._throwErrors) {
293
+ throw error;
194
294
  }
195
295
  }
196
- await multi.exec();
197
296
  }
198
297
  /**
199
298
  * Check if a key exists in the store.
@@ -202,9 +301,17 @@ var KeyvRedis = class extends EventEmitter {
202
301
  */
203
302
  async has(key) {
204
303
  const client = await this.getClient();
205
- key = this.createKeyPrefix(key, this._namespace);
206
- const exists = await client.exists(key);
207
- return exists === 1;
304
+ try {
305
+ key = this.createKeyPrefix(key, this._namespace);
306
+ const exists = await client.exists(key);
307
+ return exists === 1;
308
+ } catch (error) {
309
+ this.emit("error", error);
310
+ if (this._throwErrors) {
311
+ throw error;
312
+ }
313
+ return false;
314
+ }
208
315
  }
209
316
  /**
210
317
  * Check if many keys exist in the store. This will be done as a single transaction.
@@ -213,13 +320,21 @@ var KeyvRedis = class extends EventEmitter {
213
320
  */
214
321
  async hasMany(keys) {
215
322
  const client = await this.getClient();
216
- const multi = client.multi();
217
- for (const key of keys) {
218
- const prefixedKey = this.createKeyPrefix(key, this._namespace);
219
- multi.exists(prefixedKey);
323
+ try {
324
+ const multi = client.multi();
325
+ for (const key of keys) {
326
+ const prefixedKey = this.createKeyPrefix(key, this._namespace);
327
+ multi.exists(prefixedKey);
328
+ }
329
+ const results = await multi.exec();
330
+ return results.map((result) => result === 1);
331
+ } catch (error) {
332
+ this.emit("error", error);
333
+ if (this._throwErrors) {
334
+ throw error;
335
+ }
336
+ return Array.from({ length: keys.length }).fill(false);
220
337
  }
221
- const results = await multi.exec();
222
- return results.map((result) => result === 1);
223
338
  }
224
339
  /**
225
340
  * Get a value from the store. If the key does not exist, it will return undefined.
@@ -228,12 +343,20 @@ var KeyvRedis = class extends EventEmitter {
228
343
  */
229
344
  async get(key) {
230
345
  const client = await this.getClient();
231
- key = this.createKeyPrefix(key, this._namespace);
232
- const value = await client.get(key);
233
- if (value === null) {
346
+ try {
347
+ key = this.createKeyPrefix(key, this._namespace);
348
+ const value = await client.get(key);
349
+ if (value === null) {
350
+ return void 0;
351
+ }
352
+ return value;
353
+ } catch (error) {
354
+ this.emit("error", error);
355
+ if (this._throwErrors) {
356
+ throw error;
357
+ }
234
358
  return void 0;
235
359
  }
236
- return value;
237
360
  }
238
361
  /**
239
362
  * Get many values from the store. If a key does not exist, it will return undefined.
@@ -245,8 +368,16 @@ var KeyvRedis = class extends EventEmitter {
245
368
  return [];
246
369
  }
247
370
  keys = keys.map((key) => this.createKeyPrefix(key, this._namespace));
248
- const values = await this.mget(keys);
249
- return values;
371
+ try {
372
+ const values = await this.mget(keys);
373
+ return values;
374
+ } catch (error) {
375
+ this.emit("error", error);
376
+ if (this._throwErrors) {
377
+ throw error;
378
+ }
379
+ return Array.from({ length: keys.length }).fill(void 0);
380
+ }
250
381
  }
251
382
  /**
252
383
  * Delete a key from the store.
@@ -255,10 +386,18 @@ var KeyvRedis = class extends EventEmitter {
255
386
  */
256
387
  async delete(key) {
257
388
  const client = await this.getClient();
258
- key = this.createKeyPrefix(key, this._namespace);
259
- let deleted = 0;
260
- deleted = await (this._useUnlink ? client.unlink(key) : client.del(key));
261
- return deleted > 0;
389
+ try {
390
+ key = this.createKeyPrefix(key, this._namespace);
391
+ let deleted = 0;
392
+ deleted = await (this._useUnlink ? client.unlink(key) : client.del(key));
393
+ return deleted > 0;
394
+ } catch (error) {
395
+ this.emit("error", error);
396
+ if (this._throwErrors) {
397
+ throw error;
398
+ }
399
+ return false;
400
+ }
262
401
  }
263
402
  /**
264
403
  * Delete many keys from the store. This will be done as a single transaction.
@@ -268,19 +407,26 @@ var KeyvRedis = class extends EventEmitter {
268
407
  async deleteMany(keys) {
269
408
  let result = false;
270
409
  const client = await this.getClient();
271
- const multi = client.multi();
272
- for (const key of keys) {
273
- const prefixedKey = this.createKeyPrefix(key, this._namespace);
274
- if (this._useUnlink) {
275
- multi.unlink(prefixedKey);
276
- } else {
277
- multi.del(prefixedKey);
410
+ try {
411
+ const multi = client.multi();
412
+ for (const key of keys) {
413
+ const prefixedKey = this.createKeyPrefix(key, this._namespace);
414
+ if (this._useUnlink) {
415
+ multi.unlink(prefixedKey);
416
+ } else {
417
+ multi.del(prefixedKey);
418
+ }
278
419
  }
279
- }
280
- const results = await multi.exec();
281
- for (const deleted of results) {
282
- if (typeof deleted === "number" && deleted > 0) {
283
- result = true;
420
+ const results = await multi.exec();
421
+ for (const deleted of results) {
422
+ if (typeof deleted === "number" && deleted > 0) {
423
+ result = true;
424
+ }
425
+ }
426
+ } catch (error) {
427
+ this.emit("error", error);
428
+ if (this._throwErrors) {
429
+ throw error;
284
430
  }
285
431
  }
286
432
  return result;
@@ -490,11 +636,37 @@ var KeyvRedis = class extends EventEmitter {
490
636
  if (options.noNamespaceAffectsAll !== void 0) {
491
637
  this._noNamespaceAffectsAll = options.noNamespaceAffectsAll;
492
638
  }
639
+ if (options.throwOnConnectError !== void 0) {
640
+ this._throwOnConnectError = options.throwOnConnectError;
641
+ }
642
+ if (options.throwErrors !== void 0) {
643
+ this._throwErrors = options.throwErrors;
644
+ }
645
+ if (options.connectionTimeout !== void 0) {
646
+ this._connectionTimeout = options.connectionTimeout;
647
+ }
493
648
  }
494
649
  initClient() {
495
- this._client.on("error", (error) => {
496
- this.emit("error", error);
650
+ this._client.on("connect", () => {
651
+ this.emit("connect", this._client);
652
+ });
653
+ this._client.on("disconnect", () => {
654
+ this.emit("disconnect", this._client);
497
655
  });
656
+ this._client.on("reconnecting", (reconnectInfo) => {
657
+ this.emit("reconnecting", reconnectInfo);
658
+ });
659
+ }
660
+ async createTimeoutPromise(timeoutMs) {
661
+ return new Promise((_, reject) => (
662
+ // eslint-disable-next-line no-promise-executor-return
663
+ setTimeout(
664
+ () => {
665
+ reject(new Error(`Redis timed out after ${timeoutMs}ms`));
666
+ },
667
+ timeoutMs
668
+ )
669
+ ));
498
670
  }
499
671
  };
500
672
  function createKeyv(connect, options) {
@@ -505,8 +677,10 @@ function createKeyv(connect, options) {
505
677
  }
506
678
  export {
507
679
  Keyv2 as Keyv,
680
+ RedisErrorMessages,
508
681
  createClient2 as createClient,
509
682
  createCluster2 as createCluster,
510
683
  createKeyv,
511
- KeyvRedis as default
684
+ KeyvRedis as default,
685
+ defaultReconnectStrategy
512
686
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keyv/redis",
3
- "version": "4.4.1",
3
+ "version": "4.6.0",
4
4
  "description": "Redis storage adapter for Keyv",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -35,18 +35,20 @@
35
35
  "homepage": "https://github.com/jaredwray/keyv",
36
36
  "dependencies": {
37
37
  "@redis/client": "^1.6.0",
38
- "cluster-key-slot": "^1.1.2"
38
+ "cluster-key-slot": "^1.1.2",
39
+ "hookified": "^1.10.0"
39
40
  },
40
41
  "peerDependencies": {
41
42
  "keyv": "^5.3.4"
42
43
  },
43
44
  "devDependencies": {
44
- "@vitest/coverage-v8": "^3.2.3",
45
+ "@faker-js/faker": "^9.9.0",
46
+ "@vitest/coverage-v8": "^3.2.4",
45
47
  "rimraf": "^6.0.1",
46
48
  "timekeeper": "^2.3.1",
47
49
  "tsd": "^0.32.0",
48
- "vitest": "^3.2.3",
49
- "xo": "^1.1.0",
50
+ "vitest": "^3.2.4",
51
+ "xo": "^1.1.1",
50
52
  "@keyv/test-suite": "^2.0.8"
51
53
  },
52
54
  "tsd": {