@keyv/redis 3.0.1 → 4.0.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 +196 -39
- package/dist/index.cjs +347 -89
- package/dist/index.d.cts +200 -23
- package/dist/index.d.ts +200 -23
- package/dist/index.js +345 -89
- package/package.json +79 -79
package/dist/index.cjs
CHANGED
|
@@ -30,134 +30,392 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
|
-
|
|
33
|
+
Keyv: () => import_keyv2.Keyv,
|
|
34
|
+
createClient: () => import_redis2.createClient,
|
|
35
|
+
createCluster: () => import_redis2.createCluster,
|
|
36
|
+
createKeyv: () => createKeyv,
|
|
37
|
+
default: () => KeyvRedis
|
|
34
38
|
});
|
|
35
39
|
module.exports = __toCommonJS(src_exports);
|
|
36
40
|
var import_events = __toESM(require("events"), 1);
|
|
37
|
-
var
|
|
41
|
+
var import_redis = require("redis");
|
|
42
|
+
var import_keyv = require("keyv");
|
|
43
|
+
var import_redis2 = require("redis");
|
|
44
|
+
var import_keyv2 = require("keyv");
|
|
38
45
|
var KeyvRedis = class extends import_events.default {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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) {
|
|
44
57
|
super();
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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 });
|
|
50
173
|
} else {
|
|
51
|
-
|
|
52
|
-
this.redis = new import_ioredis.default(options.uri, options);
|
|
174
|
+
await client.set(key, value);
|
|
53
175
|
}
|
|
54
|
-
|
|
55
|
-
|
|
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
|
+
}
|
|
56
191
|
}
|
|
57
|
-
|
|
192
|
+
await multi.exec();
|
|
58
193
|
}
|
|
59
|
-
|
|
60
|
-
|
|
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;
|
|
61
204
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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);
|
|
65
216
|
}
|
|
66
|
-
|
|
67
|
-
|
|
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
|
+
*/
|
|
68
225
|
async get(key) {
|
|
69
|
-
|
|
70
|
-
|
|
226
|
+
const client = await this.getClient();
|
|
227
|
+
key = this.createKeyPrefix(key, this._namespace);
|
|
228
|
+
const value = await client.get(key);
|
|
71
229
|
if (value === null) {
|
|
72
230
|
return void 0;
|
|
73
231
|
}
|
|
74
232
|
return value;
|
|
75
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
|
+
*/
|
|
76
239
|
async getMany(keys) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return void 0;
|
|
83
|
-
}
|
|
84
|
-
key = this._getKeyName(key);
|
|
85
|
-
const set = async (redis) => {
|
|
86
|
-
if (typeof ttl === "number") {
|
|
87
|
-
await redis.set(key, value, "PX", ttl);
|
|
88
|
-
} else {
|
|
89
|
-
await redis.set(key, value);
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
if (this.opts.useRedisSets) {
|
|
93
|
-
const trx = await this.redis.multi();
|
|
94
|
-
await set(trx);
|
|
95
|
-
await trx.sadd(this._getNamespace(), key);
|
|
96
|
-
await trx.exec();
|
|
97
|
-
} else {
|
|
98
|
-
await set(this.redis);
|
|
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);
|
|
99
245
|
}
|
|
246
|
+
const values = await multi.exec();
|
|
247
|
+
return values.map((value) => value === null ? void 0 : value);
|
|
100
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
|
+
*/
|
|
101
254
|
async delete(key) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (this.
|
|
106
|
-
|
|
107
|
-
await unlink(trx);
|
|
108
|
-
await trx.srem(this._getNamespace(), key);
|
|
109
|
-
const r = await trx.exec();
|
|
110
|
-
items = r[0][1];
|
|
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);
|
|
111
260
|
} else {
|
|
112
|
-
|
|
261
|
+
deleted = await client.del(key);
|
|
113
262
|
}
|
|
114
|
-
return
|
|
263
|
+
return deleted > 0;
|
|
115
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
|
+
*/
|
|
116
270
|
async deleteMany(keys) {
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.redis.unlink([...keys]),
|
|
127
|
-
this.redis.srem(this._getNamespace(), [...keys])
|
|
128
|
-
]);
|
|
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);
|
|
129
280
|
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (
|
|
134
|
-
|
|
281
|
+
}
|
|
282
|
+
const results = await multi.exec();
|
|
283
|
+
for (const deleted of results) {
|
|
284
|
+
if (typeof deleted === "number" && deleted > 0) {
|
|
285
|
+
result = true;
|
|
135
286
|
}
|
|
136
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;
|
|
137
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
|
+
*/
|
|
138
328
|
async *iterator(namespace) {
|
|
139
|
-
const
|
|
140
|
-
const
|
|
329
|
+
const client = await this.getClient();
|
|
330
|
+
const match = namespace ? `${namespace}${this._keyPrefixSeparator}*` : "*";
|
|
141
331
|
let cursor = "0";
|
|
142
332
|
do {
|
|
143
|
-
const
|
|
144
|
-
cursor =
|
|
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
|
+
}
|
|
145
339
|
if (keys.length > 0) {
|
|
146
|
-
const values = await
|
|
340
|
+
const values = await client.mGet(keys);
|
|
147
341
|
for (const [i] of keys.entries()) {
|
|
148
|
-
const key = keys[i];
|
|
149
|
-
const value = values[i];
|
|
342
|
+
const key = this.getKeyWithoutPrefix(keys[i], namespace);
|
|
343
|
+
const value = values ? values[i] : void 0;
|
|
150
344
|
yield [key, value];
|
|
151
345
|
}
|
|
152
346
|
}
|
|
153
347
|
} while (cursor !== "0");
|
|
154
348
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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);
|
|
158
358
|
}
|
|
159
|
-
async
|
|
160
|
-
|
|
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
|
+
});
|
|
161
408
|
}
|
|
162
409
|
};
|
|
163
|
-
|
|
410
|
+
function createKeyv(connect, options) {
|
|
411
|
+
const adapter = new KeyvRedis(connect, options);
|
|
412
|
+
const keyv = new import_keyv.Keyv({ store: adapter, namespace: options?.namespace, useKeyPrefix: false });
|
|
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
|
+
});
|