@keyv/redis 5.0.0 → 5.1.1
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 +264 -19
- package/dist/index.cjs +99 -50
- package/dist/index.d.cts +13 -6
- package/dist/index.d.ts +13 -6
- package/dist/index.js +103 -55
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -35,11 +35,15 @@ Redis storage adapter for [Keyv](https://github.com/jaredwray/keyv).
|
|
|
35
35
|
* [High Memory Usage on Redis Server](#high-memory-usage-on-redis-server)
|
|
36
36
|
* [Gracefully Handling Errors and Timeouts](#gracefully-handling-errors-and-timeouts)
|
|
37
37
|
* [Using Cacheable with Redis](#using-cacheable-with-redis)
|
|
38
|
-
* [Clustering
|
|
38
|
+
* [Clustering](#clustering)
|
|
39
|
+
* [Sentinel](#sentinel)
|
|
40
|
+
* [TLS Support](#tls-support)
|
|
41
|
+
* [Keyv Redis Options](#keyv-redis-options)
|
|
39
42
|
* [API](#api)
|
|
40
43
|
* [Using Custom Redis Client Events](#using-custom-redis-client-events)
|
|
41
44
|
* [Migrating from v3 to v4](#migrating-from-v3-to-v4)
|
|
42
45
|
* [About Redis Sets and its Support in v4](#about-redis-sets-and-its-support-in-v4)
|
|
46
|
+
* [Using with NestJS](#using-with-nestjs)
|
|
43
47
|
* [License](#license)
|
|
44
48
|
|
|
45
49
|
# Installation
|
|
@@ -76,21 +80,20 @@ Here you can pass in the Redis options directly:
|
|
|
76
80
|
import Keyv from 'keyv';
|
|
77
81
|
import KeyvRedis from '@keyv/redis';
|
|
78
82
|
|
|
79
|
-
const
|
|
80
|
-
url: 'redis://localhost:6379', // The Redis server URL (use 'rediss' for TLS)
|
|
81
|
-
password: 'your_password', // Optional password if Redis has authentication enabled
|
|
83
|
+
const uri = "redis://localhost:6379";
|
|
82
84
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
85
|
+
// NOTE: please use the settings that you need to configure. Check out Keyv Redis Options section
|
|
86
|
+
const options = {
|
|
87
|
+
namespace: "test",
|
|
88
|
+
keyPrefixSeparator: "->",
|
|
89
|
+
clearBatchSize: 100,
|
|
90
|
+
useUnlink: true,
|
|
91
|
+
noNamespaceAffectsAll: true,
|
|
91
92
|
};
|
|
92
93
|
|
|
93
|
-
const
|
|
94
|
+
const keyvRedis = new KeyvRedis(uri, options);
|
|
95
|
+
|
|
96
|
+
const keyv = new Keyv(keyvRedis);
|
|
94
97
|
```
|
|
95
98
|
|
|
96
99
|
Or you can create a new Redis instance and pass it in with `KeyvOptions` such as setting the `store`:
|
|
@@ -379,9 +382,9 @@ const cache = new Cacheable( { secondary, nonBlocking: true } );
|
|
|
379
382
|
|
|
380
383
|
This will make it so that the secondary does not block the primary cache and will be very fast. 🚀
|
|
381
384
|
|
|
382
|
-
# Clustering
|
|
385
|
+
# Clustering
|
|
383
386
|
|
|
384
|
-
If you are using a Redis Cluster
|
|
387
|
+
If you are using a Redis Cluster, you can pass in the `redisOptions` directly. Here is an example of how to do that:
|
|
385
388
|
|
|
386
389
|
```js
|
|
387
390
|
import Keyv from 'keyv';
|
|
@@ -404,9 +407,42 @@ const cluster = createCluster({
|
|
|
404
407
|
const keyv = new Keyv({ store: new KeyvRedis(cluster) });
|
|
405
408
|
```
|
|
406
409
|
|
|
407
|
-
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.
|
|
410
|
+
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.
|
|
408
411
|
|
|
409
|
-
|
|
412
|
+
# Sentinel
|
|
413
|
+
|
|
414
|
+
If you are using Sentinel to provide high availability for your Redis instances, you can pass in the `redisOptions` directly. Here is an example of how to do that:
|
|
415
|
+
|
|
416
|
+
```js
|
|
417
|
+
import Keyv from 'keyv';
|
|
418
|
+
import KeyvRedis, { createSentinel } from '@keyv/redis';
|
|
419
|
+
|
|
420
|
+
const sentinel = createSentinel({
|
|
421
|
+
name: 'sentinel-db',
|
|
422
|
+
sentinelRootNodes: [
|
|
423
|
+
{
|
|
424
|
+
host: '127.0.0.1',
|
|
425
|
+
port: 26379,
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
host: '127.0.0.1',
|
|
429
|
+
port: 26380,
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
host: '127.0.0.1',
|
|
433
|
+
port: 26381,
|
|
434
|
+
},
|
|
435
|
+
],
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
const keyv = new Keyv({ store: new KeyvRedis(sentinel) });
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
You can learn more about the `createSentinel` function in the [documentation](https://github.com/redis/node-redis/blob/master/docs/sentinel.md) at https://github.com/redis/node-redis/tree/master/docs.
|
|
442
|
+
|
|
443
|
+
# TLS Support
|
|
444
|
+
|
|
445
|
+
Here is an example of how to use TLS using the `redisOptions`:
|
|
410
446
|
|
|
411
447
|
```js
|
|
412
448
|
import Keyv from 'keyv';
|
|
@@ -429,6 +465,60 @@ const tlsOptions = {
|
|
|
429
465
|
const keyv = new Keyv({ store: new KeyvRedis(tlsOptions) });
|
|
430
466
|
```
|
|
431
467
|
|
|
468
|
+
# Keyv Redis Options
|
|
469
|
+
|
|
470
|
+
Here are all the options that you can set on the constructor
|
|
471
|
+
|
|
472
|
+
```ts
|
|
473
|
+
export type KeyvRedisOptions = {
|
|
474
|
+
/**
|
|
475
|
+
* Namespace for the current instance.
|
|
476
|
+
*/
|
|
477
|
+
namespace?: string;
|
|
478
|
+
/**
|
|
479
|
+
* Separator to use between namespace and key.
|
|
480
|
+
*/
|
|
481
|
+
keyPrefixSeparator?: string;
|
|
482
|
+
/**
|
|
483
|
+
* Number of keys to delete in a single batch.
|
|
484
|
+
*/
|
|
485
|
+
clearBatchSize?: number;
|
|
486
|
+
/**
|
|
487
|
+
* Enable Unlink instead of using Del for clearing keys. This is more performant but may not be supported by all Redis versions.
|
|
488
|
+
*/
|
|
489
|
+
useUnlink?: boolean;
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Whether to allow clearing all keys when no namespace is set.
|
|
493
|
+
* If set to true and no namespace is set, iterate() will return all keys.
|
|
494
|
+
* Defaults to `false`.
|
|
495
|
+
*/
|
|
496
|
+
noNamespaceAffectsAll?: boolean;
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* This is used to throw an error if the client is not connected when trying to connect. By default, this is
|
|
500
|
+
* set to true so that it throws an error when trying to connect to the Redis server fails.
|
|
501
|
+
*/
|
|
502
|
+
throwOnConnectError?: boolean;
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* This is used to throw an error if at any point there is a failure. Use this if you want to
|
|
506
|
+
* ensure that all operations are successful and you want to handle errors. By default, this is
|
|
507
|
+
* set to false so that it does not throw an error on every operation and instead emits an error event
|
|
508
|
+
* and returns no-op responses.
|
|
509
|
+
* @default false
|
|
510
|
+
*/
|
|
511
|
+
throwOnErrors?: boolean;
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Timeout in milliseconds for the connection. Default is undefined, which uses the default timeout of the Redis client.
|
|
515
|
+
* If set, it will throw an error if the connection does not succeed within the specified time.
|
|
516
|
+
* @default undefined
|
|
517
|
+
*/
|
|
518
|
+
connectionTimeout?: number;
|
|
519
|
+
};
|
|
520
|
+
```
|
|
521
|
+
|
|
432
522
|
# API
|
|
433
523
|
* **constructor([connection], [options])**
|
|
434
524
|
* **namespace** - The namespace to use for the keys.
|
|
@@ -494,12 +584,167 @@ const keyv = new Keyv({ store: redis, namespace: 'my-namespace', useKeyPrefix: f
|
|
|
494
584
|
|
|
495
585
|
This will make it so the storage adapter `@keyv/redis` will handle the namespace and not the `keyv` instance. If you leave it on it will just look duplicated like `my-namespace:my-namespace:key`.
|
|
496
586
|
|
|
497
|
-
|
|
498
|
-
|
|
499
587
|
# About Redis Sets and its Support in v4
|
|
500
588
|
|
|
501
589
|
We no longer support redis sets. This is due to the fact that it caused significant performance issues and was not a good fit for the library.
|
|
502
590
|
|
|
591
|
+
# Using with NestJS
|
|
592
|
+
|
|
593
|
+
> You can integrate `@keyv/redis` with NestJS by creating a custom `CacheModule`. This allows you to use Keyv as a cache store in your application.
|
|
594
|
+
|
|
595
|
+
### 1. Install Dependencies
|
|
596
|
+
|
|
597
|
+
```bash
|
|
598
|
+
npm install @keyv/redis keyv @nestjs/cache-manager cache-manager cacheable
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### 2. Create a Cache Module
|
|
602
|
+
|
|
603
|
+
Create a file `cache.module.ts`:
|
|
604
|
+
|
|
605
|
+
```ts
|
|
606
|
+
import { Module } from '@nestjs/common';
|
|
607
|
+
import { CacheModule as NestCacheModule } from '@nestjs/cache-manager';
|
|
608
|
+
import { createKeyv } from '@keyv/redis';
|
|
609
|
+
|
|
610
|
+
@Module({
|
|
611
|
+
imports: [
|
|
612
|
+
NestCacheModule.registerAsync({
|
|
613
|
+
useFactory: () => ({
|
|
614
|
+
stores: [createKeyv('redis://localhost:6379')],
|
|
615
|
+
}),
|
|
616
|
+
}),
|
|
617
|
+
],
|
|
618
|
+
providers: [],
|
|
619
|
+
exports: [],
|
|
620
|
+
})
|
|
621
|
+
export class CacheModule {}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
### 3. Import the Cache Module in AppModule
|
|
625
|
+
Update `app.module.ts`:
|
|
626
|
+
|
|
627
|
+
```ts
|
|
628
|
+
import { Module } from '@nestjs/common';
|
|
629
|
+
import { CacheModule } from './modules/config/cache/cache.module';
|
|
630
|
+
|
|
631
|
+
@Module({
|
|
632
|
+
imports: [
|
|
633
|
+
CacheModule, // Import your custom cache module
|
|
634
|
+
// other modules...
|
|
635
|
+
],
|
|
636
|
+
})
|
|
637
|
+
export class AppModule {}
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### 4. Create the Cache Service
|
|
641
|
+
Create a file `cache.service.ts`:
|
|
642
|
+
|
|
643
|
+
```ts
|
|
644
|
+
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
|
645
|
+
import { Inject, Injectable } from '@nestjs/common';
|
|
646
|
+
import { Cache } from 'cache-manager';
|
|
647
|
+
|
|
648
|
+
@Injectable()
|
|
649
|
+
export class CacheService {
|
|
650
|
+
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
|
|
651
|
+
|
|
652
|
+
async get<T>(key: string): Promise<T | undefined> {
|
|
653
|
+
return this.cacheManager.get<T>(key);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
async set<T>(key: string, value: T, ttl?: number): Promise<void> {
|
|
657
|
+
await this.cacheManager.set(key, value, ttl);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
async delete(key: string): Promise<void> {
|
|
661
|
+
await this.cacheManager.del(key);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### 5. Register CacheService in CacheModule
|
|
667
|
+
Update `cache.module.ts`:
|
|
668
|
+
|
|
669
|
+
```ts
|
|
670
|
+
import { Module } from '@nestjs/common';
|
|
671
|
+
import { CacheModule as NestCacheModule } from '@nestjs/cache-manager';
|
|
672
|
+
import { createKeyv } from '@keyv/redis';
|
|
673
|
+
import { CacheService } from './services/cache.service';
|
|
674
|
+
|
|
675
|
+
@Module({
|
|
676
|
+
imports: [
|
|
677
|
+
NestCacheModule.registerAsync({
|
|
678
|
+
useFactory: () => ({
|
|
679
|
+
stores: [createKeyv('redis://localhost:6379')],
|
|
680
|
+
}),
|
|
681
|
+
}),
|
|
682
|
+
],
|
|
683
|
+
providers: [CacheService],
|
|
684
|
+
exports: [CacheService],
|
|
685
|
+
})
|
|
686
|
+
export class CacheModule {}
|
|
687
|
+
```
|
|
688
|
+
### 6. Import CacheModule in the Target Module (e.g. TaskModule)
|
|
689
|
+
```ts
|
|
690
|
+
import { Module } from '@nestjs/common';
|
|
691
|
+
import { TaskService } from './task.service';
|
|
692
|
+
import { TaskRepository } from './repositories/task.repository';
|
|
693
|
+
import { CacheModule } from 'src/modules/config/cache/cache.module';
|
|
694
|
+
|
|
695
|
+
@Module({
|
|
696
|
+
imports: [CacheModule],
|
|
697
|
+
providers: [TaskService, TaskRepository],
|
|
698
|
+
})
|
|
699
|
+
export class TaskModule {}
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
### 7. Using the Cache in a Service
|
|
703
|
+
|
|
704
|
+
```ts
|
|
705
|
+
import { Injectable, NotFoundException } from '@nestjs/common';
|
|
706
|
+
import { TaskRepository } from '../repositories/task.repository';
|
|
707
|
+
import { TaskDto } from '../dto/task.dto';
|
|
708
|
+
import { CacheService } from 'src/modules/config/cache/services/cache.service';
|
|
709
|
+
|
|
710
|
+
@Injectable()
|
|
711
|
+
export class TaskService {
|
|
712
|
+
constructor(
|
|
713
|
+
private readonly taskRepository: TaskRepository,
|
|
714
|
+
private readonly cache: CacheService, // Inject the CacheService
|
|
715
|
+
) {}
|
|
716
|
+
|
|
717
|
+
async findById(id: number): Promise<TaskDto> {
|
|
718
|
+
const cacheKey = `task:${id}`;
|
|
719
|
+
|
|
720
|
+
// 1. Try to get from cache
|
|
721
|
+
const cached = await this.cache.get<TaskDto>(cacheKey);
|
|
722
|
+
|
|
723
|
+
if (cached) {
|
|
724
|
+
return cached;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// 2. If not found in cache, fetch from database
|
|
728
|
+
const task = await this.taskRepository.findById(id);
|
|
729
|
+
|
|
730
|
+
if (!task) {
|
|
731
|
+
throw new NotFoundException('Task not found');
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// 3. Set in cache for future requests
|
|
735
|
+
await this.cache.set(cacheKey, task, 300 * 1000); // 5 minutes TTL
|
|
736
|
+
return task;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
You can learn more about caching in NestJS in the [official documentation](https://docs.nestjs.com/techniques/caching#in-memory-cache).
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
---
|
|
746
|
+
|
|
747
|
+
|
|
503
748
|
# License
|
|
504
749
|
|
|
505
750
|
[MIT © Jared Wray](LISCENCE)
|
package/dist/index.cjs
CHANGED
|
@@ -36,14 +36,15 @@ __export(index_exports, {
|
|
|
36
36
|
createCluster: () => import_client2.createCluster,
|
|
37
37
|
createKeyv: () => createKeyv,
|
|
38
38
|
createKeyvNonBlocking: () => createKeyvNonBlocking,
|
|
39
|
+
createSentinel: () => import_client2.createSentinel,
|
|
39
40
|
default: () => KeyvRedis,
|
|
40
41
|
defaultReconnectStrategy: () => defaultReconnectStrategy
|
|
41
42
|
});
|
|
42
43
|
module.exports = __toCommonJS(index_exports);
|
|
43
44
|
var import_client = require("@redis/client");
|
|
45
|
+
var import_cluster_key_slot = __toESM(require("cluster-key-slot"), 1);
|
|
44
46
|
var import_hookified = require("hookified");
|
|
45
47
|
var import_keyv = require("keyv");
|
|
46
|
-
var import_cluster_key_slot = __toESM(require("cluster-key-slot"), 1);
|
|
47
48
|
var import_client2 = require("@redis/client");
|
|
48
49
|
var import_keyv2 = require("keyv");
|
|
49
50
|
var RedisErrorMessages = /* @__PURE__ */ ((RedisErrorMessages2) => {
|
|
@@ -78,11 +79,30 @@ var KeyvRedis = class extends import_hookified.Hookified {
|
|
|
78
79
|
};
|
|
79
80
|
if (connect) {
|
|
80
81
|
if (typeof connect === "string") {
|
|
81
|
-
this._client = (0, import_client.createClient)({
|
|
82
|
+
this._client = (0, import_client.createClient)({
|
|
83
|
+
url: connect,
|
|
84
|
+
socket
|
|
85
|
+
});
|
|
82
86
|
} else if (connect.connect !== void 0) {
|
|
83
|
-
|
|
87
|
+
if (this.isClientSentinel(connect)) {
|
|
88
|
+
this._client = connect;
|
|
89
|
+
} else if (this.isClientCluster(connect)) {
|
|
90
|
+
this._client = connect;
|
|
91
|
+
} else {
|
|
92
|
+
this._client = connect;
|
|
93
|
+
}
|
|
84
94
|
} else if (connect instanceof Object) {
|
|
85
|
-
|
|
95
|
+
if (connect.sentinelRootNodes !== void 0) {
|
|
96
|
+
this._client = (0, import_client.createSentinel)(
|
|
97
|
+
connect
|
|
98
|
+
);
|
|
99
|
+
} else if (connect.rootNodes === void 0) {
|
|
100
|
+
this._client = (0, import_client.createClient)(
|
|
101
|
+
connect
|
|
102
|
+
);
|
|
103
|
+
} else {
|
|
104
|
+
this._client = (0, import_client.createCluster)(connect);
|
|
105
|
+
}
|
|
86
106
|
}
|
|
87
107
|
}
|
|
88
108
|
this.setOptions(options);
|
|
@@ -360,7 +380,9 @@ var KeyvRedis = class extends import_hookified.Hookified {
|
|
|
360
380
|
multi.exists(prefixedKey);
|
|
361
381
|
}
|
|
362
382
|
const results = await multi.exec();
|
|
363
|
-
return results.map(
|
|
383
|
+
return results.map(
|
|
384
|
+
(result) => typeof result === "number" && result === 1
|
|
385
|
+
);
|
|
364
386
|
} catch (error) {
|
|
365
387
|
this.emit("error", error);
|
|
366
388
|
if (this._throwOnErrors) {
|
|
@@ -506,6 +528,13 @@ var KeyvRedis = class extends import_hookified.Hookified {
|
|
|
506
528
|
isCluster() {
|
|
507
529
|
return this.isClientCluster(this._client);
|
|
508
530
|
}
|
|
531
|
+
/**
|
|
532
|
+
* Is the client a sentinel.
|
|
533
|
+
* @returns {boolean} - true if the client is a sentinel, false if not
|
|
534
|
+
*/
|
|
535
|
+
isSentinel() {
|
|
536
|
+
return this.isClientSentinel(this._client);
|
|
537
|
+
}
|
|
509
538
|
/**
|
|
510
539
|
* Get the master nodes in the cluster. If not a cluster, it will return the single client.
|
|
511
540
|
*
|
|
@@ -514,7 +543,9 @@ var KeyvRedis = class extends import_hookified.Hookified {
|
|
|
514
543
|
async getMasterNodes() {
|
|
515
544
|
if (this.isCluster()) {
|
|
516
545
|
const cluster = await this.getClient();
|
|
517
|
-
const nodes = cluster.masters.map(
|
|
546
|
+
const nodes = cluster.masters.map(
|
|
547
|
+
async (main) => cluster.nodeClient(main)
|
|
548
|
+
);
|
|
518
549
|
return Promise.all(nodes);
|
|
519
550
|
}
|
|
520
551
|
return [await this.getClient()];
|
|
@@ -530,7 +561,10 @@ var KeyvRedis = class extends import_hookified.Hookified {
|
|
|
530
561
|
const match = namespace ? `${namespace}${this._keyPrefixSeparator}*` : "*";
|
|
531
562
|
let cursor = "0";
|
|
532
563
|
do {
|
|
533
|
-
const result = await client.scan(cursor, {
|
|
564
|
+
const result = await client.scan(cursor, {
|
|
565
|
+
MATCH: match,
|
|
566
|
+
TYPE: "string"
|
|
567
|
+
});
|
|
534
568
|
cursor = result.cursor.toString();
|
|
535
569
|
let { keys } = result;
|
|
536
570
|
if (!namespace && !this._noNamespaceAffectsAll) {
|
|
@@ -557,29 +591,37 @@ var KeyvRedis = class extends import_hookified.Hookified {
|
|
|
557
591
|
async clear() {
|
|
558
592
|
try {
|
|
559
593
|
const clients = await this.getMasterNodes();
|
|
560
|
-
await Promise.all(
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
let cursor = "0";
|
|
566
|
-
const batchSize = this._clearBatchSize;
|
|
567
|
-
const match = this._namespace ? `${this._namespace}${this._keyPrefixSeparator}*` : "*";
|
|
568
|
-
const deletePromises = [];
|
|
569
|
-
do {
|
|
570
|
-
const result = await client.scan(cursor, { MATCH: match, COUNT: batchSize, TYPE: "string" });
|
|
571
|
-
cursor = result.cursor.toString();
|
|
572
|
-
let { keys } = result;
|
|
573
|
-
if (keys.length === 0) {
|
|
574
|
-
continue;
|
|
575
|
-
}
|
|
576
|
-
if (!this._namespace) {
|
|
577
|
-
keys = keys.filter((key) => !key.includes(this._keyPrefixSeparator));
|
|
594
|
+
await Promise.all(
|
|
595
|
+
clients.map(async (client) => {
|
|
596
|
+
if (!this._namespace && this._noNamespaceAffectsAll) {
|
|
597
|
+
await client.flushDb();
|
|
598
|
+
return;
|
|
578
599
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
600
|
+
let cursor = "0";
|
|
601
|
+
const batchSize = this._clearBatchSize;
|
|
602
|
+
const match = this._namespace ? `${this._namespace}${this._keyPrefixSeparator}*` : "*";
|
|
603
|
+
const deletePromises = [];
|
|
604
|
+
do {
|
|
605
|
+
const result = await client.scan(cursor, {
|
|
606
|
+
MATCH: match,
|
|
607
|
+
COUNT: batchSize,
|
|
608
|
+
TYPE: "string"
|
|
609
|
+
});
|
|
610
|
+
cursor = result.cursor.toString();
|
|
611
|
+
let { keys } = result;
|
|
612
|
+
if (keys.length === 0) {
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (!this._namespace) {
|
|
616
|
+
keys = keys.filter(
|
|
617
|
+
(key) => !key.includes(this._keyPrefixSeparator)
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
deletePromises.push(this.clearWithClusterSupport(keys));
|
|
621
|
+
} while (cursor !== "0");
|
|
622
|
+
await Promise.all(deletePromises);
|
|
623
|
+
})
|
|
624
|
+
);
|
|
583
625
|
} catch (error) {
|
|
584
626
|
this.emit("error", error);
|
|
585
627
|
}
|
|
@@ -591,13 +633,15 @@ var KeyvRedis = class extends import_hookified.Hookified {
|
|
|
591
633
|
async mget(keys) {
|
|
592
634
|
const slotMap = this.getSlotMap(keys);
|
|
593
635
|
const valueMap = /* @__PURE__ */ new Map();
|
|
594
|
-
await Promise.all(
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
636
|
+
await Promise.all(
|
|
637
|
+
Array.from(slotMap.entries(), async ([slot, keys2]) => {
|
|
638
|
+
const client = await this.getSlotMaster(slot);
|
|
639
|
+
const values = await client.mGet(keys2);
|
|
640
|
+
for (const [index, value] of values.entries()) {
|
|
641
|
+
valueMap.set(keys2[index], value ?? void 0);
|
|
642
|
+
}
|
|
643
|
+
})
|
|
644
|
+
);
|
|
601
645
|
return keys.map((key) => valueMap.get(key));
|
|
602
646
|
}
|
|
603
647
|
/**
|
|
@@ -607,10 +651,12 @@ var KeyvRedis = class extends import_hookified.Hookified {
|
|
|
607
651
|
async clearWithClusterSupport(keys) {
|
|
608
652
|
if (keys.length > 0) {
|
|
609
653
|
const slotMap = this.getSlotMap(keys);
|
|
610
|
-
await Promise.all(
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
654
|
+
await Promise.all(
|
|
655
|
+
Array.from(slotMap.entries(), async ([slot, keys2]) => {
|
|
656
|
+
const client = await this.getSlotMaster(slot);
|
|
657
|
+
return this._useUnlink ? client.unlink(keys2) : client.del(keys2);
|
|
658
|
+
})
|
|
659
|
+
);
|
|
614
660
|
}
|
|
615
661
|
}
|
|
616
662
|
/**
|
|
@@ -648,6 +694,9 @@ var KeyvRedis = class extends import_hookified.Hookified {
|
|
|
648
694
|
isClientCluster(client) {
|
|
649
695
|
return client.slots !== void 0;
|
|
650
696
|
}
|
|
697
|
+
isClientSentinel(client) {
|
|
698
|
+
return client.getSentinelNode !== void 0;
|
|
699
|
+
}
|
|
651
700
|
setOptions(options) {
|
|
652
701
|
if (!options) {
|
|
653
702
|
return;
|
|
@@ -689,15 +738,11 @@ var KeyvRedis = class extends import_hookified.Hookified {
|
|
|
689
738
|
});
|
|
690
739
|
}
|
|
691
740
|
async createTimeoutPromise(timeoutMs) {
|
|
692
|
-
return new Promise(
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
},
|
|
698
|
-
timeoutMs
|
|
699
|
-
)
|
|
700
|
-
));
|
|
741
|
+
return new Promise(
|
|
742
|
+
(_, reject) => setTimeout(() => {
|
|
743
|
+
reject(new Error(`Redis timed out after ${timeoutMs}ms`));
|
|
744
|
+
}, timeoutMs)
|
|
745
|
+
);
|
|
701
746
|
}
|
|
702
747
|
};
|
|
703
748
|
function createKeyv(connect, options) {
|
|
@@ -705,7 +750,10 @@ function createKeyv(connect, options) {
|
|
|
705
750
|
const adapter = new KeyvRedis(connect, options);
|
|
706
751
|
if (options?.namespace) {
|
|
707
752
|
adapter.namespace = options.namespace;
|
|
708
|
-
const keyv2 = new import_keyv.Keyv(adapter, {
|
|
753
|
+
const keyv2 = new import_keyv.Keyv(adapter, {
|
|
754
|
+
namespace: options?.namespace,
|
|
755
|
+
useKeyPrefix: false
|
|
756
|
+
});
|
|
709
757
|
if (options?.throwOnConnectError) {
|
|
710
758
|
keyv2.throwOnErrors = true;
|
|
711
759
|
}
|
|
@@ -747,5 +795,6 @@ function createKeyvNonBlocking(connect, options) {
|
|
|
747
795
|
createCluster,
|
|
748
796
|
createKeyv,
|
|
749
797
|
createKeyvNonBlocking,
|
|
798
|
+
createSentinel,
|
|
750
799
|
defaultReconnectStrategy
|
|
751
800
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { RedisClientOptions, RedisClusterOptions, RedisClientType, RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping, RedisClusterType } from '@redis/client';
|
|
2
|
-
export { RedisClientOptions, RedisClientType, RedisClusterOptions, RedisClusterType, createClient, createCluster } from '@redis/client';
|
|
1
|
+
import { RedisClientOptions, RedisClusterOptions, RedisSentinelOptions, RedisClientType, RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping, RedisClusterType, RedisSentinelType } from '@redis/client';
|
|
2
|
+
export { RedisClientOptions, RedisClientType, RedisClusterOptions, RedisClusterType, RedisSentinelType, createClient, createCluster, createSentinel } from '@redis/client';
|
|
3
3
|
import { Hookified } from 'hookified';
|
|
4
4
|
import { KeyvStoreAdapter, KeyvEntry, Keyv } from 'keyv';
|
|
5
5
|
export { Keyv } from 'keyv';
|
|
@@ -51,7 +51,7 @@ type KeyvRedisPropertyOptions = KeyvRedisOptions & {
|
|
|
51
51
|
/**
|
|
52
52
|
* Dialect used by the adapter. This is legacy so Keyv knows what is iteratable.
|
|
53
53
|
*/
|
|
54
|
-
dialect:
|
|
54
|
+
dialect: "redis";
|
|
55
55
|
/**
|
|
56
56
|
* URL used to connect to the Redis server. This is legacy so Keyv knows what is iteratable.
|
|
57
57
|
*/
|
|
@@ -80,7 +80,8 @@ declare enum RedisErrorMessages {
|
|
|
80
80
|
declare const defaultReconnectStrategy: (attempts: number) => number | Error;
|
|
81
81
|
type RedisConnectionClientType = RedisClientType | RedisClientType<RedisModules, RedisFunctions, RedisScripts, RespVersions> | RedisClientType<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping>;
|
|
82
82
|
type RedisConnectionClusterType = RedisClusterType | RedisClusterType<RedisModules, RedisFunctions, RedisScripts, RespVersions> | RedisClusterType<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping>;
|
|
83
|
-
type
|
|
83
|
+
type RedisConnectionSentinelType = RedisSentinelType | RedisSentinelType<RedisModules, RedisFunctions, RedisScripts, RespVersions> | RedisSentinelType<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping>;
|
|
84
|
+
type RedisClientConnectionType = RedisConnectionClientType | RedisConnectionClusterType | RedisConnectionSentinelType;
|
|
84
85
|
declare class KeyvRedis<T> extends Hookified implements KeyvStoreAdapter {
|
|
85
86
|
private _client;
|
|
86
87
|
private _namespace;
|
|
@@ -96,7 +97,7 @@ declare class KeyvRedis<T> extends Hookified implements KeyvStoreAdapter {
|
|
|
96
97
|
* @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.
|
|
97
98
|
* @param {KeyvRedisOptions} [options] Options for the adapter such as namespace, keyPrefixSeparator, and clearBatchSize.
|
|
98
99
|
*/
|
|
99
|
-
constructor(connect?: string | RedisClientOptions | RedisClusterOptions | RedisClientConnectionType, options?: KeyvRedisOptions);
|
|
100
|
+
constructor(connect?: string | RedisClientOptions | RedisClusterOptions | RedisSentinelOptions | RedisClientConnectionType, options?: KeyvRedisOptions);
|
|
100
101
|
/**
|
|
101
102
|
* Get the Redis client.
|
|
102
103
|
*/
|
|
@@ -278,6 +279,11 @@ declare class KeyvRedis<T> extends Hookified implements KeyvStoreAdapter {
|
|
|
278
279
|
* @returns {boolean} - true if the client is a cluster, false if not
|
|
279
280
|
*/
|
|
280
281
|
isCluster(): boolean;
|
|
282
|
+
/**
|
|
283
|
+
* Is the client a sentinel.
|
|
284
|
+
* @returns {boolean} - true if the client is a sentinel, false if not
|
|
285
|
+
*/
|
|
286
|
+
isSentinel(): boolean;
|
|
281
287
|
/**
|
|
282
288
|
* Get the master nodes in the cluster. If not a cluster, it will return the single client.
|
|
283
289
|
*
|
|
@@ -320,6 +326,7 @@ declare class KeyvRedis<T> extends Hookified implements KeyvStoreAdapter {
|
|
|
320
326
|
*/
|
|
321
327
|
private getSlotMap;
|
|
322
328
|
private isClientCluster;
|
|
329
|
+
private isClientSentinel;
|
|
323
330
|
private setOptions;
|
|
324
331
|
private initClient;
|
|
325
332
|
private createTimeoutPromise;
|
|
@@ -333,4 +340,4 @@ declare class KeyvRedis<T> extends Hookified implements KeyvStoreAdapter {
|
|
|
333
340
|
declare function createKeyv(connect?: string | RedisClientOptions | RedisClientType, options?: KeyvRedisOptions): Keyv;
|
|
334
341
|
declare function createKeyvNonBlocking(connect?: string | RedisClientOptions | RedisClientType, options?: KeyvRedisOptions): Keyv;
|
|
335
342
|
|
|
336
|
-
export { type KeyvRedisEntry, type KeyvRedisOptions, type KeyvRedisPropertyOptions, type RedisClientConnectionType, type RedisConnectionClientType, type RedisConnectionClusterType, RedisErrorMessages, createKeyv, createKeyvNonBlocking, KeyvRedis as default, defaultReconnectStrategy };
|
|
343
|
+
export { type KeyvRedisEntry, type KeyvRedisOptions, type KeyvRedisPropertyOptions, type RedisClientConnectionType, type RedisConnectionClientType, type RedisConnectionClusterType, type RedisConnectionSentinelType, RedisErrorMessages, createKeyv, createKeyvNonBlocking, KeyvRedis as default, defaultReconnectStrategy };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { RedisClientOptions, RedisClusterOptions, RedisClientType, RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping, RedisClusterType } from '@redis/client';
|
|
2
|
-
export { RedisClientOptions, RedisClientType, RedisClusterOptions, RedisClusterType, createClient, createCluster } from '@redis/client';
|
|
1
|
+
import { RedisClientOptions, RedisClusterOptions, RedisSentinelOptions, RedisClientType, RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping, RedisClusterType, RedisSentinelType } from '@redis/client';
|
|
2
|
+
export { RedisClientOptions, RedisClientType, RedisClusterOptions, RedisClusterType, RedisSentinelType, createClient, createCluster, createSentinel } from '@redis/client';
|
|
3
3
|
import { Hookified } from 'hookified';
|
|
4
4
|
import { KeyvStoreAdapter, KeyvEntry, Keyv } from 'keyv';
|
|
5
5
|
export { Keyv } from 'keyv';
|
|
@@ -51,7 +51,7 @@ type KeyvRedisPropertyOptions = KeyvRedisOptions & {
|
|
|
51
51
|
/**
|
|
52
52
|
* Dialect used by the adapter. This is legacy so Keyv knows what is iteratable.
|
|
53
53
|
*/
|
|
54
|
-
dialect:
|
|
54
|
+
dialect: "redis";
|
|
55
55
|
/**
|
|
56
56
|
* URL used to connect to the Redis server. This is legacy so Keyv knows what is iteratable.
|
|
57
57
|
*/
|
|
@@ -80,7 +80,8 @@ declare enum RedisErrorMessages {
|
|
|
80
80
|
declare const defaultReconnectStrategy: (attempts: number) => number | Error;
|
|
81
81
|
type RedisConnectionClientType = RedisClientType | RedisClientType<RedisModules, RedisFunctions, RedisScripts, RespVersions> | RedisClientType<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping>;
|
|
82
82
|
type RedisConnectionClusterType = RedisClusterType | RedisClusterType<RedisModules, RedisFunctions, RedisScripts, RespVersions> | RedisClusterType<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping>;
|
|
83
|
-
type
|
|
83
|
+
type RedisConnectionSentinelType = RedisSentinelType | RedisSentinelType<RedisModules, RedisFunctions, RedisScripts, RespVersions> | RedisSentinelType<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping>;
|
|
84
|
+
type RedisClientConnectionType = RedisConnectionClientType | RedisConnectionClusterType | RedisConnectionSentinelType;
|
|
84
85
|
declare class KeyvRedis<T> extends Hookified implements KeyvStoreAdapter {
|
|
85
86
|
private _client;
|
|
86
87
|
private _namespace;
|
|
@@ -96,7 +97,7 @@ declare class KeyvRedis<T> extends Hookified implements KeyvStoreAdapter {
|
|
|
96
97
|
* @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.
|
|
97
98
|
* @param {KeyvRedisOptions} [options] Options for the adapter such as namespace, keyPrefixSeparator, and clearBatchSize.
|
|
98
99
|
*/
|
|
99
|
-
constructor(connect?: string | RedisClientOptions | RedisClusterOptions | RedisClientConnectionType, options?: KeyvRedisOptions);
|
|
100
|
+
constructor(connect?: string | RedisClientOptions | RedisClusterOptions | RedisSentinelOptions | RedisClientConnectionType, options?: KeyvRedisOptions);
|
|
100
101
|
/**
|
|
101
102
|
* Get the Redis client.
|
|
102
103
|
*/
|
|
@@ -278,6 +279,11 @@ declare class KeyvRedis<T> extends Hookified implements KeyvStoreAdapter {
|
|
|
278
279
|
* @returns {boolean} - true if the client is a cluster, false if not
|
|
279
280
|
*/
|
|
280
281
|
isCluster(): boolean;
|
|
282
|
+
/**
|
|
283
|
+
* Is the client a sentinel.
|
|
284
|
+
* @returns {boolean} - true if the client is a sentinel, false if not
|
|
285
|
+
*/
|
|
286
|
+
isSentinel(): boolean;
|
|
281
287
|
/**
|
|
282
288
|
* Get the master nodes in the cluster. If not a cluster, it will return the single client.
|
|
283
289
|
*
|
|
@@ -320,6 +326,7 @@ declare class KeyvRedis<T> extends Hookified implements KeyvStoreAdapter {
|
|
|
320
326
|
*/
|
|
321
327
|
private getSlotMap;
|
|
322
328
|
private isClientCluster;
|
|
329
|
+
private isClientSentinel;
|
|
323
330
|
private setOptions;
|
|
324
331
|
private initClient;
|
|
325
332
|
private createTimeoutPromise;
|
|
@@ -333,4 +340,4 @@ declare class KeyvRedis<T> extends Hookified implements KeyvStoreAdapter {
|
|
|
333
340
|
declare function createKeyv(connect?: string | RedisClientOptions | RedisClientType, options?: KeyvRedisOptions): Keyv;
|
|
334
341
|
declare function createKeyvNonBlocking(connect?: string | RedisClientOptions | RedisClientType, options?: KeyvRedisOptions): Keyv;
|
|
335
342
|
|
|
336
|
-
export { type KeyvRedisEntry, type KeyvRedisOptions, type KeyvRedisPropertyOptions, type RedisClientConnectionType, type RedisConnectionClientType, type RedisConnectionClusterType, RedisErrorMessages, createKeyv, createKeyvNonBlocking, KeyvRedis as default, defaultReconnectStrategy };
|
|
343
|
+
export { type KeyvRedisEntry, type KeyvRedisOptions, type KeyvRedisPropertyOptions, type RedisClientConnectionType, type RedisConnectionClientType, type RedisConnectionClusterType, type RedisConnectionSentinelType, RedisErrorMessages, createKeyv, createKeyvNonBlocking, KeyvRedis as default, defaultReconnectStrategy };
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import {
|
|
3
3
|
createClient,
|
|
4
|
-
createCluster
|
|
4
|
+
createCluster,
|
|
5
|
+
createSentinel
|
|
5
6
|
} from "@redis/client";
|
|
7
|
+
import calculateSlot from "cluster-key-slot";
|
|
6
8
|
import { Hookified } from "hookified";
|
|
7
9
|
import { Keyv } from "keyv";
|
|
8
|
-
import calculateSlot from "cluster-key-slot";
|
|
9
10
|
import {
|
|
10
11
|
createClient as createClient2,
|
|
11
|
-
createCluster as createCluster2
|
|
12
|
+
createCluster as createCluster2,
|
|
13
|
+
createSentinel as createSentinel2
|
|
12
14
|
} from "@redis/client";
|
|
13
|
-
import {
|
|
14
|
-
Keyv as Keyv2
|
|
15
|
-
} from "keyv";
|
|
15
|
+
import { Keyv as Keyv2 } from "keyv";
|
|
16
16
|
var RedisErrorMessages = /* @__PURE__ */ ((RedisErrorMessages2) => {
|
|
17
17
|
RedisErrorMessages2["RedisClientNotConnectedThrown"] = "Redis client is not connected or has failed to connect. This is thrown because throwOnConnectError is set to true.";
|
|
18
18
|
return RedisErrorMessages2;
|
|
@@ -45,11 +45,30 @@ var KeyvRedis = class extends Hookified {
|
|
|
45
45
|
};
|
|
46
46
|
if (connect) {
|
|
47
47
|
if (typeof connect === "string") {
|
|
48
|
-
this._client = createClient({
|
|
48
|
+
this._client = createClient({
|
|
49
|
+
url: connect,
|
|
50
|
+
socket
|
|
51
|
+
});
|
|
49
52
|
} else if (connect.connect !== void 0) {
|
|
50
|
-
|
|
53
|
+
if (this.isClientSentinel(connect)) {
|
|
54
|
+
this._client = connect;
|
|
55
|
+
} else if (this.isClientCluster(connect)) {
|
|
56
|
+
this._client = connect;
|
|
57
|
+
} else {
|
|
58
|
+
this._client = connect;
|
|
59
|
+
}
|
|
51
60
|
} else if (connect instanceof Object) {
|
|
52
|
-
|
|
61
|
+
if (connect.sentinelRootNodes !== void 0) {
|
|
62
|
+
this._client = createSentinel(
|
|
63
|
+
connect
|
|
64
|
+
);
|
|
65
|
+
} else if (connect.rootNodes === void 0) {
|
|
66
|
+
this._client = createClient(
|
|
67
|
+
connect
|
|
68
|
+
);
|
|
69
|
+
} else {
|
|
70
|
+
this._client = createCluster(connect);
|
|
71
|
+
}
|
|
53
72
|
}
|
|
54
73
|
}
|
|
55
74
|
this.setOptions(options);
|
|
@@ -327,7 +346,9 @@ var KeyvRedis = class extends Hookified {
|
|
|
327
346
|
multi.exists(prefixedKey);
|
|
328
347
|
}
|
|
329
348
|
const results = await multi.exec();
|
|
330
|
-
return results.map(
|
|
349
|
+
return results.map(
|
|
350
|
+
(result) => typeof result === "number" && result === 1
|
|
351
|
+
);
|
|
331
352
|
} catch (error) {
|
|
332
353
|
this.emit("error", error);
|
|
333
354
|
if (this._throwOnErrors) {
|
|
@@ -473,6 +494,13 @@ var KeyvRedis = class extends Hookified {
|
|
|
473
494
|
isCluster() {
|
|
474
495
|
return this.isClientCluster(this._client);
|
|
475
496
|
}
|
|
497
|
+
/**
|
|
498
|
+
* Is the client a sentinel.
|
|
499
|
+
* @returns {boolean} - true if the client is a sentinel, false if not
|
|
500
|
+
*/
|
|
501
|
+
isSentinel() {
|
|
502
|
+
return this.isClientSentinel(this._client);
|
|
503
|
+
}
|
|
476
504
|
/**
|
|
477
505
|
* Get the master nodes in the cluster. If not a cluster, it will return the single client.
|
|
478
506
|
*
|
|
@@ -481,7 +509,9 @@ var KeyvRedis = class extends Hookified {
|
|
|
481
509
|
async getMasterNodes() {
|
|
482
510
|
if (this.isCluster()) {
|
|
483
511
|
const cluster = await this.getClient();
|
|
484
|
-
const nodes = cluster.masters.map(
|
|
512
|
+
const nodes = cluster.masters.map(
|
|
513
|
+
async (main) => cluster.nodeClient(main)
|
|
514
|
+
);
|
|
485
515
|
return Promise.all(nodes);
|
|
486
516
|
}
|
|
487
517
|
return [await this.getClient()];
|
|
@@ -497,7 +527,10 @@ var KeyvRedis = class extends Hookified {
|
|
|
497
527
|
const match = namespace ? `${namespace}${this._keyPrefixSeparator}*` : "*";
|
|
498
528
|
let cursor = "0";
|
|
499
529
|
do {
|
|
500
|
-
const result = await client.scan(cursor, {
|
|
530
|
+
const result = await client.scan(cursor, {
|
|
531
|
+
MATCH: match,
|
|
532
|
+
TYPE: "string"
|
|
533
|
+
});
|
|
501
534
|
cursor = result.cursor.toString();
|
|
502
535
|
let { keys } = result;
|
|
503
536
|
if (!namespace && !this._noNamespaceAffectsAll) {
|
|
@@ -524,29 +557,37 @@ var KeyvRedis = class extends Hookified {
|
|
|
524
557
|
async clear() {
|
|
525
558
|
try {
|
|
526
559
|
const clients = await this.getMasterNodes();
|
|
527
|
-
await Promise.all(
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
let cursor = "0";
|
|
533
|
-
const batchSize = this._clearBatchSize;
|
|
534
|
-
const match = this._namespace ? `${this._namespace}${this._keyPrefixSeparator}*` : "*";
|
|
535
|
-
const deletePromises = [];
|
|
536
|
-
do {
|
|
537
|
-
const result = await client.scan(cursor, { MATCH: match, COUNT: batchSize, TYPE: "string" });
|
|
538
|
-
cursor = result.cursor.toString();
|
|
539
|
-
let { keys } = result;
|
|
540
|
-
if (keys.length === 0) {
|
|
541
|
-
continue;
|
|
542
|
-
}
|
|
543
|
-
if (!this._namespace) {
|
|
544
|
-
keys = keys.filter((key) => !key.includes(this._keyPrefixSeparator));
|
|
560
|
+
await Promise.all(
|
|
561
|
+
clients.map(async (client) => {
|
|
562
|
+
if (!this._namespace && this._noNamespaceAffectsAll) {
|
|
563
|
+
await client.flushDb();
|
|
564
|
+
return;
|
|
545
565
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
566
|
+
let cursor = "0";
|
|
567
|
+
const batchSize = this._clearBatchSize;
|
|
568
|
+
const match = this._namespace ? `${this._namespace}${this._keyPrefixSeparator}*` : "*";
|
|
569
|
+
const deletePromises = [];
|
|
570
|
+
do {
|
|
571
|
+
const result = await client.scan(cursor, {
|
|
572
|
+
MATCH: match,
|
|
573
|
+
COUNT: batchSize,
|
|
574
|
+
TYPE: "string"
|
|
575
|
+
});
|
|
576
|
+
cursor = result.cursor.toString();
|
|
577
|
+
let { keys } = result;
|
|
578
|
+
if (keys.length === 0) {
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
if (!this._namespace) {
|
|
582
|
+
keys = keys.filter(
|
|
583
|
+
(key) => !key.includes(this._keyPrefixSeparator)
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
deletePromises.push(this.clearWithClusterSupport(keys));
|
|
587
|
+
} while (cursor !== "0");
|
|
588
|
+
await Promise.all(deletePromises);
|
|
589
|
+
})
|
|
590
|
+
);
|
|
550
591
|
} catch (error) {
|
|
551
592
|
this.emit("error", error);
|
|
552
593
|
}
|
|
@@ -558,13 +599,15 @@ var KeyvRedis = class extends Hookified {
|
|
|
558
599
|
async mget(keys) {
|
|
559
600
|
const slotMap = this.getSlotMap(keys);
|
|
560
601
|
const valueMap = /* @__PURE__ */ new Map();
|
|
561
|
-
await Promise.all(
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
602
|
+
await Promise.all(
|
|
603
|
+
Array.from(slotMap.entries(), async ([slot, keys2]) => {
|
|
604
|
+
const client = await this.getSlotMaster(slot);
|
|
605
|
+
const values = await client.mGet(keys2);
|
|
606
|
+
for (const [index, value] of values.entries()) {
|
|
607
|
+
valueMap.set(keys2[index], value ?? void 0);
|
|
608
|
+
}
|
|
609
|
+
})
|
|
610
|
+
);
|
|
568
611
|
return keys.map((key) => valueMap.get(key));
|
|
569
612
|
}
|
|
570
613
|
/**
|
|
@@ -574,10 +617,12 @@ var KeyvRedis = class extends Hookified {
|
|
|
574
617
|
async clearWithClusterSupport(keys) {
|
|
575
618
|
if (keys.length > 0) {
|
|
576
619
|
const slotMap = this.getSlotMap(keys);
|
|
577
|
-
await Promise.all(
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
620
|
+
await Promise.all(
|
|
621
|
+
Array.from(slotMap.entries(), async ([slot, keys2]) => {
|
|
622
|
+
const client = await this.getSlotMaster(slot);
|
|
623
|
+
return this._useUnlink ? client.unlink(keys2) : client.del(keys2);
|
|
624
|
+
})
|
|
625
|
+
);
|
|
581
626
|
}
|
|
582
627
|
}
|
|
583
628
|
/**
|
|
@@ -615,6 +660,9 @@ var KeyvRedis = class extends Hookified {
|
|
|
615
660
|
isClientCluster(client) {
|
|
616
661
|
return client.slots !== void 0;
|
|
617
662
|
}
|
|
663
|
+
isClientSentinel(client) {
|
|
664
|
+
return client.getSentinelNode !== void 0;
|
|
665
|
+
}
|
|
618
666
|
setOptions(options) {
|
|
619
667
|
if (!options) {
|
|
620
668
|
return;
|
|
@@ -656,15 +704,11 @@ var KeyvRedis = class extends Hookified {
|
|
|
656
704
|
});
|
|
657
705
|
}
|
|
658
706
|
async createTimeoutPromise(timeoutMs) {
|
|
659
|
-
return new Promise(
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
},
|
|
665
|
-
timeoutMs
|
|
666
|
-
)
|
|
667
|
-
));
|
|
707
|
+
return new Promise(
|
|
708
|
+
(_, reject) => setTimeout(() => {
|
|
709
|
+
reject(new Error(`Redis timed out after ${timeoutMs}ms`));
|
|
710
|
+
}, timeoutMs)
|
|
711
|
+
);
|
|
668
712
|
}
|
|
669
713
|
};
|
|
670
714
|
function createKeyv(connect, options) {
|
|
@@ -672,7 +716,10 @@ function createKeyv(connect, options) {
|
|
|
672
716
|
const adapter = new KeyvRedis(connect, options);
|
|
673
717
|
if (options?.namespace) {
|
|
674
718
|
adapter.namespace = options.namespace;
|
|
675
|
-
const keyv2 = new Keyv(adapter, {
|
|
719
|
+
const keyv2 = new Keyv(adapter, {
|
|
720
|
+
namespace: options?.namespace,
|
|
721
|
+
useKeyPrefix: false
|
|
722
|
+
});
|
|
676
723
|
if (options?.throwOnConnectError) {
|
|
677
724
|
keyv2.throwOnErrors = true;
|
|
678
725
|
}
|
|
@@ -713,6 +760,7 @@ export {
|
|
|
713
760
|
createCluster2 as createCluster,
|
|
714
761
|
createKeyv,
|
|
715
762
|
createKeyvNonBlocking,
|
|
763
|
+
createSentinel2 as createSentinel,
|
|
716
764
|
KeyvRedis as default,
|
|
717
765
|
defaultReconnectStrategy
|
|
718
766
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keyv/redis",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.1",
|
|
4
4
|
"description": "Redis storage adapter for Keyv",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -34,22 +34,22 @@
|
|
|
34
34
|
},
|
|
35
35
|
"homepage": "https://github.com/jaredwray/keyv",
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@redis/client": "^5.
|
|
37
|
+
"@redis/client": "^5.7.0",
|
|
38
38
|
"cluster-key-slot": "^1.1.2",
|
|
39
|
-
"hookified": "^1.
|
|
39
|
+
"hookified": "^1.11.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"keyv": "^5.
|
|
42
|
+
"keyv": "^5.5.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
+
"@biomejs/biome": "^2.2.0",
|
|
45
46
|
"@faker-js/faker": "^9.9.0",
|
|
46
47
|
"@vitest/coverage-v8": "^3.2.4",
|
|
47
48
|
"rimraf": "^6.0.1",
|
|
48
49
|
"timekeeper": "^2.3.1",
|
|
49
50
|
"tsd": "^0.32.0",
|
|
50
51
|
"vitest": "^3.2.4",
|
|
51
|
-
"
|
|
52
|
-
"@keyv/test-suite": "^2.0.9"
|
|
52
|
+
"@keyv/test-suite": "^2.1.1"
|
|
53
53
|
},
|
|
54
54
|
"tsd": {
|
|
55
55
|
"directory": "test"
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
],
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "rimraf ./dist && tsup src/index.ts --format cjs,esm --dts --clean",
|
|
66
|
-
"test": "
|
|
67
|
-
"test:ci": "
|
|
66
|
+
"test": "biome check --write && vitest run --coverage",
|
|
67
|
+
"test:ci": "biome check && vitest --run --sequence.setupFiles=list --coverage",
|
|
68
68
|
"clean": "rimraf ./node_modules ./coverage ./dist"
|
|
69
69
|
}
|
|
70
70
|
}
|