@keyv/redis 4.0.2 → 4.2.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 +34 -6
- package/dist/index.cjs +136 -58
- package/dist/index.d.cts +47 -10
- package/dist/index.d.ts +47 -10
- package/dist/index.js +136 -58
- package/package.json +10 -9
package/README.md
CHANGED
|
@@ -26,6 +26,7 @@ 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
30
|
* [Performance Considerations](#performance-considerations)
|
|
30
31
|
* [High Memory Usage on Redis Server](#high-memory-usage-on-redis-server)
|
|
31
32
|
* [Using Cacheable with Redis](#using-cacheable-with-redis)
|
|
@@ -110,14 +111,42 @@ keyv.namespace = 'my-namespace';
|
|
|
110
111
|
|
|
111
112
|
NOTE: If you plan to do many clears or deletes, it is recommended to read the [Performance Considerations](#performance-considerations) section.
|
|
112
113
|
|
|
114
|
+
## Typescript
|
|
115
|
+
|
|
116
|
+
When initializing `KeyvRedis`, you can specify the type of the values you are storing and you can also specify types when calling methods:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import Keyv from 'keyv';
|
|
120
|
+
import KeyvRedis, { createClient } from '@keyv/redis';
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
interface User {
|
|
124
|
+
id: number
|
|
125
|
+
name: string
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const redis = createClient('redis://user:pass@localhost:6379');
|
|
129
|
+
|
|
130
|
+
const keyvRedis = new KeyvRedis<User>(redis);
|
|
131
|
+
const keyv = new Keyv({ store: keyvRedis });
|
|
132
|
+
|
|
133
|
+
await keyv.set("user:1", { id: 1, name: "Alice" })
|
|
134
|
+
const user = await keyv.get("user:1")
|
|
135
|
+
console.log(user.name) // 'Alice'
|
|
136
|
+
|
|
137
|
+
// specify types when calling methods
|
|
138
|
+
const user = await keyv.get<User>("user:1")
|
|
139
|
+
console.log(user.name) // 'Alice'
|
|
140
|
+
```
|
|
141
|
+
|
|
113
142
|
# Performance Considerations
|
|
114
143
|
|
|
115
144
|
With namespaces being prefix based it is critical to understand some of the performance considerations we have made:
|
|
116
|
-
* `clear()` - We use the `SCAN` command to iterate over keys. This is a non-blocking command that is more efficient than `KEYS`. In addition we are using `UNLINK` by default instead of `DEL`. Even with that if you are iterating over a large dataset it can still be slow. It is highly recommended to use the `namespace` option to limit the keys that are being cleared and if possible to not use the `clear()` method in high performance environments.
|
|
145
|
+
* `clear()` - We use the `SCAN` command to iterate over keys. This is a non-blocking command that is more efficient than `KEYS`. In addition we are using `UNLINK` by default instead of `DEL`. Even with that if you are iterating over a large dataset it can still be slow. It is highly recommended to use the `namespace` option to limit the keys that are being cleared and if possible to not use the `clear()` method in high performance environments. If you don't set namespaces, you can enable `noNamespaceAffectsAll` to clear all keys using the `FLUSHDB` command which is faster and can be used in production environments.
|
|
117
146
|
|
|
118
147
|
* `delete()` - By default we are now using `UNLINK` instead of `DEL` for deleting keys. This is a non-blocking command that is more efficient than `DEL`. If you are deleting a large number of keys it is recommended to use the `deleteMany()` method instead of `delete()`.
|
|
119
148
|
|
|
120
|
-
* `clearBatchSize` - The `clearBatchSize` option is set to `1000` by default. This is because Redis has a limit of 1000 keys that can be deleted in a single batch.
|
|
149
|
+
* `clearBatchSize` - The `clearBatchSize` option is set to `1000` by default. This is because Redis has a limit of 1000 keys that can be deleted in a single batch. If no namespace is defined and noNamespaceAffectsAll is set to `true` this option will be ignored and the `FLUSHDB` command will be used instead.
|
|
121
150
|
|
|
122
151
|
* `useUnlink` - This option is set to `true` by default. This is because `UNLINK` is a non-blocking command that is more efficient than `DEL`. If you are not using `UNLINK` and are doing a lot of deletes it is recommended to set this option to `true`.
|
|
123
152
|
|
|
@@ -183,8 +212,6 @@ const cluster = createCluster({
|
|
|
183
212
|
const keyv = new Keyv({ store: new KeyvRedis(cluster) });
|
|
184
213
|
```
|
|
185
214
|
|
|
186
|
-
There are some features that are not supported in clustering such as `clear()` and `iterator()`. This is because the `SCAN` command is not supported in clustering. If you need to clear or delete keys you can use the `deleteMany()` method.
|
|
187
|
-
|
|
188
215
|
You can learn more about the `createCluster` function in the [documentation](https://github.com/redis/node-redis/blob/master/docs/clustering.md) at https://github.com/redis/node-redis/tree/master/docs.
|
|
189
216
|
|
|
190
217
|
Here is an example of how to use TLS:
|
|
@@ -217,6 +244,7 @@ const keyv = new Keyv({ store: new KeyvRedis(tlsOptions) });
|
|
|
217
244
|
* **keyPrefixSeparator** - The separator to use between the namespace and key.
|
|
218
245
|
* **clearBatchSize** - The number of keys to delete in a single batch.
|
|
219
246
|
* **useUnlink** - Use the `UNLINK` command for deleting keys isntead of `DEL`.
|
|
247
|
+
* **noNamespaceAffectsAll**: Whether to allow clearing all keys when no namespace is set (default is `false`).
|
|
220
248
|
* **set** - Set a key.
|
|
221
249
|
* **setMany** - Set multiple keys.
|
|
222
250
|
* **get** - Get a key.
|
|
@@ -225,9 +253,9 @@ const keyv = new Keyv({ store: new KeyvRedis(tlsOptions) });
|
|
|
225
253
|
* **hasMany** - Check if multiple keys exist.
|
|
226
254
|
* **delete** - Delete a key.
|
|
227
255
|
* **deleteMany** - Delete multiple keys.
|
|
228
|
-
* **clear** - Clear all keys. If the
|
|
256
|
+
* **clear** - Clear all keys in the namespace. If the namespace is not set it will clear all keys that are not prefixed with a namespace unless `noNamespaceAffectsAll` is set to `true`.
|
|
229
257
|
* **disconnect** - Disconnect from the Redis server.
|
|
230
|
-
* **iterator** - Create a new iterator for the keys.
|
|
258
|
+
* **iterator** - Create a new iterator for the keys. If the namespace is not set it will iterate over all keys that are not prefixed with a namespace unless `noNamespaceAffectsAll` is set to `true`.
|
|
231
259
|
|
|
232
260
|
# Migrating from v3 to v4
|
|
233
261
|
|
package/dist/index.cjs
CHANGED
|
@@ -40,6 +40,7 @@ module.exports = __toCommonJS(src_exports);
|
|
|
40
40
|
var import_node_events = __toESM(require("events"), 1);
|
|
41
41
|
var import_redis = require("redis");
|
|
42
42
|
var import_keyv = require("keyv");
|
|
43
|
+
var import_cluster_key_slot = __toESM(require("cluster-key-slot"), 1);
|
|
43
44
|
var import_redis2 = require("redis");
|
|
44
45
|
var import_keyv2 = require("keyv");
|
|
45
46
|
var KeyvRedis = class extends import_node_events.default {
|
|
@@ -48,6 +49,7 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
48
49
|
_keyPrefixSeparator = "::";
|
|
49
50
|
_clearBatchSize = 1e3;
|
|
50
51
|
_useUnlink = true;
|
|
52
|
+
_noNamespaceAffectsAll = false;
|
|
51
53
|
/**
|
|
52
54
|
* KeyvRedis constructor.
|
|
53
55
|
* @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.
|
|
@@ -92,6 +94,7 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
92
94
|
namespace: this._namespace,
|
|
93
95
|
keyPrefixSeparator: this._keyPrefixSeparator,
|
|
94
96
|
clearBatchSize: this._clearBatchSize,
|
|
97
|
+
noNamespaceAffectsAll: this._noNamespaceAffectsAll,
|
|
95
98
|
dialect: "redis",
|
|
96
99
|
url
|
|
97
100
|
};
|
|
@@ -155,6 +158,21 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
155
158
|
set useUnlink(value) {
|
|
156
159
|
this._useUnlink = value;
|
|
157
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Get if no namespace affects all keys.
|
|
163
|
+
* Whether to allow clearing all keys when no namespace is set.
|
|
164
|
+
* If set to true and no namespace is set, iterate() will return all keys.
|
|
165
|
+
* @default false
|
|
166
|
+
*/
|
|
167
|
+
get noNamespaceAffectsAll() {
|
|
168
|
+
return this._noNamespaceAffectsAll;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Set if not namespace affects all keys.
|
|
172
|
+
*/
|
|
173
|
+
set noNamespaceAffectsAll(value) {
|
|
174
|
+
this._noNamespaceAffectsAll = value;
|
|
175
|
+
}
|
|
158
176
|
/**
|
|
159
177
|
* Get the Redis URL used to connect to the server. This is used to get a connected client.
|
|
160
178
|
*/
|
|
@@ -242,14 +260,12 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
242
260
|
* @returns {Promise<Array<string | undefined>>} - array of values or undefined if the key does not exist
|
|
243
261
|
*/
|
|
244
262
|
async getMany(keys) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
for (const key of keys) {
|
|
248
|
-
const prefixedKey = this.createKeyPrefix(key, this._namespace);
|
|
249
|
-
multi.get(prefixedKey);
|
|
263
|
+
if (keys.length === 0) {
|
|
264
|
+
return [];
|
|
250
265
|
}
|
|
251
|
-
|
|
252
|
-
|
|
266
|
+
keys = keys.map((key) => this.createKeyPrefix(key, this._namespace));
|
|
267
|
+
const values = await this.mget(keys);
|
|
268
|
+
return values;
|
|
253
269
|
}
|
|
254
270
|
/**
|
|
255
271
|
* Delete a key from the store.
|
|
@@ -329,42 +345,44 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
329
345
|
return this.isClientCluster(this._client);
|
|
330
346
|
}
|
|
331
347
|
/**
|
|
332
|
-
* Get
|
|
333
|
-
*
|
|
334
|
-
* @returns {
|
|
348
|
+
* Get the master nodes in the cluster. If not a cluster, it will return the single client.
|
|
349
|
+
*
|
|
350
|
+
* @returns {Promise<RedisClientType[]>} - array of master nodes
|
|
335
351
|
*/
|
|
336
|
-
async
|
|
352
|
+
async getMasterNodes() {
|
|
337
353
|
if (this.isCluster()) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
yield* this.iteratorClient(namespace);
|
|
354
|
+
const cluster = await this.getClient();
|
|
355
|
+
return Promise.all(cluster.masters.map(async (main) => cluster.nodeClient(main)));
|
|
341
356
|
}
|
|
357
|
+
return [await this.getClient()];
|
|
342
358
|
}
|
|
343
359
|
/**
|
|
344
360
|
* Get an async iterator for the keys and values in the store. If a namespace is provided, it will only iterate over keys with that namespace.
|
|
345
361
|
* @param {string} [namespace] - the namespace to iterate over
|
|
346
362
|
* @returns {AsyncGenerator<[string, T | undefined], void, unknown>} - async iterator with key value pairs
|
|
347
363
|
*/
|
|
348
|
-
async *
|
|
349
|
-
const
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
if (keys.length > 0) {
|
|
360
|
-
const values = await client.mGet(keys);
|
|
361
|
-
for (const [i] of keys.entries()) {
|
|
362
|
-
const key = this.getKeyWithoutPrefix(keys[i], namespace);
|
|
363
|
-
const value = values ? values[i] : void 0;
|
|
364
|
-
yield [key, value];
|
|
364
|
+
async *iterator(namespace) {
|
|
365
|
+
const clients = await this.getMasterNodes();
|
|
366
|
+
for (const client of clients) {
|
|
367
|
+
const match = namespace ? `${namespace}${this._keyPrefixSeparator}*` : "*";
|
|
368
|
+
let cursor = "0";
|
|
369
|
+
do {
|
|
370
|
+
const result = await client.scan(Number.parseInt(cursor, 10), { MATCH: match, TYPE: "string" });
|
|
371
|
+
cursor = result.cursor.toString();
|
|
372
|
+
let { keys } = result;
|
|
373
|
+
if (!namespace && !this._noNamespaceAffectsAll) {
|
|
374
|
+
keys = keys.filter((key) => !key.includes(this._keyPrefixSeparator));
|
|
365
375
|
}
|
|
366
|
-
|
|
367
|
-
|
|
376
|
+
if (keys.length > 0) {
|
|
377
|
+
const values = await this.mget(keys);
|
|
378
|
+
for (const i of keys.keys()) {
|
|
379
|
+
const key = this.getKeyWithoutPrefix(keys[i], namespace);
|
|
380
|
+
const value = values[i];
|
|
381
|
+
yield [key, value];
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
} while (cursor !== "0");
|
|
385
|
+
}
|
|
368
386
|
}
|
|
369
387
|
/**
|
|
370
388
|
* Clear all keys in the store.
|
|
@@ -374,38 +392,95 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
374
392
|
* @returns {Promise<void>}
|
|
375
393
|
*/
|
|
376
394
|
async clear() {
|
|
377
|
-
await (this.isCluster() ? this.clearNamespaceCluster(this._namespace) : this.clearNamespace(this._namespace));
|
|
378
|
-
}
|
|
379
|
-
async clearNamespace(namespace) {
|
|
380
395
|
try {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const result = await client.scan(Number.parseInt(cursor, 10), { MATCH: match, COUNT: batchSize, TYPE: "string" });
|
|
387
|
-
cursor = result.cursor.toString();
|
|
388
|
-
let { keys } = result;
|
|
389
|
-
if (keys.length === 0) {
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
if (!namespace) {
|
|
393
|
-
keys = keys.filter((key) => !key.includes(this._keyPrefixSeparator));
|
|
396
|
+
const clients = await this.getMasterNodes();
|
|
397
|
+
await Promise.all(clients.map(async (client) => {
|
|
398
|
+
if (!this._namespace && this._noNamespaceAffectsAll) {
|
|
399
|
+
await client.flushDb();
|
|
400
|
+
return;
|
|
394
401
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
402
|
+
let cursor = "0";
|
|
403
|
+
const batchSize = this._clearBatchSize;
|
|
404
|
+
const match = this._namespace ? `${this._namespace}${this._keyPrefixSeparator}*` : "*";
|
|
405
|
+
const deletePromises = [];
|
|
406
|
+
do {
|
|
407
|
+
const result = await client.scan(Number.parseInt(cursor, 10), { MATCH: match, COUNT: batchSize, TYPE: "string" });
|
|
408
|
+
cursor = result.cursor.toString();
|
|
409
|
+
let { keys } = result;
|
|
410
|
+
if (keys.length === 0) {
|
|
411
|
+
continue;
|
|
400
412
|
}
|
|
401
|
-
|
|
402
|
-
|
|
413
|
+
if (!this._namespace) {
|
|
414
|
+
keys = keys.filter((key) => !key.includes(this._keyPrefixSeparator));
|
|
415
|
+
}
|
|
416
|
+
deletePromises.push(this.clearWithClusterSupport(keys));
|
|
417
|
+
} while (cursor !== "0");
|
|
418
|
+
await Promise.all(deletePromises);
|
|
419
|
+
}));
|
|
403
420
|
} catch (error) {
|
|
404
421
|
this.emit("error", error);
|
|
405
422
|
}
|
|
406
423
|
}
|
|
407
|
-
|
|
408
|
-
|
|
424
|
+
/**
|
|
425
|
+
* Get many keys. If the instance is a cluster, it will do multiple MGET calls
|
|
426
|
+
* by separating the keys by slot to solve the CROSS-SLOT restriction.
|
|
427
|
+
*/
|
|
428
|
+
async mget(keys) {
|
|
429
|
+
const slotMap = this.getSlotMap(keys);
|
|
430
|
+
const valueMap = /* @__PURE__ */ new Map();
|
|
431
|
+
await Promise.all(Array.from(slotMap.entries(), async ([slot, keys2]) => {
|
|
432
|
+
const client = await this.getSlotMaster(slot);
|
|
433
|
+
const values = await client.mGet(keys2);
|
|
434
|
+
for (const [index, value] of values.entries()) {
|
|
435
|
+
valueMap.set(keys2[index], value ?? void 0);
|
|
436
|
+
}
|
|
437
|
+
}));
|
|
438
|
+
return keys.map((key) => valueMap.get(key));
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Clear all keys in the store with a specific namespace. If the instance is a cluster, it will clear all keys
|
|
442
|
+
* by separating the keys by slot to solve the CROSS-SLOT restriction.
|
|
443
|
+
*/
|
|
444
|
+
async clearWithClusterSupport(keys) {
|
|
445
|
+
if (keys.length > 0) {
|
|
446
|
+
const slotMap = this.getSlotMap(keys);
|
|
447
|
+
await Promise.all(Array.from(slotMap.entries(), async ([slot, keys2]) => {
|
|
448
|
+
const client = await this.getSlotMaster(slot);
|
|
449
|
+
return this._useUnlink ? client.unlink(keys2) : client.del(keys2);
|
|
450
|
+
}));
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Returns the master node client for a given slot or the instance's client if it's not a cluster.
|
|
455
|
+
*/
|
|
456
|
+
async getSlotMaster(slot) {
|
|
457
|
+
const connection = await this.getClient();
|
|
458
|
+
if (this.isCluster()) {
|
|
459
|
+
const cluster = connection;
|
|
460
|
+
const mainNode = cluster.slots[slot].master;
|
|
461
|
+
return cluster.nodeClient(mainNode);
|
|
462
|
+
}
|
|
463
|
+
return connection;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Group keys by their slot.
|
|
467
|
+
*
|
|
468
|
+
* @param {string[]} keys - the keys to group
|
|
469
|
+
* @returns {Map<number, string[]>} - map of slot to keys
|
|
470
|
+
*/
|
|
471
|
+
getSlotMap(keys) {
|
|
472
|
+
const slotMap = /* @__PURE__ */ new Map();
|
|
473
|
+
if (this.isCluster()) {
|
|
474
|
+
for (const key of keys) {
|
|
475
|
+
const slot = (0, import_cluster_key_slot.default)(key);
|
|
476
|
+
const slotKeys = slotMap.get(slot) ?? [];
|
|
477
|
+
slotKeys.push(key);
|
|
478
|
+
slotMap.set(slot, slotKeys);
|
|
479
|
+
}
|
|
480
|
+
} else {
|
|
481
|
+
slotMap.set(0, keys);
|
|
482
|
+
}
|
|
483
|
+
return slotMap;
|
|
409
484
|
}
|
|
410
485
|
isClientCluster(client) {
|
|
411
486
|
if (client.options === void 0 && client.scan === void 0) {
|
|
@@ -429,6 +504,9 @@ var KeyvRedis = class extends import_node_events.default {
|
|
|
429
504
|
if (options.useUnlink !== void 0) {
|
|
430
505
|
this._useUnlink = options.useUnlink;
|
|
431
506
|
}
|
|
507
|
+
if (options.noNamespaceAffectsAll !== void 0) {
|
|
508
|
+
this._noNamespaceAffectsAll = options.noNamespaceAffectsAll;
|
|
509
|
+
}
|
|
432
510
|
}
|
|
433
511
|
initClient() {
|
|
434
512
|
this._client.on("error", (error) => {
|
package/dist/index.d.cts
CHANGED
|
@@ -21,6 +21,12 @@ type KeyvRedisOptions = {
|
|
|
21
21
|
* Enable Unlink instead of using Del for clearing keys. This is more performant but may not be supported by all Redis versions.
|
|
22
22
|
*/
|
|
23
23
|
useUnlink?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether to allow clearing all keys when no namespace is set.
|
|
26
|
+
* If set to true and no namespace is set, iterate() will return all keys.
|
|
27
|
+
* Defaults to `false`.
|
|
28
|
+
*/
|
|
29
|
+
noNamespaceAffectsAll?: boolean;
|
|
24
30
|
};
|
|
25
31
|
type KeyvRedisPropertyOptions = KeyvRedisOptions & {
|
|
26
32
|
/**
|
|
@@ -47,12 +53,13 @@ type KeyvRedisEntry<T> = {
|
|
|
47
53
|
ttl?: number;
|
|
48
54
|
};
|
|
49
55
|
type RedisClientConnectionType = RedisClientType | RedisClusterType<RedisModules, RedisFunctions, RedisScripts>;
|
|
50
|
-
declare class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
56
|
+
declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
|
|
51
57
|
private _client;
|
|
52
58
|
private _namespace;
|
|
53
59
|
private _keyPrefixSeparator;
|
|
54
60
|
private _clearBatchSize;
|
|
55
61
|
private _useUnlink;
|
|
62
|
+
private _noNamespaceAffectsAll;
|
|
56
63
|
/**
|
|
57
64
|
* KeyvRedis constructor.
|
|
58
65
|
* @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.
|
|
@@ -111,6 +118,17 @@ declare class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
111
118
|
* Set if Unlink is used instead of Del for clearing keys. This is more performant but may not be supported by all Redis versions.
|
|
112
119
|
*/
|
|
113
120
|
set useUnlink(value: boolean);
|
|
121
|
+
/**
|
|
122
|
+
* Get if no namespace affects all keys.
|
|
123
|
+
* Whether to allow clearing all keys when no namespace is set.
|
|
124
|
+
* If set to true and no namespace is set, iterate() will return all keys.
|
|
125
|
+
* @default false
|
|
126
|
+
*/
|
|
127
|
+
get noNamespaceAffectsAll(): boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Set if not namespace affects all keys.
|
|
130
|
+
*/
|
|
131
|
+
set noNamespaceAffectsAll(value: boolean);
|
|
114
132
|
/**
|
|
115
133
|
* Get the Redis URL used to connect to the server. This is used to get a connected client.
|
|
116
134
|
*/
|
|
@@ -144,13 +162,13 @@ declare class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
144
162
|
* @param {string} key - the key to get
|
|
145
163
|
* @returns {Promise<string | undefined>} - the value or undefined if the key does not exist
|
|
146
164
|
*/
|
|
147
|
-
get<T>(key: string): Promise<
|
|
165
|
+
get<U = T>(key: string): Promise<U | undefined>;
|
|
148
166
|
/**
|
|
149
167
|
* Get many values from the store. If a key does not exist, it will return undefined.
|
|
150
168
|
* @param {Array<string>} keys - the keys to get
|
|
151
169
|
* @returns {Promise<Array<string | undefined>>} - array of values or undefined if the key does not exist
|
|
152
170
|
*/
|
|
153
|
-
getMany<T>(keys: string[]): Promise<Array<
|
|
171
|
+
getMany<U = T>(keys: string[]): Promise<Array<U | undefined>>;
|
|
154
172
|
/**
|
|
155
173
|
* Delete a key from the store.
|
|
156
174
|
* @param {string} key - the key to delete
|
|
@@ -188,17 +206,17 @@ declare class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
188
206
|
*/
|
|
189
207
|
isCluster(): boolean;
|
|
190
208
|
/**
|
|
191
|
-
* Get
|
|
192
|
-
*
|
|
193
|
-
* @returns {
|
|
209
|
+
* Get the master nodes in the cluster. If not a cluster, it will return the single client.
|
|
210
|
+
*
|
|
211
|
+
* @returns {Promise<RedisClientType[]>} - array of master nodes
|
|
194
212
|
*/
|
|
195
|
-
|
|
213
|
+
getMasterNodes(): Promise<RedisClientType[]>;
|
|
196
214
|
/**
|
|
197
215
|
* Get an async iterator for the keys and values in the store. If a namespace is provided, it will only iterate over keys with that namespace.
|
|
198
216
|
* @param {string} [namespace] - the namespace to iterate over
|
|
199
217
|
* @returns {AsyncGenerator<[string, T | undefined], void, unknown>} - async iterator with key value pairs
|
|
200
218
|
*/
|
|
201
|
-
|
|
219
|
+
iterator<U = T>(namespace?: string): AsyncGenerator<[string, U | undefined], void, unknown>;
|
|
202
220
|
/**
|
|
203
221
|
* Clear all keys in the store.
|
|
204
222
|
* IMPORTANT: this can cause performance issues if there are a large number of keys in the store and worse with clusters. Use with caution as not recommended for production.
|
|
@@ -207,8 +225,27 @@ declare class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
207
225
|
* @returns {Promise<void>}
|
|
208
226
|
*/
|
|
209
227
|
clear(): Promise<void>;
|
|
210
|
-
|
|
211
|
-
|
|
228
|
+
/**
|
|
229
|
+
* Get many keys. If the instance is a cluster, it will do multiple MGET calls
|
|
230
|
+
* by separating the keys by slot to solve the CROSS-SLOT restriction.
|
|
231
|
+
*/
|
|
232
|
+
private mget;
|
|
233
|
+
/**
|
|
234
|
+
* Clear all keys in the store with a specific namespace. If the instance is a cluster, it will clear all keys
|
|
235
|
+
* by separating the keys by slot to solve the CROSS-SLOT restriction.
|
|
236
|
+
*/
|
|
237
|
+
private clearWithClusterSupport;
|
|
238
|
+
/**
|
|
239
|
+
* Returns the master node client for a given slot or the instance's client if it's not a cluster.
|
|
240
|
+
*/
|
|
241
|
+
private getSlotMaster;
|
|
242
|
+
/**
|
|
243
|
+
* Group keys by their slot.
|
|
244
|
+
*
|
|
245
|
+
* @param {string[]} keys - the keys to group
|
|
246
|
+
* @returns {Map<number, string[]>} - map of slot to keys
|
|
247
|
+
*/
|
|
248
|
+
private getSlotMap;
|
|
212
249
|
private isClientCluster;
|
|
213
250
|
private setOptions;
|
|
214
251
|
private initClient;
|
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,12 @@ type KeyvRedisOptions = {
|
|
|
21
21
|
* Enable Unlink instead of using Del for clearing keys. This is more performant but may not be supported by all Redis versions.
|
|
22
22
|
*/
|
|
23
23
|
useUnlink?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether to allow clearing all keys when no namespace is set.
|
|
26
|
+
* If set to true and no namespace is set, iterate() will return all keys.
|
|
27
|
+
* Defaults to `false`.
|
|
28
|
+
*/
|
|
29
|
+
noNamespaceAffectsAll?: boolean;
|
|
24
30
|
};
|
|
25
31
|
type KeyvRedisPropertyOptions = KeyvRedisOptions & {
|
|
26
32
|
/**
|
|
@@ -47,12 +53,13 @@ type KeyvRedisEntry<T> = {
|
|
|
47
53
|
ttl?: number;
|
|
48
54
|
};
|
|
49
55
|
type RedisClientConnectionType = RedisClientType | RedisClusterType<RedisModules, RedisFunctions, RedisScripts>;
|
|
50
|
-
declare class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
56
|
+
declare class KeyvRedis<T> extends EventEmitter implements KeyvStoreAdapter {
|
|
51
57
|
private _client;
|
|
52
58
|
private _namespace;
|
|
53
59
|
private _keyPrefixSeparator;
|
|
54
60
|
private _clearBatchSize;
|
|
55
61
|
private _useUnlink;
|
|
62
|
+
private _noNamespaceAffectsAll;
|
|
56
63
|
/**
|
|
57
64
|
* KeyvRedis constructor.
|
|
58
65
|
* @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.
|
|
@@ -111,6 +118,17 @@ declare class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
111
118
|
* Set if Unlink is used instead of Del for clearing keys. This is more performant but may not be supported by all Redis versions.
|
|
112
119
|
*/
|
|
113
120
|
set useUnlink(value: boolean);
|
|
121
|
+
/**
|
|
122
|
+
* Get if no namespace affects all keys.
|
|
123
|
+
* Whether to allow clearing all keys when no namespace is set.
|
|
124
|
+
* If set to true and no namespace is set, iterate() will return all keys.
|
|
125
|
+
* @default false
|
|
126
|
+
*/
|
|
127
|
+
get noNamespaceAffectsAll(): boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Set if not namespace affects all keys.
|
|
130
|
+
*/
|
|
131
|
+
set noNamespaceAffectsAll(value: boolean);
|
|
114
132
|
/**
|
|
115
133
|
* Get the Redis URL used to connect to the server. This is used to get a connected client.
|
|
116
134
|
*/
|
|
@@ -144,13 +162,13 @@ declare class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
144
162
|
* @param {string} key - the key to get
|
|
145
163
|
* @returns {Promise<string | undefined>} - the value or undefined if the key does not exist
|
|
146
164
|
*/
|
|
147
|
-
get<T>(key: string): Promise<
|
|
165
|
+
get<U = T>(key: string): Promise<U | undefined>;
|
|
148
166
|
/**
|
|
149
167
|
* Get many values from the store. If a key does not exist, it will return undefined.
|
|
150
168
|
* @param {Array<string>} keys - the keys to get
|
|
151
169
|
* @returns {Promise<Array<string | undefined>>} - array of values or undefined if the key does not exist
|
|
152
170
|
*/
|
|
153
|
-
getMany<T>(keys: string[]): Promise<Array<
|
|
171
|
+
getMany<U = T>(keys: string[]): Promise<Array<U | undefined>>;
|
|
154
172
|
/**
|
|
155
173
|
* Delete a key from the store.
|
|
156
174
|
* @param {string} key - the key to delete
|
|
@@ -188,17 +206,17 @@ declare class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
188
206
|
*/
|
|
189
207
|
isCluster(): boolean;
|
|
190
208
|
/**
|
|
191
|
-
* Get
|
|
192
|
-
*
|
|
193
|
-
* @returns {
|
|
209
|
+
* Get the master nodes in the cluster. If not a cluster, it will return the single client.
|
|
210
|
+
*
|
|
211
|
+
* @returns {Promise<RedisClientType[]>} - array of master nodes
|
|
194
212
|
*/
|
|
195
|
-
|
|
213
|
+
getMasterNodes(): Promise<RedisClientType[]>;
|
|
196
214
|
/**
|
|
197
215
|
* Get an async iterator for the keys and values in the store. If a namespace is provided, it will only iterate over keys with that namespace.
|
|
198
216
|
* @param {string} [namespace] - the namespace to iterate over
|
|
199
217
|
* @returns {AsyncGenerator<[string, T | undefined], void, unknown>} - async iterator with key value pairs
|
|
200
218
|
*/
|
|
201
|
-
|
|
219
|
+
iterator<U = T>(namespace?: string): AsyncGenerator<[string, U | undefined], void, unknown>;
|
|
202
220
|
/**
|
|
203
221
|
* Clear all keys in the store.
|
|
204
222
|
* IMPORTANT: this can cause performance issues if there are a large number of keys in the store and worse with clusters. Use with caution as not recommended for production.
|
|
@@ -207,8 +225,27 @@ declare class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
207
225
|
* @returns {Promise<void>}
|
|
208
226
|
*/
|
|
209
227
|
clear(): Promise<void>;
|
|
210
|
-
|
|
211
|
-
|
|
228
|
+
/**
|
|
229
|
+
* Get many keys. If the instance is a cluster, it will do multiple MGET calls
|
|
230
|
+
* by separating the keys by slot to solve the CROSS-SLOT restriction.
|
|
231
|
+
*/
|
|
232
|
+
private mget;
|
|
233
|
+
/**
|
|
234
|
+
* Clear all keys in the store with a specific namespace. If the instance is a cluster, it will clear all keys
|
|
235
|
+
* by separating the keys by slot to solve the CROSS-SLOT restriction.
|
|
236
|
+
*/
|
|
237
|
+
private clearWithClusterSupport;
|
|
238
|
+
/**
|
|
239
|
+
* Returns the master node client for a given slot or the instance's client if it's not a cluster.
|
|
240
|
+
*/
|
|
241
|
+
private getSlotMaster;
|
|
242
|
+
/**
|
|
243
|
+
* Group keys by their slot.
|
|
244
|
+
*
|
|
245
|
+
* @param {string[]} keys - the keys to group
|
|
246
|
+
* @returns {Map<number, string[]>} - map of slot to keys
|
|
247
|
+
*/
|
|
248
|
+
private getSlotMap;
|
|
212
249
|
private isClientCluster;
|
|
213
250
|
private setOptions;
|
|
214
251
|
private initClient;
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
createCluster
|
|
6
6
|
} from "redis";
|
|
7
7
|
import { Keyv } from "keyv";
|
|
8
|
+
import calculateSlot from "cluster-key-slot";
|
|
8
9
|
import {
|
|
9
10
|
createClient as createClient2,
|
|
10
11
|
createCluster as createCluster2
|
|
@@ -18,6 +19,7 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
18
19
|
_keyPrefixSeparator = "::";
|
|
19
20
|
_clearBatchSize = 1e3;
|
|
20
21
|
_useUnlink = true;
|
|
22
|
+
_noNamespaceAffectsAll = false;
|
|
21
23
|
/**
|
|
22
24
|
* KeyvRedis constructor.
|
|
23
25
|
* @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.
|
|
@@ -62,6 +64,7 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
62
64
|
namespace: this._namespace,
|
|
63
65
|
keyPrefixSeparator: this._keyPrefixSeparator,
|
|
64
66
|
clearBatchSize: this._clearBatchSize,
|
|
67
|
+
noNamespaceAffectsAll: this._noNamespaceAffectsAll,
|
|
65
68
|
dialect: "redis",
|
|
66
69
|
url
|
|
67
70
|
};
|
|
@@ -125,6 +128,21 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
125
128
|
set useUnlink(value) {
|
|
126
129
|
this._useUnlink = value;
|
|
127
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Get if no namespace affects all keys.
|
|
133
|
+
* Whether to allow clearing all keys when no namespace is set.
|
|
134
|
+
* If set to true and no namespace is set, iterate() will return all keys.
|
|
135
|
+
* @default false
|
|
136
|
+
*/
|
|
137
|
+
get noNamespaceAffectsAll() {
|
|
138
|
+
return this._noNamespaceAffectsAll;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Set if not namespace affects all keys.
|
|
142
|
+
*/
|
|
143
|
+
set noNamespaceAffectsAll(value) {
|
|
144
|
+
this._noNamespaceAffectsAll = value;
|
|
145
|
+
}
|
|
128
146
|
/**
|
|
129
147
|
* Get the Redis URL used to connect to the server. This is used to get a connected client.
|
|
130
148
|
*/
|
|
@@ -212,14 +230,12 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
212
230
|
* @returns {Promise<Array<string | undefined>>} - array of values or undefined if the key does not exist
|
|
213
231
|
*/
|
|
214
232
|
async getMany(keys) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
for (const key of keys) {
|
|
218
|
-
const prefixedKey = this.createKeyPrefix(key, this._namespace);
|
|
219
|
-
multi.get(prefixedKey);
|
|
233
|
+
if (keys.length === 0) {
|
|
234
|
+
return [];
|
|
220
235
|
}
|
|
221
|
-
|
|
222
|
-
|
|
236
|
+
keys = keys.map((key) => this.createKeyPrefix(key, this._namespace));
|
|
237
|
+
const values = await this.mget(keys);
|
|
238
|
+
return values;
|
|
223
239
|
}
|
|
224
240
|
/**
|
|
225
241
|
* Delete a key from the store.
|
|
@@ -299,42 +315,44 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
299
315
|
return this.isClientCluster(this._client);
|
|
300
316
|
}
|
|
301
317
|
/**
|
|
302
|
-
* Get
|
|
303
|
-
*
|
|
304
|
-
* @returns {
|
|
318
|
+
* Get the master nodes in the cluster. If not a cluster, it will return the single client.
|
|
319
|
+
*
|
|
320
|
+
* @returns {Promise<RedisClientType[]>} - array of master nodes
|
|
305
321
|
*/
|
|
306
|
-
async
|
|
322
|
+
async getMasterNodes() {
|
|
307
323
|
if (this.isCluster()) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
yield* this.iteratorClient(namespace);
|
|
324
|
+
const cluster = await this.getClient();
|
|
325
|
+
return Promise.all(cluster.masters.map(async (main) => cluster.nodeClient(main)));
|
|
311
326
|
}
|
|
327
|
+
return [await this.getClient()];
|
|
312
328
|
}
|
|
313
329
|
/**
|
|
314
330
|
* Get an async iterator for the keys and values in the store. If a namespace is provided, it will only iterate over keys with that namespace.
|
|
315
331
|
* @param {string} [namespace] - the namespace to iterate over
|
|
316
332
|
* @returns {AsyncGenerator<[string, T | undefined], void, unknown>} - async iterator with key value pairs
|
|
317
333
|
*/
|
|
318
|
-
async *
|
|
319
|
-
const
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (keys.length > 0) {
|
|
330
|
-
const values = await client.mGet(keys);
|
|
331
|
-
for (const [i] of keys.entries()) {
|
|
332
|
-
const key = this.getKeyWithoutPrefix(keys[i], namespace);
|
|
333
|
-
const value = values ? values[i] : void 0;
|
|
334
|
-
yield [key, value];
|
|
334
|
+
async *iterator(namespace) {
|
|
335
|
+
const clients = await this.getMasterNodes();
|
|
336
|
+
for (const client of clients) {
|
|
337
|
+
const match = namespace ? `${namespace}${this._keyPrefixSeparator}*` : "*";
|
|
338
|
+
let cursor = "0";
|
|
339
|
+
do {
|
|
340
|
+
const result = await client.scan(Number.parseInt(cursor, 10), { MATCH: match, TYPE: "string" });
|
|
341
|
+
cursor = result.cursor.toString();
|
|
342
|
+
let { keys } = result;
|
|
343
|
+
if (!namespace && !this._noNamespaceAffectsAll) {
|
|
344
|
+
keys = keys.filter((key) => !key.includes(this._keyPrefixSeparator));
|
|
335
345
|
}
|
|
336
|
-
|
|
337
|
-
|
|
346
|
+
if (keys.length > 0) {
|
|
347
|
+
const values = await this.mget(keys);
|
|
348
|
+
for (const i of keys.keys()) {
|
|
349
|
+
const key = this.getKeyWithoutPrefix(keys[i], namespace);
|
|
350
|
+
const value = values[i];
|
|
351
|
+
yield [key, value];
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
} while (cursor !== "0");
|
|
355
|
+
}
|
|
338
356
|
}
|
|
339
357
|
/**
|
|
340
358
|
* Clear all keys in the store.
|
|
@@ -344,38 +362,95 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
344
362
|
* @returns {Promise<void>}
|
|
345
363
|
*/
|
|
346
364
|
async clear() {
|
|
347
|
-
await (this.isCluster() ? this.clearNamespaceCluster(this._namespace) : this.clearNamespace(this._namespace));
|
|
348
|
-
}
|
|
349
|
-
async clearNamespace(namespace) {
|
|
350
365
|
try {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const result = await client.scan(Number.parseInt(cursor, 10), { MATCH: match, COUNT: batchSize, TYPE: "string" });
|
|
357
|
-
cursor = result.cursor.toString();
|
|
358
|
-
let { keys } = result;
|
|
359
|
-
if (keys.length === 0) {
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
if (!namespace) {
|
|
363
|
-
keys = keys.filter((key) => !key.includes(this._keyPrefixSeparator));
|
|
366
|
+
const clients = await this.getMasterNodes();
|
|
367
|
+
await Promise.all(clients.map(async (client) => {
|
|
368
|
+
if (!this._namespace && this._noNamespaceAffectsAll) {
|
|
369
|
+
await client.flushDb();
|
|
370
|
+
return;
|
|
364
371
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
372
|
+
let cursor = "0";
|
|
373
|
+
const batchSize = this._clearBatchSize;
|
|
374
|
+
const match = this._namespace ? `${this._namespace}${this._keyPrefixSeparator}*` : "*";
|
|
375
|
+
const deletePromises = [];
|
|
376
|
+
do {
|
|
377
|
+
const result = await client.scan(Number.parseInt(cursor, 10), { MATCH: match, COUNT: batchSize, TYPE: "string" });
|
|
378
|
+
cursor = result.cursor.toString();
|
|
379
|
+
let { keys } = result;
|
|
380
|
+
if (keys.length === 0) {
|
|
381
|
+
continue;
|
|
370
382
|
}
|
|
371
|
-
|
|
372
|
-
|
|
383
|
+
if (!this._namespace) {
|
|
384
|
+
keys = keys.filter((key) => !key.includes(this._keyPrefixSeparator));
|
|
385
|
+
}
|
|
386
|
+
deletePromises.push(this.clearWithClusterSupport(keys));
|
|
387
|
+
} while (cursor !== "0");
|
|
388
|
+
await Promise.all(deletePromises);
|
|
389
|
+
}));
|
|
373
390
|
} catch (error) {
|
|
374
391
|
this.emit("error", error);
|
|
375
392
|
}
|
|
376
393
|
}
|
|
377
|
-
|
|
378
|
-
|
|
394
|
+
/**
|
|
395
|
+
* Get many keys. If the instance is a cluster, it will do multiple MGET calls
|
|
396
|
+
* by separating the keys by slot to solve the CROSS-SLOT restriction.
|
|
397
|
+
*/
|
|
398
|
+
async mget(keys) {
|
|
399
|
+
const slotMap = this.getSlotMap(keys);
|
|
400
|
+
const valueMap = /* @__PURE__ */ new Map();
|
|
401
|
+
await Promise.all(Array.from(slotMap.entries(), async ([slot, keys2]) => {
|
|
402
|
+
const client = await this.getSlotMaster(slot);
|
|
403
|
+
const values = await client.mGet(keys2);
|
|
404
|
+
for (const [index, value] of values.entries()) {
|
|
405
|
+
valueMap.set(keys2[index], value ?? void 0);
|
|
406
|
+
}
|
|
407
|
+
}));
|
|
408
|
+
return keys.map((key) => valueMap.get(key));
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Clear all keys in the store with a specific namespace. If the instance is a cluster, it will clear all keys
|
|
412
|
+
* by separating the keys by slot to solve the CROSS-SLOT restriction.
|
|
413
|
+
*/
|
|
414
|
+
async clearWithClusterSupport(keys) {
|
|
415
|
+
if (keys.length > 0) {
|
|
416
|
+
const slotMap = this.getSlotMap(keys);
|
|
417
|
+
await Promise.all(Array.from(slotMap.entries(), async ([slot, keys2]) => {
|
|
418
|
+
const client = await this.getSlotMaster(slot);
|
|
419
|
+
return this._useUnlink ? client.unlink(keys2) : client.del(keys2);
|
|
420
|
+
}));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Returns the master node client for a given slot or the instance's client if it's not a cluster.
|
|
425
|
+
*/
|
|
426
|
+
async getSlotMaster(slot) {
|
|
427
|
+
const connection = await this.getClient();
|
|
428
|
+
if (this.isCluster()) {
|
|
429
|
+
const cluster = connection;
|
|
430
|
+
const mainNode = cluster.slots[slot].master;
|
|
431
|
+
return cluster.nodeClient(mainNode);
|
|
432
|
+
}
|
|
433
|
+
return connection;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Group keys by their slot.
|
|
437
|
+
*
|
|
438
|
+
* @param {string[]} keys - the keys to group
|
|
439
|
+
* @returns {Map<number, string[]>} - map of slot to keys
|
|
440
|
+
*/
|
|
441
|
+
getSlotMap(keys) {
|
|
442
|
+
const slotMap = /* @__PURE__ */ new Map();
|
|
443
|
+
if (this.isCluster()) {
|
|
444
|
+
for (const key of keys) {
|
|
445
|
+
const slot = calculateSlot(key);
|
|
446
|
+
const slotKeys = slotMap.get(slot) ?? [];
|
|
447
|
+
slotKeys.push(key);
|
|
448
|
+
slotMap.set(slot, slotKeys);
|
|
449
|
+
}
|
|
450
|
+
} else {
|
|
451
|
+
slotMap.set(0, keys);
|
|
452
|
+
}
|
|
453
|
+
return slotMap;
|
|
379
454
|
}
|
|
380
455
|
isClientCluster(client) {
|
|
381
456
|
if (client.options === void 0 && client.scan === void 0) {
|
|
@@ -399,6 +474,9 @@ var KeyvRedis = class extends EventEmitter {
|
|
|
399
474
|
if (options.useUnlink !== void 0) {
|
|
400
475
|
this._useUnlink = options.useUnlink;
|
|
401
476
|
}
|
|
477
|
+
if (options.noNamespaceAffectsAll !== void 0) {
|
|
478
|
+
this._noNamespaceAffectsAll = options.noNamespaceAffectsAll;
|
|
479
|
+
}
|
|
402
480
|
}
|
|
403
481
|
initClient() {
|
|
404
482
|
this._client.on("error", (error) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keyv/redis",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "Redis storage adapter for Keyv",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -34,17 +34,18 @@
|
|
|
34
34
|
},
|
|
35
35
|
"homepage": "https://github.com/jaredwray/keyv",
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"
|
|
38
|
-
"redis": "^4.7.0"
|
|
37
|
+
"cluster-key-slot": "^1.1.2",
|
|
38
|
+
"redis": "^4.7.0",
|
|
39
|
+
"keyv": "^5.2.2"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
|
-
"@
|
|
42
|
-
"@vitest/coverage-v8": "^2.1.5",
|
|
42
|
+
"@vitest/coverage-v8": "^2.1.8",
|
|
43
43
|
"rimraf": "^6.0.1",
|
|
44
44
|
"timekeeper": "^2.3.1",
|
|
45
45
|
"tsd": "^0.31.2",
|
|
46
|
-
"vitest": "^2.1.
|
|
47
|
-
"xo": "^0.
|
|
46
|
+
"vitest": "^2.1.8",
|
|
47
|
+
"xo": "^0.60.0",
|
|
48
|
+
"@keyv/test-suite": "^2.0.3"
|
|
48
49
|
},
|
|
49
50
|
"tsd": {
|
|
50
51
|
"directory": "test"
|
|
@@ -58,8 +59,8 @@
|
|
|
58
59
|
],
|
|
59
60
|
"scripts": {
|
|
60
61
|
"build": "rimraf ./dist && tsup src/index.ts --format cjs,esm --dts --clean",
|
|
61
|
-
"test": "xo --fix && vitest run --coverage",
|
|
62
|
-
"test:ci": "xo && vitest --run --sequence.setupFiles=list",
|
|
62
|
+
"test": "xo --fix && vitest run --coverage --typecheck",
|
|
63
|
+
"test:ci": "xo && vitest --run --sequence.setupFiles=list --typecheck",
|
|
63
64
|
"clean": "rimraf ./node_modules ./coverage ./dist"
|
|
64
65
|
}
|
|
65
66
|
}
|