@keyv/redis 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -41
- package/dist/index.cjs +421 -0
- package/dist/index.d.cts +210 -0
- package/dist/index.d.ts +210 -0
- package/dist/index.js +388 -0
- package/package.json +79 -77
- package/dist/cjs/index.d.ts +0 -22
- package/dist/cjs/index.js +0 -144
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/types.d.ts +0 -9
- package/dist/cjs/types.js +0 -3
- package/dist/cjs/types.js.map +0 -1
- package/dist/esm/index.d.ts +0 -22
- package/dist/esm/index.js +0 -139
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/types.d.ts +0 -9
- package/dist/esm/types.js +0 -2
- package/dist/esm/types.js.map +0 -1
package/README.md
CHANGED
|
@@ -9,93 +9,230 @@
|
|
|
9
9
|
|
|
10
10
|
Redis storage adapter for [Keyv](https://github.com/jaredwray/keyv).
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
# Features
|
|
13
|
+
* Built on top of [redis](https://npmjs.com/package/redis).
|
|
14
|
+
* TTL is handled directly by Redis.
|
|
15
|
+
* Supports Redis Clusters.
|
|
16
|
+
* Url connection string support or pass in your Redis Options
|
|
17
|
+
* Easily add in your own Redis client.
|
|
18
|
+
* Namespace support for key management.
|
|
19
|
+
* Unlink as default delete method for performance.
|
|
20
|
+
* Access to the Redis client for advanced use cases.
|
|
21
|
+
* Keyv and Redis Libraries are exported for advanced use cases.
|
|
22
|
+
* `createKeyv` function for easy creation of Keyv instances.
|
|
23
|
+
* jsDoc comments for easy documentation.
|
|
24
|
+
* CJS / ESM and TypeScript supported out of the box.
|
|
25
|
+
|
|
26
|
+
# Table of Contents
|
|
27
|
+
* [Usage](#usage)
|
|
28
|
+
* [Namespaces](#namespaces)
|
|
29
|
+
* [Performance Considerations](#performance-considerations)
|
|
30
|
+
* [High Memory Usage on Redis Server](#high-memory-usage-on-redis-server)
|
|
31
|
+
* [Using Cacheable with Redis](#using-cacheable-with-redis)
|
|
32
|
+
* [Clustering and TLS Support](#clustering-and-tls-support)
|
|
33
|
+
* [API](#api)
|
|
34
|
+
* [Migrating from v3 to v4](#migrating-from-v3-to-v4)
|
|
35
|
+
* [About Redis Sets and its Support in v4](#about-redis-sets-and-its-support-in-v4)
|
|
36
|
+
* [License](#license)
|
|
37
|
+
|
|
38
|
+
# Usage
|
|
39
|
+
|
|
40
|
+
Here is a standard use case where we implement `Keyv` and `@keyv/redis`:
|
|
13
41
|
|
|
14
|
-
|
|
42
|
+
```js
|
|
43
|
+
import Keyv from 'keyv';
|
|
44
|
+
import KeyvRedis from '@keyv/redis';
|
|
15
45
|
|
|
16
|
-
|
|
17
|
-
|
|
46
|
+
const keyv = new Keyv(new KeyvRedis('redis://user:pass@localhost:6379'));
|
|
47
|
+
keyv.on('error', handleConnectionError);
|
|
18
48
|
```
|
|
19
49
|
|
|
20
|
-
|
|
50
|
+
Here you can pass in the Redis options directly:
|
|
21
51
|
|
|
22
52
|
```js
|
|
23
53
|
import Keyv from 'keyv';
|
|
24
54
|
import KeyvRedis from '@keyv/redis';
|
|
25
55
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
56
|
+
const redisOptions = {
|
|
57
|
+
url: 'redis://localhost:6379', // The Redis server URL (use 'rediss' for TLS)
|
|
58
|
+
password: 'your_password', // Optional password if Redis has authentication enabled
|
|
29
59
|
|
|
30
|
-
|
|
60
|
+
socket: {
|
|
61
|
+
host: 'localhost', // Hostname of the Redis server
|
|
62
|
+
port: 6379, // Port number
|
|
63
|
+
reconnectStrategy: (retries) => Math.min(retries * 50, 2000), // Custom reconnect logic
|
|
31
64
|
|
|
32
|
-
|
|
65
|
+
tls: false, // Enable TLS if you need to connect over SSL
|
|
66
|
+
keepAlive: 30000, // Keep-alive timeout (in milliseconds)
|
|
67
|
+
}
|
|
68
|
+
};
|
|
33
69
|
|
|
34
|
-
|
|
35
|
-
const keyv = new Keyv(new KeyvRedis('redis://user:pass@localhost:6379', { disable_resubscribing: true }));
|
|
70
|
+
const keyv = new Keyv(new KeyvRedis(redisOptions));
|
|
36
71
|
```
|
|
37
72
|
|
|
38
|
-
Or you can
|
|
73
|
+
Or you can create a new Redis instance and pass it in with `KeyvOptions`:
|
|
39
74
|
|
|
40
75
|
```js
|
|
41
76
|
import Keyv from 'keyv';
|
|
42
|
-
import KeyvRedis from '@keyv/redis';
|
|
77
|
+
import KeyvRedis, { createClient } from '@keyv/redis';
|
|
43
78
|
|
|
44
|
-
const
|
|
79
|
+
const redis = createClient('redis://user:pass@localhost:6379', { namespace: 'my-namespace'});
|
|
80
|
+
const keyvRedis = new KeyvRedis(redis);
|
|
45
81
|
const keyv = new Keyv({ store: keyvRedis });
|
|
46
82
|
```
|
|
47
83
|
|
|
48
|
-
|
|
84
|
+
Here is the same example but with the `Keyv` instance created with the `createKeyv` function:
|
|
49
85
|
|
|
50
86
|
```js
|
|
51
|
-
import
|
|
52
|
-
import Redis from 'ioredis';
|
|
53
|
-
import KeyvRedis from '@keyv/redis';
|
|
87
|
+
import { createKeyv } from '@keyv/redis';
|
|
54
88
|
|
|
55
|
-
const
|
|
56
|
-
const keyvRedis = new KeyvRedis(redis);
|
|
57
|
-
const keyv = new Keyv({ store: keyvRedis });
|
|
89
|
+
const keyv = createKeyv('redis://user:pass@localhost:6379', { namespace: 'my-namespace' });
|
|
58
90
|
```
|
|
59
91
|
|
|
60
|
-
|
|
92
|
+
You only have to import the `@keyv/redis` library if you are using the `createKeyv` function. 🎉 Otherwise, you can import `Keyv` and `@keyv/redis` independently.
|
|
93
|
+
|
|
94
|
+
# Namspaces
|
|
95
|
+
|
|
96
|
+
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:
|
|
61
97
|
|
|
62
98
|
```js
|
|
63
99
|
import Keyv from 'keyv';
|
|
64
|
-
import Redis from 'ioredis';
|
|
65
100
|
import KeyvRedis from '@keyv/redis';
|
|
66
101
|
|
|
67
|
-
const
|
|
68
|
-
const keyvRedis = new KeyvRedis(redis);
|
|
69
|
-
const keyv = new Keyv({ store: keyvRedis });
|
|
102
|
+
const keyv = new Keyv(new KeyvRedis('redis://user:pass@localhost:6379', { namespace: 'my-namespace' }));
|
|
70
103
|
```
|
|
71
|
-
## Options
|
|
72
104
|
|
|
73
|
-
|
|
105
|
+
This will prefix all keys with `my-namespace:`. You can also set the namespace after the fact:
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
keyv.namespace = 'my-namespace';
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
NOTE: If you plan to do many clears or deletes, it is recommended to read the [Performance Considerations](#performance-considerations) section.
|
|
112
|
+
|
|
113
|
+
# Performance Considerations
|
|
114
|
+
|
|
115
|
+
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.
|
|
74
117
|
|
|
75
|
-
|
|
118
|
+
* `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()`.
|
|
76
119
|
|
|
77
|
-
|
|
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.
|
|
78
121
|
|
|
79
|
-
-
|
|
80
|
-
- When a key is deleted, it's removed not only from the main storage but also from the Redis set.
|
|
81
|
-
- When clearing all keys (using the `clear` function), all keys in the Redis set are looked up for deletion. The set itself is also deleted.
|
|
122
|
+
* `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`.
|
|
82
123
|
|
|
83
|
-
|
|
124
|
+
* `setMany`, `getMany`, `deleteMany` - These methods are more efficient than their singular counterparts. If you are doing multiple operations it is recommended to use these methods.
|
|
84
125
|
|
|
85
|
-
If you
|
|
126
|
+
If you want to see even better performance please see the [Using Cacheable with Redis](#using-cacheable-with-redis) section as it has non-blocking and in-memory primary caching that goes along well with this library and Keyv.
|
|
86
127
|
|
|
87
|
-
|
|
128
|
+
# High Memory Usage on Redis Server
|
|
88
129
|
|
|
89
|
-
|
|
130
|
+
This is because we are using `UNLINK` by default instead of `DEL`. This is a non-blocking command that is more efficient than `DEL` but will slowly remove the memory allocation.
|
|
90
131
|
|
|
91
|
-
|
|
132
|
+
If you are deleting or clearing a large number of keys you can disable this by setting the `useUnlink` option to `false`. This will use `DEL` instead of `UNLINK` and should reduce the memory usage.
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
const keyv = new Keyv(new KeyvRedis('redis://user:pass@localhost:6379', { useUnlink: false }));
|
|
136
|
+
// Or
|
|
137
|
+
keyv.useUnlink = false;
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
# Using Cacheable with Redis
|
|
141
|
+
|
|
142
|
+
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:
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
import KeyvRedis from '@keyv/redis';
|
|
146
|
+
import Cacheable from 'cacheable';
|
|
147
|
+
|
|
148
|
+
const secondary = new KeyvRedis('redis://user:pass@localhost:6379');
|
|
149
|
+
|
|
150
|
+
const cache = new Cacheable( { secondary } );
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
For even higher performance you can set the `nonBlocking` option to `true`:
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
const cache = new Cacheable( { secondary, nonBlocking: true } );
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
This will make it so that the secondary does not block the primary cache and will be very fast. 🚀
|
|
160
|
+
|
|
161
|
+
# Clustering and TLS Support
|
|
162
|
+
|
|
163
|
+
If you are using a Redis Cluster or need to use TLS, you can pass in the `redisOptions` directly. Here is an example of how to do that:
|
|
92
164
|
|
|
93
165
|
```js
|
|
94
166
|
import Keyv from 'keyv';
|
|
167
|
+
import KeyvRedis, { createCluster } from '@keyv/redis';
|
|
168
|
+
|
|
169
|
+
const cluster = createCluster({
|
|
170
|
+
rootNodes: [
|
|
171
|
+
{
|
|
172
|
+
url: 'redis://127.0.0.1:7000',
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
url: 'redis://127.0.0.1:7001',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
url: 'redis://127.0.0.1:7002',
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const keyv = new Keyv({ store: new KeyvRedis(cluster) });
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Here is an example of how to use TLS:
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
import Keyv from 'keyv';
|
|
190
|
+
import KeyvRedis from '@keyv/redis';
|
|
95
191
|
|
|
96
|
-
const
|
|
192
|
+
const tlsOptions = {
|
|
193
|
+
socket: {
|
|
194
|
+
host: 'localhost',
|
|
195
|
+
port: 6379,
|
|
196
|
+
tls: true, // Enable TLS connection
|
|
197
|
+
rejectUnauthorized: false, // Ignore self-signed certificate errors (for testing)
|
|
198
|
+
|
|
199
|
+
// Alternatively, provide CA, key, and cert for mutual authentication
|
|
200
|
+
ca: fs.readFileSync('/path/to/ca-cert.pem'),
|
|
201
|
+
cert: fs.readFileSync('/path/to/client-cert.pem'), // Optional for client auth
|
|
202
|
+
key: fs.readFileSync('/path/to/client-key.pem'), // Optional for client auth
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const keyv = new Keyv({ store: new KeyvRedis(tlsOptions) });
|
|
97
207
|
```
|
|
98
208
|
|
|
99
|
-
|
|
209
|
+
# API
|
|
210
|
+
* **constructor([connection], [options])**
|
|
211
|
+
* **namespace** - The namespace to use for the keys.
|
|
212
|
+
* **client** - The Redis client instance.
|
|
213
|
+
* **keyPrefixSeparator** - The separator to use between the namespace and key.
|
|
214
|
+
* **clearBatchSize** - The number of keys to delete in a single batch.
|
|
215
|
+
* **useUnlink** - Use the `UNLINK` command for deleting keys isntead of `DEL`.
|
|
216
|
+
* **set** - Set a key.
|
|
217
|
+
* **setMany** - Set multiple keys.
|
|
218
|
+
* **get** - Get a key.
|
|
219
|
+
* **getMany** - Get multiple keys.
|
|
220
|
+
* **has** - Check if a key exists.
|
|
221
|
+
* **hasMany** - Check if multiple keys exist.
|
|
222
|
+
* **delete** - Delete a key.
|
|
223
|
+
* **deleteMany** - Delete multiple keys.
|
|
224
|
+
* **clear** - Clear all keys. If the `namespace` is set it will only clear keys with that namespace.
|
|
225
|
+
* **disconnect** - Disconnect from the Redis server.
|
|
226
|
+
* **iterator** - Create a new iterator for the keys.
|
|
227
|
+
|
|
228
|
+
# Migrating from v3 to v4
|
|
229
|
+
|
|
230
|
+
The main change in v4 is the removal of the `ioredis` library in favor of the `@keyv/redis` library. This was done to provide a more consistent experience across all Keyv storage adapters. The `@keyv/redis` library is a wrapper around the `redis` library and provides a more consistent experience across all Keyv storage adapters. The only other change is that we no longer do redis sets as they caused performance issues.
|
|
231
|
+
|
|
232
|
+
# About Redis Sets and its Support in v4
|
|
233
|
+
|
|
234
|
+
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.
|
|
235
|
+
|
|
236
|
+
# License
|
|
100
237
|
|
|
101
238
|
[MIT © Jared Wray](LISCENCE)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
Keyv: () => import_keyv2.Keyv,
|
|
34
|
+
createClient: () => import_redis2.createClient,
|
|
35
|
+
createCluster: () => import_redis2.createCluster,
|
|
36
|
+
createKeyv: () => createKeyv,
|
|
37
|
+
default: () => KeyvRedis
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(src_exports);
|
|
40
|
+
var import_events = __toESM(require("events"), 1);
|
|
41
|
+
var import_redis = require("redis");
|
|
42
|
+
var import_keyv = require("keyv");
|
|
43
|
+
var import_redis2 = require("redis");
|
|
44
|
+
var import_keyv2 = require("keyv");
|
|
45
|
+
var KeyvRedis = class extends import_events.default {
|
|
46
|
+
_client = (0, import_redis.createClient)();
|
|
47
|
+
_namespace;
|
|
48
|
+
_keyPrefixSeparator = "::";
|
|
49
|
+
_clearBatchSize = 1e3;
|
|
50
|
+
_useUnlink = true;
|
|
51
|
+
/**
|
|
52
|
+
* KeyvRedis constructor.
|
|
53
|
+
* @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.
|
|
54
|
+
* @param {KeyvRedisOptions} [options] Options for the adapter such as namespace, keyPrefixSeparator, and clearBatchSize.
|
|
55
|
+
*/
|
|
56
|
+
constructor(connect, options) {
|
|
57
|
+
super();
|
|
58
|
+
if (connect) {
|
|
59
|
+
if (typeof connect === "string") {
|
|
60
|
+
this._client = (0, import_redis.createClient)({ url: connect });
|
|
61
|
+
} else if (connect.connect !== void 0) {
|
|
62
|
+
this._client = connect;
|
|
63
|
+
} else if (connect instanceof Object) {
|
|
64
|
+
this._client = (0, import_redis.createClient)(connect);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
this.setOptions(options);
|
|
68
|
+
this.initClient();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get the Redis client.
|
|
72
|
+
*/
|
|
73
|
+
get client() {
|
|
74
|
+
return this._client;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Set the Redis client.
|
|
78
|
+
*/
|
|
79
|
+
set client(value) {
|
|
80
|
+
this._client = value;
|
|
81
|
+
this.initClient();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get the options for the adapter.
|
|
85
|
+
*/
|
|
86
|
+
get opts() {
|
|
87
|
+
return {
|
|
88
|
+
namespace: this._namespace,
|
|
89
|
+
keyPrefixSeparator: this._keyPrefixSeparator,
|
|
90
|
+
clearBatchSize: this._clearBatchSize,
|
|
91
|
+
dialect: "redis",
|
|
92
|
+
url: this._client?.options?.url ?? "redis://localhost:6379"
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Set the options for the adapter.
|
|
97
|
+
*/
|
|
98
|
+
set opts(options) {
|
|
99
|
+
this.setOptions(options);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Get the namespace for the adapter. If undefined, it will not use a namespace including keyPrefixing.
|
|
103
|
+
* @default undefined
|
|
104
|
+
*/
|
|
105
|
+
get namespace() {
|
|
106
|
+
return this._namespace;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Set the namespace for the adapter. If undefined, it will not use a namespace including keyPrefixing.
|
|
110
|
+
*/
|
|
111
|
+
set namespace(value) {
|
|
112
|
+
this._namespace = value;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get the separator between the namespace and key.
|
|
116
|
+
* @default '::'
|
|
117
|
+
*/
|
|
118
|
+
get keyPrefixSeparator() {
|
|
119
|
+
return this._keyPrefixSeparator;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Set the separator between the namespace and key.
|
|
123
|
+
*/
|
|
124
|
+
set keyPrefixSeparator(value) {
|
|
125
|
+
this._keyPrefixSeparator = value;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get the number of keys to delete in a single batch.
|
|
129
|
+
* @default 1000
|
|
130
|
+
*/
|
|
131
|
+
get clearBatchSize() {
|
|
132
|
+
return this._clearBatchSize;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Set the number of keys to delete in a single batch.
|
|
136
|
+
*/
|
|
137
|
+
set clearBatchSize(value) {
|
|
138
|
+
this._clearBatchSize = value;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get if Unlink is used instead of Del for clearing keys. This is more performant but may not be supported by all Redis versions.
|
|
142
|
+
* @default true
|
|
143
|
+
*/
|
|
144
|
+
get useUnlink() {
|
|
145
|
+
return this._useUnlink;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Set if Unlink is used instead of Del for clearing keys. This is more performant but may not be supported by all Redis versions.
|
|
149
|
+
*/
|
|
150
|
+
set useUnlink(value) {
|
|
151
|
+
this._useUnlink = value;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get the Redis URL used to connect to the server. This is used to get a connected client.
|
|
155
|
+
*/
|
|
156
|
+
async getClient() {
|
|
157
|
+
if (!this._client.isOpen) {
|
|
158
|
+
await this._client.connect();
|
|
159
|
+
}
|
|
160
|
+
return this._client;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Set a key value pair in the store. TTL is in milliseconds.
|
|
164
|
+
* @param {string} key - the key to set
|
|
165
|
+
* @param {string} value - the value to set
|
|
166
|
+
* @param {number} [ttl] - the time to live in milliseconds
|
|
167
|
+
*/
|
|
168
|
+
async set(key, value, ttl) {
|
|
169
|
+
const client = await this.getClient();
|
|
170
|
+
key = this.createKeyPrefix(key, this._namespace);
|
|
171
|
+
if (ttl) {
|
|
172
|
+
await client.set(key, value, { PX: ttl });
|
|
173
|
+
} else {
|
|
174
|
+
await client.set(key, value);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Will set many key value pairs in the store. TTL is in milliseconds. This will be done as a single transaction.
|
|
179
|
+
* @param {Array<KeyvRedisEntry<string>>} entries - the key value pairs to set with optional ttl
|
|
180
|
+
*/
|
|
181
|
+
async setMany(entries) {
|
|
182
|
+
const client = await this.getClient();
|
|
183
|
+
const multi = client.multi();
|
|
184
|
+
for (const { key, value, ttl } of entries) {
|
|
185
|
+
const prefixedKey = this.createKeyPrefix(key, this._namespace);
|
|
186
|
+
if (ttl) {
|
|
187
|
+
multi.set(prefixedKey, value, { PX: ttl });
|
|
188
|
+
} else {
|
|
189
|
+
multi.set(prefixedKey, value);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
await multi.exec();
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Check if a key exists in the store.
|
|
196
|
+
* @param {string} key - the key to check
|
|
197
|
+
* @returns {Promise<boolean>} - true if the key exists, false if not
|
|
198
|
+
*/
|
|
199
|
+
async has(key) {
|
|
200
|
+
const client = await this.getClient();
|
|
201
|
+
key = this.createKeyPrefix(key, this._namespace);
|
|
202
|
+
const exists = await client.exists(key);
|
|
203
|
+
return exists === 1;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Check if many keys exist in the store. This will be done as a single transaction.
|
|
207
|
+
* @param {Array<string>} keys - the keys to check
|
|
208
|
+
* @returns {Promise<Array<boolean>>} - array of booleans for each key if it exists
|
|
209
|
+
*/
|
|
210
|
+
async hasMany(keys) {
|
|
211
|
+
const client = await this.getClient();
|
|
212
|
+
const multi = client.multi();
|
|
213
|
+
for (const key of keys) {
|
|
214
|
+
const prefixedKey = this.createKeyPrefix(key, this._namespace);
|
|
215
|
+
multi.exists(prefixedKey);
|
|
216
|
+
}
|
|
217
|
+
const results = await multi.exec();
|
|
218
|
+
return results.map((result) => result === 1);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get a value from the store. If the key does not exist, it will return undefined.
|
|
222
|
+
* @param {string} key - the key to get
|
|
223
|
+
* @returns {Promise<string | undefined>} - the value or undefined if the key does not exist
|
|
224
|
+
*/
|
|
225
|
+
async get(key) {
|
|
226
|
+
const client = await this.getClient();
|
|
227
|
+
key = this.createKeyPrefix(key, this._namespace);
|
|
228
|
+
const value = await client.get(key);
|
|
229
|
+
if (value === null) {
|
|
230
|
+
return void 0;
|
|
231
|
+
}
|
|
232
|
+
return value;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Get many values from the store. If a key does not exist, it will return undefined.
|
|
236
|
+
* @param {Array<string>} keys - the keys to get
|
|
237
|
+
* @returns {Promise<Array<string | undefined>>} - array of values or undefined if the key does not exist
|
|
238
|
+
*/
|
|
239
|
+
async getMany(keys) {
|
|
240
|
+
const client = await this.getClient();
|
|
241
|
+
const multi = client.multi();
|
|
242
|
+
for (const key of keys) {
|
|
243
|
+
const prefixedKey = this.createKeyPrefix(key, this._namespace);
|
|
244
|
+
multi.get(prefixedKey);
|
|
245
|
+
}
|
|
246
|
+
const values = await multi.exec();
|
|
247
|
+
return values.map((value) => value === null ? void 0 : value);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Delete a key from the store.
|
|
251
|
+
* @param {string} key - the key to delete
|
|
252
|
+
* @returns {Promise<boolean>} - true if the key was deleted, false if not
|
|
253
|
+
*/
|
|
254
|
+
async delete(key) {
|
|
255
|
+
const client = await this.getClient();
|
|
256
|
+
key = this.createKeyPrefix(key, this._namespace);
|
|
257
|
+
let deleted = 0;
|
|
258
|
+
if (this._useUnlink) {
|
|
259
|
+
deleted = await client.unlink(key);
|
|
260
|
+
} else {
|
|
261
|
+
deleted = await client.del(key);
|
|
262
|
+
}
|
|
263
|
+
return deleted > 0;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Delete many keys from the store. This will be done as a single transaction.
|
|
267
|
+
* @param {Array<string>} keys - the keys to delete
|
|
268
|
+
* @returns {Promise<boolean>} - true if any key was deleted, false if not
|
|
269
|
+
*/
|
|
270
|
+
async deleteMany(keys) {
|
|
271
|
+
let result = false;
|
|
272
|
+
const client = await this.getClient();
|
|
273
|
+
const multi = client.multi();
|
|
274
|
+
for (const key of keys) {
|
|
275
|
+
const prefixedKey = this.createKeyPrefix(key, this._namespace);
|
|
276
|
+
if (this._useUnlink) {
|
|
277
|
+
multi.unlink(prefixedKey);
|
|
278
|
+
} else {
|
|
279
|
+
multi.del(prefixedKey);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const results = await multi.exec();
|
|
283
|
+
for (const deleted of results) {
|
|
284
|
+
if (typeof deleted === "number" && deleted > 0) {
|
|
285
|
+
result = true;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Disconnect from the Redis server.
|
|
292
|
+
* @returns {Promise<void>}
|
|
293
|
+
*/
|
|
294
|
+
async disconnect() {
|
|
295
|
+
if (this._client.isOpen) {
|
|
296
|
+
await this._client.disconnect();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Helper function to create a key with a namespace.
|
|
301
|
+
* @param {string} key - the key to prefix
|
|
302
|
+
* @param {string} namespace - the namespace to prefix the key with
|
|
303
|
+
* @returns {string} - the key with the namespace such as 'namespace::key'
|
|
304
|
+
*/
|
|
305
|
+
createKeyPrefix(key, namespace) {
|
|
306
|
+
if (namespace) {
|
|
307
|
+
return `${namespace}${this._keyPrefixSeparator}${key}`;
|
|
308
|
+
}
|
|
309
|
+
return key;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Helper function to get a key without the namespace.
|
|
313
|
+
* @param {string} key - the key to remove the namespace from
|
|
314
|
+
* @param {string} namespace - the namespace to remove from the key
|
|
315
|
+
* @returns {string} - the key without the namespace such as 'key'
|
|
316
|
+
*/
|
|
317
|
+
getKeyWithoutPrefix(key, namespace) {
|
|
318
|
+
if (namespace) {
|
|
319
|
+
return key.replace(`${namespace}${this._keyPrefixSeparator}`, "");
|
|
320
|
+
}
|
|
321
|
+
return key;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* 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.
|
|
325
|
+
* @param {string} [namespace] - the namespace to iterate over
|
|
326
|
+
* @returns {AsyncGenerator<[string, T | undefined], void, unknown>} - async iterator with key value pairs
|
|
327
|
+
*/
|
|
328
|
+
async *iterator(namespace) {
|
|
329
|
+
const client = await this.getClient();
|
|
330
|
+
const match = namespace ? `${namespace}${this._keyPrefixSeparator}*` : "*";
|
|
331
|
+
let cursor = "0";
|
|
332
|
+
do {
|
|
333
|
+
const result = await client.scan(Number.parseInt(cursor, 10), { MATCH: match, TYPE: "string" });
|
|
334
|
+
cursor = result.cursor.toString();
|
|
335
|
+
let { keys } = result;
|
|
336
|
+
if (!namespace) {
|
|
337
|
+
keys = keys.filter((key) => !key.includes(this._keyPrefixSeparator));
|
|
338
|
+
}
|
|
339
|
+
if (keys.length > 0) {
|
|
340
|
+
const values = await client.mGet(keys);
|
|
341
|
+
for (const [i] of keys.entries()) {
|
|
342
|
+
const key = this.getKeyWithoutPrefix(keys[i], namespace);
|
|
343
|
+
const value = values ? values[i] : void 0;
|
|
344
|
+
yield [key, value];
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} while (cursor !== "0");
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Clear all keys in the store.
|
|
351
|
+
* IMPORTANT: this can cause performance issues if there are a large number of keys in the store. Use with caution as not recommended for production.
|
|
352
|
+
* If a namespace is not set it will clear all keys with no prefix.
|
|
353
|
+
* If a namespace is set it will clear all keys with that namespace.
|
|
354
|
+
* @returns {Promise<void>}
|
|
355
|
+
*/
|
|
356
|
+
async clear() {
|
|
357
|
+
await this.clearNamespace(this._namespace);
|
|
358
|
+
}
|
|
359
|
+
async clearNamespace(namespace) {
|
|
360
|
+
try {
|
|
361
|
+
let cursor = "0";
|
|
362
|
+
const batchSize = this._clearBatchSize;
|
|
363
|
+
const match = namespace ? `${namespace}${this._keyPrefixSeparator}*` : "*";
|
|
364
|
+
const client = await this.getClient();
|
|
365
|
+
do {
|
|
366
|
+
const result = await client.scan(Number.parseInt(cursor, 10), { MATCH: match, COUNT: batchSize, TYPE: "string" });
|
|
367
|
+
cursor = result.cursor.toString();
|
|
368
|
+
let { keys } = result;
|
|
369
|
+
if (keys.length === 0) {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (!namespace) {
|
|
373
|
+
keys = keys.filter((key) => !key.includes(this._keyPrefixSeparator));
|
|
374
|
+
}
|
|
375
|
+
if (keys.length > 0) {
|
|
376
|
+
if (this._useUnlink) {
|
|
377
|
+
await client.unlink(keys);
|
|
378
|
+
} else {
|
|
379
|
+
await client.del(keys);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
} while (cursor !== "0");
|
|
383
|
+
} catch (error) {
|
|
384
|
+
this.emit("error", error);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
setOptions(options) {
|
|
388
|
+
if (!options) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (options.namespace) {
|
|
392
|
+
this._namespace = options.namespace;
|
|
393
|
+
}
|
|
394
|
+
if (options.keyPrefixSeparator) {
|
|
395
|
+
this._keyPrefixSeparator = options.keyPrefixSeparator;
|
|
396
|
+
}
|
|
397
|
+
if (options.clearBatchSize) {
|
|
398
|
+
this._clearBatchSize = options.clearBatchSize;
|
|
399
|
+
}
|
|
400
|
+
if (options.useUnlink !== void 0) {
|
|
401
|
+
this._useUnlink = options.useUnlink;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
initClient() {
|
|
405
|
+
this._client.on("error", (error) => {
|
|
406
|
+
this.emit("error", error);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
function createKeyv(connect, options) {
|
|
411
|
+
const adapter = new KeyvRedis(connect, options);
|
|
412
|
+
const keyv = new import_keyv.Keyv({ store: adapter, namespace: options?.namespace });
|
|
413
|
+
return keyv;
|
|
414
|
+
}
|
|
415
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
416
|
+
0 && (module.exports = {
|
|
417
|
+
Keyv,
|
|
418
|
+
createClient,
|
|
419
|
+
createCluster,
|
|
420
|
+
createKeyv
|
|
421
|
+
});
|