keyv 6.0.0-alpha.3 → 6.0.0-beta.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/LICENSE +1 -1
- package/README.md +270 -56
- package/dist/index.cjs +2269 -1100
- package/dist/index.d.cts +1329 -367
- package/dist/index.d.mts +1351 -0
- package/dist/index.mjs +2267 -0
- package/package.json +14 -8
- package/dist/index.d.ts +0 -389
- package/dist/index.js +0 -1087
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2267 @@
|
|
|
1
|
+
import { Hookified } from "hookified";
|
|
2
|
+
//#region src/capabilities.ts
|
|
3
|
+
const keyvMethodNames = [
|
|
4
|
+
"get",
|
|
5
|
+
"set",
|
|
6
|
+
"delete",
|
|
7
|
+
"clear",
|
|
8
|
+
"has",
|
|
9
|
+
"getMany",
|
|
10
|
+
"setMany",
|
|
11
|
+
"deleteMany",
|
|
12
|
+
"hasMany",
|
|
13
|
+
"disconnect",
|
|
14
|
+
"getRaw",
|
|
15
|
+
"getManyRaw",
|
|
16
|
+
"setRaw",
|
|
17
|
+
"setManyRaw",
|
|
18
|
+
"iterator"
|
|
19
|
+
];
|
|
20
|
+
const keyvPropertyNames = ["hooks", "stats"];
|
|
21
|
+
const keyvStorageMethodNames = [
|
|
22
|
+
"get",
|
|
23
|
+
"getMany",
|
|
24
|
+
"has",
|
|
25
|
+
"hasMany",
|
|
26
|
+
"set",
|
|
27
|
+
"setMany",
|
|
28
|
+
"delete",
|
|
29
|
+
"deleteMany",
|
|
30
|
+
"clear",
|
|
31
|
+
"disconnect",
|
|
32
|
+
"iterator"
|
|
33
|
+
];
|
|
34
|
+
const keyvCompressionMethodNames = ["compress", "decompress"];
|
|
35
|
+
const keyvSerializationMethodNames = ["stringify", "parse"];
|
|
36
|
+
const keyvEncryptionMethodNames = ["encrypt", "decrypt"];
|
|
37
|
+
function isMethod(obj, name) {
|
|
38
|
+
return name in obj && typeof obj[name] === "function";
|
|
39
|
+
}
|
|
40
|
+
function isProperty(obj, name) {
|
|
41
|
+
return name in obj;
|
|
42
|
+
}
|
|
43
|
+
function resolveMethodType(obj, name) {
|
|
44
|
+
if (!(name in obj)) return "none";
|
|
45
|
+
const value = obj[name];
|
|
46
|
+
if (typeof value !== "function") return "none";
|
|
47
|
+
return value.constructor.name === "AsyncFunction" ? "async" : "sync";
|
|
48
|
+
}
|
|
49
|
+
function resolveMethod(obj, name) {
|
|
50
|
+
return {
|
|
51
|
+
exists: isMethod(obj, name),
|
|
52
|
+
methodType: resolveMethodType(obj, name)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const noneMethod = {
|
|
56
|
+
exists: false,
|
|
57
|
+
methodType: "none"
|
|
58
|
+
};
|
|
59
|
+
function buildMethods(obj, names) {
|
|
60
|
+
const methods = {};
|
|
61
|
+
for (const name of names) methods[name] = obj ? resolveMethod(obj, name) : { ...noneMethod };
|
|
62
|
+
return methods;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Detect whether an object implements the full Keyv interface
|
|
66
|
+
* @param obj - The object to check
|
|
67
|
+
* @returns A {@link KeyvCapability} where `compatible` is `true` only when all required capabilities are present
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* import Keyv, { detectKeyv } from 'keyv';
|
|
71
|
+
*
|
|
72
|
+
* const result = detectKeyv(new Keyv());
|
|
73
|
+
* result.compatible; // true — all capabilities present
|
|
74
|
+
* result.methods.get.exists; // true
|
|
75
|
+
* result.methods.get.methodType; // "async"
|
|
76
|
+
*
|
|
77
|
+
* const partial = detectKeyv(new Map());
|
|
78
|
+
* partial.compatible; // false — missing getMany, setMany, hooks, stats, etc.
|
|
79
|
+
* partial.methods.get.exists; // true
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
function detectKeyv(obj) {
|
|
83
|
+
if (obj === null || obj === void 0 || typeof obj !== "object") return {
|
|
84
|
+
compatible: false,
|
|
85
|
+
methods: buildMethods(null, keyvMethodNames),
|
|
86
|
+
properties: {
|
|
87
|
+
hooks: false,
|
|
88
|
+
stats: false
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const methods = buildMethods(obj, keyvMethodNames);
|
|
92
|
+
const properties = {
|
|
93
|
+
hooks: isProperty(obj, "hooks"),
|
|
94
|
+
stats: isProperty(obj, "stats")
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
compatible: [...keyvMethodNames, ...keyvPropertyNames].every((k) => {
|
|
98
|
+
if (k === "hooks" || k === "stats") return properties[k];
|
|
99
|
+
return methods[k].exists;
|
|
100
|
+
}),
|
|
101
|
+
methods,
|
|
102
|
+
properties
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Detect whether an object implements the Keyv storage adapter interface
|
|
107
|
+
* @param obj - The object to check
|
|
108
|
+
* @returns A {@link KeyvStorageCapability} where:
|
|
109
|
+
* - `compatible` is `true` when the object is a valid storage adapter (`"keyvStorage"`, `"mapLike"`, or `"asyncMap"`)
|
|
110
|
+
* - `store` indicates the detected store type: `"keyvStorage"`, `"mapLike"`, `"asyncMap"`, or `"none"`
|
|
111
|
+
* - `methods` maps each method name to `{ exists, methodType }`
|
|
112
|
+
* @example
|
|
113
|
+
* ```typescript
|
|
114
|
+
* import { detectKeyvStorage } from 'keyv';
|
|
115
|
+
*
|
|
116
|
+
* const map = detectKeyvStorage(new Map());
|
|
117
|
+
* map.compatible; // true
|
|
118
|
+
* map.store; // "mapLike"
|
|
119
|
+
* map.methods.get.exists; // true
|
|
120
|
+
* map.methods.get.methodType; // "sync"
|
|
121
|
+
*
|
|
122
|
+
* const adapter = detectKeyvStorage(asyncAdapter);
|
|
123
|
+
* adapter.compatible; // true
|
|
124
|
+
* adapter.store; // "keyvStorage"
|
|
125
|
+
* adapter.methods.get.methodType; // "async"
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
function detectKeyvStorage(obj) {
|
|
129
|
+
if (obj === null || obj === void 0 || typeof obj !== "object") return {
|
|
130
|
+
compatible: false,
|
|
131
|
+
store: "none",
|
|
132
|
+
methods: buildMethods(null, keyvStorageMethodNames)
|
|
133
|
+
};
|
|
134
|
+
const methods = buildMethods(obj, keyvStorageMethodNames);
|
|
135
|
+
if ([
|
|
136
|
+
"get",
|
|
137
|
+
"has",
|
|
138
|
+
"hasMany",
|
|
139
|
+
"set",
|
|
140
|
+
"setMany",
|
|
141
|
+
"delete",
|
|
142
|
+
"deleteMany",
|
|
143
|
+
"clear"
|
|
144
|
+
].every((k) => methods[k].exists && methods[k].methodType === "async")) return {
|
|
145
|
+
compatible: true,
|
|
146
|
+
store: "keyvStorage",
|
|
147
|
+
methods
|
|
148
|
+
};
|
|
149
|
+
if ([
|
|
150
|
+
"get",
|
|
151
|
+
"set",
|
|
152
|
+
"delete",
|
|
153
|
+
"has"
|
|
154
|
+
].every((m) => methods[m].exists && methods[m].methodType === "sync")) return {
|
|
155
|
+
compatible: true,
|
|
156
|
+
store: "mapLike",
|
|
157
|
+
methods
|
|
158
|
+
};
|
|
159
|
+
if ([
|
|
160
|
+
"get",
|
|
161
|
+
"set",
|
|
162
|
+
"delete",
|
|
163
|
+
"clear"
|
|
164
|
+
].every((m) => methods[m].exists)) return {
|
|
165
|
+
compatible: true,
|
|
166
|
+
store: "asyncMap",
|
|
167
|
+
methods
|
|
168
|
+
};
|
|
169
|
+
return {
|
|
170
|
+
compatible: false,
|
|
171
|
+
store: "none",
|
|
172
|
+
methods
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Detect whether an object implements the Keyv compression adapter interface
|
|
177
|
+
* @param obj - The object to check
|
|
178
|
+
* @returns A {@link KeyvCompressionCapability} where `compatible` is `true` when both `compress` and `decompress` methods are present
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* import { detectKeyvCompression } from 'keyv';
|
|
182
|
+
*
|
|
183
|
+
* detectKeyvCompression({ compress: (d) => d, decompress: (d) => d });
|
|
184
|
+
* // { compatible: true, methods: { compress: { exists: true, methodType: "sync" }, decompress: { exists: true, methodType: "sync" } } }
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
function detectKeyvCompression(obj) {
|
|
188
|
+
if (obj === null || obj === void 0 || typeof obj !== "object") return {
|
|
189
|
+
compatible: false,
|
|
190
|
+
methods: buildMethods(null, keyvCompressionMethodNames)
|
|
191
|
+
};
|
|
192
|
+
const methods = buildMethods(obj, keyvCompressionMethodNames);
|
|
193
|
+
return {
|
|
194
|
+
compatible: keyvCompressionMethodNames.every((k) => methods[k].exists),
|
|
195
|
+
methods
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Detect whether an object implements the Keyv serialization adapter interface
|
|
200
|
+
* @param obj - The object to check
|
|
201
|
+
* @returns A {@link KeyvSerializationCapability} where `compatible` is `true` when both `stringify` and `parse` methods are present
|
|
202
|
+
* @example
|
|
203
|
+
* ```typescript
|
|
204
|
+
* import { detectKeyvSerialization } from 'keyv';
|
|
205
|
+
*
|
|
206
|
+
* detectKeyvSerialization(JSON);
|
|
207
|
+
* // { compatible: true, methods: { stringify: { exists: true, methodType: "sync" }, parse: { exists: true, methodType: "sync" } } }
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
210
|
+
function detectKeyvSerialization(obj) {
|
|
211
|
+
if (obj === null || obj === void 0 || typeof obj !== "object") return {
|
|
212
|
+
compatible: false,
|
|
213
|
+
methods: buildMethods(null, keyvSerializationMethodNames)
|
|
214
|
+
};
|
|
215
|
+
const methods = buildMethods(obj, keyvSerializationMethodNames);
|
|
216
|
+
return {
|
|
217
|
+
compatible: keyvSerializationMethodNames.every((k) => methods[k].exists),
|
|
218
|
+
methods
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Detect whether an object implements the Keyv encryption adapter interface
|
|
223
|
+
* @param obj - The object to check
|
|
224
|
+
* @returns A {@link KeyvEncryptionCapability} where `compatible` is `true` when both `encrypt` and `decrypt` methods are present
|
|
225
|
+
* @example
|
|
226
|
+
* ```typescript
|
|
227
|
+
* import { detectKeyvEncryption } from 'keyv';
|
|
228
|
+
*
|
|
229
|
+
* detectKeyvEncryption({ encrypt: (d) => d, decrypt: (d) => d });
|
|
230
|
+
* // { compatible: true, methods: { encrypt: { exists: true, methodType: "sync" }, decrypt: { exists: true, methodType: "sync" } } }
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
function detectKeyvEncryption(obj) {
|
|
234
|
+
if (obj === null || obj === void 0 || typeof obj !== "object") return {
|
|
235
|
+
compatible: false,
|
|
236
|
+
methods: buildMethods(null, keyvEncryptionMethodNames)
|
|
237
|
+
};
|
|
238
|
+
const methods = buildMethods(obj, keyvEncryptionMethodNames);
|
|
239
|
+
return {
|
|
240
|
+
compatible: keyvEncryptionMethodNames.every((k) => methods[k].exists),
|
|
241
|
+
methods
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
//#endregion
|
|
245
|
+
//#region src/types/keyv.ts
|
|
246
|
+
/**
|
|
247
|
+
* Events emitted by Keyv for error handling and telemetry.
|
|
248
|
+
*/
|
|
249
|
+
let KeyvEvents = /* @__PURE__ */ function(KeyvEvents) {
|
|
250
|
+
/** Emitted when an error occurs in a store operation. */
|
|
251
|
+
KeyvEvents["ERROR"] = "error";
|
|
252
|
+
/** Emitted for informational messages. */
|
|
253
|
+
KeyvEvents["INFO"] = "info";
|
|
254
|
+
/** Emitted for warning messages. */
|
|
255
|
+
KeyvEvents["WARN"] = "warn";
|
|
256
|
+
/** Telemetry: cache hit. */
|
|
257
|
+
KeyvEvents["STAT_HIT"] = "stat:hit";
|
|
258
|
+
/** Telemetry: cache miss. */
|
|
259
|
+
KeyvEvents["STAT_MISS"] = "stat:miss";
|
|
260
|
+
/** Telemetry: value set. */
|
|
261
|
+
KeyvEvents["STAT_SET"] = "stat:set";
|
|
262
|
+
/** Telemetry: value deleted. */
|
|
263
|
+
KeyvEvents["STAT_DELETE"] = "stat:delete";
|
|
264
|
+
/** Telemetry: operation error. */
|
|
265
|
+
KeyvEvents["STAT_ERROR"] = "stat:error";
|
|
266
|
+
return KeyvEvents;
|
|
267
|
+
}({});
|
|
268
|
+
/**
|
|
269
|
+
* Hook names for intercepting Keyv operations.
|
|
270
|
+
* Register hooks via `keyv.on(KeyvHooks.BEFORE_SET, callback)` to run logic before/after operations.
|
|
271
|
+
*/
|
|
272
|
+
let KeyvHooks = /* @__PURE__ */ function(KeyvHooks) {
|
|
273
|
+
/** @deprecated Use BEFORE_SET instead */
|
|
274
|
+
KeyvHooks["PRE_SET"] = "preSet";
|
|
275
|
+
/** @deprecated Use AFTER_SET instead */
|
|
276
|
+
KeyvHooks["POST_SET"] = "postSet";
|
|
277
|
+
/** @deprecated Use BEFORE_GET instead */
|
|
278
|
+
KeyvHooks["PRE_GET"] = "preGet";
|
|
279
|
+
/** @deprecated Use AFTER_GET instead */
|
|
280
|
+
KeyvHooks["POST_GET"] = "postGet";
|
|
281
|
+
/** @deprecated Use BEFORE_GET_MANY instead */
|
|
282
|
+
KeyvHooks["PRE_GET_MANY"] = "preGetMany";
|
|
283
|
+
/** @deprecated Use AFTER_GET_MANY instead */
|
|
284
|
+
KeyvHooks["POST_GET_MANY"] = "postGetMany";
|
|
285
|
+
/** @deprecated Use BEFORE_GET_RAW instead */
|
|
286
|
+
KeyvHooks["PRE_GET_RAW"] = "preGetRaw";
|
|
287
|
+
/** @deprecated Use AFTER_GET_RAW instead */
|
|
288
|
+
KeyvHooks["POST_GET_RAW"] = "postGetRaw";
|
|
289
|
+
/** @deprecated Use BEFORE_GET_MANY_RAW instead */
|
|
290
|
+
KeyvHooks["PRE_GET_MANY_RAW"] = "preGetManyRaw";
|
|
291
|
+
/** @deprecated Use AFTER_GET_MANY_RAW instead */
|
|
292
|
+
KeyvHooks["POST_GET_MANY_RAW"] = "postGetManyRaw";
|
|
293
|
+
/** @deprecated Use BEFORE_SET_RAW instead */
|
|
294
|
+
KeyvHooks["PRE_SET_RAW"] = "preSetRaw";
|
|
295
|
+
/** @deprecated Use AFTER_SET_RAW instead */
|
|
296
|
+
KeyvHooks["POST_SET_RAW"] = "postSetRaw";
|
|
297
|
+
/** @deprecated Use BEFORE_SET_MANY_RAW instead */
|
|
298
|
+
KeyvHooks["PRE_SET_MANY_RAW"] = "preSetManyRaw";
|
|
299
|
+
/** @deprecated Use AFTER_SET_MANY_RAW instead */
|
|
300
|
+
KeyvHooks["POST_SET_MANY_RAW"] = "postSetManyRaw";
|
|
301
|
+
/** @deprecated Use BEFORE_SET_MANY instead */
|
|
302
|
+
KeyvHooks["PRE_SET_MANY"] = "preSetMany";
|
|
303
|
+
/** @deprecated Use AFTER_SET_MANY instead */
|
|
304
|
+
KeyvHooks["POST_SET_MANY"] = "postSetMany";
|
|
305
|
+
/** @deprecated Use BEFORE_DELETE instead */
|
|
306
|
+
KeyvHooks["PRE_DELETE"] = "preDelete";
|
|
307
|
+
/** @deprecated Use AFTER_DELETE instead */
|
|
308
|
+
KeyvHooks["POST_DELETE"] = "postDelete";
|
|
309
|
+
/** @deprecated Use BEFORE_DELETE_MANY instead */
|
|
310
|
+
KeyvHooks["PRE_DELETE_MANY"] = "preDeleteMany";
|
|
311
|
+
/** @deprecated Use AFTER_DELETE_MANY instead */
|
|
312
|
+
KeyvHooks["POST_DELETE_MANY"] = "postDeleteMany";
|
|
313
|
+
KeyvHooks["BEFORE_SET"] = "before:set";
|
|
314
|
+
KeyvHooks["AFTER_SET"] = "after:set";
|
|
315
|
+
KeyvHooks["BEFORE_GET"] = "before:get";
|
|
316
|
+
KeyvHooks["AFTER_GET"] = "after:get";
|
|
317
|
+
KeyvHooks["BEFORE_GET_MANY"] = "before:getMany";
|
|
318
|
+
KeyvHooks["AFTER_GET_MANY"] = "after:getMany";
|
|
319
|
+
KeyvHooks["BEFORE_GET_RAW"] = "before:getRaw";
|
|
320
|
+
KeyvHooks["AFTER_GET_RAW"] = "after:getRaw";
|
|
321
|
+
KeyvHooks["BEFORE_GET_MANY_RAW"] = "before:getManyRaw";
|
|
322
|
+
KeyvHooks["AFTER_GET_MANY_RAW"] = "after:getManyRaw";
|
|
323
|
+
KeyvHooks["BEFORE_SET_RAW"] = "before:setRaw";
|
|
324
|
+
KeyvHooks["AFTER_SET_RAW"] = "after:setRaw";
|
|
325
|
+
KeyvHooks["BEFORE_SET_MANY"] = "before:setMany";
|
|
326
|
+
KeyvHooks["AFTER_SET_MANY"] = "after:setMany";
|
|
327
|
+
KeyvHooks["BEFORE_SET_MANY_RAW"] = "before:setManyRaw";
|
|
328
|
+
KeyvHooks["AFTER_SET_MANY_RAW"] = "after:setManyRaw";
|
|
329
|
+
KeyvHooks["BEFORE_DELETE"] = "before:delete";
|
|
330
|
+
KeyvHooks["AFTER_DELETE"] = "after:delete";
|
|
331
|
+
KeyvHooks["BEFORE_DELETE_MANY"] = "before:deleteMany";
|
|
332
|
+
KeyvHooks["AFTER_DELETE_MANY"] = "after:deleteMany";
|
|
333
|
+
KeyvHooks["BEFORE_HAS"] = "before:has";
|
|
334
|
+
KeyvHooks["AFTER_HAS"] = "after:has";
|
|
335
|
+
KeyvHooks["BEFORE_HAS_MANY"] = "before:hasMany";
|
|
336
|
+
KeyvHooks["AFTER_HAS_MANY"] = "after:hasMany";
|
|
337
|
+
KeyvHooks["BEFORE_CLEAR"] = "before:clear";
|
|
338
|
+
KeyvHooks["AFTER_CLEAR"] = "after:clear";
|
|
339
|
+
KeyvHooks["BEFORE_DISCONNECT"] = "before:disconnect";
|
|
340
|
+
KeyvHooks["AFTER_DISCONNECT"] = "after:disconnect";
|
|
341
|
+
return KeyvHooks;
|
|
342
|
+
}({});
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region src/utils.ts
|
|
345
|
+
/**
|
|
346
|
+
* Check whether a deserialized entry has expired based on its `expires` timestamp.
|
|
347
|
+
*/
|
|
348
|
+
function isDataExpired(data) {
|
|
349
|
+
return typeof data.expires === "number" && Date.now() > data.expires;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Calculate an absolute expiry timestamp from a TTL value.
|
|
353
|
+
* Returns `undefined` when `ttl` is absent, zero, negative, or non-finite
|
|
354
|
+
* (meaning "no expiry").
|
|
355
|
+
*
|
|
356
|
+
* @param ttl - Time-to-live in milliseconds, or `undefined`
|
|
357
|
+
* @returns Absolute expiry timestamp (ms since epoch), or `undefined`
|
|
358
|
+
*/
|
|
359
|
+
function calculateExpires(ttl) {
|
|
360
|
+
if (typeof ttl !== "number" || ttl <= 0 || !Number.isFinite(ttl)) return;
|
|
361
|
+
return Date.now() + ttl;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Resolve a TTL value by falling back to a default when none is given,
|
|
365
|
+
* then normalising zero, negative, or non-finite values to `undefined` (meaning "no expiry").
|
|
366
|
+
*
|
|
367
|
+
* @param ttl - Explicit TTL in milliseconds, or `undefined`
|
|
368
|
+
* @param defaultTtl - Fallback TTL (typically `Keyv._ttl`), or `undefined`
|
|
369
|
+
* @returns The resolved TTL in milliseconds, or `undefined` for no expiry
|
|
370
|
+
*/
|
|
371
|
+
function resolveTtl(ttl, defaultTtl) {
|
|
372
|
+
const resolved = ttl ?? defaultTtl;
|
|
373
|
+
if (resolved === void 0 || resolved <= 0 || !Number.isFinite(resolved)) return;
|
|
374
|
+
return resolved;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Derive a store-level TTL from an absolute `expires` timestamp.
|
|
378
|
+
* Returns `undefined` when `expires` is absent, non-finite, or when the derived
|
|
379
|
+
* TTL is zero or negative (i.e. the entry has already expired).
|
|
380
|
+
*
|
|
381
|
+
* @param expires - Absolute expiry timestamp in milliseconds since epoch, or `undefined`
|
|
382
|
+
* @returns The remaining TTL in milliseconds, or `undefined`
|
|
383
|
+
*/
|
|
384
|
+
function ttlFromExpires(expires) {
|
|
385
|
+
if (typeof expires !== "number" || !Number.isFinite(expires)) return;
|
|
386
|
+
const remaining = expires - Date.now();
|
|
387
|
+
return remaining > 0 ? remaining : void 0;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Scan parallel `keys` and `data` arrays, nullify any expired entries in
|
|
391
|
+
* `data`, and batch-delete the corresponding keys via `keyv.deleteMany()`.
|
|
392
|
+
*/
|
|
393
|
+
async function deleteExpiredKeys(keys, data, keyv) {
|
|
394
|
+
const expiredKeys = [];
|
|
395
|
+
for (const [index, row] of data.entries()) if (row !== void 0 && row !== null && isDataExpired(row)) {
|
|
396
|
+
expiredKeys.push(keys[index]);
|
|
397
|
+
data[index] = void 0;
|
|
398
|
+
}
|
|
399
|
+
if (expiredKeys.length > 0) await keyv.deleteMany(expiredKeys);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Maps new hook names to their deprecated equivalents so both fire during migration.
|
|
403
|
+
*/
|
|
404
|
+
const deprecatedHookAliases = new Map([
|
|
405
|
+
[KeyvHooks.BEFORE_SET, KeyvHooks.PRE_SET],
|
|
406
|
+
[KeyvHooks.AFTER_SET, KeyvHooks.POST_SET],
|
|
407
|
+
[KeyvHooks.BEFORE_GET, KeyvHooks.PRE_GET],
|
|
408
|
+
[KeyvHooks.AFTER_GET, KeyvHooks.POST_GET],
|
|
409
|
+
[KeyvHooks.BEFORE_GET_MANY, KeyvHooks.PRE_GET_MANY],
|
|
410
|
+
[KeyvHooks.AFTER_GET_MANY, KeyvHooks.POST_GET_MANY],
|
|
411
|
+
[KeyvHooks.BEFORE_GET_RAW, KeyvHooks.PRE_GET_RAW],
|
|
412
|
+
[KeyvHooks.AFTER_GET_RAW, KeyvHooks.POST_GET_RAW],
|
|
413
|
+
[KeyvHooks.BEFORE_GET_MANY_RAW, KeyvHooks.PRE_GET_MANY_RAW],
|
|
414
|
+
[KeyvHooks.AFTER_GET_MANY_RAW, KeyvHooks.POST_GET_MANY_RAW],
|
|
415
|
+
[KeyvHooks.BEFORE_SET_RAW, KeyvHooks.PRE_SET_RAW],
|
|
416
|
+
[KeyvHooks.AFTER_SET_RAW, KeyvHooks.POST_SET_RAW],
|
|
417
|
+
[KeyvHooks.BEFORE_SET_MANY, KeyvHooks.PRE_SET_MANY],
|
|
418
|
+
[KeyvHooks.AFTER_SET_MANY, KeyvHooks.POST_SET_MANY],
|
|
419
|
+
[KeyvHooks.BEFORE_SET_MANY_RAW, KeyvHooks.PRE_SET_MANY_RAW],
|
|
420
|
+
[KeyvHooks.AFTER_SET_MANY_RAW, KeyvHooks.POST_SET_MANY_RAW],
|
|
421
|
+
[KeyvHooks.BEFORE_DELETE, KeyvHooks.PRE_DELETE],
|
|
422
|
+
[KeyvHooks.AFTER_DELETE, KeyvHooks.POST_DELETE],
|
|
423
|
+
[KeyvHooks.BEFORE_DELETE_MANY, KeyvHooks.PRE_DELETE_MANY],
|
|
424
|
+
[KeyvHooks.AFTER_DELETE_MANY, KeyvHooks.POST_DELETE_MANY]
|
|
425
|
+
]);
|
|
426
|
+
/**
|
|
427
|
+
* Build the deprecated-hooks map used by Hookified to warn when old PRE_/POST_ hook names are registered.
|
|
428
|
+
*/
|
|
429
|
+
function buildDeprecatedHooks() {
|
|
430
|
+
return new Map([
|
|
431
|
+
["preSet", "Use KeyvHooks.BEFORE_SET ('before:set') instead"],
|
|
432
|
+
["postSet", "Use KeyvHooks.AFTER_SET ('after:set') instead"],
|
|
433
|
+
["preGet", "Use KeyvHooks.BEFORE_GET ('before:get') instead"],
|
|
434
|
+
["postGet", "Use KeyvHooks.AFTER_GET ('after:get') instead"],
|
|
435
|
+
["preGetMany", "Use KeyvHooks.BEFORE_GET_MANY ('before:getMany') instead"],
|
|
436
|
+
["postGetMany", "Use KeyvHooks.AFTER_GET_MANY ('after:getMany') instead"],
|
|
437
|
+
["preGetRaw", "Use KeyvHooks.BEFORE_GET_RAW ('before:getRaw') instead"],
|
|
438
|
+
["postGetRaw", "Use KeyvHooks.AFTER_GET_RAW ('after:getRaw') instead"],
|
|
439
|
+
["preGetManyRaw", "Use KeyvHooks.BEFORE_GET_MANY_RAW ('before:getManyRaw') instead"],
|
|
440
|
+
["postGetManyRaw", "Use KeyvHooks.AFTER_GET_MANY_RAW ('after:getManyRaw') instead"],
|
|
441
|
+
["preSetRaw", "Use KeyvHooks.BEFORE_SET_RAW ('before:setRaw') instead"],
|
|
442
|
+
["postSetRaw", "Use KeyvHooks.AFTER_SET_RAW ('after:setRaw') instead"],
|
|
443
|
+
["preSetMany", "Use KeyvHooks.BEFORE_SET_MANY ('before:setMany') instead"],
|
|
444
|
+
["postSetMany", "Use KeyvHooks.AFTER_SET_MANY ('after:setMany') instead"],
|
|
445
|
+
["preSetManyRaw", "Use KeyvHooks.BEFORE_SET_MANY_RAW ('before:setManyRaw') instead"],
|
|
446
|
+
["postSetManyRaw", "Use KeyvHooks.AFTER_SET_MANY_RAW ('after:setManyRaw') instead"],
|
|
447
|
+
["preDelete", "Use KeyvHooks.BEFORE_DELETE ('before:delete') instead"],
|
|
448
|
+
["postDelete", "Use KeyvHooks.AFTER_DELETE ('after:delete') instead"],
|
|
449
|
+
["preDeleteMany", "Use KeyvHooks.BEFORE_DELETE_MANY ('before:deleteMany') instead"],
|
|
450
|
+
["postDeleteMany", "Use KeyvHooks.AFTER_DELETE_MANY ('after:deleteMany') instead"]
|
|
451
|
+
]);
|
|
452
|
+
}
|
|
453
|
+
//#endregion
|
|
454
|
+
//#region src/adapters/bridge.ts
|
|
455
|
+
/**
|
|
456
|
+
* A bridge storage adapter for Keyv that wraps any promise-based store.
|
|
457
|
+
*
|
|
458
|
+
* This class provides a unified interface for using various async stores
|
|
459
|
+
* with Keyv, handling namespace prefixing, TTL-based expiration, and batch operations.
|
|
460
|
+
* If the underlying store implements optional methods (has, hasMany, getMany, etc.),
|
|
461
|
+
* the bridge will delegate to them. Otherwise, it falls back to using primitives.
|
|
462
|
+
*
|
|
463
|
+
* @example
|
|
464
|
+
* ```typescript
|
|
465
|
+
* // Using with a promise-based store
|
|
466
|
+
* const bridge = new KeyvBridgeAdapter(myAsyncStore, { namespace: 'cache' });
|
|
467
|
+
*
|
|
468
|
+
* // Using with Keyv
|
|
469
|
+
* const keyv = new Keyv({ store: new KeyvBridgeAdapter(myAsyncStore) });
|
|
470
|
+
* ```
|
|
471
|
+
*/
|
|
472
|
+
var KeyvBridgeAdapter = class extends Hookified {
|
|
473
|
+
_store;
|
|
474
|
+
_namespace;
|
|
475
|
+
_keySeparator = ":";
|
|
476
|
+
_capabilities;
|
|
477
|
+
/**
|
|
478
|
+
* Creates a new KeyvBridgeAdapter instance.
|
|
479
|
+
* @param store - The underlying promise-based store to bridge
|
|
480
|
+
* @param options - Configuration options for the adapter
|
|
481
|
+
*/
|
|
482
|
+
constructor(store, options) {
|
|
483
|
+
super({ throwOnHookError: false });
|
|
484
|
+
this._store = store;
|
|
485
|
+
if (options?.keySeparator) this._keySeparator = options.keySeparator;
|
|
486
|
+
if (options?.namespace) this._namespace = options.namespace;
|
|
487
|
+
this._capabilities = detectKeyvStorage(store);
|
|
488
|
+
if (typeof store.on === "function") store.on(KeyvEvents.ERROR, (error) => this.emit(KeyvEvents.ERROR, error));
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Gets the underlying store instance.
|
|
492
|
+
*/
|
|
493
|
+
get store() {
|
|
494
|
+
return this._store;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Sets the underlying store instance.
|
|
498
|
+
*/
|
|
499
|
+
set store(store) {
|
|
500
|
+
this._store = store;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Gets the detected capabilities of the underlying store.
|
|
504
|
+
*/
|
|
505
|
+
get capabilities() {
|
|
506
|
+
return this._capabilities;
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Gets the current key separator used between namespace and key.
|
|
510
|
+
*/
|
|
511
|
+
get keySeparator() {
|
|
512
|
+
return this._keySeparator;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Sets the key separator used between namespace and key.
|
|
516
|
+
*/
|
|
517
|
+
set keySeparator(separator) {
|
|
518
|
+
this._keySeparator = separator;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Gets the current namespace.
|
|
522
|
+
*/
|
|
523
|
+
get namespace() {
|
|
524
|
+
return this._namespace;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Sets the namespace.
|
|
528
|
+
*/
|
|
529
|
+
set namespace(namespace) {
|
|
530
|
+
this._namespace = namespace;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Creates a prefixed key by combining the namespace and key with the separator.
|
|
534
|
+
* @param key - The base key
|
|
535
|
+
* @param namespace - Optional namespace to prefix the key with
|
|
536
|
+
* @returns The prefixed key if namespace is provided, otherwise the original key
|
|
537
|
+
*/
|
|
538
|
+
getKeyPrefix(key, namespace) {
|
|
539
|
+
if (namespace) return `${namespace}${this._keySeparator}${key}`;
|
|
540
|
+
return key;
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Parses a prefixed key to extract the namespace and original key.
|
|
544
|
+
* @param key - The prefixed key to parse
|
|
545
|
+
* @returns An object containing the namespace (if present) and the original key
|
|
546
|
+
*/
|
|
547
|
+
getKeyPrefixData(key) {
|
|
548
|
+
if (this._namespace && key.startsWith(`${this._namespace}${this._keySeparator}`)) return {
|
|
549
|
+
namespace: this._namespace,
|
|
550
|
+
key: key.slice(this._namespace.length + this._keySeparator.length)
|
|
551
|
+
};
|
|
552
|
+
return { key };
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Retrieves a value from the store by key.
|
|
556
|
+
* Automatically handles namespace prefixing and TTL expiration.
|
|
557
|
+
* @param key - The key to retrieve
|
|
558
|
+
* @returns The stored data, or undefined if not found or expired
|
|
559
|
+
*/
|
|
560
|
+
async get(key) {
|
|
561
|
+
const keyPrefix = this.getKeyPrefix(key, this._namespace);
|
|
562
|
+
const data = await this._store.get(keyPrefix);
|
|
563
|
+
if (data === void 0 || data === null) return;
|
|
564
|
+
if (isDataExpired(data)) {
|
|
565
|
+
await this._store.delete(keyPrefix);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
return data;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Retrieves multiple values from the store by their keys.
|
|
572
|
+
* Delegates to the store's native getMany if available.
|
|
573
|
+
* @param keys - Array of keys to retrieve
|
|
574
|
+
* @returns Array of stored data in the same order as the input keys
|
|
575
|
+
*/
|
|
576
|
+
async getMany(keys) {
|
|
577
|
+
if (this._capabilities.methods.getMany.exists) {
|
|
578
|
+
const prefixedKeys = keys.map((key) => this.getKeyPrefix(key, this._namespace));
|
|
579
|
+
/* v8 ignore next -- @preserve */
|
|
580
|
+
const results = await this._store.getMany?.(prefixedKeys) ?? [];
|
|
581
|
+
const values = [];
|
|
582
|
+
for (const [index, data] of results.entries()) {
|
|
583
|
+
if (data === void 0 || data === null) {
|
|
584
|
+
values.push(void 0);
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
if (isDataExpired(data)) {
|
|
588
|
+
await this._store.delete(prefixedKeys[index]);
|
|
589
|
+
values.push(void 0);
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
values.push(data);
|
|
593
|
+
}
|
|
594
|
+
return values;
|
|
595
|
+
}
|
|
596
|
+
const values = [];
|
|
597
|
+
for (const key of keys) {
|
|
598
|
+
const data = await this.get(key);
|
|
599
|
+
values.push(data);
|
|
600
|
+
}
|
|
601
|
+
return values;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Stores a value in the store with an optional TTL.
|
|
605
|
+
* @param key - The key to store the value under
|
|
606
|
+
* @param value - The value to store
|
|
607
|
+
* @param ttl - Optional time-to-live in milliseconds
|
|
608
|
+
* @returns Always returns true indicating success
|
|
609
|
+
*/
|
|
610
|
+
async set(key, value, ttl) {
|
|
611
|
+
const keyPrefix = this.getKeyPrefix(key, this._namespace);
|
|
612
|
+
const result = await this._store.set(keyPrefix, value, ttl);
|
|
613
|
+
if (typeof result === "boolean") return result;
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Stores multiple entries in the store at once.
|
|
618
|
+
* Delegates to the store's native setMany if available, otherwise loops over set.
|
|
619
|
+
* @param entries - Array of entries containing key, value, and optional TTL
|
|
620
|
+
*/
|
|
621
|
+
async setMany(entries) {
|
|
622
|
+
if (this._capabilities.methods.setMany.exists) {
|
|
623
|
+
const prefixedEntries = entries.map((entry) => ({
|
|
624
|
+
...entry,
|
|
625
|
+
key: this.getKeyPrefix(entry.key, this._namespace)
|
|
626
|
+
}));
|
|
627
|
+
await this._store.setMany?.(prefixedEntries);
|
|
628
|
+
return entries.map(() => true);
|
|
629
|
+
}
|
|
630
|
+
const results = [];
|
|
631
|
+
for (const entry of entries) {
|
|
632
|
+
await this.set(entry.key, entry.value, entry.ttl);
|
|
633
|
+
results.push(true);
|
|
634
|
+
}
|
|
635
|
+
return results;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Checks if a key exists in the store and is not expired.
|
|
639
|
+
* Delegates to the store's native has if available.
|
|
640
|
+
* @param key - The key to check
|
|
641
|
+
* @returns True if the key exists and is not expired, false otherwise
|
|
642
|
+
*/
|
|
643
|
+
async has(key) {
|
|
644
|
+
if (this._capabilities.methods.has.exists) {
|
|
645
|
+
const keyPrefix = this.getKeyPrefix(key, this._namespace);
|
|
646
|
+
/* v8 ignore next -- @preserve */
|
|
647
|
+
return this._store.has?.(keyPrefix) ?? false;
|
|
648
|
+
}
|
|
649
|
+
return await this.get(key) !== void 0;
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Checks if multiple keys exist in the store and are not expired.
|
|
653
|
+
* Delegates to the store's native hasMany if available.
|
|
654
|
+
* @param keys - Array of keys to check
|
|
655
|
+
* @returns Array of booleans indicating existence for each key
|
|
656
|
+
*/
|
|
657
|
+
async hasMany(keys) {
|
|
658
|
+
if (this._capabilities.methods.hasMany.exists) {
|
|
659
|
+
const prefixedKeys = keys.map((key) => this.getKeyPrefix(key, this._namespace));
|
|
660
|
+
/* v8 ignore next -- @preserve */
|
|
661
|
+
return this._store.hasMany?.(prefixedKeys) ?? [];
|
|
662
|
+
}
|
|
663
|
+
const results = [];
|
|
664
|
+
for (const key of keys) results.push(await this.has(key));
|
|
665
|
+
return results;
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Deletes a value from the store by key.
|
|
669
|
+
* @param key - The key to delete
|
|
670
|
+
* @returns True if the key was deleted, false otherwise
|
|
671
|
+
*/
|
|
672
|
+
async delete(key) {
|
|
673
|
+
const keyPrefix = this.getKeyPrefix(key, this._namespace);
|
|
674
|
+
return this._store.delete(keyPrefix);
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Deletes multiple keys from the store at once.
|
|
678
|
+
* Delegates to the store's native deleteMany if available.
|
|
679
|
+
* @param keys - Array of keys to delete
|
|
680
|
+
* @returns Array of booleans indicating success for each key
|
|
681
|
+
*/
|
|
682
|
+
async deleteMany(keys) {
|
|
683
|
+
if (this._capabilities.methods.deleteMany.exists) {
|
|
684
|
+
const prefixedKeys = keys.map((key) => this.getKeyPrefix(key, this._namespace));
|
|
685
|
+
/* v8 ignore next -- @preserve */
|
|
686
|
+
const result = await this._store.deleteMany?.(prefixedKeys) ?? [];
|
|
687
|
+
return Array.isArray(result) ? result : keys.map(() => result);
|
|
688
|
+
}
|
|
689
|
+
const results = [];
|
|
690
|
+
for (const key of keys) try {
|
|
691
|
+
const keyPrefix = this.getKeyPrefix(key, this._namespace);
|
|
692
|
+
const result = await this._store.delete(keyPrefix);
|
|
693
|
+
results.push(result);
|
|
694
|
+
} catch (error) {
|
|
695
|
+
this.emit(KeyvEvents.ERROR, error);
|
|
696
|
+
results.push(false);
|
|
697
|
+
}
|
|
698
|
+
return results;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Clears entries from the store. If a namespace is set and the store supports
|
|
702
|
+
* iteration, only entries within that namespace are removed. Otherwise, the
|
|
703
|
+
* entire store is cleared.
|
|
704
|
+
*/
|
|
705
|
+
async clear() {
|
|
706
|
+
if (!this._namespace || !this._capabilities.methods.iterator.exists) {
|
|
707
|
+
await this._store.clear();
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const prefix = `${this._namespace}${this._keySeparator}`;
|
|
711
|
+
const keysToDelete = [];
|
|
712
|
+
/* v8 ignore next -- @preserve */
|
|
713
|
+
for await (const entry of this._store.iterator?.(this._namespace) ?? []) {
|
|
714
|
+
const key = Array.isArray(entry) ? entry[0] : entry;
|
|
715
|
+
if (typeof key === "string" && key.startsWith(prefix)) keysToDelete.push(key);
|
|
716
|
+
}
|
|
717
|
+
for (const key of keysToDelete) await this._store.delete(key);
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Creates an async iterator for iterating over store entries.
|
|
721
|
+
* If the underlying store does not support iteration, returns an empty generator.
|
|
722
|
+
* @returns An async generator yielding [key, value] pairs
|
|
723
|
+
*/
|
|
724
|
+
async *iterator() {
|
|
725
|
+
if (!this._capabilities.methods.iterator.exists) return;
|
|
726
|
+
const namespace = this._namespace;
|
|
727
|
+
const prefix = namespace ? `${namespace}${this._keySeparator}` : void 0;
|
|
728
|
+
/* v8 ignore next -- @preserve */
|
|
729
|
+
for await (const entry of this._store.iterator?.(this._namespace) ?? []) {
|
|
730
|
+
const [key, data] = Array.isArray(entry) ? entry : [entry];
|
|
731
|
+
if (prefix && typeof key === "string" && !key.startsWith(prefix)) continue;
|
|
732
|
+
if (data && isDataExpired(data)) {
|
|
733
|
+
await this._store.delete(key);
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
yield [prefix && typeof key === "string" ? key.slice(prefix.length) : key, data];
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Disconnects from the underlying store.
|
|
741
|
+
* No-op if the store does not support disconnect.
|
|
742
|
+
*/
|
|
743
|
+
async disconnect() {
|
|
744
|
+
if (this._capabilities.methods.disconnect.exists) await this._store.disconnect?.();
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
//#endregion
|
|
748
|
+
//#region src/json-serializer.ts
|
|
749
|
+
function getGlobalBuffer() {
|
|
750
|
+
return globalThis.Buffer;
|
|
751
|
+
}
|
|
752
|
+
function bytesToBase64(bytes) {
|
|
753
|
+
const buffer = getGlobalBuffer();
|
|
754
|
+
if (buffer) return buffer.from(bytes).toString("base64");
|
|
755
|
+
let binary = "";
|
|
756
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
757
|
+
return btoa(binary);
|
|
758
|
+
}
|
|
759
|
+
function base64ToBytes(value) {
|
|
760
|
+
const buffer = getGlobalBuffer();
|
|
761
|
+
if (buffer) return buffer.from(value, "base64");
|
|
762
|
+
const binary = atob(value);
|
|
763
|
+
const bytes = new Uint8Array(binary.length);
|
|
764
|
+
for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
|
|
765
|
+
return bytes;
|
|
766
|
+
}
|
|
767
|
+
function isBinaryValue(value) {
|
|
768
|
+
if (getGlobalBuffer()?.isBuffer(value)) return true;
|
|
769
|
+
return value instanceof Uint8Array;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Pre-process a value tree, converting Buffer and BigInt to tagged strings
|
|
773
|
+
* before JSON.stringify can call toJSON() on them.
|
|
774
|
+
*/
|
|
775
|
+
function prepare(value) {
|
|
776
|
+
if (value === null || value === void 0) return value;
|
|
777
|
+
if (isBinaryValue(value)) return `:base64:${bytesToBase64(value)}`;
|
|
778
|
+
if (typeof value === "bigint") return `:bigint:${value.toString()}`;
|
|
779
|
+
if (typeof value === "string") return value.startsWith(":") ? `:${value}` : value;
|
|
780
|
+
if (Array.isArray(value)) return value.map((item) => prepare(item));
|
|
781
|
+
if (typeof value === "object") {
|
|
782
|
+
if (typeof value.toJSON === "function") return prepare(value.toJSON());
|
|
783
|
+
const result = {};
|
|
784
|
+
for (const key of Object.keys(value)) if (value[key] !== void 0) result[key] = prepare(value[key]);
|
|
785
|
+
return result;
|
|
786
|
+
}
|
|
787
|
+
return value;
|
|
788
|
+
}
|
|
789
|
+
var KeyvJsonSerializer = class {
|
|
790
|
+
stringify(object) {
|
|
791
|
+
return JSON.stringify(prepare(object));
|
|
792
|
+
}
|
|
793
|
+
parse(data) {
|
|
794
|
+
return JSON.parse(data, (_, value) => {
|
|
795
|
+
if (typeof value === "string") {
|
|
796
|
+
if (value.startsWith(":bigint:")) return BigInt(value.slice(8));
|
|
797
|
+
if (value.startsWith(":base64:")) return base64ToBytes(value.slice(8));
|
|
798
|
+
return value.startsWith(":") ? value.slice(1) : value;
|
|
799
|
+
}
|
|
800
|
+
return value;
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
const jsonSerializer = new KeyvJsonSerializer();
|
|
805
|
+
//#endregion
|
|
806
|
+
//#region src/sanitize.ts
|
|
807
|
+
const categoryPatterns = {
|
|
808
|
+
sql: [
|
|
809
|
+
/;/g,
|
|
810
|
+
/--/g,
|
|
811
|
+
/\/\*/g
|
|
812
|
+
],
|
|
813
|
+
mongo: [/^\$/g, /\{\s*\$/g],
|
|
814
|
+
escape: [
|
|
815
|
+
/\0/g,
|
|
816
|
+
/\r/g,
|
|
817
|
+
/\n/g
|
|
818
|
+
],
|
|
819
|
+
path: [/\.\.\//g, /\.\.\\/g]
|
|
820
|
+
};
|
|
821
|
+
/**
|
|
822
|
+
* Compile an array of RegExp patterns from the enabled categories.
|
|
823
|
+
* Returns `undefined` when nothing is enabled.
|
|
824
|
+
*/
|
|
825
|
+
function buildPatterns(options) {
|
|
826
|
+
const patterns = [];
|
|
827
|
+
for (const [category, regexes] of Object.entries(categoryPatterns)) if (options[category] !== false) patterns.push(...regexes);
|
|
828
|
+
return patterns.length > 0 ? patterns : void 0;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Run all patterns against a string, stripping matched sequences.
|
|
832
|
+
*/
|
|
833
|
+
function applyPatterns(value, patterns) {
|
|
834
|
+
for (const pattern of patterns) {
|
|
835
|
+
pattern.lastIndex = 0;
|
|
836
|
+
value = value.replace(pattern, "");
|
|
837
|
+
}
|
|
838
|
+
return value;
|
|
839
|
+
}
|
|
840
|
+
const allOn = {
|
|
841
|
+
escape: true,
|
|
842
|
+
mongo: true,
|
|
843
|
+
path: true,
|
|
844
|
+
sql: true
|
|
845
|
+
};
|
|
846
|
+
const allOff = {
|
|
847
|
+
escape: false,
|
|
848
|
+
mongo: false,
|
|
849
|
+
path: false,
|
|
850
|
+
sql: false
|
|
851
|
+
};
|
|
852
|
+
/**
|
|
853
|
+
* Encapsulates key and namespace sanitization with an LRU result cache.
|
|
854
|
+
*/
|
|
855
|
+
var KeyvSanitize = class {
|
|
856
|
+
_keys = { ...allOff };
|
|
857
|
+
_namespace = { ...allOff };
|
|
858
|
+
_keyPatterns;
|
|
859
|
+
_namespacePatterns;
|
|
860
|
+
_enabled = false;
|
|
861
|
+
_cacheKeys = /* @__PURE__ */ new Map();
|
|
862
|
+
_cacheNamespaces = /* @__PURE__ */ new Map();
|
|
863
|
+
_cacheMax = 1e4;
|
|
864
|
+
constructor(options) {
|
|
865
|
+
if (options !== void 0) this.updateOptions(options);
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* The key sanitization pattern configuration.
|
|
869
|
+
*/
|
|
870
|
+
get keys() {
|
|
871
|
+
return this._keys;
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Whether any sanitization pattern (keys or namespace) is enabled.
|
|
875
|
+
*/
|
|
876
|
+
get enabled() {
|
|
877
|
+
return this._enabled;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* The namespace sanitization pattern configuration.
|
|
881
|
+
*/
|
|
882
|
+
get namespace() {
|
|
883
|
+
return this._namespace;
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Update the sanitization configuration. Recompiles patterns and clears the cache.
|
|
887
|
+
*/
|
|
888
|
+
updateOptions(options) {
|
|
889
|
+
this._keys = this.resolvePatterns(options.keys);
|
|
890
|
+
this._namespace = this.resolvePatterns(options.namespace);
|
|
891
|
+
this._enabled = Object.values(this._keys).some(Boolean) || Object.values(this._namespace).some(Boolean);
|
|
892
|
+
this._keyPatterns = buildPatterns(this._keys);
|
|
893
|
+
this._namespacePatterns = buildPatterns(this._namespace);
|
|
894
|
+
this._cacheKeys.clear();
|
|
895
|
+
this._cacheNamespaces.clear();
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Sanitize a single key. Uses an LRU cache for repeated lookups.
|
|
899
|
+
*/
|
|
900
|
+
cleanKey(key) {
|
|
901
|
+
if (!this._keyPatterns) return key;
|
|
902
|
+
const cached = this._cacheKeys.get(key);
|
|
903
|
+
if (cached !== void 0) {
|
|
904
|
+
this._cacheKeys.delete(key);
|
|
905
|
+
this._cacheKeys.set(key, cached);
|
|
906
|
+
return cached;
|
|
907
|
+
}
|
|
908
|
+
const result = applyPatterns(key, this._keyPatterns);
|
|
909
|
+
this._cacheKeys.set(key, result);
|
|
910
|
+
if (this._cacheKeys.size > this._cacheMax) {
|
|
911
|
+
const first = this._cacheKeys.keys().next().value;
|
|
912
|
+
if (first !== void 0) this._cacheKeys.delete(first);
|
|
913
|
+
}
|
|
914
|
+
return result;
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Sanitize an array of keys.
|
|
918
|
+
*/
|
|
919
|
+
cleanKeys(keys) {
|
|
920
|
+
if (!this._keyPatterns) return keys;
|
|
921
|
+
return keys.map((k) => this.cleanKey(k));
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Sanitize a namespace string. Uses an LRU cache for repeated lookups.
|
|
925
|
+
*/
|
|
926
|
+
cleanNamespace(ns) {
|
|
927
|
+
if (!this._namespacePatterns) return ns;
|
|
928
|
+
const cached = this._cacheNamespaces.get(ns);
|
|
929
|
+
if (cached !== void 0) {
|
|
930
|
+
this._cacheNamespaces.delete(ns);
|
|
931
|
+
this._cacheNamespaces.set(ns, cached);
|
|
932
|
+
return cached;
|
|
933
|
+
}
|
|
934
|
+
const result = applyPatterns(ns, this._namespacePatterns);
|
|
935
|
+
this._cacheNamespaces.set(ns, result);
|
|
936
|
+
if (this._cacheNamespaces.size > this._cacheMax) {
|
|
937
|
+
const first = this._cacheNamespaces.keys().next().value;
|
|
938
|
+
if (first !== void 0) this._cacheNamespaces.delete(first);
|
|
939
|
+
}
|
|
940
|
+
return result;
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Clear the LRU caches.
|
|
944
|
+
*/
|
|
945
|
+
clearCache() {
|
|
946
|
+
this._cacheKeys.clear();
|
|
947
|
+
this._cacheNamespaces.clear();
|
|
948
|
+
}
|
|
949
|
+
resolvePatterns(options) {
|
|
950
|
+
if (options === false || options === void 0) return { ...allOff };
|
|
951
|
+
if (options === true) return { ...allOn };
|
|
952
|
+
return {
|
|
953
|
+
sql: options.sql !== false,
|
|
954
|
+
mongo: options.mongo !== false,
|
|
955
|
+
escape: options.escape !== false,
|
|
956
|
+
path: options.path !== false
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
//#endregion
|
|
961
|
+
//#region src/stats.ts
|
|
962
|
+
var KeyvStats = class {
|
|
963
|
+
_hits = 0;
|
|
964
|
+
_misses = 0;
|
|
965
|
+
_sets = 0;
|
|
966
|
+
_deletes = 0;
|
|
967
|
+
_errors = 0;
|
|
968
|
+
_maxEntries = 1e3;
|
|
969
|
+
_enabled = false;
|
|
970
|
+
hitKeysMap = /* @__PURE__ */ new Map();
|
|
971
|
+
missKeysMap = /* @__PURE__ */ new Map();
|
|
972
|
+
setKeysMap = /* @__PURE__ */ new Map();
|
|
973
|
+
deleteKeysMap = /* @__PURE__ */ new Map();
|
|
974
|
+
errorKeysMap = /* @__PURE__ */ new Map();
|
|
975
|
+
_emitter;
|
|
976
|
+
_listeners = /* @__PURE__ */ new Map();
|
|
977
|
+
constructor(options) {
|
|
978
|
+
if (options?.maxEntries !== void 0) this._maxEntries = options.maxEntries;
|
|
979
|
+
if (options?.enabled !== void 0) this._enabled = options.enabled;
|
|
980
|
+
this.reset();
|
|
981
|
+
if (options?.emitter) if (this._enabled === true) this.subscribe(options.emitter);
|
|
982
|
+
else this._emitter = options.emitter;
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Total number of cache hits.
|
|
986
|
+
*/
|
|
987
|
+
get hits() {
|
|
988
|
+
return this._hits;
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Total number of cache misses.
|
|
992
|
+
*/
|
|
993
|
+
get misses() {
|
|
994
|
+
return this._misses;
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Total number of cache sets.
|
|
998
|
+
*/
|
|
999
|
+
get sets() {
|
|
1000
|
+
return this._sets;
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Total number of cache deletes.
|
|
1004
|
+
*/
|
|
1005
|
+
get deletes() {
|
|
1006
|
+
return this._deletes;
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Total number of cache errors.
|
|
1010
|
+
*/
|
|
1011
|
+
get errors() {
|
|
1012
|
+
return this._errors;
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* LRU-bounded map of key to hit count.
|
|
1016
|
+
*/
|
|
1017
|
+
get hitKeys() {
|
|
1018
|
+
return this.hitKeysMap;
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* LRU-bounded map of key to miss count.
|
|
1022
|
+
*/
|
|
1023
|
+
get missKeys() {
|
|
1024
|
+
return this.missKeysMap;
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* LRU-bounded map of key to set count.
|
|
1028
|
+
*/
|
|
1029
|
+
get setKeys() {
|
|
1030
|
+
return this.setKeysMap;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* LRU-bounded map of key to delete count.
|
|
1034
|
+
*/
|
|
1035
|
+
get deleteKeys() {
|
|
1036
|
+
return this.deleteKeysMap;
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* LRU-bounded map of key to error count.
|
|
1040
|
+
*/
|
|
1041
|
+
get errorKeys() {
|
|
1042
|
+
return this.errorKeysMap;
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Maximum number of entries per event-type LRU map.
|
|
1046
|
+
* @default 1000
|
|
1047
|
+
*/
|
|
1048
|
+
get maxEntries() {
|
|
1049
|
+
return this._maxEntries;
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Set the maximum number of entries per event-type LRU map.
|
|
1053
|
+
* @param {number} value the new maximum entries
|
|
1054
|
+
*/
|
|
1055
|
+
/* v8 ignore next 3 -- @preserve */
|
|
1056
|
+
set maxEntries(value) {
|
|
1057
|
+
this._maxEntries = value;
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* Whether stats tracking is enabled.
|
|
1061
|
+
* @default false
|
|
1062
|
+
*/
|
|
1063
|
+
get enabled() {
|
|
1064
|
+
return this._enabled;
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Enable or disable stats tracking. If false it will unsubscribe from the events
|
|
1068
|
+
* @param {boolean} value true to enable, false to disable
|
|
1069
|
+
*/
|
|
1070
|
+
set enabled(value) {
|
|
1071
|
+
this._enabled = value;
|
|
1072
|
+
if (this._enabled === false && this._listeners.size !== 0) {
|
|
1073
|
+
const emitter = this._emitter;
|
|
1074
|
+
this.unsubscribe();
|
|
1075
|
+
this._emitter = emitter;
|
|
1076
|
+
} else if (this._enabled === true && this._emitter && this._listeners.size === 0) this.subscribe(this._emitter);
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Build a composite key from a telemetry event.
|
|
1080
|
+
* Format: "namespace:key" if namespace is present, otherwise just "key".
|
|
1081
|
+
*/
|
|
1082
|
+
buildKeyEventName(event) {
|
|
1083
|
+
if (event.namespace && event.key) return `${event.namespace}:${event.key}`;
|
|
1084
|
+
return event.key ?? "";
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Increment the count for a key in an LRU-bounded map.
|
|
1088
|
+
* Deletes and re-inserts to maintain LRU order, evicts the oldest entry when full.
|
|
1089
|
+
*/
|
|
1090
|
+
incrementKeys(map, compositeKey) {
|
|
1091
|
+
if (this.maxEntries <= 0 || !compositeKey) return;
|
|
1092
|
+
const current = map.get(compositeKey) ?? 0;
|
|
1093
|
+
map.delete(compositeKey);
|
|
1094
|
+
map.set(compositeKey, current + 1);
|
|
1095
|
+
if (map.size > this.maxEntries) {
|
|
1096
|
+
const first = map.keys().next().value;
|
|
1097
|
+
if (first !== void 0) map.delete(first);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Subscribe to telemetry events from an emitter (e.g. a Keyv instance).
|
|
1102
|
+
* Automatically increments the corresponding stat counters and LRU key maps on each event.
|
|
1103
|
+
* @param {IEventEmitter} emitter the event emitter to subscribe to
|
|
1104
|
+
*/
|
|
1105
|
+
subscribe(emitter) {
|
|
1106
|
+
this.unsubscribe();
|
|
1107
|
+
this._emitter = emitter;
|
|
1108
|
+
const hitListener = (event) => {
|
|
1109
|
+
this._hits++;
|
|
1110
|
+
this.incrementKeys(this.hitKeysMap, this.buildKeyEventName(event));
|
|
1111
|
+
};
|
|
1112
|
+
const missListener = (event) => {
|
|
1113
|
+
this._misses++;
|
|
1114
|
+
this.incrementKeys(this.missKeysMap, this.buildKeyEventName(event));
|
|
1115
|
+
};
|
|
1116
|
+
const setListener = (event) => {
|
|
1117
|
+
this._sets++;
|
|
1118
|
+
this.incrementKeys(this.setKeysMap, this.buildKeyEventName(event));
|
|
1119
|
+
};
|
|
1120
|
+
const deleteListener = (event) => {
|
|
1121
|
+
this._deletes++;
|
|
1122
|
+
this.incrementKeys(this.deleteKeysMap, this.buildKeyEventName(event));
|
|
1123
|
+
};
|
|
1124
|
+
const errorListener = (event) => {
|
|
1125
|
+
this._errors++;
|
|
1126
|
+
this.incrementKeys(this.errorKeysMap, this.buildKeyEventName(event));
|
|
1127
|
+
};
|
|
1128
|
+
this._listeners.set("stat:hit", hitListener);
|
|
1129
|
+
this._listeners.set("stat:miss", missListener);
|
|
1130
|
+
this._listeners.set("stat:set", setListener);
|
|
1131
|
+
this._listeners.set("stat:delete", deleteListener);
|
|
1132
|
+
this._listeners.set("stat:error", errorListener);
|
|
1133
|
+
emitter.on("stat:hit", hitListener);
|
|
1134
|
+
emitter.on("stat:miss", missListener);
|
|
1135
|
+
emitter.on("stat:set", setListener);
|
|
1136
|
+
emitter.on("stat:delete", deleteListener);
|
|
1137
|
+
emitter.on("stat:error", errorListener);
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Unsubscribe from the currently subscribed emitter, removing all telemetry event listeners.
|
|
1141
|
+
*/
|
|
1142
|
+
unsubscribe() {
|
|
1143
|
+
if (!this._emitter) return;
|
|
1144
|
+
for (const [eventName, listener] of this._listeners) this._emitter.off(eventName, listener);
|
|
1145
|
+
this._listeners.clear();
|
|
1146
|
+
this._emitter = void 0;
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Reset all counters and LRU key maps to their initial state.
|
|
1150
|
+
*/
|
|
1151
|
+
reset() {
|
|
1152
|
+
this._hits = 0;
|
|
1153
|
+
this._misses = 0;
|
|
1154
|
+
this._sets = 0;
|
|
1155
|
+
this._deletes = 0;
|
|
1156
|
+
this._errors = 0;
|
|
1157
|
+
this.hitKeysMap.clear();
|
|
1158
|
+
this.missKeysMap.clear();
|
|
1159
|
+
this.setKeysMap.clear();
|
|
1160
|
+
this.deleteKeysMap.clear();
|
|
1161
|
+
this.errorKeysMap.clear();
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
//#endregion
|
|
1165
|
+
//#region src/keyv.ts
|
|
1166
|
+
var Keyv = class Keyv extends Hookified {
|
|
1167
|
+
/**
|
|
1168
|
+
* Stats manager for tracking cache operation metrics (hits, misses, sets, deletes, errors).
|
|
1169
|
+
* @default this is disabled.
|
|
1170
|
+
*/
|
|
1171
|
+
_stats;
|
|
1172
|
+
/**
|
|
1173
|
+
* Default time to live in milliseconds. Can be overridden per-key via {@link set}.
|
|
1174
|
+
*/
|
|
1175
|
+
_ttl;
|
|
1176
|
+
/**
|
|
1177
|
+
* Key prefix namespace used to isolate keys across different Keyv instances sharing the same store.
|
|
1178
|
+
*/
|
|
1179
|
+
_namespace;
|
|
1180
|
+
/**
|
|
1181
|
+
* The underlying storage adapter. Defaults to an in-memory {@link Map}.
|
|
1182
|
+
*/
|
|
1183
|
+
_store = new KeyvMemoryAdapter(/* @__PURE__ */ new Map());
|
|
1184
|
+
/**
|
|
1185
|
+
* Pluggable serialization adapter with `stringify` and `parse` methods.
|
|
1186
|
+
* When `undefined`, the built-in {@link KeyvJsonSerializer} is used.
|
|
1187
|
+
*/
|
|
1188
|
+
_serialization;
|
|
1189
|
+
/**
|
|
1190
|
+
* Pluggable compression adapter with `compress` and `decompress` methods.
|
|
1191
|
+
*/
|
|
1192
|
+
_compression;
|
|
1193
|
+
/**
|
|
1194
|
+
* Pluggable encryption adapter with `encrypt` and `decrypt` methods.
|
|
1195
|
+
*/
|
|
1196
|
+
_encryption;
|
|
1197
|
+
/**
|
|
1198
|
+
* Sanitization handler for keys and namespaces. By default it is disabled.
|
|
1199
|
+
*/
|
|
1200
|
+
_sanitize;
|
|
1201
|
+
/**
|
|
1202
|
+
* When true, Keyv checks expiry at its layer on get/getMany/has/hasMany.
|
|
1203
|
+
*/
|
|
1204
|
+
_checkExpired = false;
|
|
1205
|
+
/**
|
|
1206
|
+
* Keyv Constructor
|
|
1207
|
+
* @param {KeyvStorageAdapter | KeyvOptions} store
|
|
1208
|
+
* @param {Omit<KeyvOptions, 'store'>} [options] if you provide the store you can then provide the Keyv Options
|
|
1209
|
+
*/
|
|
1210
|
+
constructor(store, options) {
|
|
1211
|
+
const mergedOptions = Keyv.resolveOptions(store, options);
|
|
1212
|
+
super({
|
|
1213
|
+
throwOnHookError: false,
|
|
1214
|
+
throwOnEmptyListeners: true,
|
|
1215
|
+
throwOnEmitError: mergedOptions.throwOnErrors ?? false
|
|
1216
|
+
});
|
|
1217
|
+
this.deprecatedHooks = buildDeprecatedHooks();
|
|
1218
|
+
this._compression = mergedOptions.compression;
|
|
1219
|
+
this._encryption = mergedOptions.encryption;
|
|
1220
|
+
this.initSerialization(mergedOptions);
|
|
1221
|
+
this.initSanitize(mergedOptions);
|
|
1222
|
+
this.initNamespace(mergedOptions.namespace);
|
|
1223
|
+
this.initStats(mergedOptions);
|
|
1224
|
+
if (mergedOptions.store) this.setStore(mergedOptions.store);
|
|
1225
|
+
this.setTtl(mergedOptions.ttl);
|
|
1226
|
+
this._checkExpired = mergedOptions.checkExpired ?? false;
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Get the current storage adapter.
|
|
1230
|
+
* @returns {KeyvStorageAdapter} The current storage adapter.
|
|
1231
|
+
*/
|
|
1232
|
+
get store() {
|
|
1233
|
+
return this._store;
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Set the storage adapter.
|
|
1237
|
+
* @param {KeyvStorageAdapter | Map<any, any> | any} store The storage adapter to set.
|
|
1238
|
+
*/
|
|
1239
|
+
set store(store) {
|
|
1240
|
+
this.setStore(store);
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Get the current compression adapter.
|
|
1244
|
+
* @returns {KeyvCompressionAdapter | undefined} The current compression adapter.
|
|
1245
|
+
*/
|
|
1246
|
+
get compression() {
|
|
1247
|
+
return this._compression;
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Set the compression adapter.
|
|
1251
|
+
* @param {KeyvCompressionAdapter | undefined} compress The compression adapter to set.
|
|
1252
|
+
*/
|
|
1253
|
+
set compression(compress) {
|
|
1254
|
+
this._compression = compress;
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Get the current encryption adapter.
|
|
1258
|
+
* @returns {KeyvEncryptionAdapter | undefined} The current encryption adapter.
|
|
1259
|
+
*/
|
|
1260
|
+
get encryption() {
|
|
1261
|
+
return this._encryption;
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Set the encryption adapter.
|
|
1265
|
+
* @param {KeyvEncryptionAdapter | undefined} encryption The encryption adapter to set.
|
|
1266
|
+
*/
|
|
1267
|
+
set encryption(encryption) {
|
|
1268
|
+
this._encryption = encryption;
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Get the current namespace.
|
|
1272
|
+
* @returns {string | undefined} The current namespace.
|
|
1273
|
+
*/
|
|
1274
|
+
get namespace() {
|
|
1275
|
+
return this._namespace;
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Set the current namespace.
|
|
1279
|
+
* @param {string | undefined} namespace The namespace to set.
|
|
1280
|
+
*/
|
|
1281
|
+
set namespace(namespace) {
|
|
1282
|
+
this._namespace = namespace && this._sanitize.enabled ? this._sanitize.cleanNamespace(namespace) : namespace;
|
|
1283
|
+
this._store.namespace = this._namespace;
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Get the current TTL.
|
|
1287
|
+
* @returns {number} The current TTL in milliseconds.
|
|
1288
|
+
*/
|
|
1289
|
+
get ttl() {
|
|
1290
|
+
return this._ttl;
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Set the current TTL.
|
|
1294
|
+
* @param {number} ttl The TTL to set in milliseconds.
|
|
1295
|
+
*/
|
|
1296
|
+
set ttl(ttl) {
|
|
1297
|
+
this.setTtl(ttl);
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Get the current serialization adapter. If `undefined`, serialization is not enabled.
|
|
1301
|
+
* @returns {KeyvSerializationAdapter | undefined} The current serialization adapter.
|
|
1302
|
+
*/
|
|
1303
|
+
get serialization() {
|
|
1304
|
+
return this._serialization;
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Set the current serialization adapter. Pass a `KeyvSerializationAdapter` to enable
|
|
1308
|
+
* custom serialization, or `undefined` to disable serialization entirely.
|
|
1309
|
+
* @param {KeyvSerializationAdapter | undefined} serialization The serialization adapter to set.
|
|
1310
|
+
*/
|
|
1311
|
+
set serialization(serialization) {
|
|
1312
|
+
this._serialization = serialization === false ? void 0 : serialization;
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Get the current throwOnErrors value. When enabled, all errors with throw. By default, errors
|
|
1316
|
+
* will only throw if there are no listeners to the error event.
|
|
1317
|
+
* @return {boolean} The current throwOnErrors value.
|
|
1318
|
+
*/
|
|
1319
|
+
get throwOnErrors() {
|
|
1320
|
+
return this.throwOnEmitError;
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Set the current throwOnErrors value. When enabled, all errors will throw. By default, errors
|
|
1324
|
+
* will only throw if there are no listeners to the error event.
|
|
1325
|
+
* @param {boolean} value The throwOnErrors value to set.
|
|
1326
|
+
*/
|
|
1327
|
+
set throwOnErrors(value) {
|
|
1328
|
+
this.throwOnEmitError = value;
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Get the current sanitize adapter. Sanitization is disabled by default. To
|
|
1332
|
+
* enable it `sanitize.keys` or `sanitize.namespace` to true or set KeyvSanitizePatterns
|
|
1333
|
+
* to each.
|
|
1334
|
+
* @returns {KeyvSanitizeAdapter} The current sanitize adapter.
|
|
1335
|
+
*/
|
|
1336
|
+
get sanitize() {
|
|
1337
|
+
return this._sanitize;
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Set the sanitize adapter directly and will run sanitization on namespace.
|
|
1341
|
+
* @param {KeyvSanitizeAdapter} value The sanitize adapter to use.
|
|
1342
|
+
*/
|
|
1343
|
+
set sanitize(value) {
|
|
1344
|
+
this._sanitize = value;
|
|
1345
|
+
/* v8 ignore next -- @preserve */
|
|
1346
|
+
this._namespace = this._namespace && this._sanitize.enabled ? this._sanitize.cleanNamespace(this._namespace) : this._namespace;
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Get the stats. This is just for this instance
|
|
1350
|
+
* @returns {KeyvStats} The current stats.
|
|
1351
|
+
*/
|
|
1352
|
+
get stats() {
|
|
1353
|
+
return this._stats;
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* When true, Keyv checks expiry at its layer on get/getMany/has/hasMany.
|
|
1357
|
+
* When false (default), trusts the storage adapter.
|
|
1358
|
+
*/
|
|
1359
|
+
get checkExpired() {
|
|
1360
|
+
return this._checkExpired;
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Set the stats. When setting a new instance it will unsubscribe the old listeners
|
|
1364
|
+
* and subscribe the new instance.
|
|
1365
|
+
* @param {KeyvStats} stats The stats instance to set.
|
|
1366
|
+
*/
|
|
1367
|
+
set stats(stats) {
|
|
1368
|
+
this._stats.unsubscribe();
|
|
1369
|
+
this._stats = stats;
|
|
1370
|
+
this._stats.subscribe(this);
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Resolves a store to a fully-compliant KeyvStorageAdapter using a 3-tier detection chain:
|
|
1374
|
+
* 1. If the store already implements the full KeyvStorageAdapter interface, use it directly.
|
|
1375
|
+
* 2. If the store is map-like (synchronous get/set/delete/has), wrap it in KeyvMemoryAdapter.
|
|
1376
|
+
* 3. If the store has async get/set/delete/clear, wrap it in KeyvBridgeAdapter.
|
|
1377
|
+
* 4. Otherwise, emit an error and fall back to a default in-memory KeyvMemoryAdapter.
|
|
1378
|
+
*
|
|
1379
|
+
* NOTE: this is used for internal but provided public for custom adapter testing
|
|
1380
|
+
* @param {unknown} store The store to resolve.
|
|
1381
|
+
* @returns {KeyvStorageAdapter} A fully-compliant storage adapter.
|
|
1382
|
+
*/
|
|
1383
|
+
resolveStore(store) {
|
|
1384
|
+
const cap = detectKeyvStorage(store);
|
|
1385
|
+
if (cap.store === "keyvStorage") return store;
|
|
1386
|
+
if (cap.store === "mapLike") return new KeyvMemoryAdapter(store);
|
|
1387
|
+
if (cap.store === "asyncMap") return new KeyvBridgeAdapter(store);
|
|
1388
|
+
this.emit(KeyvEvents.ERROR, /* @__PURE__ */ new Error("Could not use the provided storage adapter, falling back to KeyvMemoryAdapter with Map"));
|
|
1389
|
+
return new KeyvMemoryAdapter(/* @__PURE__ */ new Map());
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Sets the storage adapter by resolving it via {@link resolveStore}, then wires up
|
|
1393
|
+
* error forwarding and namespace propagation.
|
|
1394
|
+
* @param {KeyvStorageAdapter | Map<any, any> | any} store The storage adapter to set.
|
|
1395
|
+
*/
|
|
1396
|
+
setStore(store) {
|
|
1397
|
+
this._store = this.resolveStore(store);
|
|
1398
|
+
if (typeof this._store.on === "function") this._store.on(KeyvEvents.ERROR, (error) => this.emit(KeyvEvents.ERROR, error));
|
|
1399
|
+
this._store.namespace = this._namespace;
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Sets the TTL, treating zero and negative values as undefined (no TTL).
|
|
1403
|
+
* @param {number | undefined} ttl The TTL to set in milliseconds.
|
|
1404
|
+
*/
|
|
1405
|
+
setTtl(ttl) {
|
|
1406
|
+
if (typeof ttl === "number" && ttl <= 0) {
|
|
1407
|
+
this._ttl = void 0;
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
this._ttl = ttl;
|
|
1411
|
+
}
|
|
1412
|
+
async get(key) {
|
|
1413
|
+
if (Array.isArray(key)) return this.getMany(key);
|
|
1414
|
+
key = this._sanitize.enabled ? this._sanitize.cleanKey(key) : key;
|
|
1415
|
+
if (key === "") return;
|
|
1416
|
+
await this.hookWithDeprecated(KeyvHooks.BEFORE_GET, { key });
|
|
1417
|
+
let rawData;
|
|
1418
|
+
try {
|
|
1419
|
+
rawData = await this._store.get(key);
|
|
1420
|
+
} catch (error) {
|
|
1421
|
+
this.emit(KeyvEvents.ERROR, error);
|
|
1422
|
+
this.emitTelemetry(KeyvEvents.STAT_ERROR, key);
|
|
1423
|
+
}
|
|
1424
|
+
let data;
|
|
1425
|
+
if (this._checkExpired) [data] = await this.decodeWithExpire(key, rawData);
|
|
1426
|
+
else data = rawData === void 0 || rawData === null ? void 0 : typeof rawData === "string" ? await this.decode(rawData) : rawData;
|
|
1427
|
+
if (data === void 0) {
|
|
1428
|
+
await this.hookWithDeprecated(KeyvHooks.AFTER_GET, {
|
|
1429
|
+
key,
|
|
1430
|
+
value: void 0
|
|
1431
|
+
});
|
|
1432
|
+
this.emitTelemetry(KeyvEvents.STAT_MISS, key);
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
await this.hookWithDeprecated(KeyvHooks.AFTER_GET, {
|
|
1436
|
+
key,
|
|
1437
|
+
value: data
|
|
1438
|
+
});
|
|
1439
|
+
this.emitTelemetry(KeyvEvents.STAT_HIT, key);
|
|
1440
|
+
return data.value;
|
|
1441
|
+
}
|
|
1442
|
+
/**
|
|
1443
|
+
* Get many values of keys
|
|
1444
|
+
* @param {string[]} keys passing in a single key or multiple as an array
|
|
1445
|
+
*/
|
|
1446
|
+
async getMany(keys) {
|
|
1447
|
+
keys = this._sanitize.enabled ? this._sanitize.cleanKeys(keys) : keys;
|
|
1448
|
+
await this.hookWithDeprecated(KeyvHooks.BEFORE_GET_MANY, { keys });
|
|
1449
|
+
const rawData = await this._store.getMany(keys);
|
|
1450
|
+
let deserialized;
|
|
1451
|
+
if (this._checkExpired) deserialized = await this.decodeWithExpire(keys, rawData);
|
|
1452
|
+
else deserialized = await Promise.all(rawData.map(async (row) => {
|
|
1453
|
+
if (row === void 0 || row === null) return;
|
|
1454
|
+
return typeof row === "string" ? this.decode(row) : row;
|
|
1455
|
+
}));
|
|
1456
|
+
const result = deserialized.map((row) => row !== void 0 ? row.value : void 0);
|
|
1457
|
+
await this.hookWithDeprecated(KeyvHooks.AFTER_GET_MANY, result);
|
|
1458
|
+
for (let i = 0; i < result.length; i++) if (result[i] === void 0) this.emitTelemetry(KeyvEvents.STAT_MISS, keys[i]);
|
|
1459
|
+
else this.emitTelemetry(KeyvEvents.STAT_HIT, keys[i]);
|
|
1460
|
+
return result;
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Get the raw value of a key. This is the replacement for setting raw to true in the get() method.
|
|
1464
|
+
* @param {string} key the key to get
|
|
1465
|
+
* @returns {Promise<KeyvStorageGetResult<Value>>} will return a KeyvStorageGetResult<Value> or undefined
|
|
1466
|
+
* if the key does not exist or is expired.
|
|
1467
|
+
*/
|
|
1468
|
+
async getRaw(key) {
|
|
1469
|
+
key = this._sanitize.enabled ? this._sanitize.cleanKey(key) : key;
|
|
1470
|
+
if (key === "") return;
|
|
1471
|
+
await this.hookWithDeprecated(KeyvHooks.BEFORE_GET_RAW, { key });
|
|
1472
|
+
const rawData = await this._store.get(key);
|
|
1473
|
+
let data;
|
|
1474
|
+
if (this._checkExpired) [data] = await this.decodeWithExpire(key, rawData);
|
|
1475
|
+
else data = rawData === void 0 || rawData === null ? void 0 : typeof rawData === "string" ? await this.decode(rawData) : rawData;
|
|
1476
|
+
if (data === void 0) {
|
|
1477
|
+
await this.hookWithDeprecated(KeyvHooks.AFTER_GET_RAW, {
|
|
1478
|
+
key,
|
|
1479
|
+
value: void 0
|
|
1480
|
+
});
|
|
1481
|
+
this.emitTelemetry(KeyvEvents.STAT_MISS, key);
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
this.emitTelemetry(KeyvEvents.STAT_HIT, key);
|
|
1485
|
+
await this.hookWithDeprecated(KeyvHooks.AFTER_GET_RAW, {
|
|
1486
|
+
key,
|
|
1487
|
+
value: data
|
|
1488
|
+
});
|
|
1489
|
+
return data;
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* Get the raw values of many keys. This is the replacement for setting raw to true in the getMany() method.
|
|
1493
|
+
* @param {string[]} keys the keys to get
|
|
1494
|
+
* @returns {Promise<Array<KeyvStorageGetResult<Value>>>} will return an array of KeyvStorageGetResult<Value> or undefined if the key does not exist or is expired.
|
|
1495
|
+
*/
|
|
1496
|
+
async getManyRaw(keys) {
|
|
1497
|
+
/* v8 ignore next -- @preserve */
|
|
1498
|
+
keys = this._sanitize.enabled ? this._sanitize.cleanKeys(keys) : keys;
|
|
1499
|
+
await this.hookWithDeprecated(KeyvHooks.BEFORE_GET_MANY_RAW, { keys });
|
|
1500
|
+
if (keys.length === 0) {
|
|
1501
|
+
const result = [];
|
|
1502
|
+
await this.hookWithDeprecated(KeyvHooks.AFTER_GET_MANY_RAW, {
|
|
1503
|
+
keys,
|
|
1504
|
+
values: result
|
|
1505
|
+
});
|
|
1506
|
+
return result;
|
|
1507
|
+
}
|
|
1508
|
+
const rawData = await this._store.getMany(keys);
|
|
1509
|
+
let result;
|
|
1510
|
+
if (this._checkExpired) result = await this.decodeWithExpire(keys, rawData);
|
|
1511
|
+
else result = await Promise.all(rawData.map(async (row) => {
|
|
1512
|
+
if (row === void 0 || row === null) return;
|
|
1513
|
+
return typeof row === "string" ? this.decode(row) : row;
|
|
1514
|
+
}));
|
|
1515
|
+
for (let i = 0; i < result.length; i++) if (result[i] === void 0) this.emitTelemetry(KeyvEvents.STAT_MISS, keys[i]);
|
|
1516
|
+
else this.emitTelemetry(KeyvEvents.STAT_HIT, keys[i]);
|
|
1517
|
+
await this.hookWithDeprecated(KeyvHooks.AFTER_GET_MANY_RAW, {
|
|
1518
|
+
keys,
|
|
1519
|
+
values: result
|
|
1520
|
+
});
|
|
1521
|
+
return result;
|
|
1522
|
+
}
|
|
1523
|
+
/**
|
|
1524
|
+
* Set an item to the store
|
|
1525
|
+
* @param {string | Array<KeyvEntry<Value>>} key the key to use. If you pass in an array of KeyvEntry it will set many items
|
|
1526
|
+
* @param {Value} value the value of the key
|
|
1527
|
+
* @param {number} [ttl] time to live in milliseconds
|
|
1528
|
+
* @returns {boolean} if it sets then it will return a true. On failure will return false.
|
|
1529
|
+
*/
|
|
1530
|
+
async set(key, value, ttl) {
|
|
1531
|
+
key = this._sanitize.enabled ? this._sanitize.cleanKey(key) : key;
|
|
1532
|
+
if (key === "") return false;
|
|
1533
|
+
const data = {
|
|
1534
|
+
key,
|
|
1535
|
+
value,
|
|
1536
|
+
ttl
|
|
1537
|
+
};
|
|
1538
|
+
await this.hookWithDeprecated(KeyvHooks.BEFORE_SET, data);
|
|
1539
|
+
data.ttl = resolveTtl(data.ttl, this._ttl);
|
|
1540
|
+
const expires = calculateExpires(data.ttl);
|
|
1541
|
+
if (typeof data.value === "symbol") {
|
|
1542
|
+
this.emit(KeyvEvents.ERROR, "symbol cannot be serialized");
|
|
1543
|
+
this.emitTelemetry(KeyvEvents.STAT_ERROR, key);
|
|
1544
|
+
return false;
|
|
1545
|
+
}
|
|
1546
|
+
const formattedValue = {
|
|
1547
|
+
value: data.value,
|
|
1548
|
+
expires
|
|
1549
|
+
};
|
|
1550
|
+
let result = true;
|
|
1551
|
+
let encodedValue = formattedValue;
|
|
1552
|
+
try {
|
|
1553
|
+
encodedValue = await this.encode(formattedValue);
|
|
1554
|
+
result = await this._store.set(data.key, encodedValue, data.ttl);
|
|
1555
|
+
} catch (error) {
|
|
1556
|
+
result = false;
|
|
1557
|
+
this.emit(KeyvEvents.ERROR, error);
|
|
1558
|
+
this.emitTelemetry(KeyvEvents.STAT_ERROR, key);
|
|
1559
|
+
}
|
|
1560
|
+
await this.hookWithDeprecated(KeyvHooks.AFTER_SET, {
|
|
1561
|
+
key,
|
|
1562
|
+
value: encodedValue,
|
|
1563
|
+
ttl
|
|
1564
|
+
});
|
|
1565
|
+
if (result) this.emitTelemetry(KeyvEvents.STAT_SET, key);
|
|
1566
|
+
return result;
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Set many items to the store
|
|
1570
|
+
* @param {Array<KeyvEntry<Value>>} entries the entries to set
|
|
1571
|
+
* @returns {boolean[]} will return an array of booleans if it sets then it will return a true. On failure will return false.
|
|
1572
|
+
*/
|
|
1573
|
+
async setMany(entries) {
|
|
1574
|
+
entries = entries.map((e) => ({
|
|
1575
|
+
...e,
|
|
1576
|
+
key: this._sanitize.enabled ? this._sanitize.cleanKey(e.key) : e.key
|
|
1577
|
+
}));
|
|
1578
|
+
const data = { entries };
|
|
1579
|
+
await this.hookWithDeprecated(KeyvHooks.BEFORE_SET_MANY, data);
|
|
1580
|
+
entries = data.entries;
|
|
1581
|
+
let results = [];
|
|
1582
|
+
try {
|
|
1583
|
+
const serializedEntries = await Promise.all(entries.map(async ({ key, value, ttl }) => {
|
|
1584
|
+
ttl = resolveTtl(ttl, this._ttl);
|
|
1585
|
+
/* v8 ignore next -- @preserve */
|
|
1586
|
+
const expires = calculateExpires(ttl);
|
|
1587
|
+
/* v8 ignore next -- @preserve */
|
|
1588
|
+
if (typeof value === "symbol") {
|
|
1589
|
+
this.emit(KeyvEvents.ERROR, "symbol cannot be serialized");
|
|
1590
|
+
this.emitTelemetry(KeyvEvents.STAT_ERROR, key);
|
|
1591
|
+
throw new Error("symbol cannot be serialized");
|
|
1592
|
+
}
|
|
1593
|
+
const formattedValue = {
|
|
1594
|
+
value,
|
|
1595
|
+
expires
|
|
1596
|
+
};
|
|
1597
|
+
return {
|
|
1598
|
+
key,
|
|
1599
|
+
value: await this.encode(formattedValue),
|
|
1600
|
+
ttl
|
|
1601
|
+
};
|
|
1602
|
+
}));
|
|
1603
|
+
const storeResult = await this._store.setMany(serializedEntries);
|
|
1604
|
+
/* v8 ignore next -- @preserve */
|
|
1605
|
+
results = Array.isArray(storeResult) ? storeResult : entries.map(() => true);
|
|
1606
|
+
this.emitTelemetry(KeyvEvents.STAT_SET, entries.map((e) => e.key));
|
|
1607
|
+
} catch (error) {
|
|
1608
|
+
this.emit(KeyvEvents.ERROR, error);
|
|
1609
|
+
this.emitTelemetry(KeyvEvents.STAT_ERROR, entries.map((e) => e.key));
|
|
1610
|
+
results = entries.map(() => false);
|
|
1611
|
+
}
|
|
1612
|
+
await this.hookWithDeprecated(KeyvHooks.AFTER_SET_MANY, {
|
|
1613
|
+
entries,
|
|
1614
|
+
values: results
|
|
1615
|
+
});
|
|
1616
|
+
return results;
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Set a raw value to the store without wrapping or serialization. This is the write-side counterpart to getRaw().
|
|
1620
|
+
* The value should be a KeyvValue object with { value, expires? }. If you need TTL-based expiration,
|
|
1621
|
+
* set `expires` on the value directly (e.g. `{ value: 'bar', expires: Date.now() + 60000 }`).
|
|
1622
|
+
* The store-level TTL is derived automatically from `value.expires`.
|
|
1623
|
+
* @param {string} key the key to set
|
|
1624
|
+
* @param {KeyvValue<Value>} value the raw value envelope to store
|
|
1625
|
+
* @returns {boolean} if it sets then it will return a true. On failure will return false.
|
|
1626
|
+
*/
|
|
1627
|
+
async setRaw(key, value) {
|
|
1628
|
+
key = this._sanitize.enabled ? this._sanitize.cleanKey(key) : key;
|
|
1629
|
+
if (key === "") return false;
|
|
1630
|
+
const data = {
|
|
1631
|
+
key,
|
|
1632
|
+
value
|
|
1633
|
+
};
|
|
1634
|
+
await this.hookWithDeprecated(KeyvHooks.BEFORE_SET_RAW, data);
|
|
1635
|
+
const ttl = ttlFromExpires(data.value.expires);
|
|
1636
|
+
let result = true;
|
|
1637
|
+
try {
|
|
1638
|
+
const encodedValue = await this.encode(data.value);
|
|
1639
|
+
const storeResult = await this._store.set(data.key, encodedValue, ttl);
|
|
1640
|
+
if (typeof storeResult === "boolean") result = storeResult;
|
|
1641
|
+
} catch (error) {
|
|
1642
|
+
result = false;
|
|
1643
|
+
this.emit(KeyvEvents.ERROR, error);
|
|
1644
|
+
this.emitTelemetry(KeyvEvents.STAT_ERROR, key);
|
|
1645
|
+
}
|
|
1646
|
+
await this.hookWithDeprecated(KeyvHooks.AFTER_SET_RAW, {
|
|
1647
|
+
key,
|
|
1648
|
+
value: data.value,
|
|
1649
|
+
ttl
|
|
1650
|
+
});
|
|
1651
|
+
if (result) this.emitTelemetry(KeyvEvents.STAT_SET, key);
|
|
1652
|
+
return result;
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* Set many raw values to the store without wrapping or serialization. This is the write-side counterpart to getManyRaw().
|
|
1656
|
+
* Each entry's value should be a KeyvValue object with { value, expires? }. If you need TTL-based expiration,
|
|
1657
|
+
* set `expires` on each value directly. The store-level TTL is derived automatically from `value.expires`.
|
|
1658
|
+
* @param {KeyvEntry<KeyvValue<Value>>[]} entries the raw entries to set
|
|
1659
|
+
* @returns {boolean[]} will return an array of booleans if it sets then it will return a true. On failure will return false.
|
|
1660
|
+
*/
|
|
1661
|
+
async setManyRaw(entries) {
|
|
1662
|
+
entries = entries.map((e) => ({
|
|
1663
|
+
...e,
|
|
1664
|
+
key: this._sanitize.enabled ? this._sanitize.cleanKey(e.key) : e.key
|
|
1665
|
+
}));
|
|
1666
|
+
let results = [];
|
|
1667
|
+
await this.hookWithDeprecated(KeyvHooks.BEFORE_SET_MANY_RAW, { entries });
|
|
1668
|
+
try {
|
|
1669
|
+
const rawEntries = await Promise.all(entries.map(async ({ key, value }) => {
|
|
1670
|
+
const ttl = ttlFromExpires(value.expires);
|
|
1671
|
+
return {
|
|
1672
|
+
key,
|
|
1673
|
+
value: await this.encode(value),
|
|
1674
|
+
ttl
|
|
1675
|
+
};
|
|
1676
|
+
}));
|
|
1677
|
+
const storeResult = await this._store.setMany(rawEntries);
|
|
1678
|
+
results = Array.isArray(storeResult) ? storeResult : entries.map(() => true);
|
|
1679
|
+
this.emitTelemetry(KeyvEvents.STAT_SET, entries.map((e) => e.key));
|
|
1680
|
+
} catch (error) {
|
|
1681
|
+
this.emit(KeyvEvents.ERROR, error);
|
|
1682
|
+
this.emitTelemetry(KeyvEvents.STAT_ERROR, entries.map((e) => e.key));
|
|
1683
|
+
results = entries.map(() => false);
|
|
1684
|
+
}
|
|
1685
|
+
await this.hookWithDeprecated(KeyvHooks.AFTER_SET_MANY_RAW, {
|
|
1686
|
+
entries,
|
|
1687
|
+
results
|
|
1688
|
+
});
|
|
1689
|
+
return results;
|
|
1690
|
+
}
|
|
1691
|
+
async delete(key) {
|
|
1692
|
+
if (Array.isArray(key)) return this.deleteMany(key);
|
|
1693
|
+
key = this._sanitize.enabled ? this._sanitize.cleanKey(key) : key;
|
|
1694
|
+
if (key === "") return false;
|
|
1695
|
+
await this.hookWithDeprecated(KeyvHooks.BEFORE_DELETE, { key });
|
|
1696
|
+
let result = true;
|
|
1697
|
+
try {
|
|
1698
|
+
result = await this._store.delete(key);
|
|
1699
|
+
} catch (error) {
|
|
1700
|
+
result = false;
|
|
1701
|
+
this.emit(KeyvEvents.ERROR, error);
|
|
1702
|
+
this.emitTelemetry(KeyvEvents.STAT_ERROR, key);
|
|
1703
|
+
}
|
|
1704
|
+
await this.hookWithDeprecated(KeyvHooks.AFTER_DELETE, {
|
|
1705
|
+
key,
|
|
1706
|
+
value: result
|
|
1707
|
+
});
|
|
1708
|
+
this.emitTelemetry(KeyvEvents.STAT_DELETE, key);
|
|
1709
|
+
return result;
|
|
1710
|
+
}
|
|
1711
|
+
/**
|
|
1712
|
+
* Delete many items from the store
|
|
1713
|
+
* @param {string[]} keys the keys to be deleted
|
|
1714
|
+
* @returns {boolean[]} array of booleans indicating success for each key
|
|
1715
|
+
*/
|
|
1716
|
+
async deleteMany(keys) {
|
|
1717
|
+
/* v8 ignore next -- @preserve */
|
|
1718
|
+
keys = this._sanitize.enabled ? this._sanitize.cleanKeys(keys) : keys;
|
|
1719
|
+
await this.hookWithDeprecated(KeyvHooks.BEFORE_DELETE_MANY, { keys });
|
|
1720
|
+
await this.hookWithDeprecated(KeyvHooks.BEFORE_DELETE, { key: keys });
|
|
1721
|
+
let results;
|
|
1722
|
+
try {
|
|
1723
|
+
results = await this._store.deleteMany(keys);
|
|
1724
|
+
this.emitTelemetry(KeyvEvents.STAT_DELETE, keys);
|
|
1725
|
+
} catch (error) {
|
|
1726
|
+
this.emit(KeyvEvents.ERROR, error);
|
|
1727
|
+
this.emitTelemetry(KeyvEvents.STAT_ERROR, keys);
|
|
1728
|
+
results = keys.map(() => false);
|
|
1729
|
+
}
|
|
1730
|
+
await this.hookWithDeprecated(KeyvHooks.AFTER_DELETE_MANY, {
|
|
1731
|
+
keys,
|
|
1732
|
+
values: results
|
|
1733
|
+
});
|
|
1734
|
+
await this.hookWithDeprecated(KeyvHooks.AFTER_DELETE, {
|
|
1735
|
+
key: keys,
|
|
1736
|
+
value: results
|
|
1737
|
+
});
|
|
1738
|
+
return results;
|
|
1739
|
+
}
|
|
1740
|
+
async has(key) {
|
|
1741
|
+
if (Array.isArray(key)) return this.hasMany(key);
|
|
1742
|
+
key = this._sanitize.enabled ? this._sanitize.cleanKey(key) : key;
|
|
1743
|
+
if (key === "") return false;
|
|
1744
|
+
await this.hookWithDeprecated(KeyvHooks.BEFORE_HAS, { key });
|
|
1745
|
+
let result = false;
|
|
1746
|
+
try {
|
|
1747
|
+
if (this._checkExpired) {
|
|
1748
|
+
const rawData = await this._store.get(key);
|
|
1749
|
+
if (rawData !== void 0 && rawData !== null) {
|
|
1750
|
+
const [data] = await this.decodeWithExpire(key, rawData);
|
|
1751
|
+
result = data !== void 0;
|
|
1752
|
+
}
|
|
1753
|
+
} else result = await this._store.has(key);
|
|
1754
|
+
} catch (error) {
|
|
1755
|
+
this.emit(KeyvEvents.ERROR, error);
|
|
1756
|
+
this.emitTelemetry(KeyvEvents.STAT_ERROR, key);
|
|
1757
|
+
}
|
|
1758
|
+
await this.hookWithDeprecated(KeyvHooks.AFTER_HAS, {
|
|
1759
|
+
key,
|
|
1760
|
+
value: result
|
|
1761
|
+
});
|
|
1762
|
+
return result;
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* Check if many keys exist
|
|
1766
|
+
* @param {string[]} keys the keys to check
|
|
1767
|
+
* @returns {boolean[]} will return an array of booleans if the keys exist
|
|
1768
|
+
*/
|
|
1769
|
+
async hasMany(keys) {
|
|
1770
|
+
keys = this._sanitize.enabled ? this._sanitize.cleanKeys(keys) : keys;
|
|
1771
|
+
await this.hookWithDeprecated(KeyvHooks.BEFORE_HAS_MANY, { keys });
|
|
1772
|
+
let results = [];
|
|
1773
|
+
try {
|
|
1774
|
+
if (this._checkExpired) {
|
|
1775
|
+
const rawData = await this._store.getMany(keys);
|
|
1776
|
+
results = (await this.decodeWithExpire(keys, rawData)).map((row) => row !== void 0);
|
|
1777
|
+
} else results = await this._store.hasMany(keys);
|
|
1778
|
+
} catch (error) {
|
|
1779
|
+
this.emit(KeyvEvents.ERROR, error);
|
|
1780
|
+
this.emitTelemetry(KeyvEvents.STAT_ERROR, keys);
|
|
1781
|
+
results = keys.map(() => false);
|
|
1782
|
+
}
|
|
1783
|
+
await this.hookWithDeprecated(KeyvHooks.AFTER_HAS_MANY, {
|
|
1784
|
+
keys,
|
|
1785
|
+
values: results
|
|
1786
|
+
});
|
|
1787
|
+
return results;
|
|
1788
|
+
}
|
|
1789
|
+
/**
|
|
1790
|
+
* Clear the store
|
|
1791
|
+
* @returns {void}
|
|
1792
|
+
*/
|
|
1793
|
+
async clear() {
|
|
1794
|
+
this.emit("clear");
|
|
1795
|
+
await this.hook(KeyvHooks.BEFORE_CLEAR, { namespace: this._namespace });
|
|
1796
|
+
try {
|
|
1797
|
+
await this._store.clear();
|
|
1798
|
+
} catch (error) {
|
|
1799
|
+
this.emit(KeyvEvents.ERROR, error);
|
|
1800
|
+
this.emitTelemetry(KeyvEvents.STAT_ERROR);
|
|
1801
|
+
}
|
|
1802
|
+
await this.hook(KeyvHooks.AFTER_CLEAR, { namespace: this._namespace });
|
|
1803
|
+
}
|
|
1804
|
+
/**
|
|
1805
|
+
* Will disconnect the store. This is only available if the store has a disconnect method
|
|
1806
|
+
* @returns {Promise<void>}
|
|
1807
|
+
*/
|
|
1808
|
+
async disconnect() {
|
|
1809
|
+
this.emit("disconnect");
|
|
1810
|
+
await this.hook(KeyvHooks.BEFORE_DISCONNECT, { namespace: this._namespace });
|
|
1811
|
+
try {
|
|
1812
|
+
if (this._store.disconnect) await this._store.disconnect();
|
|
1813
|
+
} catch (error) {
|
|
1814
|
+
this.emit(KeyvEvents.ERROR, error);
|
|
1815
|
+
}
|
|
1816
|
+
await this.hook(KeyvHooks.AFTER_DISCONNECT, { namespace: this._namespace });
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* Iterate over all key-value pairs in the store. Automatically deserializes values,
|
|
1820
|
+
* filters out expired entries, and deletes them from the store.
|
|
1821
|
+
* @returns {AsyncGenerator<Array<string | unknown>, void>} An async generator yielding `[key, value]` pairs.
|
|
1822
|
+
*/
|
|
1823
|
+
async *iterator() {
|
|
1824
|
+
/* v8 ignore next 3 -- @preserve */
|
|
1825
|
+
if (this._store.iterator === void 0) return;
|
|
1826
|
+
for await (const [key, raw] of this._store.iterator()) {
|
|
1827
|
+
const data = await this.decode(raw);
|
|
1828
|
+
if (this._checkExpired && data && isDataExpired(data)) {
|
|
1829
|
+
await this.delete(key);
|
|
1830
|
+
continue;
|
|
1831
|
+
}
|
|
1832
|
+
yield [key, data?.value];
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Encodes a value for storage. Pipeline: serialize → compress → encrypt.
|
|
1837
|
+
* If serialization is not configured, returns the data as-is.
|
|
1838
|
+
* @param {KeyvValue<T>} data The value envelope to encode.
|
|
1839
|
+
* @returns {Promise<unknown>} The encoded value, or the original data on failure.
|
|
1840
|
+
*/
|
|
1841
|
+
async encode(data) {
|
|
1842
|
+
if (!this._serialization) return data;
|
|
1843
|
+
let result = await this._serialization.stringify(data);
|
|
1844
|
+
if (this._compression?.compress) result = await this._compression.compress(result);
|
|
1845
|
+
if (this._encryption?.encrypt) result = await this._encryption.encrypt(result);
|
|
1846
|
+
return result;
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Decodes a stored value. Pipeline: decrypt → decompress → deserialize (reverse of encode).
|
|
1850
|
+
* If serialization is not configured, returns the data as a KeyvValue or undefined for strings.
|
|
1851
|
+
* @param {unknown} data The raw data from the store.
|
|
1852
|
+
* @returns {Promise<KeyvValue<T> | undefined>} The decoded value envelope, or undefined on failure.
|
|
1853
|
+
*/
|
|
1854
|
+
async decode(data) {
|
|
1855
|
+
if (data === void 0 || data === null) return;
|
|
1856
|
+
if (!this._serialization) return typeof data === "string" ? void 0 : data;
|
|
1857
|
+
try {
|
|
1858
|
+
let result = data;
|
|
1859
|
+
if (this._encryption?.decrypt) result = await this._encryption.decrypt(result);
|
|
1860
|
+
if (this._compression?.decompress) result = await this._compression.decompress(result);
|
|
1861
|
+
if (typeof result === "string") return await this._serialization.parse(result);
|
|
1862
|
+
return result;
|
|
1863
|
+
} catch (error) {
|
|
1864
|
+
this.emit(KeyvEvents.ERROR, error);
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
/**
|
|
1869
|
+
* Deserializes raw data from the store, checks for expiry, and deletes expired keys.
|
|
1870
|
+
* Accepts a single key/value or arrays. Returns an array of decoded KeyvValue objects
|
|
1871
|
+
* (undefined for missing or expired entries).
|
|
1872
|
+
* @param {string | string[]} keys the key(s) to process
|
|
1873
|
+
* @param {unknown | unknown[]} rawData the raw data from the store
|
|
1874
|
+
* @returns {Promise<Array<KeyvValue<Value> | undefined>>} decoded values with expired entries removed
|
|
1875
|
+
*/
|
|
1876
|
+
async decodeWithExpire(keys, rawData) {
|
|
1877
|
+
const keyArray = Array.isArray(keys) ? keys : [keys];
|
|
1878
|
+
const dataArray = Array.isArray(rawData) ? rawData : [rawData];
|
|
1879
|
+
const results = [];
|
|
1880
|
+
for (const row of dataArray) {
|
|
1881
|
+
if (row === void 0 || row === null) {
|
|
1882
|
+
results.push(void 0);
|
|
1883
|
+
continue;
|
|
1884
|
+
}
|
|
1885
|
+
const deserialized = typeof row === "string" ? await this.decode(row) : row;
|
|
1886
|
+
if (deserialized === void 0 || deserialized === null) {
|
|
1887
|
+
results.push(void 0);
|
|
1888
|
+
continue;
|
|
1889
|
+
}
|
|
1890
|
+
results.push(deserialized);
|
|
1891
|
+
}
|
|
1892
|
+
await deleteExpiredKeys(keyArray, results, this);
|
|
1893
|
+
return results;
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Fires a hook under its new name and also under the deprecated alias (if any),
|
|
1897
|
+
* so that integrations still subscribing to the old PRE_/POST_ names keep working.
|
|
1898
|
+
*/
|
|
1899
|
+
async hookWithDeprecated(event, ...args) {
|
|
1900
|
+
await this.hook(event, ...args);
|
|
1901
|
+
const deprecated = deprecatedHookAliases.get(event);
|
|
1902
|
+
if (deprecated && this.getHooks(deprecated)?.length) await this.hook(deprecated, ...args);
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Emit a telemetry event for cache operations.
|
|
1906
|
+
* @param {KeyvEvents} event the telemetry event type
|
|
1907
|
+
* @param {string | string[]} [key] the cache key or keys (emits one event per key)
|
|
1908
|
+
*/
|
|
1909
|
+
emitTelemetry(event, key) {
|
|
1910
|
+
if (key === void 0) {
|
|
1911
|
+
this.emit(event, {
|
|
1912
|
+
event: event.replace("stat:", ""),
|
|
1913
|
+
namespace: this._namespace,
|
|
1914
|
+
timestamp: Date.now()
|
|
1915
|
+
});
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
const keys = Array.isArray(key) ? key : [key];
|
|
1919
|
+
for (const k of keys) this.emit(event, {
|
|
1920
|
+
event: event.replace("stat:", ""),
|
|
1921
|
+
key: k,
|
|
1922
|
+
namespace: this._namespace,
|
|
1923
|
+
timestamp: Date.now()
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* Merges the overloaded constructor arguments into a single KeyvOptions object.
|
|
1928
|
+
*/
|
|
1929
|
+
static resolveOptions(store, options) {
|
|
1930
|
+
options ??= {};
|
|
1931
|
+
store ??= {};
|
|
1932
|
+
const merged = { ...options };
|
|
1933
|
+
if (store && store.get) merged.store = store;
|
|
1934
|
+
else Object.assign(merged, store);
|
|
1935
|
+
return merged;
|
|
1936
|
+
}
|
|
1937
|
+
/**
|
|
1938
|
+
* Initializes the serialization adapter from options.
|
|
1939
|
+
*/
|
|
1940
|
+
initSerialization(options) {
|
|
1941
|
+
if (options.serialization === false) this._serialization = void 0;
|
|
1942
|
+
else this._serialization = options.serialization ?? new KeyvJsonSerializer();
|
|
1943
|
+
}
|
|
1944
|
+
/**
|
|
1945
|
+
* Initializes the sanitization handler from options.
|
|
1946
|
+
*/
|
|
1947
|
+
initSanitize(options) {
|
|
1948
|
+
const sanitize = new KeyvSanitize();
|
|
1949
|
+
if (options.sanitize) sanitize.updateOptions(options.sanitize);
|
|
1950
|
+
this._sanitize = sanitize;
|
|
1951
|
+
}
|
|
1952
|
+
/**
|
|
1953
|
+
* Initializes the stats manager from options.
|
|
1954
|
+
*/
|
|
1955
|
+
initStats(options) {
|
|
1956
|
+
this._stats = new KeyvStats({
|
|
1957
|
+
emitter: this,
|
|
1958
|
+
enabled: options.stats ?? false
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Initializes the namespace, applying sanitization if enabled.
|
|
1963
|
+
*/
|
|
1964
|
+
initNamespace(namespace) {
|
|
1965
|
+
this._namespace = namespace;
|
|
1966
|
+
if (this._namespace && this._sanitize.enabled) this._namespace = this._sanitize.cleanNamespace(this._namespace);
|
|
1967
|
+
}
|
|
1968
|
+
};
|
|
1969
|
+
//#endregion
|
|
1970
|
+
//#region src/adapters/memory.ts
|
|
1971
|
+
/**
|
|
1972
|
+
* An in-memory storage adapter for Keyv that wraps any Map-like object.
|
|
1973
|
+
*
|
|
1974
|
+
* This class provides a unified interface for using various Map-like stores
|
|
1975
|
+
* with Keyv, handling namespace prefixing, TTL-based expiration, and batch operations.
|
|
1976
|
+
*
|
|
1977
|
+
* @example
|
|
1978
|
+
* ```typescript
|
|
1979
|
+
* // Using with a standard Map
|
|
1980
|
+
* const store = new KeyvMemoryAdapter(new Map(), { namespace: 'cache' });
|
|
1981
|
+
*
|
|
1982
|
+
* // Using with a custom store
|
|
1983
|
+
* const customStore = new KeyvMemoryAdapter(myCustomMapLikeStore, {
|
|
1984
|
+
* namespace: 'tenant-123',
|
|
1985
|
+
* keySeparator: ':'
|
|
1986
|
+
* });
|
|
1987
|
+
* ```
|
|
1988
|
+
*/
|
|
1989
|
+
var KeyvMemoryAdapter = class extends Hookified {
|
|
1990
|
+
_store;
|
|
1991
|
+
_namespace;
|
|
1992
|
+
_keySeparator = ":";
|
|
1993
|
+
_capabilities;
|
|
1994
|
+
/**
|
|
1995
|
+
* Creates a new KeyvMemoryAdapter instance.
|
|
1996
|
+
* @param store - The underlying Map or Map-like object to use for storage
|
|
1997
|
+
* @param options - Configuration options for the store
|
|
1998
|
+
*/
|
|
1999
|
+
constructor(store, options) {
|
|
2000
|
+
super({ throwOnHookError: false });
|
|
2001
|
+
this._store = store;
|
|
2002
|
+
this._capabilities = detectKeyvStorage(store);
|
|
2003
|
+
if (options?.keySeparator) this._keySeparator = options.keySeparator;
|
|
2004
|
+
if (options?.namespace) this._namespace = options?.namespace;
|
|
2005
|
+
}
|
|
2006
|
+
/**
|
|
2007
|
+
* Gets the detected capabilities of the underlying store.
|
|
2008
|
+
*/
|
|
2009
|
+
get capabilities() {
|
|
2010
|
+
return this._capabilities;
|
|
2011
|
+
}
|
|
2012
|
+
/**
|
|
2013
|
+
* Gets the underlying store instance.
|
|
2014
|
+
*/
|
|
2015
|
+
get store() {
|
|
2016
|
+
return this._store;
|
|
2017
|
+
}
|
|
2018
|
+
/**
|
|
2019
|
+
* Sets the underlying store instance.
|
|
2020
|
+
*/
|
|
2021
|
+
set store(store) {
|
|
2022
|
+
this._store = store;
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* Gets the current key separator used between namespace and key.
|
|
2026
|
+
*/
|
|
2027
|
+
get keySeparator() {
|
|
2028
|
+
return this._keySeparator;
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Sets the key separator used between namespace and key.
|
|
2032
|
+
*/
|
|
2033
|
+
set keySeparator(separator) {
|
|
2034
|
+
this._keySeparator = separator;
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Gets the current namespace.
|
|
2038
|
+
*/
|
|
2039
|
+
get namespace() {
|
|
2040
|
+
return this._namespace;
|
|
2041
|
+
}
|
|
2042
|
+
/**
|
|
2043
|
+
* Sets the namespace.
|
|
2044
|
+
*/
|
|
2045
|
+
set namespace(namespace) {
|
|
2046
|
+
this._namespace = namespace;
|
|
2047
|
+
}
|
|
2048
|
+
/**
|
|
2049
|
+
* Creates a prefixed key by combining the namespace and key with the separator.
|
|
2050
|
+
* @param key - The base key
|
|
2051
|
+
* @param namespace - Optional namespace to prefix the key with
|
|
2052
|
+
* @returns The prefixed key if namespace is provided, otherwise the original key
|
|
2053
|
+
*/
|
|
2054
|
+
getKeyPrefix(key, namespace) {
|
|
2055
|
+
if (namespace) return `${namespace}${this._keySeparator}${key}`;
|
|
2056
|
+
return key;
|
|
2057
|
+
}
|
|
2058
|
+
/**
|
|
2059
|
+
* Parses a prefixed key to extract the namespace and original key.
|
|
2060
|
+
* @param key - The prefixed key to parse
|
|
2061
|
+
* @returns An object containing the namespace (if present) and the original key
|
|
2062
|
+
*/
|
|
2063
|
+
getKeyPrefixData(key) {
|
|
2064
|
+
if (this._namespace && key.startsWith(`${this._namespace}${this._keySeparator}`)) return {
|
|
2065
|
+
namespace: this._namespace,
|
|
2066
|
+
key: key.slice(this._namespace.length + this._keySeparator.length)
|
|
2067
|
+
};
|
|
2068
|
+
return { key };
|
|
2069
|
+
}
|
|
2070
|
+
/**
|
|
2071
|
+
* Retrieves a value from the store by key.
|
|
2072
|
+
* Automatically handles namespace prefixing and TTL expiration.
|
|
2073
|
+
* @param key - The key to retrieve
|
|
2074
|
+
* @returns The stored data, or undefined if not found or expired
|
|
2075
|
+
*/
|
|
2076
|
+
async get(key) {
|
|
2077
|
+
const keyPrefix = this.getKeyPrefix(key, this._namespace);
|
|
2078
|
+
const entry = this._store.get(keyPrefix);
|
|
2079
|
+
if (entry === void 0 || entry === null) return;
|
|
2080
|
+
if (entry.expires !== void 0 && Date.now() > entry.expires) {
|
|
2081
|
+
this._store.delete(keyPrefix);
|
|
2082
|
+
return;
|
|
2083
|
+
}
|
|
2084
|
+
return entry.value;
|
|
2085
|
+
}
|
|
2086
|
+
/**
|
|
2087
|
+
* Stores a value in the store with an optional TTL.
|
|
2088
|
+
* @param key - The key to store the value under
|
|
2089
|
+
* @param value - The value to store
|
|
2090
|
+
* @param ttl - Optional time-to-live in milliseconds
|
|
2091
|
+
* @returns Always returns true indicating success
|
|
2092
|
+
*/
|
|
2093
|
+
async set(key, value, ttl) {
|
|
2094
|
+
const keyPrefix = this.getKeyPrefix(key, this._namespace);
|
|
2095
|
+
const entry = {
|
|
2096
|
+
value,
|
|
2097
|
+
expires: ttl ? Date.now() + ttl : void 0
|
|
2098
|
+
};
|
|
2099
|
+
this._store.set(keyPrefix, entry, ttl);
|
|
2100
|
+
return true;
|
|
2101
|
+
}
|
|
2102
|
+
/**
|
|
2103
|
+
* Stores multiple entries in the store at once.
|
|
2104
|
+
* @param entries - Array of entries containing key, value, and optional TTL
|
|
2105
|
+
*/
|
|
2106
|
+
async setMany(entries) {
|
|
2107
|
+
const results = [];
|
|
2108
|
+
for (const entry of entries) {
|
|
2109
|
+
const keyPrefix = this.getKeyPrefix(entry.key, this._namespace);
|
|
2110
|
+
const memEntry = {
|
|
2111
|
+
value: entry.value,
|
|
2112
|
+
expires: entry.ttl ? Date.now() + entry.ttl : void 0
|
|
2113
|
+
};
|
|
2114
|
+
this._store.set(keyPrefix, memEntry, entry.ttl);
|
|
2115
|
+
results.push(true);
|
|
2116
|
+
}
|
|
2117
|
+
return results;
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Deletes a value from the store by key.
|
|
2121
|
+
* @param key - The key to delete
|
|
2122
|
+
* @returns True if the key was deleted, false otherwise
|
|
2123
|
+
*/
|
|
2124
|
+
async delete(key) {
|
|
2125
|
+
const keyPrefix = this.getKeyPrefix(key, this._namespace);
|
|
2126
|
+
return this._store.delete(keyPrefix);
|
|
2127
|
+
}
|
|
2128
|
+
/**
|
|
2129
|
+
* Clears entries from the store. If a namespace is set, only entries
|
|
2130
|
+
* within that namespace are removed. Otherwise, the entire store is cleared.
|
|
2131
|
+
* NOTE: if there is no `keys()` then we just do a full clear.
|
|
2132
|
+
*/
|
|
2133
|
+
async clear() {
|
|
2134
|
+
if (!this._namespace || typeof this._store.keys !== "function") {
|
|
2135
|
+
this._store.clear();
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
const prefix = `${this._namespace}${this._keySeparator}`;
|
|
2139
|
+
const keysToDelete = [];
|
|
2140
|
+
for (const key of this._store.keys()) if (key.startsWith(prefix)) keysToDelete.push(key);
|
|
2141
|
+
for (const key of keysToDelete) this._store.delete(key);
|
|
2142
|
+
}
|
|
2143
|
+
/**
|
|
2144
|
+
* Checks if a key exists in the store and is not expired.
|
|
2145
|
+
* @param key - The key to check
|
|
2146
|
+
* @returns True if the key exists and is not expired, false otherwise
|
|
2147
|
+
*/
|
|
2148
|
+
async has(key) {
|
|
2149
|
+
const keyPrefix = this.getKeyPrefix(key, this._namespace);
|
|
2150
|
+
const entry = this._store.get(keyPrefix);
|
|
2151
|
+
if (entry === void 0 || entry === null) return false;
|
|
2152
|
+
if (entry.expires !== void 0 && Date.now() > entry.expires) {
|
|
2153
|
+
this._store.delete(keyPrefix);
|
|
2154
|
+
return false;
|
|
2155
|
+
}
|
|
2156
|
+
return true;
|
|
2157
|
+
}
|
|
2158
|
+
/**
|
|
2159
|
+
* Checks if multiple keys exist in the store and are not expired.
|
|
2160
|
+
* @param keys - Array of keys to check
|
|
2161
|
+
* @returns Array of booleans indicating existence for each key
|
|
2162
|
+
*/
|
|
2163
|
+
async hasMany(keys) {
|
|
2164
|
+
const results = [];
|
|
2165
|
+
for (const key of keys) results.push(await this.has(key));
|
|
2166
|
+
return results;
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* Retrieves multiple values from the store by their keys.
|
|
2170
|
+
* @param keys - Array of keys to retrieve
|
|
2171
|
+
* @returns Array of stored data in the same order as the input keys
|
|
2172
|
+
*/
|
|
2173
|
+
async getMany(keys) {
|
|
2174
|
+
const values = [];
|
|
2175
|
+
for (const key of keys) {
|
|
2176
|
+
const keyPrefix = this.getKeyPrefix(key, this._namespace);
|
|
2177
|
+
const entry = this._store.get(keyPrefix);
|
|
2178
|
+
if (entry === void 0 || entry === null) {
|
|
2179
|
+
values.push(void 0);
|
|
2180
|
+
continue;
|
|
2181
|
+
}
|
|
2182
|
+
if (entry.expires !== void 0 && Date.now() > entry.expires) {
|
|
2183
|
+
this._store.delete(keyPrefix);
|
|
2184
|
+
values.push(void 0);
|
|
2185
|
+
continue;
|
|
2186
|
+
}
|
|
2187
|
+
values.push(entry.value);
|
|
2188
|
+
}
|
|
2189
|
+
return values;
|
|
2190
|
+
}
|
|
2191
|
+
/**
|
|
2192
|
+
* Deletes multiple keys from the store at once.
|
|
2193
|
+
* @param keys - Array of keys to delete
|
|
2194
|
+
* @returns Array of booleans indicating success for each key
|
|
2195
|
+
*/
|
|
2196
|
+
async deleteMany(keys) {
|
|
2197
|
+
const results = [];
|
|
2198
|
+
for (const key of keys) try {
|
|
2199
|
+
const keyPrefix = this.getKeyPrefix(key, this._namespace);
|
|
2200
|
+
const existed = this._store.has(keyPrefix);
|
|
2201
|
+
this._store.delete(keyPrefix);
|
|
2202
|
+
results.push(existed);
|
|
2203
|
+
} catch (error) {
|
|
2204
|
+
this.emit(KeyvEvents.ERROR, error);
|
|
2205
|
+
results.push(false);
|
|
2206
|
+
}
|
|
2207
|
+
return results;
|
|
2208
|
+
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Creates an async iterator for iterating over store entries.
|
|
2211
|
+
* If the underlying store does not support iteration, returns an empty generator.
|
|
2212
|
+
* @returns {AsyncGenerator<Array<string | Awaited<Value> | undefined>, void>} An async generator yielding [key, value] pairs
|
|
2213
|
+
*/
|
|
2214
|
+
async *iterator() {
|
|
2215
|
+
if (typeof this._store.entries !== "function") return;
|
|
2216
|
+
const namespace = this._namespace;
|
|
2217
|
+
for (const [key, raw] of this._store.entries()) {
|
|
2218
|
+
if (namespace) {
|
|
2219
|
+
if (!key.startsWith(`${namespace}${this._keySeparator}`)) continue;
|
|
2220
|
+
}
|
|
2221
|
+
const entry = raw;
|
|
2222
|
+
if (entry?.expires !== void 0 && Date.now() > entry.expires) {
|
|
2223
|
+
this._store.delete(key);
|
|
2224
|
+
continue;
|
|
2225
|
+
}
|
|
2226
|
+
yield [namespace ? key.slice(namespace.length + this._keySeparator.length) : key, entry?.value];
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
/**
|
|
2230
|
+
* No-op disconnect for in-memory stores.
|
|
2231
|
+
*/
|
|
2232
|
+
async disconnect() {}
|
|
2233
|
+
};
|
|
2234
|
+
/**
|
|
2235
|
+
* Creates a Keyv instance with a memory adapter optimized for in-memory storage.
|
|
2236
|
+
*
|
|
2237
|
+
* This factory function configures Keyv to bypass serialization/deserialization
|
|
2238
|
+
* and key prefixing, resulting in faster performance for in-memory use cases
|
|
2239
|
+
* where data doesn't need to be persisted or transmitted.
|
|
2240
|
+
*
|
|
2241
|
+
* @param store - The underlying Map or Map-like object to use for storage
|
|
2242
|
+
* @param options - Configuration options for the memory adapter
|
|
2243
|
+
* @returns A configured Keyv instance with optimized settings for in-memory storage
|
|
2244
|
+
*
|
|
2245
|
+
* @example
|
|
2246
|
+
* ```typescript
|
|
2247
|
+
* // Create a simple in-memory cache
|
|
2248
|
+
* const cache = createKeyv(new Map());
|
|
2249
|
+
* await cache.set('user:1', { name: 'John' });
|
|
2250
|
+
*
|
|
2251
|
+
* // Create with namespace for multi-tenant scenarios
|
|
2252
|
+
* const tenantCache = createKeyv(new Map(), {
|
|
2253
|
+
* namespace: 'tenant-123',
|
|
2254
|
+
* keySeparator: ':'
|
|
2255
|
+
* });
|
|
2256
|
+
* ```
|
|
2257
|
+
*/
|
|
2258
|
+
function createKeyv(store, options) {
|
|
2259
|
+
const { namespace, ...adapterOptions } = options ?? {};
|
|
2260
|
+
return new Keyv({
|
|
2261
|
+
store: new KeyvMemoryAdapter(store, adapterOptions),
|
|
2262
|
+
serialization: false,
|
|
2263
|
+
namespace
|
|
2264
|
+
});
|
|
2265
|
+
}
|
|
2266
|
+
//#endregion
|
|
2267
|
+
export { Keyv, Keyv as default, KeyvBridgeAdapter, KeyvEvents, KeyvHooks, KeyvJsonSerializer, KeyvMemoryAdapter, KeyvSanitize, KeyvStats, createKeyv, detectKeyv, detectKeyvCompression, detectKeyvEncryption, detectKeyvSerialization, detectKeyvStorage, jsonSerializer };
|