@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.cjs CHANGED
@@ -35,18 +35,19 @@ __export(index_exports, {
35
35
  createClient: () => import_client2.createClient,
36
36
  createCluster: () => import_client2.createCluster,
37
37
  createKeyv: () => createKeyv,
38
+ createKeyvNonBlocking: () => createKeyvNonBlocking,
38
39
  default: () => KeyvRedis,
39
40
  defaultReconnectStrategy: () => defaultReconnectStrategy
40
41
  });
41
42
  module.exports = __toCommonJS(index_exports);
42
- var import_node_events = __toESM(require("events"), 1);
43
43
  var import_client = require("@redis/client");
44
+ var import_hookified = require("hookified");
44
45
  var import_keyv = require("keyv");
45
46
  var import_cluster_key_slot = __toESM(require("cluster-key-slot"), 1);
46
47
  var import_client2 = require("@redis/client");
47
48
  var import_keyv2 = require("keyv");
48
49
  var RedisErrorMessages = /* @__PURE__ */ ((RedisErrorMessages2) => {
49
- RedisErrorMessages2["RedisClientNotConnected"] = "Redis client is not connected or has failed to connect";
50
+ RedisErrorMessages2["RedisClientNotConnectedThrown"] = "Redis client is not connected or has failed to connect. This is thrown because throwOnConnectError is set to true.";
50
51
  return RedisErrorMessages2;
51
52
  })(RedisErrorMessages || {});
