@keyv/redis 4.5.0 → 5.0.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/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 {
@@ -14,7 +14,7 @@ import {
14
14
  Keyv as Keyv2
15
15
  } from "keyv";
16
16
  var RedisErrorMessages = /* @__PURE__ */ ((RedisErrorMessages2) => {
17
- RedisErrorMessages2["RedisClientNotConnected"] = "Redis client is not connected or has failed to connect";
17
+ RedisErrorMessages2["RedisClientNotConnectedThrown"] = "Redis client is not connected or has failed to connect. This is thrown because throwOnConnectError is set to true.";
18
18
  return RedisErrorMessages2;
19
19
  })(RedisErrorMessages || {});
20
20
  var defaultReconnectStrategy = (attempts) => {
@@ -22,17 +22,16 @@ var defaultReconnectStrategy = (attempts) => {
22
22
  const jitter = (Math.random() - 0.5) * 100;
23
23
  return backoff + jitter;
24
24
  };
25
- var KeyvRedis = class extends EventEmitter {
25
+ var KeyvRedis = class extends Hookified {
26
26
  _client = createClient();
27
27
  _namespace;
28
28
  _keyPrefixSeparator = "::";
29
29
  _clearBatchSize = 1e3;
30
30
  _useUnlink = true;
31
31
  _noNamespaceAffectsAll = false;
32
- _connectTimeout = 200;
33
- // Timeout for connecting to Redis in milliseconds
34
- _reconnectClient = false;
35
- // Whether to reconnect the client
32
+ _throwOnConnectError = true;
33
+ _throwOnErrors = false;
34
+ _connectionTimeout;
36
35
  /**
37
36
  * KeyvRedis constructor.
38
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.
@@ -42,6 +41,7 @@ var KeyvRedis = class extends EventEmitter {
42
41
  super();
43
42
  const socket = {
44
43
  reconnectStrategy: defaultReconnectStrategy
44
+ // Default timeout for the connection
45
45
  };
46
46
  if (connect) {
47
47
  if (typeof connect === "string") {
@@ -84,6 +84,10 @@ var KeyvRedis = class extends EventEmitter {
84
84
  keyPrefixSeparator: this._keyPrefixSeparator,
85
85
  clearBatchSize: this._clearBatchSize,
86
86
  noNamespaceAffectsAll: this._noNamespaceAffectsAll,
87
+ useUnlink: this._useUnlink,
88
+ throwOnConnectError: this._throwOnConnectError,
89
+ throwOnErrors: this._throwOnErrors,
90
+ connectionTimeout: this._connectionTimeout,
87
91
  dialect: "redis",
88
92
  url
89
93
  };
@@ -167,49 +171,82 @@ var KeyvRedis = class extends EventEmitter {
167
171
  this._noNamespaceAffectsAll = value;
168
172
  }
169
173
  /**
170
- * Get the timeout for connecting to Redis in milliseconds. This is used to prevent hanging indefinitely when connecting to Redis.
171
- * @default 200
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
172
178
  */
173
- get connectTimeout() {
174
- return this._connectTimeout;
179
+ get throwOnConnectError() {
180
+ return this._throwOnConnectError;
175
181
  }
176
182
  /**
177
- * Set the timeout for connecting to Redis in milliseconds. This is used to prevent hanging indefinitely when connecting to Redis.
178
- * @default 200
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.
179
186
  */
180
- set connectTimeout(value) {
181
- if (value > 0) {
182
- this._connectTimeout = value;
183
- this._reconnectClient = true;
184
- } else {
185
- this.emit("error", "connectTimeout must be greater than 0");
186
- }
187
+ set throwOnConnectError(value) {
188
+ this._throwOnConnectError = value;
189
+ }
190
+ /**
191
+ * Get if throwOnErrors 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 throwOnErrors() {
199
+ return this._throwOnErrors;
200
+ }
201
+ /**
202
+ * Set if throwOnErrors 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 throwOnErrors(value) {
209
+ this._throwOnErrors = 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;
187
224
  }
188
225
  /**
189
226
  * Get the Redis URL used to connect to the server. This is used to get a connected client.
190
227
  */
191
228
  async getClient() {
192
- if (this._client.isOpen && !this._reconnectClient) {
229
+ if (this._client.isOpen) {
193
230
  return this._client;
194
231
  }
195
- if (this._reconnectClient && this._client.isOpen) {
196
- await this._client.disconnect();
197
- }
198
232
  try {
199
- const timeoutPromise = new Promise((resolves, reject) => setTimeout(() => {
200
- reject(new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */));
201
- }, this._connectTimeout));
202
- await Promise.race([
203
- this._client.connect(),
204
- timeoutPromise
205
- ]);
206
- this._reconnectClient = false;
207
- this.initClient();
208
- return this._client;
233
+ if (this._connectionTimeout === void 0) {
234
+ await this._client.connect();
235
+ } else {
236
+ await Promise.race([
237
+ this._client.connect(),
238
+ this.createTimeoutPromise(this._connectionTimeout)
239
+ ]);
240
+ }
209
241
  } catch (error) {
210
242
  this.emit("error", error);
211
- return void 0;
243
+ await this.disconnect(true);
244
+ if (this._throwOnConnectError) {
245
+ throw new Error("Redis client is not connected or has failed to connect. This is thrown because throwOnConnectError is set to true." /* RedisClientNotConnectedThrown */);
246
+ }
212
247
  }
248
+ this.initClient();
249
+ return this._client;
213
250
  }
214
251
  /**
215
252
  * Set a key value pair in the store. TTL is in milliseconds.
@@ -219,15 +256,18 @@ var KeyvRedis = class extends EventEmitter {
219
256
  */
220
257
  async set(key, value, ttl) {
221
258
  const client = await this.getClient();
222
- if (!client) {
223
- this.emit("error", new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */));
224
- return;
225
- }
226
- key = this.createKeyPrefix(key, this._namespace);
227
- if (ttl) {
228
- await client.set(key, value, { PX: ttl });
229
- } else {
230
- 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._throwOnErrors) {
269
+ throw error;
270
+ }
231
271
  }
232
272
  }
233
273
  /**
@@ -236,20 +276,23 @@ var KeyvRedis = class extends EventEmitter {
236
276
  */
237
277
  async setMany(entries) {
238
278
  const client = await this.getClient();
239
- if (!client) {
240
- this.emit("error", new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */));
241
- return;
242
- }
243
- const multi = client.multi();
244
- for (const { key, value, ttl } of entries) {
245
- const prefixedKey = this.createKeyPrefix(key, this._namespace);
246
- if (ttl) {
247
- multi.set(prefixedKey, value, { PX: ttl });
248
- } else {
249
- 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._throwOnErrors) {
293
+ throw error;
250
294
  }
251
295
  }
252
- await multi.exec();
253
296
  }
254
297
  /**
255
298
  * Check if a key exists in the store.
@@ -258,13 +301,17 @@ var KeyvRedis = class extends EventEmitter {
258
301
  */
259
302
  async has(key) {
260
303
  const client = await this.getClient();
261
- if (!client) {
262
- this.emit("error", new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */));
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._throwOnErrors) {
311
+ throw error;
312
+ }
263
313
  return false;
264
314
  }
265
- key = this.createKeyPrefix(key, this._namespace);
266
- const exists = await client.exists(key);
267
- return exists === 1;
268
315
  }
269
316
  /**
270
317
  * Check if many keys exist in the store. This will be done as a single transaction.
@@ -273,17 +320,21 @@ var KeyvRedis = class extends EventEmitter {
273
320
  */
274
321
  async hasMany(keys) {
275
322
  const client = await this.getClient();
276
- if (!client) {
277
- this.emit("error", new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */));
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) => typeof result === "number" && result === 1);
331
+ } catch (error) {
332
+ this.emit("error", error);
333
+ if (this._throwOnErrors) {
334
+ throw error;
335
+ }
278
336
  return Array.from({ length: keys.length }).fill(false);
279
337
  }
