@keyv/redis 4.4.1 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -3
- package/dist/index.cjs +109 -9
- package/dist/index.d.cts +26 -2
- package/dist/index.d.ts +26 -2
- package/dist/index.js +106 -8
- package/package.json +2 -1
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
|
-
* [
|
|
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 Connection Errors, Retries, and Timeouts](#gracefully-handling-connection-errors-retries-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,71 @@ 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
|
+
* Timeout for connecting to Redis in milliseconds. This is used to prevent hanging indefinitely when connecting to Redis.
|
|
135
|
+
* Defaults to `200`.
|
|
136
|
+
*/
|
|
137
|
+
connectTimeout?: number;
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
You can pass these options when creating a new `KeyvRedis` instance:
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
import Keyv from 'keyv';
|
|
144
|
+
import KeyvRedis from '@keyv/redis';
|
|
145
|
+
|
|
146
|
+
const keyvRedis = new KeyvRedis({
|
|
147
|
+
namespace: 'my-namespace',
|
|
148
|
+
keyPrefixSeparator: ':',
|
|
149
|
+
clearBatchSize: 1000,
|
|
150
|
+
useUnlink: true,
|
|
151
|
+
noNamespaceAffectsAll: false,
|
|
152
|
+
connectTimeout: 200
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const keyv = new Keyv({ store: keyvRedis });
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
You can also set these options after the fact by using the `KeyvRedis` instance properties:
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
import {createKeyv} from '@keyv/redis';
|
|
162
|
+
|
|
163
|
+
const keyv = createKeyv('redis://user:pass@localhost:6379');
|
|
164
|
+
keyv.store.namespace = 'my-namespace';
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
|
|
102
168
|
# Namespaces
|
|
103
169
|
|
|
104
170
|
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 +186,7 @@ keyv.namespace = 'my-namespace';
|
|
|
120
186
|
|
|
121
187
|
NOTE: If you plan to do many clears or deletes, it is recommended to read the [Performance Considerations](#performance-considerations) section.
|
|
122
188
|
|
|
123
|
-
##
|
|
189
|
+
## Using Generic Types
|
|
124
190
|
|
|
125
191
|
When initializing `KeyvRedis`, you can specify the type of the values you are storing and you can also specify types when calling methods:
|
|
126
192
|
|
|
@@ -129,7 +195,7 @@ import Keyv from 'keyv';
|
|
|
129
195
|
import KeyvRedis, { createClient } from '@keyv/redis';
|
|
130
196
|
|
|
131
197
|
|
|
132
|
-
|
|
198
|
+
type User {
|
|
133
199
|
id: number
|
|
134
200
|
name: string
|
|
135
201
|
}
|
|
@@ -175,6 +241,38 @@ const keyv = new Keyv(new KeyvRedis('redis://user:pass@localhost:6379', { useUnl
|
|
|
175
241
|
keyv.useUnlink = false;
|
|
176
242
|
```
|
|
177
243
|
|
|
244
|
+
# Gracefully Handling Connection Errors, Retries, and Timeouts
|
|
245
|
+
|
|
246
|
+
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:
|
|
247
|
+
|
|
248
|
+
```js
|
|
249
|
+
import Keyv from 'keyv';
|
|
250
|
+
import KeyvRedis from '@keyv/redis';
|
|
251
|
+
const keyv = new Keyv(new KeyvRedis('redis://user:pass@localhost:6379'));
|
|
252
|
+
keyv.on('error', (error) => {
|
|
253
|
+
console.error('error', error);
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
We also attempt to connect to Redis and have a `connectTimeout` option that defaults to `200ms`. If the connection is not established within this time, it will emit an error. You can catch this error and handle it accordingly.
|
|
258
|
+
|
|
259
|
+
On `get`, `getMany`, `set`, `setMany`, `delete`, `deleteMany`, and `clear` methods, 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.
|
|
260
|
+
|
|
261
|
+
If you pass in just a `uri` connection string we will automatically create a Redis client for you with the following reconnect strategy:
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
export const defaultReconnectStrategy = (attempts: number): number | Error => {
|
|
265
|
+
// Exponential backoff base: double each time, capped at 2s.
|
|
266
|
+
// Parentheses make it clear we do (2 ** attempts) first, then * 100
|
|
267
|
+
const backoff = Math.min((2 ** attempts) * 100, 2000);
|
|
268
|
+
|
|
269
|
+
// Add random jitter of up to ±50ms to avoid thundering herds:
|
|
270
|
+
const jitter = (Math.random() - 0.5) * 100;
|
|
271
|
+
|
|
272
|
+
return backoff + jitter;
|
|
273
|
+
};
|
|
274
|
+
```
|
|
275
|
+
|
|
178
276
|
# Using Cacheable with Redis
|
|
179
277
|
|
|
180
278
|
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,10 +31,12 @@ 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
42
|
var import_node_events = __toESM(require("events"), 1);
|
|
@@ -43,6 +45,15 @@ 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");
|
|
48
|
+
var RedisErrorMessages = /* @__PURE__ */ ((RedisErrorMessages2) => {
|
|
49
|
+
RedisErrorMessages2["RedisClientNotConnected"] = "Redis client is not connected or has failed to connect";
|
|
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
|
+
};
|
|
46
57
|
var KeyvRedis = class extends import_node_events.default {
|
|
47
58
|
_client = (0, import_client.createClient)();
|
|
48
59
|
_namespace;
|
|
@@ -50,6 +61,10 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
50
61
|
_clearBatchSize = 1e3;
|
|
51
62
|
_useUnlink = true;
|
|
52
63
|
_noNamespaceAffectsAll = false;
|
|
64
|
+
_connectTimeout = 200;
|
|
65
|
+
// Timeout for connecting to Redis in milliseconds
|
|
66
|
+
_reconnectClient = false;
|
|
67
|
+
// Whether to reconnect the client
|
|
53
68
|
/**
|
|
54
69
|
* KeyvRedis constructor.
|
|
55
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.
|
|
@@ -57,9 +72,12 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
57
72
|
*/
|
|
58
73
|
constructor(connect, options) {
|
|
59
74
|
super();
|
|
75
|
+
const socket = {
|
|
76
|
+
reconnectStrategy: defaultReconnectStrategy
|
|
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) {
|
|
@@ -180,18 +198,50 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
180
198
|
set noNamespaceAffectsAll(value) {
|
|
181
199
|
this._noNamespaceAffectsAll = value;
|
|
182
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Get the timeout for connecting to Redis in milliseconds. This is used to prevent hanging indefinitely when connecting to Redis.
|
|
203
|
+
* @default 200
|
|
204
|
+
*/
|
|
205
|
+
get connectTimeout() {
|
|
206
|
+
return this._connectTimeout;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Set the timeout for connecting to Redis in milliseconds. This is used to prevent hanging indefinitely when connecting to Redis.
|
|
210
|
+
* @default 200
|
|
211
|
+
*/
|
|
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
|
+
}
|
|
219
|
+
}
|
|
183
220
|
/**
|
|
184
221
|
* Get the Redis URL used to connect to the server. This is used to get a connected client.
|
|
185
222
|
*/
|
|
186
223
|
async getClient() {
|
|
224
|
+
if (this._client.isOpen && !this._reconnectClient) {
|
|
225
|
+
return this._client;
|
|
226
|
+
}
|
|
227
|
+
if (this._reconnectClient && this._client.isOpen) {
|
|
228
|
+
await this._client.disconnect();
|
|
229
|
+
}
|
|
187
230
|
try {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
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;
|
|
191
241
|
} catch (error) {
|
|
192
242
|
this.emit("error", error);
|
|
243
|
+
return void 0;
|
|
193
244
|
}
|
|
194
|
-
return this._client;
|
|
195
245
|
}
|
|
196
246
|
/**
|
|
197
247
|
* Set a key value pair in the store. TTL is in milliseconds.
|
|
@@ -201,6 +251,10 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
201
251
|
*/
|
|
202
252
|
async set(key, value, ttl) {
|
|
203
253
|
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
|
+
}
|
|
204
258
|
key = this.createKeyPrefix(key, this._namespace);
|
|
205
259
|
if (ttl) {
|
|
206
260
|
await client.set(key, value, { PX: ttl });
|
|
@@ -214,6 +268,10 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
214
268
|
*/
|
|
215
269
|
async setMany(entries) {
|
|
216
270
|
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
|
+
}
|
|
217
275
|
const multi = client.multi();
|
|
218
276
|
for (const { key, value, ttl } of entries) {
|
|
219
277
|
const prefixedKey = this.createKeyPrefix(key, this._namespace);
|
|
@@ -232,6 +290,10 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
232
290
|
*/
|
|
233
291
|
async has(key) {
|
|
234
292
|
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 */));
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
235
297
|
key = this.createKeyPrefix(key, this._namespace);
|
|
236
298
|
const exists = await client.exists(key);
|
|
237
299
|
return exists === 1;
|
|
@@ -243,6 +305,10 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
243
305
|
*/
|
|
244
306
|
async hasMany(keys) {
|
|
245
307
|
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 */));
|
|
310
|
+
return Array.from({ length: keys.length }).fill(false);
|
|
311
|
+
}
|
|
246
312
|
const multi = client.multi();
|
|
247
313
|
for (const key of keys) {
|
|
248
314
|
const prefixedKey = this.createKeyPrefix(key, this._namespace);
|
|
@@ -258,6 +324,10 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
258
324
|
*/
|
|
259
325
|
async get(key) {
|
|
260
326
|
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
|
+
}
|
|
261
331
|
key = this.createKeyPrefix(key, this._namespace);
|
|
262
332
|
const value = await client.get(key);
|
|
263
333
|
if (value === null) {
|
|
@@ -275,8 +345,13 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
275
345
|
return [];
|
|
276
346
|
}
|
|
277
347
|
keys = keys.map((key) => this.createKeyPrefix(key, this._namespace));
|
|
278
|
-
|
|
279
|
-
|
|
348
|
+
try {
|
|
349
|
+
const values = await this.mget(keys);
|
|
350
|
+
return values;
|
|
351
|
+
} catch (error) {
|
|
352
|
+
this.emit("error", error);
|
|
353
|
+
return Array.from({ length: keys.length }).fill(void 0);
|
|
354
|
+
}
|
|
280
355
|
}
|
|
281
356
|
/**
|
|
282
357
|
* Delete a key from the store.
|
|
@@ -285,6 +360,10 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
285
360
|
*/
|
|
286
361
|
async delete(key) {
|
|
287
362
|
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 */));
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
288
367
|
key = this.createKeyPrefix(key, this._namespace);
|
|
289
368
|
let deleted = 0;
|
|
290
369
|
deleted = await (this._useUnlink ? client.unlink(key) : client.del(key));
|
|
@@ -298,6 +377,10 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
298
377
|
async deleteMany(keys) {
|
|
299
378
|
let result = false;
|
|
300
379
|
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
|
+
}
|
|
301
384
|
const multi = client.multi();
|
|
302
385
|
for (const key of keys) {
|
|
303
386
|
const prefixedKey = this.createKeyPrefix(key, this._namespace);
|
|
@@ -468,6 +551,9 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
468
551
|
*/
|
|
469
552
|
async getSlotMaster(slot) {
|
|
470
553
|
const connection = await this.getClient();
|
|
554
|
+
if (!connection) {
|
|
555
|
+
throw new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */);
|
|
556
|
+
}
|
|
471
557
|
if (this.isCluster()) {
|
|
472
558
|
const cluster = connection;
|
|
473
559
|
const mainNode = cluster.slots[slot].master;
|
|
@@ -520,11 +606,23 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
520
606
|
if (options.noNamespaceAffectsAll !== void 0) {
|
|
521
607
|
this._noNamespaceAffectsAll = options.noNamespaceAffectsAll;
|
|
522
608
|
}
|
|
609
|
+
if (options.connectTimeout !== void 0 && options.connectTimeout > 0) {
|
|
610
|
+
this._connectTimeout = options.connectTimeout;
|
|
611
|
+
}
|
|
523
612
|
}
|
|
524
613
|
initClient() {
|
|
525
614
|
this._client.on("error", (error) => {
|
|
526
615
|
this.emit("error", error);
|
|
527
616
|
});
|
|
617
|
+
this._client.on("connect", () => {
|
|
618
|
+
this.emit("connect", this._client);
|
|
619
|
+
});
|
|
620
|
+
this._client.on("disconnect", () => {
|
|
621
|
+
this.emit("disconnect", this._client);
|
|
622
|
+
});
|
|
623
|
+
this._client.on("reconnecting", (reconnectInfo) => {
|
|
624
|
+
this.emit("reconnecting", reconnectInfo);
|
|
625
|
+
});
|
|
528
626
|
}
|
|
529
627
|
};
|
|
530
628
|
function createKeyv(connect, options) {
|
|
@@ -536,7 +634,9 @@ function createKeyv(connect, options) {
|
|
|
536
634
|
// Annotate the CommonJS export names for ESM import in node:
|
|
537
635
|
0 && (module.exports = {
|
|
538
636
|
Keyv,
|
|
637
|
+
RedisErrorMessages,
|
|
539
638
|
createClient,
|
|
540
639
|
createCluster,
|
|
541
|
-
createKeyv
|
|
640
|
+
createKeyv,
|
|
641
|
+
defaultReconnectStrategy
|
|
542
642
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -27,6 +27,11 @@ type KeyvRedisOptions = {
|
|
|
27
27
|
* Defaults to `false`.
|
|
28
28
|
*/
|
|
29
29
|
noNamespaceAffectsAll?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Timeout for connecting to Redis in milliseconds. This is used to prevent hanging indefinitely when connecting to Redis.
|
|
32
|
+
* Defaults to `200`.
|
|
33
|
+
*/
|
|
34
|
+
connectTimeout?: number;
|
|
30
35
|
};
|
|
31
36
|
type KeyvRedisPropertyOptions = KeyvRedisOptions & {
|
|
32
37
|
/**
|
|
@@ -52,6 +57,13 @@ type KeyvRedisEntry<T> = {
|
|
|
52
57
|
*/
|
|
53
58
|
ttl?: number;
|
|
54
59
|
};
|
|
60
|
+
declare enum RedisErrorMessages {
|
|
61
|
+
/**
|
|
62
|
+
* Error message when the Redis client is not connected.
|
|
63
|
+
*/
|
|
64
|
+
RedisClientNotConnected = "Redis client is not connected or has failed to connect"
|
|
65
|
+
}
|
|
66
|
+
declare const defaultReconnectStrategy: (attempts: number) => number | Error;
|
|
55
67
|
type RedisClientConnectionType = RedisClientType | RedisClusterType<RedisModules, RedisFunctions, RedisScripts>;
|
|
56
68
|
declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
|
|
57
69
|
private _client;
|
|
@@ -60,6 +72,8 @@ declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
|
|
|
60
72
|
private _clearBatchSize;
|
|
61
73
|
private _useUnlink;
|
|
62
74
|
private _noNamespaceAffectsAll;
|
|
75
|
+
private _connectTimeout;
|
|
76
|
+
private _reconnectClient;
|
|
63
77
|
/**
|
|
64
78
|
* KeyvRedis constructor.
|
|
65
79
|
* @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,10 +143,20 @@ declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
|
|
|
129
143
|
* Set if not namespace affects all keys.
|
|
130
144
|
*/
|
|
131
145
|
set noNamespaceAffectsAll(value: boolean);
|
|
146
|
+
/**
|
|
147
|
+
* Get the timeout for connecting to Redis in milliseconds. This is used to prevent hanging indefinitely when connecting to Redis.
|
|
148
|
+
* @default 200
|
|
149
|
+
*/
|
|
150
|
+
get connectTimeout(): number;
|
|
151
|
+
/**
|
|
152
|
+
* Set the timeout for connecting to Redis in milliseconds. This is used to prevent hanging indefinitely when connecting to Redis.
|
|
153
|
+
* @default 200
|
|
154
|
+
*/
|
|
155
|
+
set connectTimeout(value: number);
|
|
132
156
|
/**
|
|
133
157
|
* Get the Redis URL used to connect to the server. This is used to get a connected client.
|
|
134
158
|
*/
|
|
135
|
-
getClient(): Promise<RedisClientConnectionType>;
|
|
159
|
+
getClient(): Promise<RedisClientConnectionType | undefined>;
|
|
136
160
|
/**
|
|
137
161
|
* Set a key value pair in the store. TTL is in milliseconds.
|
|
138
162
|
* @param {string} key - the key to set
|
|
@@ -260,4 +284,4 @@ declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
|
|
|
260
284
|
*/
|
|
261
285
|
declare function createKeyv(connect?: string | RedisClientOptions | RedisClientType, options?: KeyvRedisOptions): Keyv;
|
|
262
286
|
|
|
263
|
-
export { type KeyvRedisEntry, type KeyvRedisOptions, type KeyvRedisPropertyOptions, type RedisClientConnectionType, createKeyv, KeyvRedis as default };
|
|
287
|
+
export { type KeyvRedisEntry, type KeyvRedisOptions, type KeyvRedisPropertyOptions, type RedisClientConnectionType, RedisErrorMessages, createKeyv, KeyvRedis as default, defaultReconnectStrategy };
|
package/dist/index.d.ts
CHANGED
|
@@ -27,6 +27,11 @@ type KeyvRedisOptions = {
|
|
|
27
27
|
* Defaults to `false`.
|
|
28
28
|
*/
|
|
29
29
|
noNamespaceAffectsAll?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Timeout for connecting to Redis in milliseconds. This is used to prevent hanging indefinitely when connecting to Redis.
|
|
32
|
+
* Defaults to `200`.
|
|
33
|
+
*/
|
|
34
|
+
connectTimeout?: number;
|
|
30
35
|
};
|
|
31
36
|
type KeyvRedisPropertyOptions = KeyvRedisOptions & {
|
|
32
37
|
/**
|
|
@@ -52,6 +57,13 @@ type KeyvRedisEntry<T> = {
|
|
|
52
57
|
*/
|
|
53
58
|
ttl?: number;
|
|
54
59
|
};
|
|
60
|
+
declare enum RedisErrorMessages {
|
|
61
|
+
/**
|
|
62
|
+
* Error message when the Redis client is not connected.
|
|
63
|
+
*/
|
|
64
|
+
RedisClientNotConnected = "Redis client is not connected or has failed to connect"
|
|
65
|
+
}
|
|
66
|
+
declare const defaultReconnectStrategy: (attempts: number) => number | Error;
|
|
55
67
|
type RedisClientConnectionType = RedisClientType | RedisClusterType<RedisModules, RedisFunctions, RedisScripts>;
|
|
56
68
|
declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
|
|
57
69
|
private _client;
|
|
@@ -60,6 +72,8 @@ declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
|
|
|
60
72
|
private _clearBatchSize;
|
|
61
73
|
private _useUnlink;
|
|
62
74
|
private _noNamespaceAffectsAll;
|
|
75
|
+
private _connectTimeout;
|
|
76
|
+
private _reconnectClient;
|
|
63
77
|
/**
|
|
64
78
|
* KeyvRedis constructor.
|
|
65
79
|
* @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,10 +143,20 @@ declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
|
|
|
129
143
|
* Set if not namespace affects all keys.
|
|
130
144
|
*/
|
|
131
145
|
set noNamespaceAffectsAll(value: boolean);
|
|
146
|
+
/**
|
|
147
|
+
* Get the timeout for connecting to Redis in milliseconds. This is used to prevent hanging indefinitely when connecting to Redis.
|
|
148
|
+
* @default 200
|
|
149
|
+
*/
|
|
150
|
+
get connectTimeout(): number;
|
|
151
|
+
/**
|
|
152
|
+
* Set the timeout for connecting to Redis in milliseconds. This is used to prevent hanging indefinitely when connecting to Redis.
|
|
153
|
+
* @default 200
|
|
154
|
+
*/
|
|
155
|
+
set connectTimeout(value: number);
|
|
132
156
|
/**
|
|
133
157
|
* Get the Redis URL used to connect to the server. This is used to get a connected client.
|
|
134
158
|
*/
|
|
135
|
-
getClient(): Promise<RedisClientConnectionType>;
|
|
159
|
+
getClient(): Promise<RedisClientConnectionType | undefined>;
|
|
136
160
|
/**
|
|
137
161
|
* Set a key value pair in the store. TTL is in milliseconds.
|
|
138
162
|
* @param {string} key - the key to set
|
|
@@ -260,4 +284,4 @@ declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
|
|
|
260
284
|
*/
|
|
261
285
|
declare function createKeyv(connect?: string | RedisClientOptions | RedisClientType, options?: KeyvRedisOptions): Keyv;
|
|
262
286
|
|
|
263
|
-
export { type KeyvRedisEntry, type KeyvRedisOptions, type KeyvRedisPropertyOptions, type RedisClientConnectionType, createKeyv, KeyvRedis as default };
|
|
287
|
+
export { type KeyvRedisEntry, type KeyvRedisOptions, type KeyvRedisPropertyOptions, type RedisClientConnectionType, RedisErrorMessages, createKeyv, KeyvRedis as default, defaultReconnectStrategy };
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,15 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
Keyv as Keyv2
|
|
15
15
|
} from "keyv";
|
|
16
|
+
var RedisErrorMessages = /* @__PURE__ */ ((RedisErrorMessages2) => {
|
|
17
|
+
RedisErrorMessages2["RedisClientNotConnected"] = "Redis client is not connected or has failed to connect";
|
|
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
|
+
};
|
|
16
25
|
var KeyvRedis = class extends EventEmitter {
|
|
17
26
|
_client = createClient();
|
|
18
27
|
_namespace;
|
|
@@ -20,6 +29,10 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
20
29
|
_clearBatchSize = 1e3;
|
|
21
30
|
_useUnlink = true;
|
|
22
31
|
_noNamespaceAffectsAll = false;
|
|
32
|
+
_connectTimeout = 200;
|
|
33
|
+
// Timeout for connecting to Redis in milliseconds
|
|
34
|
+
_reconnectClient = false;
|
|
35
|
+
// Whether to reconnect the client
|
|
23
36
|
/**
|
|
24
37
|
* KeyvRedis constructor.
|
|
25
38
|
* @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 +40,12 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
27
40
|
*/
|
|
28
41
|
constructor(connect, options) {
|
|
29
42
|
super();
|
|
43
|
+
const socket = {
|
|
44
|
+
reconnectStrategy: defaultReconnectStrategy
|
|
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) {
|
|
@@ -150,18 +166,50 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
150
166
|
set noNamespaceAffectsAll(value) {
|
|
151
167
|
this._noNamespaceAffectsAll = value;
|
|
152
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Get the timeout for connecting to Redis in milliseconds. This is used to prevent hanging indefinitely when connecting to Redis.
|
|
171
|
+
* @default 200
|
|
172
|
+
*/
|
|
173
|
+
get connectTimeout() {
|
|
174
|
+
return this._connectTimeout;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Set the timeout for connecting to Redis in milliseconds. This is used to prevent hanging indefinitely when connecting to Redis.
|
|
178
|
+
* @default 200
|
|
179
|
+
*/
|
|
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
|
+
}
|
|
153
188
|
/**
|
|
154
189
|
* Get the Redis URL used to connect to the server. This is used to get a connected client.
|
|
155
190
|
*/
|
|
156
191
|
async getClient() {
|
|
192
|
+
if (this._client.isOpen && !this._reconnectClient) {
|
|
193
|
+
return this._client;
|
|
194
|
+
}
|
|
195
|
+
if (this._reconnectClient && this._client.isOpen) {
|
|
196
|
+
await this._client.disconnect();
|
|
197
|
+
}
|
|
157
198
|
try {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
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;
|
|
161
209
|
} catch (error) {
|
|
162
210
|
this.emit("error", error);
|
|
211
|
+
return void 0;
|
|
163
212
|
}
|
|
164
|
-
return this._client;
|
|
165
213
|
}
|
|
166
214
|
/**
|
|
167
215
|
* Set a key value pair in the store. TTL is in milliseconds.
|
|
@@ -171,6 +219,10 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
171
219
|
*/
|
|
172
220
|
async set(key, value, ttl) {
|
|
173
221
|
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
|
+
}
|
|
174
226
|
key = this.createKeyPrefix(key, this._namespace);
|
|
175
227
|
if (ttl) {
|
|
176
228
|
await client.set(key, value, { PX: ttl });
|
|
@@ -184,6 +236,10 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
184
236
|
*/
|
|
185
237
|
async setMany(entries) {
|
|
186
238
|
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
|
+
}
|
|
187
243
|
const multi = client.multi();
|
|
188
244
|
for (const { key, value, ttl } of entries) {
|
|
189
245
|
const prefixedKey = this.createKeyPrefix(key, this._namespace);
|
|
@@ -202,6 +258,10 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
202
258
|
*/
|
|
203
259
|
async has(key) {
|
|
204
260
|
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 */));
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
205
265
|
key = this.createKeyPrefix(key, this._namespace);
|
|
206
266
|
const exists = await client.exists(key);
|
|
207
267
|
return exists === 1;
|
|
@@ -213,6 +273,10 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
213
273
|
*/
|
|
214
274
|
async hasMany(keys) {
|
|
215
275
|
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 */));
|
|
278
|
+
return Array.from({ length: keys.length }).fill(false);
|
|
279
|
+
}
|
|
216
280
|
const multi = client.multi();
|
|
217
281
|
for (const key of keys) {
|
|
218
282
|
const prefixedKey = this.createKeyPrefix(key, this._namespace);
|
|
@@ -228,6 +292,10 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
228
292
|
*/
|
|
229
293
|
async get(key) {
|
|
230
294
|
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
|
+
}
|
|
231
299
|
key = this.createKeyPrefix(key, this._namespace);
|
|
232
300
|
const value = await client.get(key);
|
|
233
301
|
if (value === null) {
|
|
@@ -245,8 +313,13 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
245
313
|
return [];
|
|
246
314
|
}
|
|
247
315
|
keys = keys.map((key) => this.createKeyPrefix(key, this._namespace));
|
|
248
|
-
|
|
249
|
-
|
|
316
|
+
try {
|
|
317
|
+
const values = await this.mget(keys);
|
|
318
|
+
return values;
|
|
319
|
+
} catch (error) {
|
|
320
|
+
this.emit("error", error);
|
|
321
|
+
return Array.from({ length: keys.length }).fill(void 0);
|
|
322
|
+
}
|
|
250
323
|
}
|
|
251
324
|
/**
|
|
252
325
|
* Delete a key from the store.
|
|
@@ -255,6 +328,10 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
255
328
|
*/
|
|
256
329
|
async delete(key) {
|
|
257
330
|
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 */));
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
258
335
|
key = this.createKeyPrefix(key, this._namespace);
|
|
259
336
|
let deleted = 0;
|
|
260
337
|
deleted = await (this._useUnlink ? client.unlink(key) : client.del(key));
|
|
@@ -268,6 +345,10 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
268
345
|
async deleteMany(keys) {
|
|
269
346
|
let result = false;
|
|
270
347
|
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
|
+
}
|
|
271
352
|
const multi = client.multi();
|
|
272
353
|
for (const key of keys) {
|
|
273
354
|
const prefixedKey = this.createKeyPrefix(key, this._namespace);
|
|
@@ -438,6 +519,9 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
438
519
|
*/
|
|
439
520
|
async getSlotMaster(slot) {
|
|
440
521
|
const connection = await this.getClient();
|
|
522
|
+
if (!connection) {
|
|
523
|
+
throw new Error("Redis client is not connected or has failed to connect" /* RedisClientNotConnected */);
|
|
524
|
+
}
|
|
441
525
|
if (this.isCluster()) {
|
|
442
526
|
const cluster = connection;
|
|
443
527
|
const mainNode = cluster.slots[slot].master;
|
|
@@ -490,11 +574,23 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
490
574
|
if (options.noNamespaceAffectsAll !== void 0) {
|
|
491
575
|
this._noNamespaceAffectsAll = options.noNamespaceAffectsAll;
|
|
492
576
|
}
|
|
577
|
+
if (options.connectTimeout !== void 0 && options.connectTimeout > 0) {
|
|
578
|
+
this._connectTimeout = options.connectTimeout;
|
|
579
|
+
}
|
|
493
580
|
}
|
|
494
581
|
initClient() {
|
|
495
582
|
this._client.on("error", (error) => {
|
|
496
583
|
this.emit("error", error);
|
|
497
584
|
});
|
|
585
|
+
this._client.on("connect", () => {
|
|
586
|
+
this.emit("connect", this._client);
|
|
587
|
+
});
|
|
588
|
+
this._client.on("disconnect", () => {
|
|
589
|
+
this.emit("disconnect", this._client);
|
|
590
|
+
});
|
|
591
|
+
this._client.on("reconnecting", (reconnectInfo) => {
|
|
592
|
+
this.emit("reconnecting", reconnectInfo);
|
|
593
|
+
});
|
|
498
594
|
}
|
|
499
595
|
};
|
|
500
596
|
function createKeyv(connect, options) {
|
|
@@ -505,8 +601,10 @@ function createKeyv(connect, options) {
|
|
|
505
601
|
}
|
|
506
602
|
export {
|
|
507
603
|
Keyv2 as Keyv,
|
|
604
|
+
RedisErrorMessages,
|
|
508
605
|
createClient2 as createClient,
|
|
509
606
|
createCluster2 as createCluster,
|
|
510
607
|
createKeyv,
|
|
511
|
-
KeyvRedis as default
|
|
608
|
+
KeyvRedis as default,
|
|
609
|
+
defaultReconnectStrategy
|
|
512
610
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keyv/redis",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "Redis storage adapter for Keyv",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"keyv": "^5.3.4"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
+
"@faker-js/faker": "^9.8.0",
|
|
44
45
|
"@vitest/coverage-v8": "^3.2.3",
|
|
45
46
|
"rimraf": "^6.0.1",
|
|
46
47
|
"timekeeper": "^2.3.1",
|