52
53
  var defaultReconnectStrategy = (attempts) => {
@@ -54,17 +55,16 @@ var defaultReconnectStrategy = (attempts) => {
54
55
  const jitter = (Math.random() - 0.5) * 100;
55
56
  return backoff + jitter;
56
57
  };
57
- var KeyvRedis = class extends import_node_events.default {
58
+ var KeyvRedis = class extends import_hookified.Hookified {
58
59
  _client = (0, import_client.createClient)();
59
60
  _namespace;
60
61
  _keyPrefixSeparator = "::";
61
62
  _clearBatchSize = 1e3;
62
63
  _useUnlink = true;
63
64
  _noNamespaceAffectsAll = false;
64
- _connectTimeout = 200;
65
- // Timeout for connecting to Redis in milliseconds
66
- _reconnectClient = false;
67
- // Whether to reconnect the client
65
+ _throwOnConnectError = true;
66
+ _throwOnErrors = false;
67
+ _connectionTimeout;
68
68
  /**
69
69
  * KeyvRedis constructor.
70
70
  * @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.
@@ -74,6 +74,7 @@ var KeyvRedis = class extends import_node_events.default {
74
74
  super();
75
75
  const socket = {
76
76
  reconnectStrategy: defaultReconnectStrategy
77
+ // Default timeout for the connection
77
78
  };
78
79
  if (connect) {
79
80
  if (typeof connect === "string") {
@@ -116,6 +117,10 @@ var KeyvRedis = class extends import_node_events.default {
116
117
  keyPrefixSeparator: this._keyPrefixSeparator,
117
118
  clearBatchSize: this._clearBatchSize,
118
119
  noNamespaceAffectsAll: this._noNamespaceAffectsAll,
120
+ useUnlink: this._useUnlink,
121
+ throwOnConnectError: this._throwOnConnectError,
122
+ throwOnErrors: this._throwOnErrors,
123
+ connectionTimeout: this._connectionTimeout,
119
124
  dialect: "redis",
120
125
  url
121
126
  };
@@ -199,49 +204,82 @@ var KeyvRedis = class extends import_node_events.default {
199
204
  this._noNamespaceAffectsAll = value;
200
205
  }
201
206
  /**
202
- * Get the timeout for connecting to Redis in milliseconds. This is used to prevent hanging indefinitely when connecting to Redis.
203
- * @default 200
207
+ * Get if throwOnConnectError is set to true.
208
+ * This is used to throw an error if the client is not connected when trying to connect. By default, this is
209
+ * set to true so that it throws an error when trying to connect to the Redis server fails.
210
+ * @default true
204
211
  */
205
- get connectTimeout() {
206
- return this._connectTimeout;
212
+ get throwOnConnectError() {
213
+ return this._throwOnConnectError;
207
214
  }
208
215
  /**
209
- * Set the timeout for connecting to Redis in milliseconds. This is used to prevent hanging indefinitely when connecting to Redis.
210
- * @default 200
216
+ * Set if throwOnConnectError is set to true.
217
+ * This is used to throw an error if the client is not connected when trying to connect. By default, this is
218
+ * set to true so that it throws an error when trying to connect to the Redis server fails.
211
219
  */
212
- set connectTimeout(value) {
213
- if (value > 0) {
214
- this._connectTimeout = value;
215
- this._reconnectClient = true;
216
- } else {
217
- this.emit("error", "connectTimeout must be greater than 0");
218
- }
220
+ set throwOnConnectError(value) {
221
+ this._throwOnConnectError = value;
222
+ }
223
+ /**
224
+ * Get if throwOnErrors is set to true.
225
+ * This is used to throw an error if at any point there is a failure. Use this if you want to
226
+ * ensure that all operations are successful and you want to handle errors. By default, this is
227
+ * set to false so that it does not throw an error on every operation and instead emits an error event
228
+ * and returns no-op responses.
229
+ * @default false
230
+ */
231
+ get throwOnErrors() {
232
+ return this._throwOnErrors;
233
+ }
234
+ /**
235
+ * Set if throwOnErrors is set to true.
236
+ * This is used to throw an error if at any point there is a failure. Use this if you want to
237
+ * ensure that all operations are successful and you want to handle errors. By default, this is
238
+ * set to false so that it does not throw an error on every operation and instead emits an error event
239
+ * and returns no-op responses.
240
+ */
241
+ set throwOnErrors(value) {
242
+ this._throwOnErrors = value;
243
+ }
244
+ /**
245
+ * Get the connection timeout in milliseconds such as 5000 (5 seconds). Default is undefined. If undefined, it will use the default.
246
+ * @default undefined
247
+ */
248
+ get connectionTimeout() {
249
+ return this._connectionTimeout;
250
+ }
251
+ /**
252
+ * Set the connection timeout in milliseconds such as 5000 (5 seconds). Default is undefined. If undefined, it will use the default.
253
+ * @default undefined
254
+ */
255
+ set connectionTimeout(value) {
256
+ this._connectionTimeout = value;
219
257
  }
220
258
  /**
221
259
  * Get the Redis URL used to connect to the server. This is used to get a connected client.
222
260
  */
223
261
  async getClient() {
224
- if (this._client.isOpen && !this._reconnectClient) {
262
+ if (this._client.isOpen) {
225
263
  return this._client;
226
264
  }
227
- if (this._reconnectClient && this._client.isOpen) {
228
- await this._client.disconnect();
229
- }
230
265
  try {
231
- const timeoutPromise = new Promise((resolves, reject) => setTimeout(() => {
232
- reject(new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */));
233
- }, this._connectTimeout));
234
- await Promise.race([
235
- this._client.connect(),
236
- timeoutPromise
237
- ]);
238
- this._reconnectClient = false;
239
- this.initClient();
240
- return this._client;
266
+ if (this._connectionTimeout === void 0) {
267
+ await this._client.connect();
268
+ } else {
269
+ await Promise.race([
270
+ this._client.connect(),
271
+ this.createTimeoutPromise(this._connectionTimeout)
272
+ ]);
273
+ }
241
274
  } catch (error) {
242
275
  this.emit("error", error);
243
- return void 0;
276
+ await this.disconnect(true);
277
+ if (this._throwOnConnectError) {
278
+ throw new Error("Redis client is not connected or has failed to connect. This is thrown because throwOnConnectError is set to true." /* RedisClientNotConnectedThrown */);
279
+ }
244
280
  }
281
+ this.initClient();
282
+ return this._client;
245
283
  }
246
284
  /**
247
285
  * Set a key value pair in the store. TTL is in milliseconds.
@@ -251,15 +289,18 @@ var KeyvRedis = class extends import_node_events.default {
251
289
  */
252
290
  async set(key, value, ttl) {
253
291
  const client = await this.getClient();
254
- if (!client) {
255
- this.emit("error", new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */));
256
- return;
257
- }
258
- key = this.createKeyPrefix(key, this._namespace);
259
- if (ttl) {
260
- await client.set(key, value, { PX: ttl });
261
- } else {
262
- await client.set(key, value);
292
+ try {
293
+ key = this.createKeyPrefix(key, this._namespace);
294
+ if (ttl) {
295
+ await client.set(key, value, { PX: ttl });
296
+ } else {
297
+ await client.set(key, value);
298
+ }
299
+ } catch (error) {
300
+ this.emit("error", error);
301
+ if (this._throwOnErrors) {
302
+ throw error;
303
+ }
263
304
  }
264
305
  }
265
306
  /**
@@ -268,20 +309,23 @@ var KeyvRedis = class extends import_node_events.default {
268
309
  */
269
310
  async setMany(entries) {
270
311
  const client = await this.getClient();
271
- if (!client) {
272
- this.emit("error", new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */));
273
- return;
274
- }
275
- const multi = client.multi();
276
- for (const { key, value, ttl } of entries) {
277
- const prefixedKey = this.createKeyPrefix(key, this._namespace);
278
- if (ttl) {
279
- multi.set(prefixedKey, value, { PX: ttl });
280
- } else {
281
- multi.set(prefixedKey, value);
312
+ try {
313
+ const multi = client.multi();
314
+ for (const { key, value, ttl } of entries) {
315
+ const prefixedKey = this.createKeyPrefix(key, this._namespace);
316
+ if (ttl) {
317
+ multi.set(prefixedKey, value, { PX: ttl });
318
+ } else {
319
+ multi.set(prefixedKey, value);
320
+ }
321
+ }
322
+ await multi.exec();
323
+ } catch (error) {
324
+ this.emit("error", error);
325
+ if (this._throwOnErrors) {
326
+ throw error;
282
327
  }
283
328
  }
284
- await multi.exec();
285
329
  }
286
330
  /**
287
331
  * Check if a key exists in the store.
@@ -290,13 +334,17 @@ var KeyvRedis = class extends import_node_events.default {
290
334
  */
291
335
  async has(key) {
292
336
  const client = await this.getClient();
293
- if (!client) {
294
- this.emit("error", new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */));
337
+ try {
338
+ key = this.createKeyPrefix(key, this._namespace);
339
+ const exists = await client.exists(key);
340
+ return exists === 1;
341
+ } catch (error) {
342
+ this.emit("error", error);
343
+ if (this._throwOnErrors) {
344
+ throw error;
345
+ }
295
346
  return false;
296
347
  }
297
- key = this.createKeyPrefix(key, this._namespace);
298
- const exists = await client.exists(key);
299
- return exists === 1;
300
348
  }
301
349
  /**
302
350
  * Check if many keys exist in the store. This will be done as a single transaction.
@@ -305,17 +353,21 @@ var KeyvRedis = class extends import_node_events.default {
305
353
  */
306
354
  async hasMany(keys) {
307
355
  const client = await this.getClient();
308
- if (!client) {
309
- this.emit("error", new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */));
356
+ try {
357
+ const multi = client.multi();
358
+ for (const key of keys) {
359
+ const prefixedKey = this.createKeyPrefix(key, this._namespace);
360
+ multi.exists(prefixedKey);
361
+ }
362
+ const results = await multi.exec();
363
+ return results.map((result) => typeof result === "number" && result === 1);
364
+ } catch (error) {
365
+ this.emit("error", error);
366
+ if (this._throwOnErrors) {
367
+ throw error;
368
+ }
310
369
  return Array.from({ length: keys.length }).fill(false);
311
370
  }
312
- const multi = client.multi();
313
- for (const key of keys) {
314
- const prefixedKey = this.createKeyPrefix(key, this._namespace);
315
- multi.exists(prefixedKey);
316
- }
317
- const results = await multi.exec();
318
- return results.map((result) => result === 1);
319
371
  }
320
372
  /**
321
373
  * Get a value from the store. If the key does not exist, it will return undefined.
@@ -324,16 +376,20 @@ var KeyvRedis = class extends import_node_events.default {
324
376
  */
325
377
  async get(key) {
326
378
  const client = await this.getClient();
327
- if (!client) {
328
- this.emit("error", new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */));
329
- return void 0;
330
- }
331
- key = this.createKeyPrefix(key, this._namespace);
332
- const value = await client.get(key);
333
- if (value === null) {
379
+ try {
380
+ key = this.createKeyPrefix(key, this._namespace);
381
+ const value = await client.get(key);
382
+ if (value === null) {
383
+ return void 0;
384
+ }
385
+ return value;
386
+ } catch (error) {
387
+ this.emit("error", error);
388
+ if (this._throwOnErrors) {
389
+ throw error;
390
+ }
334
391
  return void 0;
335
392
  }
336
- return value;
337
393
  }
338
394
  /**
339
395
  * Get many values from the store. If a key does not exist, it will return undefined.
@@ -350,6 +406,9 @@ var KeyvRedis = class extends import_node_events.default {
350
406
  return values;
351
407
  } catch (error) {
352
408
  this.emit("error", error);
409
+ if (this._throwOnErrors) {
410
+ throw error;
411
+ }
353
412
  return Array.from({ length: keys.length }).fill(void 0);
354
413
  }
355
414
  }
@@ -360,14 +419,18 @@ var KeyvRedis = class extends import_node_events.default {
360
419
  */
361
420
  async delete(key) {
362
421
  const client = await this.getClient();
363
- if (!client) {
364
- this.emit("error", new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */));
422
+ try {
423
+ key = this.createKeyPrefix(key, this._namespace);
424
+ let deleted = 0;
425
+ deleted = await (this._useUnlink ? client.unlink(key) : client.del(key));
426
+ return deleted > 0;
427
+ } catch (error) {
428
+ this.emit("error", error);
429
+ if (this._throwOnErrors) {
430
+ throw error;
431
+ }
365
432
  return false;
366
433
  }
367
- key = this.createKeyPrefix(key, this._namespace);
368
- let deleted = 0;
369
- deleted = await (this._useUnlink ? client.unlink(key) : client.del(key));
370
- return deleted > 0;
371
434
  }
372
435
  /**
373
436
  * Delete many keys from the store. This will be done as a single transaction.
@@ -377,23 +440,26 @@ var KeyvRedis = class extends import_node_events.default {
377
440
  async deleteMany(keys) {
378
441
  let result = false;
379
442
  const client = await this.getClient();
380
- if (!client) {
381
- this.emit("error", new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */));
382
- return false;
383
- }
384
- const multi = client.multi();
385
- for (const key of keys) {
386
- const prefixedKey = this.createKeyPrefix(key, this._namespace);
387
- if (this._useUnlink) {
388
- multi.unlink(prefixedKey);
389
- } else {
390
- multi.del(prefixedKey);
443
+ try {
444
+ const multi = client.multi();
445
+ for (const key of keys) {
446
+ const prefixedKey = this.createKeyPrefix(key, this._namespace);
447
+ if (this._useUnlink) {
448
+ multi.unlink(prefixedKey);
449
+ } else {
450
+ multi.del(prefixedKey);
451
+ }
391
452
  }
392
- }
393
- const results = await multi.exec();
394
- for (const deleted of results) {
395
- if (typeof deleted === "number" && deleted > 0) {
396
- result = true;
453
+ const results = await multi.exec();
454
+ for (const deleted of results) {
455
+ if (typeof deleted === "number" && deleted > 0) {
456
+ result = true;
457
+ }
458
+ }
459
+ } catch (error) {
460
+ this.emit("error", error);
461
+ if (this._throwOnErrors) {
462
+ throw error;
397
463
  }
398
464
  }
399
465
  return result;
@@ -406,7 +472,7 @@ var KeyvRedis = class extends import_node_events.default {
406
472
  */
407
473
  async disconnect(force) {
408
474
  if (this._client.isOpen) {
409
- await (force ? this._client.disconnect() : this._client.quit());
475
+ await (force ? this._client.destroy() : this._client.close());
410
476
  }
411
477
  }
412
478
  /**
@@ -448,7 +514,8 @@ var KeyvRedis = class extends import_node_events.default {
448
514
  async getMasterNodes() {
449
515
  if (this.isCluster()) {
450
516
  const cluster = await this.getClient();
451
- return Promise.all(cluster.masters.map(async (main) => cluster.nodeClient(main)));
517
+ const nodes = cluster.masters.map(async (main) => cluster.nodeClient(main));
518
+ return Promise.all(nodes);
452
519
  }
453
520
  return [await this.getClient()];
454
521
  }
@@ -463,7 +530,7 @@ var KeyvRedis = class extends import_node_events.default {
463
530
  const match = namespace ? `${namespace}${this._keyPrefixSeparator}*` : "*";
464
531
  let cursor = "0";
465
532
  do {
466
- const result = await client.scan(Number.parseInt(cursor, 10), { MATCH: match, TYPE: "string" });
533
+ const result = await client.scan(cursor, { MATCH: match, TYPE: "string" });
467
534
  cursor = result.cursor.toString();
468
535
  let { keys } = result;
469
536
  if (!namespace && !this._noNamespaceAffectsAll) {
@@ -500,7 +567,7 @@ var KeyvRedis = class extends import_node_events.default {
500
567
  const match = this._namespace ? `${this._namespace}${this._keyPrefixSeparator}*` : "*";
501
568
  const deletePromises = [];
502
569
  do {
503
- const result = await client.scan(Number.parseInt(cursor, 10), { MATCH: match, COUNT: batchSize, TYPE: "string" });
570
+ const result = await client.scan(cursor, { MATCH: match, COUNT: batchSize, TYPE: "string" });
504
571
  cursor = result.cursor.toString();
505
572
  let { keys } = result;
506
573
  if (keys.length === 0) {
@@ -551,9 +618,6 @@ var KeyvRedis = class extends import_node_events.default {
551
618
  */
552
619
  async getSlotMaster(slot) {
553
620
  const connection = await this.getClient();
554
- if (!connection) {
555
- throw new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */);
556
- }
557
621
  if (this.isCluster()) {
558
622
  const cluster = connection;
559
623
  const mainNode = cluster.slots[slot].master;
@@ -582,10 +646,7 @@ var KeyvRedis = class extends import_node_events.default {
582
646
  return slotMap;
583
647
  }
584
648
  isClientCluster(client) {
585
- if (client.options === void 0 && client.scan === void 0) {
586
- return true;
587
- }
588
- return false;
649
+ return client.slots !== void 0;
589
650
  }
590
651
  setOptions(options) {
591
652
  if (!options) {
@@ -606,14 +667,17 @@ var KeyvRedis = class extends import_node_events.default {
606
667
  if (options.noNamespaceAffectsAll !== void 0) {
607
668
  this._noNamespaceAffectsAll = options.noNamespaceAffectsAll;
608
669
  }
609
- if (options.connectTimeout !== void 0 && options.connectTimeout > 0) {
610
- this._connectTimeout = options.connectTimeout;
670
+ if (options.throwOnConnectError !== void 0) {
671
+ this._throwOnConnectError = options.throwOnConnectError;
672
+ }
673
+ if (options.throwOnErrors !== void 0) {
674
+ this._throwOnErrors = options.throwOnErrors;
675
+ }
676
+ if (options.connectionTimeout !== void 0) {
677
+ this._connectionTimeout = options.connectionTimeout;
611
678
  }
612
679
  }
613
680
  initClient() {
614
- this._client.on("error", (error) => {
615
- this.emit("error", error);
616
- });
617
681
  this._client.on("connect", () => {
618
682
  this.emit("connect", this._client);
619
683
  });
@@ -624,11 +688,55 @@ var KeyvRedis = class extends import_node_events.default {
624
688
  this.emit("reconnecting", reconnectInfo);
625
689
  });
626
690
  }
691
+ async createTimeoutPromise(timeoutMs) {
692
+ return new Promise((_, reject) => (
693
+ // eslint-disable-next-line no-promise-executor-return
694
+ setTimeout(
695
+ () => {
696
+ reject(new Error(`Redis timed out after ${timeoutMs}ms`));
697
+ },
698
+ timeoutMs
699
+ )
700
+ ));
701
+ }
627
702
  };
628
703
  function createKeyv(connect, options) {
629
704
  connect ??= "redis://localhost:6379";
630
705
  const adapter = new KeyvRedis(connect, options);
631
- const keyv = new import_keyv.Keyv(adapter, { namespace: options?.namespace, useKeyPrefix: false });
706
+ if (options?.namespace) {
707
+ adapter.namespace = options.namespace;
708
+ const keyv2 = new import_keyv.Keyv(adapter, { namespace: options?.namespace, useKeyPrefix: false });
709
+ if (options?.throwOnConnectError) {
710
+ keyv2.throwOnErrors = true;
711
+ }
712
+ if (options?.throwOnErrors) {
713
+ keyv2.throwOnErrors = true;
714
+ }
715
+ return keyv2;
716
+ }
717
+ const keyv = new import_keyv.Keyv(adapter, { useKeyPrefix: false });
718
+ if (options?.throwOnConnectError) {
719
+ keyv.throwOnErrors = true;
720
+ }
721
+ if (options?.throwOnErrors) {
722
+ keyv.throwOnErrors = true;
723
+ }
724
+ keyv.namespace = void 0;
725
+ return keyv;
726
+ }
727
+ function createKeyvNonBlocking(connect, options) {
728
+ const keyv = createKeyv(connect, options);
729
+ const keyvStore = keyv.store;
730
+ keyvStore.throwOnConnectError = false;
731
+ keyvStore.throwOnErrors = false;
732
+ const redisClient = keyvStore.client;
733
+ if (redisClient.options) {
734
+ redisClient.options.disableOfflineQueue = true;
735
+ if (redisClient.options.socket) {
736
+ redisClient.options.socket.reconnectStrategy = false;
737
+ }
738
+ }
739
+ keyv.throwOnErrors = false;
632
740
  return keyv;
633
741
  }
634
742
  // Annotate the CommonJS export names for ESM import in node:
@@ -638,5 +746,6 @@ function createKeyv(connect, options) {
638
746
  createClient,
639
747
  createCluster,
640
748
  createKeyv,
749
+ createKeyvNonBlocking,
641
750
  defaultReconnectStrategy
642
751
  });