280
- const multi = client.multi();
281
- for (const key of keys) {
282
- const prefixedKey = this.createKeyPrefix(key, this._namespace);
283
- multi.exists(prefixedKey);
284
- }
285
- const results = await multi.exec();
286
- return results.map((result) => result === 1);
287
338
  }
288
339
  /**
289
340
  * Get a value from the store. If the key does not exist, it will return undefined.
@@ -292,16 +343,20 @@ var KeyvRedis = class extends EventEmitter {
292
343
  */
293
344
  async get(key) {
294
345
  const client = await this.getClient();
295
- if (!client) {
296
- this.emit("error", new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */));
297
- return void 0;
298
- }
299
- key = this.createKeyPrefix(key, this._namespace);
300
- const value = await client.get(key);
301
- 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._throwOnErrors) {
356
+ throw error;
357
+ }
302
358
  return void 0;
303
359
  }
304
- return value;
305
360
  }
306
361
  /**
307
362
  * Get many values from the store. If a key does not exist, it will return undefined.
@@ -318,6 +373,9 @@ var KeyvRedis = class extends EventEmitter {
318
373
  return values;
319
374
  } catch (error) {
320
375
  this.emit("error", error);
376
+ if (this._throwOnErrors) {
377
+ throw error;
378
+ }
321
379
  return Array.from({ length: keys.length }).fill(void 0);
322
380
  }
323
381
  }
@@ -328,14 +386,18 @@ var KeyvRedis = class extends EventEmitter {
328
386
  */
