965 lines
28 KiB
JavaScript
965 lines
28 KiB
JavaScript
// src/index.ts
|
|
import { defaultDeserialize, defaultSerialize } from "@keyv/serialize";
|
|
|
|
// src/event-manager.ts
|
|
var EventManager = class {
|
|
_eventListeners;
|
|
_maxListeners;
|
|
constructor() {
|
|
this._eventListeners = /* @__PURE__ */ new Map();
|
|
this._maxListeners = 100;
|
|
}
|
|
maxListeners() {
|
|
return this._maxListeners;
|
|
}
|
|
// Add an event listener
|
|
addListener(event, listener) {
|
|
this.on(event, listener);
|
|
}
|
|
on(event, listener) {
|
|
if (!this._eventListeners.has(event)) {
|
|
this._eventListeners.set(event, []);
|
|
}
|
|
const listeners = this._eventListeners.get(event);
|
|
if (listeners) {
|
|
if (listeners.length >= this._maxListeners) {
|
|
console.warn(
|
|
`MaxListenersExceededWarning: Possible event memory leak detected. ${listeners.length + 1} ${event} listeners added. Use setMaxListeners() to increase limit.`
|
|
);
|
|
}
|
|
listeners.push(listener);
|
|
}
|
|
return this;
|
|
}
|
|
// Remove an event listener
|
|
removeListener(event, listener) {
|
|
this.off(event, listener);
|
|
}
|
|
off(event, listener) {
|
|
const listeners = this._eventListeners.get(event) ?? [];
|
|
const index = listeners.indexOf(listener);
|
|
if (index !== -1) {
|
|
listeners.splice(index, 1);
|
|
}
|
|
if (listeners.length === 0) {
|
|
this._eventListeners.delete(event);
|
|
}
|
|
}
|
|
once(event, listener) {
|
|
const onceListener = (...arguments_) => {
|
|
listener(...arguments_);
|
|
this.off(event, onceListener);
|
|
};
|
|
this.on(event, onceListener);
|
|
}
|
|
// Emit an event
|
|
// biome-ignore lint/suspicious/noExplicitAny: type format
|
|
emit(event, ...arguments_) {
|
|
const listeners = this._eventListeners.get(event);
|
|
if (listeners && listeners.length > 0) {
|
|
for (const listener of listeners) {
|
|
listener(...arguments_);
|
|
}
|
|
}
|
|
}
|
|
// Get all listeners for a specific event
|
|
listeners(event) {
|
|
return this._eventListeners.get(event) ?? [];
|
|
}
|
|
// Remove all listeners for a specific event
|
|
removeAllListeners(event) {
|
|
if (event) {
|
|
this._eventListeners.delete(event);
|
|
} else {
|
|
this._eventListeners.clear();
|
|
}
|
|
}
|
|
// Set the maximum number of listeners for a single event
|
|
setMaxListeners(n) {
|
|
this._maxListeners = n;
|
|
}
|
|
};
|
|
var event_manager_default = EventManager;
|
|
|
|
// src/hooks-manager.ts
|
|
var HooksManager = class extends event_manager_default {
|
|
_hookHandlers;
|
|
constructor() {
|
|
super();
|
|
this._hookHandlers = /* @__PURE__ */ new Map();
|
|
}
|
|
// Adds a handler function for a specific event
|
|
addHandler(event, handler) {
|
|
const eventHandlers = this._hookHandlers.get(event);
|
|
if (eventHandlers) {
|
|
eventHandlers.push(handler);
|
|
} else {
|
|
this._hookHandlers.set(event, [handler]);
|
|
}
|
|
}
|
|
// Removes a specific handler function for a specific event
|
|
removeHandler(event, handler) {
|
|
const eventHandlers = this._hookHandlers.get(event);
|
|
if (eventHandlers) {
|
|
const index = eventHandlers.indexOf(handler);
|
|
if (index !== -1) {
|
|
eventHandlers.splice(index, 1);
|
|
}
|
|
}
|
|
}
|
|
// Triggers all handlers for a specific event with provided data
|
|
// biome-ignore lint/suspicious/noExplicitAny: type format
|
|
trigger(event, data) {
|
|
const eventHandlers = this._hookHandlers.get(event);
|
|
if (eventHandlers) {
|
|
for (const handler of eventHandlers) {
|
|
try {
|
|
handler(data);
|
|
} catch (error) {
|
|
this.emit(
|
|
"error",
|
|
new Error(
|
|
`Error in hook handler for event "${event}": ${error.message}`
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Provides read-only access to the current handlers
|
|
get handlers() {
|
|
return new Map(this._hookHandlers);
|
|
}
|
|
};
|
|
var hooks_manager_default = HooksManager;
|
|
|
|
// src/stats-manager.ts
|
|
var StatsManager = class extends event_manager_default {
|
|
enabled = true;
|
|
hits = 0;
|
|
misses = 0;
|
|
sets = 0;
|
|
deletes = 0;
|
|
errors = 0;
|
|
constructor(enabled) {
|
|
super();
|
|
if (enabled !== void 0) {
|
|
this.enabled = enabled;
|
|
}
|
|
this.reset();
|
|
}
|
|
hit() {
|
|
if (this.enabled) {
|
|
this.hits++;
|
|
}
|
|
}
|
|
miss() {
|
|
if (this.enabled) {
|
|
this.misses++;
|
|
}
|
|
}
|
|
set() {
|
|
if (this.enabled) {
|
|
this.sets++;
|
|
}
|
|
}
|
|
delete() {
|
|
if (this.enabled) {
|
|
this.deletes++;
|
|
}
|
|
}
|
|
hitsOrMisses(array) {
|
|
for (const item of array) {
|
|
if (item === void 0) {
|
|
this.miss();
|
|
} else {
|
|
this.hit();
|
|
}
|
|
}
|
|
}
|
|
reset() {
|
|
this.hits = 0;
|
|
this.misses = 0;
|
|
this.sets = 0;
|
|
this.deletes = 0;
|
|
this.errors = 0;
|
|
}
|
|
};
|
|
var stats_manager_default = StatsManager;
|
|
|
|
// src/index.ts
|
|
var KeyvHooks = /* @__PURE__ */ ((KeyvHooks2) => {
|
|
KeyvHooks2["PRE_SET"] = "preSet";
|
|
KeyvHooks2["POST_SET"] = "postSet";
|
|
KeyvHooks2["PRE_GET"] = "preGet";
|
|
KeyvHooks2["POST_GET"] = "postGet";
|
|
KeyvHooks2["PRE_GET_MANY"] = "preGetMany";
|
|
KeyvHooks2["POST_GET_MANY"] = "postGetMany";
|
|
KeyvHooks2["PRE_GET_RAW"] = "preGetRaw";
|
|
KeyvHooks2["POST_GET_RAW"] = "postGetRaw";
|
|
KeyvHooks2["PRE_GET_MANY_RAW"] = "preGetManyRaw";
|
|
KeyvHooks2["POST_GET_MANY_RAW"] = "postGetManyRaw";
|
|
KeyvHooks2["PRE_DELETE"] = "preDelete";
|
|
KeyvHooks2["POST_DELETE"] = "postDelete";
|
|
return KeyvHooks2;
|
|
})(KeyvHooks || {});
|
|
var iterableAdapters = [
|
|
"sqlite",
|
|
"postgres",
|
|
"mysql",
|
|
"mongo",
|
|
"redis",
|
|
"valkey",
|
|
"etcd"
|
|
];
|
|
var Keyv = class extends event_manager_default {
|
|
opts;
|
|
iterator;
|
|
hooks = new hooks_manager_default();
|
|
stats = new stats_manager_default(false);
|
|
/**
|
|
* Time to live in milliseconds
|
|
*/
|
|
_ttl;
|
|
/**
|
|
* Namespace
|
|
*/
|
|
_namespace;
|
|
/**
|
|
* Store
|
|
*/
|
|
// biome-ignore lint/suspicious/noExplicitAny: type format
|
|
_store = /* @__PURE__ */ new Map();
|
|
_serialize = defaultSerialize;
|
|
_deserialize = defaultDeserialize;
|
|
_compression;
|
|
_useKeyPrefix = true;
|
|
_throwOnErrors = false;
|
|
/**
|
|
* Keyv Constructor
|
|
* @param {KeyvStoreAdapter | KeyvOptions} store
|
|
* @param {Omit<KeyvOptions, 'store'>} [options] if you provide the store you can then provide the Keyv Options
|
|
*/
|
|
constructor(store, options) {
|
|
super();
|
|
options ??= {};
|
|
store ??= {};
|
|
this.opts = {
|
|
namespace: "keyv",
|
|
serialize: defaultSerialize,
|
|
deserialize: defaultDeserialize,
|
|
emitErrors: true,
|
|
// @ts-expect-error - Map is not a KeyvStoreAdapter
|
|
store: /* @__PURE__ */ new Map(),
|
|
...options
|
|
};
|
|
if (store && store.get) {
|
|
this.opts.store = store;
|
|
} else {
|
|
this.opts = {
|
|
...this.opts,
|
|
...store
|
|
};
|
|
}
|
|
this._store = this.opts.store ?? /* @__PURE__ */ new Map();
|
|
this._compression = this.opts.compression;
|
|
this._serialize = this.opts.serialize;
|
|
this._deserialize = this.opts.deserialize;
|
|
if (this.opts.namespace) {
|
|
this._namespace = this.opts.namespace;
|
|
}
|
|
if (this._store) {
|
|
if (!this._isValidStorageAdapter(this._store)) {
|
|
throw new Error("Invalid storage adapter");
|
|
}
|
|
if (typeof this._store.on === "function") {
|
|
this._store.on("error", (error) => this.emit("error", error));
|
|
}
|
|
this._store.namespace = this._namespace;
|
|
if (typeof this._store[Symbol.iterator] === "function" && this._store instanceof Map) {
|
|
this.iterator = this.generateIterator(
|
|
this._store
|
|
);
|
|
} else if ("iterator" in this._store && this._store.opts && this._checkIterableAdapter()) {
|
|
this.iterator = this.generateIterator(
|
|
// biome-ignore lint/style/noNonNullAssertion: need to fix
|
|
this._store.iterator.bind(this._store)
|
|
);
|
|
}
|
|
}
|
|
if (this.opts.stats) {
|
|
this.stats.enabled = this.opts.stats;
|
|
}
|
|
if (this.opts.ttl) {
|
|
this._ttl = this.opts.ttl;
|
|
}
|
|
if (this.opts.useKeyPrefix !== void 0) {
|
|
this._useKeyPrefix = this.opts.useKeyPrefix;
|
|
}
|
|
if (this.opts.throwOnErrors !== void 0) {
|
|
this._throwOnErrors = this.opts.throwOnErrors;
|
|
}
|
|
}
|
|
/**
|
|
* Get the current store
|
|
*/
|
|
// biome-ignore lint/suspicious/noExplicitAny: type format
|
|
get store() {
|
|
return this._store;
|
|
}
|
|
/**
|
|
* Set the current store. This will also set the namespace, event error handler, and generate the iterator. If the store is not valid it will throw an error.
|
|
* @param {KeyvStoreAdapter | Map<any, any> | any} store the store to set
|
|
*/
|
|
// biome-ignore lint/suspicious/noExplicitAny: type format
|
|
set store(store) {
|
|
if (this._isValidStorageAdapter(store)) {
|
|
this._store = store;
|
|
this.opts.store = store;
|
|
if (typeof store.on === "function") {
|
|
store.on("error", (error) => this.emit("error", error));
|
|
}
|
|
if (this._namespace) {
|
|
this._store.namespace = this._namespace;
|
|
}
|
|
if (typeof store[Symbol.iterator] === "function" && store instanceof Map) {
|
|
this.iterator = this.generateIterator(
|
|
store
|
|
);
|
|
} else if ("iterator" in store && store.opts && this._checkIterableAdapter()) {
|
|
this.iterator = this.generateIterator(store.iterator?.bind(store));
|
|
}
|
|
} else {
|
|
throw new Error("Invalid storage adapter");
|
|
}
|
|
}
|
|
/**
|
|
* Get the current compression function
|
|
* @returns {CompressionAdapter} The current compression function
|
|
*/
|
|
get compression() {
|
|
return this._compression;
|
|
}
|
|
/**
|
|
* Set the current compression function
|
|
* @param {CompressionAdapter} compress The compression function to set
|
|
*/
|
|
set compression(compress) {
|
|
this._compression = compress;
|
|
}
|
|
/**
|
|
* Get the current namespace.
|
|
* @returns {string | undefined} The current namespace.
|
|
*/
|
|
get namespace() {
|
|
return this._namespace;
|
|
}
|
|
/**
|
|
* Set the current namespace.
|
|
* @param {string | undefined} namespace The namespace to set.
|
|
*/
|
|
set namespace(namespace) {
|
|
this._namespace = namespace;
|
|
this.opts.namespace = namespace;
|
|
this._store.namespace = namespace;
|
|
if (this.opts.store) {
|
|
this.opts.store.namespace = namespace;
|
|
}
|
|
}
|
|
/**
|
|
* Get the current TTL.
|
|
* @returns {number} The current TTL in milliseconds.
|
|
*/
|
|
get ttl() {
|
|
return this._ttl;
|
|
}
|
|
/**
|
|
* Set the current TTL.
|
|
* @param {number} ttl The TTL to set in milliseconds.
|
|
*/
|
|
set ttl(ttl) {
|
|
this.opts.ttl = ttl;
|
|
this._ttl = ttl;
|
|
}
|
|
/**
|
|
* Get the current serialize function.
|
|
* @returns {Serialize} The current serialize function.
|
|
*/
|
|
get serialize() {
|
|
return this._serialize;
|
|
}
|
|
/**
|
|
* Set the current serialize function.
|
|
* @param {Serialize} serialize The serialize function to set.
|
|
*/
|
|
set serialize(serialize) {
|
|
this.opts.serialize = serialize;
|
|
this._serialize = serialize;
|
|
}
|
|
/**
|
|
* Get the current deserialize function.
|
|
* @returns {Deserialize} The current deserialize function.
|
|
*/
|
|
get deserialize() {
|
|
return this._deserialize;
|
|
}
|
|
/**
|
|
* Set the current deserialize function.
|
|
* @param {Deserialize} deserialize The deserialize function to set.
|
|
*/
|
|
set deserialize(deserialize) {
|
|
this.opts.deserialize = deserialize;
|
|
this._deserialize = deserialize;
|
|
}
|
|
/**
|
|
* Get the current useKeyPrefix value. This will enable or disable key prefixing.
|
|
* @returns {boolean} The current useKeyPrefix value.
|
|
* @default true
|
|
*/
|
|
get useKeyPrefix() {
|
|
return this._useKeyPrefix;
|
|
}
|
|
/**
|
|
* Set the current useKeyPrefix value. This will enable or disable key prefixing.
|
|
* @param {boolean} value The useKeyPrefix value to set.
|
|
*/
|
|
set useKeyPrefix(value) {
|
|
this._useKeyPrefix = value;
|
|
this.opts.useKeyPrefix = value;
|
|
}
|
|
/**
|
|
* Get the current throwErrors value. This will enable or disable throwing errors on methods in addition to emitting them.
|
|
* @return {boolean} The current throwOnErrors value.
|
|
*/
|
|
get throwOnErrors() {
|
|
return this._throwOnErrors;
|
|
}
|
|
/**
|
|
* Set the current throwOnErrors value. This will enable or disable throwing errors on methods in addition to emitting them.
|
|
* @param {boolean} value The throwOnErrors value to set.
|
|
*/
|
|
set throwOnErrors(value) {
|
|
this._throwOnErrors = value;
|
|
this.opts.throwOnErrors = value;
|
|
}
|
|
generateIterator(iterator) {
|
|
const function_ = async function* () {
|
|
for await (const [key, raw] of typeof iterator === "function" ? iterator(this._store.namespace) : iterator) {
|
|
const data = await this.deserializeData(raw);
|
|
if (this._useKeyPrefix && this._store.namespace && !key.includes(this._store.namespace)) {
|
|
continue;
|
|
}
|
|
if (typeof data.expires === "number" && Date.now() > data.expires) {
|
|
await this.delete(key);
|
|
continue;
|
|
}
|
|
yield [this._getKeyUnprefix(key), data.value];
|
|
}
|
|
};
|
|
return function_.bind(this);
|
|
}
|
|
_checkIterableAdapter() {
|
|
return iterableAdapters.includes(this._store.opts.dialect) || iterableAdapters.some(
|
|
(element) => this._store.opts.url.includes(element)
|
|
);
|
|
}
|
|
_getKeyPrefix(key) {
|
|
if (!this._useKeyPrefix) {
|
|
return key;
|
|
}
|
|
if (!this._namespace) {
|
|
return key;
|
|
}
|
|
if (key.startsWith(`${this._namespace}:`)) {
|
|
return key;
|
|
}
|
|
return `${this._namespace}:${key}`;
|
|
}
|
|
_getKeyPrefixArray(keys) {
|
|
if (!this._useKeyPrefix) {
|
|
return keys;
|
|
}
|
|
if (!this._namespace) {
|
|
return keys;
|
|
}
|
|
return keys.map((key) => `${this._namespace}:${key}`);
|
|
}
|
|
_getKeyUnprefix(key) {
|
|
if (!this._useKeyPrefix) {
|
|
return key;
|
|
}
|
|
return key.split(":").splice(1).join(":");
|
|
}
|
|
// biome-ignore lint/suspicious/noExplicitAny: type format
|
|
_isValidStorageAdapter(store) {
|
|
return store instanceof Map || typeof store.get === "function" && typeof store.set === "function" && typeof store.delete === "function" && typeof store.clear === "function";
|
|
}
|
|
// eslint-disable-next-line @stylistic/max-len
|
|
async get(key, options) {
|
|
const { store } = this.opts;
|
|
const isArray = Array.isArray(key);
|
|
const keyPrefixed = isArray ? this._getKeyPrefixArray(key) : this._getKeyPrefix(key);
|
|
const isDataExpired = (data) => typeof data.expires === "number" && Date.now() > data.expires;
|
|
if (isArray) {
|
|
if (options?.raw === true) {
|
|
return this.getMany(key, { raw: true });
|
|
}
|
|
return this.getMany(key, { raw: false });
|
|
}
|
|
this.hooks.trigger("preGet" /* PRE_GET */, { key: keyPrefixed });
|
|
let rawData;
|
|
try {
|
|
rawData = await store.get(keyPrefixed);
|
|
} catch (error) {
|
|
if (this.throwOnErrors) {
|
|
throw error;
|
|
}
|
|
}
|
|
const deserializedData = typeof rawData === "string" || this.opts.compression ? await this.deserializeData(rawData) : rawData;
|
|
if (deserializedData === void 0 || deserializedData === null) {
|
|
this.hooks.trigger("postGet" /* POST_GET */, {
|
|
key: keyPrefixed,
|
|
value: void 0
|
|
});
|
|
this.stats.miss();
|
|
return void 0;
|
|
}
|
|
if (isDataExpired(deserializedData)) {
|
|
await this.delete(key);
|
|
this.hooks.trigger("postGet" /* POST_GET */, {
|
|
key: keyPrefixed,
|
|
value: void 0
|
|
});
|
|
this.stats.miss();
|
|
return void 0;
|
|
}
|
|
this.hooks.trigger("postGet" /* POST_GET */, {
|
|
key: keyPrefixed,
|
|
value: deserializedData
|
|
});
|
|
this.stats.hit();
|
|
return options?.raw ? deserializedData : deserializedData.value;
|
|
}
|
|
async getMany(keys, options) {
|
|
const { store } = this.opts;
|
|
const keyPrefixed = this._getKeyPrefixArray(keys);
|
|
const isDataExpired = (data) => typeof data.expires === "number" && Date.now() > data.expires;
|
|
this.hooks.trigger("preGetMany" /* PRE_GET_MANY */, { keys: keyPrefixed });
|
|
if (store.getMany === void 0) {
|
|
const promises = keyPrefixed.map(async (key) => {
|
|
const rawData2 = await store.get(key);
|
|
const deserializedRow = typeof rawData2 === "string" || this.opts.compression ? await this.deserializeData(rawData2) : rawData2;
|
|
if (deserializedRow === void 0 || deserializedRow === null) {
|
|
return void 0;
|
|
}
|
|
if (isDataExpired(deserializedRow)) {
|
|
await this.delete(key);
|
|
return void 0;
|
|
}
|
|
return options?.raw ? deserializedRow : deserializedRow.value;
|
|
});
|
|
const deserializedRows = await Promise.allSettled(promises);
|
|
const result2 = deserializedRows.map(
|
|
// biome-ignore lint/suspicious/noExplicitAny: type format
|
|
(row) => row.value
|
|
);
|
|
this.hooks.trigger("postGetMany" /* POST_GET_MANY */, result2);
|
|
if (result2.length > 0) {
|
|
this.stats.hit();
|
|
}
|
|
return result2;
|
|
}
|
|
const rawData = await store.getMany(keyPrefixed);
|
|
const result = [];
|
|
const expiredKeys = [];
|
|
for (const index in rawData) {
|
|
let row = rawData[index];
|
|
if (typeof row === "string") {
|
|
row = await this.deserializeData(row);
|
|
}
|
|
if (row === void 0 || row === null) {
|
|
result.push(void 0);
|
|
continue;
|
|
}
|
|
if (isDataExpired(row)) {
|
|
expiredKeys.push(keys[index]);
|
|
result.push(void 0);
|
|
continue;
|
|
}
|
|
const value = options?.raw ? row : row.value;
|
|
result.push(value);
|
|
}
|
|
if (expiredKeys.length > 0) {
|
|
await this.deleteMany(expiredKeys);
|
|
}
|
|
this.hooks.trigger("postGetMany" /* POST_GET_MANY */, result);
|
|
if (result.length > 0) {
|
|
this.stats.hit();
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Get the raw value of a key. This is the replacement for setting raw to true in the get() method.
|
|
* @param {string} key the key to get
|
|
* @returns {Promise<StoredDataRaw<Value> | undefined>} will return a StoredDataRaw<Value> or undefined if the key does not exist or is expired.
|
|
*/
|
|
async getRaw(key) {
|
|
const { store } = this.opts;
|
|
const keyPrefixed = this._getKeyPrefix(key);
|
|
this.hooks.trigger("preGetRaw" /* PRE_GET_RAW */, { key: keyPrefixed });
|
|
const rawData = await store.get(keyPrefixed);
|
|
if (rawData === void 0 || rawData === null) {
|
|
this.hooks.trigger("postGetRaw" /* POST_GET_RAW */, {
|
|
key: keyPrefixed,
|
|
value: void 0
|
|
});
|
|
this.stats.miss();
|
|
return void 0;
|
|
}
|
|
const deserializedData = typeof rawData === "string" || this.opts.compression ? await this.deserializeData(rawData) : rawData;
|
|
if (deserializedData !== void 0 && deserializedData.expires !== void 0 && deserializedData.expires !== null && // biome-ignore lint/style/noNonNullAssertion: need to fix
|
|
deserializedData.expires < Date.now()) {
|
|
this.hooks.trigger("postGetRaw" /* POST_GET_RAW */, {
|
|
key: keyPrefixed,
|
|
value: void 0
|
|
});
|
|
this.stats.miss();
|
|
await this.delete(key);
|
|
return void 0;
|
|
}
|
|
this.stats.hit();
|
|
this.hooks.trigger("postGetRaw" /* POST_GET_RAW */, {
|
|
key: keyPrefixed,
|
|
value: deserializedData
|
|
});
|
|
return deserializedData;
|
|
}
|
|
/**
|
|
* Get the raw values of many keys. This is the replacement for setting raw to true in the getMany() method.
|
|
* @param {string[]} keys the keys to get
|
|
* @returns {Promise<Array<StoredDataRaw<Value>>>} will return an array of StoredDataRaw<Value> or undefined if the key does not exist or is expired.
|
|
*/
|
|
async getManyRaw(keys) {
|
|
const { store } = this.opts;
|
|
const keyPrefixed = this._getKeyPrefixArray(keys);
|
|
if (keys.length === 0) {
|
|
const result2 = Array.from({ length: keys.length }).fill(
|
|
void 0
|
|
);
|
|
this.stats.misses += keys.length;
|
|
this.hooks.trigger("postGetManyRaw" /* POST_GET_MANY_RAW */, {
|
|
keys: keyPrefixed,
|
|
values: result2
|
|
});
|
|
return result2;
|
|
}
|
|
let result = [];
|
|
if (store.getMany === void 0) {
|
|
const promises = keyPrefixed.map(async (key) => {
|
|
const rawData = await store.get(key);
|
|
if (rawData !== void 0 && rawData !== null) {
|
|
return this.deserializeData(rawData);
|
|
}
|
|
return void 0;
|
|
});
|
|
const deserializedRows = await Promise.allSettled(promises);
|
|
result = deserializedRows.map(
|
|
// biome-ignore lint/suspicious/noExplicitAny: type format
|
|
(row) => row.value
|
|
);
|
|
} else {
|
|
const rawData = await store.getMany(keyPrefixed);
|
|
for (const row of rawData) {
|
|
if (row !== void 0 && row !== null) {
|
|
result.push(await this.deserializeData(row));
|
|
} else {
|
|
result.push(void 0);
|
|
}
|
|
}
|
|
}
|
|
const expiredKeys = [];
|
|
const isDataExpired = (data) => typeof data.expires === "number" && Date.now() > data.expires;
|
|
for (const [index, row] of result.entries()) {
|
|
if (row !== void 0 && isDataExpired(row)) {
|
|
expiredKeys.push(keyPrefixed[index]);
|
|
result[index] = void 0;
|
|
}
|
|
}
|
|
if (expiredKeys.length > 0) {
|
|
await this.deleteMany(expiredKeys);
|
|
}
|
|
this.stats.hitsOrMisses(result);
|
|
this.hooks.trigger("postGetManyRaw" /* POST_GET_MANY_RAW */, {
|
|
keys: keyPrefixed,
|
|
values: result
|
|
});
|
|
return result;
|
|
}
|
|
/**
|
|
* Set an item to the store
|
|
* @param {string | Array<KeyvEntry>} key the key to use. If you pass in an array of KeyvEntry it will set many items
|
|
* @param {Value} value the value of the key
|
|
* @param {number} [ttl] time to live in milliseconds
|
|
* @returns {boolean} if it sets then it will return a true. On failure will return false.
|
|
*/
|
|
async set(key, value, ttl) {
|
|
const data = { key, value, ttl };
|
|
this.hooks.trigger("preSet" /* PRE_SET */, data);
|
|
const keyPrefixed = this._getKeyPrefix(data.key);
|
|
data.ttl ??= this._ttl;
|
|
if (data.ttl === 0) {
|
|
data.ttl = void 0;
|
|
}
|
|
const { store } = this.opts;
|
|
const expires = typeof data.ttl === "number" ? Date.now() + data.ttl : void 0;
|
|
if (typeof data.value === "symbol") {
|
|
this.emit("error", "symbol cannot be serialized");
|
|
throw new Error("symbol cannot be serialized");
|
|
}
|
|
const formattedValue = { value: data.value, expires };
|
|
const serializedValue = await this.serializeData(formattedValue);
|
|
let result = true;
|
|
try {
|
|
const value2 = await store.set(keyPrefixed, serializedValue, data.ttl);
|
|
if (typeof value2 === "boolean") {
|
|
result = value2;
|
|
}
|
|
} catch (error) {
|
|
result = false;
|
|
this.emit("error", error);
|
|
if (this._throwOnErrors) {
|
|
throw error;
|
|
}
|
|
}
|
|
this.hooks.trigger("postSet" /* POST_SET */, {
|
|
key: keyPrefixed,
|
|
value: serializedValue,
|
|
ttl
|
|
});
|
|
this.stats.set();
|
|
return result;
|
|
}
|
|
/**
|
|
* Set many items to the store
|
|
* @param {Array<KeyvEntry>} entries the entries to set
|
|
* @returns {boolean[]} will return an array of booleans if it sets then it will return a true. On failure will return false.
|
|
*/
|
|
// biome-ignore lint/correctness/noUnusedVariables: type format
|
|
async setMany(entries) {
|
|
let results = [];
|
|
try {
|
|
if (this._store.setMany === void 0) {
|
|
const promises = [];
|
|
for (const entry of entries) {
|
|
promises.push(this.set(entry.key, entry.value, entry.ttl));
|
|
}
|
|
const promiseResults = await Promise.all(promises);
|
|
results = promiseResults;
|
|
} else {
|
|
const serializedEntries = await Promise.all(
|
|
entries.map(async ({ key, value, ttl }) => {
|
|
ttl ??= this._ttl;
|
|
if (ttl === 0) {
|
|
ttl = void 0;
|
|
}
|
|
const expires = typeof ttl === "number" ? Date.now() + ttl : void 0;
|
|
if (typeof value === "symbol") {
|
|
this.emit("error", "symbol cannot be serialized");
|
|
throw new Error("symbol cannot be serialized");
|
|
}
|
|
const formattedValue = { value, expires };
|
|
const serializedValue = await this.serializeData(formattedValue);
|
|
const keyPrefixed = this._getKeyPrefix(key);
|
|
return { key: keyPrefixed, value: serializedValue, ttl };
|
|
})
|
|
);
|
|
results = await this._store.setMany(serializedEntries);
|
|
}
|
|
} catch (error) {
|
|
this.emit("error", error);
|
|
if (this._throwOnErrors) {
|
|
throw error;
|
|
}
|
|
results = entries.map(() => false);
|
|
}
|
|
return results;
|
|
}
|
|
/**
|
|
* Delete an Entry
|
|
* @param {string | string[]} key the key to be deleted. if an array it will delete many items
|
|
* @returns {boolean} will return true if item or items are deleted. false if there is an error
|
|
*/
|
|
async delete(key) {
|
|
const { store } = this.opts;
|
|
if (Array.isArray(key)) {
|
|
return this.deleteMany(key);
|
|
}
|
|
const keyPrefixed = this._getKeyPrefix(key);
|
|
this.hooks.trigger("preDelete" /* PRE_DELETE */, { key: keyPrefixed });
|
|
let result = true;
|
|
try {
|
|
const value = await store.delete(keyPrefixed);
|
|
if (typeof value === "boolean") {
|
|
result = value;
|
|
}
|
|
} catch (error) {
|
|
result = false;
|
|
this.emit("error", error);
|
|
if (this._throwOnErrors) {
|
|
throw error;
|
|
}
|
|
}
|
|
this.hooks.trigger("postDelete" /* POST_DELETE */, {
|
|
key: keyPrefixed,
|
|
value: result
|
|
});
|
|
this.stats.delete();
|
|
return result;
|
|
}
|
|
/**
|
|
* Delete many items from the store
|
|
* @param {string[]} keys the keys to be deleted
|
|
* @returns {boolean} will return true if item or items are deleted. false if there is an error
|
|
*/
|
|
async deleteMany(keys) {
|
|
try {
|
|
const { store } = this.opts;
|
|
const keyPrefixed = this._getKeyPrefixArray(keys);
|
|
this.hooks.trigger("preDelete" /* PRE_DELETE */, { key: keyPrefixed });
|
|
if (store.deleteMany !== void 0) {
|
|
return await store.deleteMany(keyPrefixed);
|
|
}
|
|
const promises = keyPrefixed.map(async (key) => store.delete(key));
|
|
const results = await Promise.all(promises);
|
|
const returnResult = results.every(Boolean);
|
|
this.hooks.trigger("postDelete" /* POST_DELETE */, {
|
|
key: keyPrefixed,
|
|
value: returnResult
|
|
});
|
|
return returnResult;
|
|
} catch (error) {
|
|
this.emit("error", error);
|
|
if (this._throwOnErrors) {
|
|
throw error;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
/**
|
|
* Clear the store
|
|
* @returns {void}
|
|
*/
|
|
async clear() {
|
|
this.emit("clear");
|
|
const { store } = this.opts;
|
|
try {
|
|
await store.clear();
|
|
} catch (error) {
|
|
this.emit("error", error);
|
|
if (this._throwOnErrors) {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
async has(key) {
|
|
if (Array.isArray(key)) {
|
|
return this.hasMany(key);
|
|
}
|
|
const keyPrefixed = this._getKeyPrefix(key);
|
|
const { store } = this.opts;
|
|
if (store.has !== void 0 && !(store instanceof Map)) {
|
|
return store.has(keyPrefixed);
|
|
}
|
|
let rawData;
|
|
try {
|
|
rawData = await store.get(keyPrefixed);
|
|
} catch (error) {
|
|
this.emit("error", error);
|
|
if (this._throwOnErrors) {
|
|
throw error;
|
|
}
|
|
return false;
|
|
}
|
|
if (rawData) {
|
|
const data = await this.deserializeData(rawData);
|
|
if (data) {
|
|
if (data.expires === void 0 || data.expires === null) {
|
|
return true;
|
|
}
|
|
return data.expires > Date.now();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Check if many keys exist
|
|
* @param {string[]} keys the keys to check
|
|
* @returns {boolean[]} will return an array of booleans if the keys exist
|
|
*/
|
|
async hasMany(keys) {
|
|
const keyPrefixed = this._getKeyPrefixArray(keys);
|
|
const { store } = this.opts;
|
|
if (store.hasMany !== void 0) {
|
|
return store.hasMany(keyPrefixed);
|
|
}
|
|
const results = [];
|
|
for (const key of keys) {
|
|
results.push(await this.has(key));
|
|
}
|
|
return results;
|
|
}
|
|
/**
|
|
* Will disconnect the store. This is only available if the store has a disconnect method
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async disconnect() {
|
|
const { store } = this.opts;
|
|
this.emit("disconnect");
|
|
if (typeof store.disconnect === "function") {
|
|
return store.disconnect();
|
|
}
|
|
}
|
|
// biome-ignore lint/suspicious/noExplicitAny: type format
|
|
emit(event, ...arguments_) {
|
|
if (event === "error" && !this.opts.emitErrors) {
|
|
return;
|
|
}
|
|
super.emit(event, ...arguments_);
|
|
}
|
|
async serializeData(data) {
|
|
if (!this._serialize) {
|
|
return data;
|
|
}
|
|
if (this._compression?.compress) {
|
|
return this._serialize({
|
|
value: await this._compression.compress(data.value),
|
|
expires: data.expires
|
|
});
|
|
}
|
|
return this._serialize(data);
|
|
}
|
|
async deserializeData(data) {
|
|
if (!this._deserialize) {
|
|
return data;
|
|
}
|
|
if (this._compression?.decompress && typeof data === "string") {
|
|
const result = await this._deserialize(data);
|
|
return {
|
|
value: await this._compression.decompress(result?.value),
|
|
expires: result?.expires
|
|
};
|
|
}
|
|
if (typeof data === "string") {
|
|
return this._deserialize(data);
|
|
}
|
|
return void 0;
|
|
}
|
|
};
|
|
var index_default = Keyv;
|
|
export {
|
|
Keyv,
|
|
KeyvHooks,
|
|
index_default as default
|
|
};
|
|
/* v8 ignore next -- @preserve */
|