329
387
  async delete(key) {
330
388
  const client = await this.getClient();
331
- if (!client) {
332
- this.emit("error", new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */));
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._throwOnErrors) {
397
+ throw error;
398
+ }
333
399
  return false;
334
400
  }
335
- key = this.createKeyPrefix(key, this._namespace);
336
- let deleted = 0;
337
- deleted = await (this._useUnlink ? client.unlink(key) : client.del(key));
338
- return deleted > 0;
339
401
  }
340
402
  /**
341
403
  * Delete many keys from the store. This will be done as a single transaction.
@@ -345,23 +407,26 @@ var KeyvRedis = class extends EventEmitter {
345
407
  async deleteMany(keys) {
346
408
  let result = false;
347
409
  const client = await this.getClient();
348
- if (!client) {
349
- this.emit("error", new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */));
350
- return false;
351
- }
352
- const multi = client.multi();
353
- for (const key of keys) {
354
- const prefixedKey = this.createKeyPrefix(key, this._namespace);
355
- if (this._useUnlink) {
356
- multi.unlink(prefixedKey);
357
- } else {
358
- 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
+ }
359
419
  }
360
- }
361
- const results = await multi.exec();
362
- for (const deleted of results) {
363
- if (typeof deleted === "number" && deleted > 0) {
364
- 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._throwOnErrors) {
429
+ throw error;
365
430
  }
366
431
  }
367
432
  return result;
@@ -374,7 +439,7 @@ var KeyvRedis = class extends EventEmitter {
374
439
  */
375
440
  async disconnect(force) {
376
441
  if (this._client.isOpen) {
377
- await (force ? this._client.disconnect() : this._client.quit());
442
+ await (force ? this._client.destroy() : this._client.close());
378
443
  }
379
444
  }
380
445
  /**
@@ -416,7 +481,8 @@ var KeyvRedis = class extends EventEmitter {
416
481
  async getMasterNodes() {
417
482
  if (this.isCluster()) {
418
483
  const cluster = await this.getClient();
419
- return Promise.all(cluster.masters.map(async (main) => cluster.nodeClient(main)));
484
+ const nodes = cluster.masters.map(async (main) => cluster.nodeClient(main));
485
+ return Promise.all(nodes);
420
486
  }
421
487
  return [await this.getClient()];
422
488
  }
@@ -431,7 +497,7 @@ var KeyvRedis = class extends EventEmitter {
431
497
  const match = namespace ? `${namespace}${this._keyPrefixSeparator}*` : "*";
432
498
  let cursor = "0";
433
499
  do {
434
- const result = await client.scan(Number.parseInt(cursor, 10), { MATCH: match, TYPE: "string" });
500
+ const result = await client.scan(cursor, { MATCH: match, TYPE: "string" });
435
501
  cursor = result.cursor.toString();
436
502
  let { keys } = result;
437
503
  if (!namespace && !this._noNamespaceAffectsAll) {
@@ -468,7 +534,7 @@ var KeyvRedis = class extends EventEmitter {
468
534
  const match = this._namespace ? `${this._namespace}${this._keyPrefixSeparator}*` : "*";
469
535
  const deletePromises = [];
470
536
  do {
471
- const result = await client.scan(Number.parseInt(cursor, 10), { MATCH: match, COUNT: batchSize, TYPE: "string" });
537
+ const result = await client.scan(cursor, { MATCH: match, COUNT: batchSize, TYPE: "string" });
472
538
  cursor = result.cursor.toString();
473
539
  let { keys } = result;
474
540
  if (keys.length === 0) {
@@ -519,9 +585,6 @@ var KeyvRedis = class extends EventEmitter {
519
585
  */
520
586
  async getSlotMaster(slot) {
521
587
  const connection = await this.getClient();
522
- if (!connection) {
523
- throw new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */);
524
- }
525
588
  if (this.isCluster()) {
526
589
  const cluster = connection;
527
590
  const mainNode = cluster.slots[slot].master;
@@ -550,10 +613,7 @@ var KeyvRedis = class extends EventEmitter {
550
613
  return slotMap;
551
614
  }
552
615
  isClientCluster(client) {
553
- if (client.options === void 0 && client.scan === void 0) {
554
- return true;
555
- }
556
- return false;
616
+ return client.slots !== void 0;
557
617
  }
558
618
  setOptions(options) {
559
619
  if (!options) {
@@ -574,14 +634,17 @@ var KeyvRedis = class extends EventEmitter {
574
634
  if (options.noNamespaceAffectsAll !== void 0) {
575
635
  this._noNamespaceAffectsAll = options.noNamespaceAffectsAll;
576
636
  }
577
- if (options.connectTimeout !== void 0 && options.connectTimeout > 0) {
578
- this._connectTimeout = options.connectTimeout;
637
+ if (options.throwOnConnectError !== void 0) {
638
+ this._throwOnConnectError = options.throwOnConnectError;
639
+ }
640
+ if (options.throwOnErrors !== void 0) {
641
+ this._throwOnErrors = options.throwOnErrors;
642
+ }
643
+ if (options.connectionTimeout !== void 0) {
644
+ this._connectionTimeout = options.connectionTimeout;
579
645
  }
580
646
  }
581
647
  initClient() {
582
- this._client.on("error", (error) => {
583
- this.emit("error", error);
584
- });
585
648
  this._client.on("connect", () => {
586
649
  this.emit("connect", this._client);
587
650
  });
@@ -592,11 +655,55 @@ var KeyvRedis = class extends EventEmitter {
592
655
  this.emit("reconnecting", reconnectInfo);
593
656
  });
594
657
  }
658
+ async createTimeoutPromise(timeoutMs) {
659
+ return new Promise((_, reject) => (
660
+ // eslint-disable-next-line no-promise-executor-return
661
+ setTimeout(
662
+ () => {
663
+ reject(new Error(`Redis timed out after ${timeoutMs}ms`));
664
+ },
665
+ timeoutMs
666
+ )
667
+ ));
668
+ }
595
669
  };
596
670
  function createKeyv(connect, options) {
597
671
  connect ??= "redis://localhost:6379";
598
672
  const adapter = new KeyvRedis(connect, options);
599
- const keyv = new Keyv(adapter, { namespace: options?.namespace, useKeyPrefix: false });
673
+ if (options?.namespace) {
674
+ adapter.namespace = options.namespace;
675
+ const keyv2 = new Keyv(adapter, { namespace: options?.namespace, useKeyPrefix: false });
676
+ if (options?.throwOnConnectError) {
677
+ keyv2.throwOnErrors = true;
678
+ }
679
+ if (options?.throwOnErrors) {
680
+ keyv2.throwOnErrors = true;
681
+ }
682
+ return keyv2;
683
+ }
684
+ const keyv = new Keyv(adapter, { useKeyPrefix: false });
685
+ if (options?.throwOnConnectError) {
686
+ keyv.throwOnErrors = true;
687
+ }
688
+ if (options?.throwOnErrors) {
689
+ keyv.throwOnErrors = true;
690
+ }
691
+ keyv.namespace = void 0;
692
+ return keyv;
693
+ }
694
+ function createKeyvNonBlocking(connect, options) {
695
+ const keyv = createKeyv(connect, options);
696
+ const keyvStore = keyv.store;
697
+ keyvStore.throwOnConnectError = false;
698
+ keyvStore.throwOnErrors = false;
699
+ const redisClient = keyvStore.client;
700
+ if (redisClient.options) {
701
+ redisClient.options.disableOfflineQueue = true;
702
+ if (redisClient.options.socket) {
703
+ redisClient.options.socket.reconnectStrategy = false;
704
+ }
705
+ }
706
+ keyv.throwOnErrors = false;
600
707
  return keyv;
601
708
  }
602
709
  export {
@@ -605,6 +712,7 @@ export {
605
712
  createClient2 as createClient,
606
713
  createCluster2 as createCluster,
607
714
  createKeyv,
715
+ createKeyvNonBlocking,
608
716
  KeyvRedis as default,
609
717
  defaultReconnectStrategy
610
718
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keyv/redis",
3
- "version": "4.5.0",
3
+ "version": "5.0.0",
4
4
  "description": "Redis storage adapter for Keyv",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -34,21 +34,22 @@
34
34
  },
35
35
  "homepage": "https://github.com/jaredwray/keyv",
36
36
  "dependencies": {
37
- "@redis/client": "^1.6.0",
38
- "cluster-key-slot": "^1.1.2"
37
+ "@redis/client": "^5.6.0",
38
+ "cluster-key-slot": "^1.1.2",
39
+ "hookified": "^1.10.0"
39
40
  },
40
41
  "peerDependencies": {
41
- "keyv": "^5.3.4"
42
+ "keyv": "^5.4.0"
42
43
  },
43
44
  "devDependencies": {
44
- "@faker-js/faker": "^9.8.0",
45
- "@vitest/coverage-v8": "^3.2.3",
45
+ "@faker-js/faker": "^9.9.0",
46
+ "@vitest/coverage-v8": "^3.2.4",
46
47
  "rimraf": "^6.0.1",
47
48
  "timekeeper": "^2.3.1",
48
49
  "tsd": "^0.32.0",
49
- "vitest": "^3.2.3",
50
- "xo": "^1.1.0",
51
- "@keyv/test-suite": "^2.0.8"
50
+ "vitest": "^3.2.4",
51
+ "xo": "^1.1.1",
52
+ "@keyv/test-suite": "^2.0.9"
52
53
  },
53
54
  "tsd": {
54
55
  "directory": "test"