UNPKG

keyv

Version:

Simple key-value storage with support for multiple backends

2,529 lines • 87 kB
import { Hookified } from "hookified";
//#region src/capabilities.ts
const keyvMethodNames = [
	"get",
	"set",
	"delete",
	"clear",
	"has",
	"getMany",
	"setMany",
	"deleteMany",
	"hasMany",
	"disconnect",
	"getRaw",
	"getManyRaw",
	"setRaw",
	"setManyRaw",
	"iterator"
];
const keyvPropertyNames = ["hooks", "stats"];
const keyvStorageMethodNames = [
	"get",
	"getMany",
	"has",
	"hasMany",
	"set",
	"setMany",
	"delete",
	"deleteMany",
	"clear",
	"disconnect",
	"iterator"
];
const keyvCompressionMethodNames = ["compress", "decompress"];
const keyvSerializationMethodNames = ["stringify", "parse"];
const keyvEncryptionMethodNames = ["encrypt", "decrypt"];
function isMethod(obj, name) {
	return name in obj && typeof obj[name] === "function";
}
function isProperty(obj, name) {
	return name in obj;
}
function resolveMethodType(obj, name) {
	if (!(name in obj)) return "none";
	const value = obj[name];
	if (typeof value !== "function") return "none";
	return value.constructor.name === "AsyncFunction" ? "async" : "sync";
}
function resolveMethod(obj, name) {
	return {
		exists: isMethod(obj, name),
		methodType: resolveMethodType(obj, name)
	};
}
const noneMethod = {
	exists: false,
	methodType: "none"
};
function buildMethods(obj, names) {
	const methods = {};
	for (const name of names) methods[name] = obj ? resolveMethod(obj, name) : { ...noneMethod };
	return methods;
}
/** The methods a full storage adapter implements. */
const keyvStorageRequiredMethods = [
	"get",
	"has",
	"hasMany",
	"set",
	"setMany",
	"delete",
	"deleteMany",
	"clear"
];
/**
* Whether a detected store has every method of a full storage adapter, whether or not they are
* native `async` functions.
* @param capability - The result of {@link detectKeyvStorage}
* @returns `true` when all of the full storage adapter's methods exist
*/
function hasKeyvStorageMethods(capability) {
	return keyvStorageRequiredMethods.every((m) => capability.methods[m].exists);
}
/**
* Detect whether an object implements the full Keyv interface
* @param obj - The object to check
* @returns A {@link KeyvCapability} where `compatible` is `true` only when all required capabilities are present
* @example
* ```typescript
* import Keyv, { detectKeyv } from 'keyv';
*
* const result = detectKeyv(new Keyv());
* result.compatible;              // true — all capabilities present
* result.methods.get.exists;      // true
* result.methods.get.methodType;  // "async"
*
* const partial = detectKeyv(new Map());
* partial.compatible;             // false — missing getMany, setMany, hooks, stats, etc.
* partial.methods.get.exists;     // true
* ```
*/
function detectKeyv(obj) {
	if (obj === null || obj === void 0 || typeof obj !== "object") return {
		compatible: false,
		methods: buildMethods(null, keyvMethodNames),
		properties: {
			hooks: false,
			stats: false
		}
	};
	const methods = buildMethods(obj, keyvMethodNames);
	const properties = {
		hooks: isProperty(obj, "hooks"),
		stats: isProperty(obj, "stats")
	};
	return {
		compatible: [...keyvMethodNames, ...keyvPropertyNames].every((k) => {
			if (k === "hooks" || k === "stats") return properties[k];
			return methods[k].exists;
		}),
		methods,
		properties
	};
}
/**
* Detect whether an object implements the Keyv storage adapter interface
* @param obj - The object to check
* @returns A {@link KeyvStorageCapability} where:
* - `compatible` is `true` when the object is a valid storage adapter (`"keyvStorage"`, `"mapLike"`, or `"asyncMap"`)
* - `store` indicates the detected store type: `"keyvStorage"`, `"mapLike"`, `"asyncMap"`, or `"none"`
* - `methods` maps each method name to `{ exists, methodType }`
* @example
* ```typescript
* import { detectKeyvStorage } from 'keyv';
*
* const map = detectKeyvStorage(new Map());
* map.compatible;               // true
* map.store;                    // "mapLike"
* map.methods.get.exists;       // true
* map.methods.get.methodType;   // "sync"
*
* const adapter = detectKeyvStorage(asyncAdapter);
* adapter.compatible;               // true
* adapter.store;                    // "keyvStorage"
* adapter.methods.get.methodType;   // "async"
* ```
*/
function detectKeyvStorage(obj) {
	if (obj === null || obj === void 0 || typeof obj !== "object") return {
		compatible: false,
		store: "none",
		methods: buildMethods(null, keyvStorageMethodNames)
	};
	const methods = buildMethods(obj, keyvStorageMethodNames);
	if (keyvStorageRequiredMethods.every((k) => methods[k].exists && methods[k].methodType === "async")) return {
		compatible: true,
		store: "keyvStorage",
		methods
	};
	if ([
		"get",
		"set",
		"delete",
		"has"
	].every((m) => methods[m].exists && methods[m].methodType === "sync")) return {
		compatible: true,
		store: "mapLike",
		methods
	};
	if ([
		"get",
		"set",
		"delete",
		"clear"
	].every((m) => methods[m].exists)) return {
		compatible: true,
		store: "asyncMap",
		methods
	};
	return {
		compatible: false,
		store: "none",
		methods
	};
}
/**
* Build the capability descriptor for a v6 storage adapter: the structurally detected
* methods plus `expires: true`, declaring that the adapter accepts an absolute `expires`
* timestamp on `set`/`setMany`. First-party adapters expose this from their `capabilities`
* getter so Keyv uses them directly instead of bridging.
* @param adapter - The storage adapter to describe (typically `this`).
* @returns A {@link KeyvStorageCapability} with `expires` set to `true`.
* @example
* ```typescript
* public get capabilities(): KeyvStorageCapability {
*   return keyvStorageCapability(this);
* }
* ```
*/
function keyvStorageCapability(adapter) {
	return {
		...detectKeyvStorage(adapter),
		expires: true
	};
}
/**
* Detect whether an object implements the Keyv compression adapter interface
* @param obj - The object to check
* @returns A {@link KeyvCompressionCapability} where `compatible` is `true` when both `compress` and `decompress` methods are present
* @example
* ```typescript
* import { detectKeyvCompression } from 'keyv';
*
* detectKeyvCompression({ compress: (d) => d, decompress: (d) => d });
* // { compatible: true, methods: { compress: { exists: true, methodType: "sync" }, decompress: { exists: true, methodType: "sync" } } }
* ```
*/
function detectKeyvCompression(obj) {
	if (obj === null || obj === void 0 || typeof obj !== "object") return {
		compatible: false,
		methods: buildMethods(null, keyvCompressionMethodNames)
	};
	const methods = buildMethods(obj, keyvCompressionMethodNames);
	return {
		compatible: keyvCompressionMethodNames.every((k) => methods[k].exists),
		methods
	};
}
/**
* Detect whether an object implements the Keyv serialization adapter interface
* @param obj - The object to check
* @returns A {@link KeyvSerializationCapability} where `compatible` is `true` when both `stringify` and `parse` methods are present
* @example
* ```typescript
* import { detectKeyvSerialization } from 'keyv';
*
* detectKeyvSerialization(JSON);
* // { compatible: true, methods: { stringify: { exists: true, methodType: "sync" }, parse: { exists: true, methodType: "sync" } } }
* ```
*/
function detectKeyvSerialization(obj) {
	if (obj === null || obj === void 0 || typeof obj !== "object") return {
		compatible: false,
		methods: buildMethods(null, keyvSerializationMethodNames)
	};
	const methods = buildMethods(obj, keyvSerializationMethodNames);
	return {
		compatible: keyvSerializationMethodNames.every((k) => methods[k].exists),
		methods
	};
}
/**
* Detect whether an object implements the Keyv encryption adapter interface
* @param obj - The object to check
* @returns A {@link KeyvEncryptionCapability} where `compatible` is `true` when both `encrypt` and `decrypt` methods are present
* @example
* ```typescript
* import { detectKeyvEncryption } from 'keyv';
*
* detectKeyvEncryption({ encrypt: (d) => d, decrypt: (d) => d });
* // { compatible: true, methods: { encrypt: { exists: true, methodType: "sync" }, decrypt: { exists: true, methodType: "sync" } } }
* ```
*/
function detectKeyvEncryption(obj) {
	if (obj === null || obj === void 0 || typeof obj !== "object") return {
		compatible: false,
		methods: buildMethods(null, keyvEncryptionMethodNames)
	};
	const methods = buildMethods(obj, keyvEncryptionMethodNames);
	return {
		compatible: keyvEncryptionMethodNames.every((k) => methods[k].exists),
		methods
	};
}
//#endregion
//#region src/types/keyv.ts
/**
* Events emitted by Keyv for error handling and telemetry.
*/
let KeyvEvents = /* @__PURE__ */ function(KeyvEvents) {
	/** Emitted when an error occurs in a store operation. */
	KeyvEvents["ERROR"] = "error";
	/** Emitted for informational messages. */
	KeyvEvents["INFO"] = "info";
	/** Emitted for warning messages. */
	KeyvEvents["WARN"] = "warn";
	/** Telemetry: cache hit. */
	KeyvEvents["STAT_HIT"] = "stat:hit";
	/** Telemetry: cache miss. */
	KeyvEvents["STAT_MISS"] = "stat:miss";
	/** Telemetry: value set. */
	KeyvEvents["STAT_SET"] = "stat:set";
	/** Telemetry: value deleted. */
	KeyvEvents["STAT_DELETE"] = "stat:delete";
	/** Telemetry: operation error. */
	KeyvEvents["STAT_ERROR"] = "stat:error";
	return KeyvEvents;
}({});
/**
* Hook names for intercepting Keyv operations.
* Register hooks via `keyv.on(KeyvHooks.BEFORE_SET, callback)` to run logic before/after operations.
*/
let KeyvHooks = /* @__PURE__ */ function(KeyvHooks) {
	/** @deprecated Use BEFORE_SET instead */
	KeyvHooks["PRE_SET"] = "preSet";
	/** @deprecated Use AFTER_SET instead */
	KeyvHooks["POST_SET"] = "postSet";
	/** @deprecated Use BEFORE_GET instead */
	KeyvHooks["PRE_GET"] = "preGet";
	/** @deprecated Use AFTER_GET instead */
	KeyvHooks["POST_GET"] = "postGet";
	/** @deprecated Use BEFORE_GET_MANY instead */
	KeyvHooks["PRE_GET_MANY"] = "preGetMany";
	/** @deprecated Use AFTER_GET_MANY instead */
	KeyvHooks["POST_GET_MANY"] = "postGetMany";
	/** @deprecated Use BEFORE_GET_RAW instead */
	KeyvHooks["PRE_GET_RAW"] = "preGetRaw";
	/** @deprecated Use AFTER_GET_RAW instead */
	KeyvHooks["POST_GET_RAW"] = "postGetRaw";
	/** @deprecated Use BEFORE_GET_MANY_RAW instead */
	KeyvHooks["PRE_GET_MANY_RAW"] = "preGetManyRaw";
	/** @deprecated Use AFTER_GET_MANY_RAW instead */
	KeyvHooks["POST_GET_MANY_RAW"] = "postGetManyRaw";
	/** @deprecated Use BEFORE_SET_RAW instead */
	KeyvHooks["PRE_SET_RAW"] = "preSetRaw";
	/** @deprecated Use AFTER_SET_RAW instead */
	KeyvHooks["POST_SET_RAW"] = "postSetRaw";
	/** @deprecated Use BEFORE_SET_MANY_RAW instead */
	KeyvHooks["PRE_SET_MANY_RAW"] = "preSetManyRaw";
	/** @deprecated Use AFTER_SET_MANY_RAW instead */
	KeyvHooks["POST_SET_MANY_RAW"] = "postSetManyRaw";
	/** @deprecated Use BEFORE_SET_MANY instead */
	KeyvHooks["PRE_SET_MANY"] = "preSetMany";
	/** @deprecated Use AFTER_SET_MANY instead */
	KeyvHooks["POST_SET_MANY"] = "postSetMany";
	/** @deprecated Use BEFORE_DELETE instead */
	KeyvHooks["PRE_DELETE"] = "preDelete";
	/** @deprecated Use AFTER_DELETE instead */
	KeyvHooks["POST_DELETE"] = "postDelete";
	/** @deprecated Use BEFORE_DELETE_MANY instead */
	KeyvHooks["PRE_DELETE_MANY"] = "preDeleteMany";
	/** @deprecated Use AFTER_DELETE_MANY instead */
	KeyvHooks["POST_DELETE_MANY"] = "postDeleteMany";
	KeyvHooks["BEFORE_SET"] = "before:set";
	KeyvHooks["AFTER_SET"] = "after:set";
	KeyvHooks["BEFORE_GET"] = "before:get";
	KeyvHooks["AFTER_GET"] = "after:get";
	KeyvHooks["BEFORE_GET_MANY"] = "before:getMany";
	KeyvHooks["AFTER_GET_MANY"] = "after:getMany";
	KeyvHooks["BEFORE_GET_RAW"] = "before:getRaw";
	KeyvHooks["AFTER_GET_RAW"] = "after:getRaw";
	KeyvHooks["BEFORE_GET_MANY_RAW"] = "before:getManyRaw";
	KeyvHooks["AFTER_GET_MANY_RAW"] = "after:getManyRaw";
	KeyvHooks["BEFORE_SET_RAW"] = "before:setRaw";
	KeyvHooks["AFTER_SET_RAW"] = "after:setRaw";
	KeyvHooks["BEFORE_SET_MANY"] = "before:setMany";
	KeyvHooks["AFTER_SET_MANY"] = "after:setMany";
	KeyvHooks["BEFORE_SET_MANY_RAW"] = "before:setManyRaw";
	KeyvHooks["AFTER_SET_MANY_RAW"] = "after:setManyRaw";
	KeyvHooks["BEFORE_DELETE"] = "before:delete";
	KeyvHooks["AFTER_DELETE"] = "after:delete";
	KeyvHooks["BEFORE_DELETE_MANY"] = "before:deleteMany";
	KeyvHooks["AFTER_DELETE_MANY"] = "after:deleteMany";
	KeyvHooks["BEFORE_HAS"] = "before:has";
	KeyvHooks["AFTER_HAS"] = "after:has";
	KeyvHooks["BEFORE_HAS_MANY"] = "before:hasMany";
	KeyvHooks["AFTER_HAS_MANY"] = "after:hasMany";
	KeyvHooks["BEFORE_CLEAR"] = "before:clear";
	KeyvHooks["AFTER_CLEAR"] = "after:clear";
	KeyvHooks["BEFORE_DISCONNECT"] = "before:disconnect";
	KeyvHooks["AFTER_DISCONNECT"] = "after:disconnect";
	return KeyvHooks;
}({});
//#endregion
//#region src/utils.ts
/**
* Check whether a deserialized entry has expired based on its `expires` timestamp.
*/
function isDataExpired(data) {
	return typeof data.expires === "number" && Date.now() > data.expires;
}
/**
* Calculate an absolute expiry timestamp from a TTL value, in whole milliseconds.
* Returns `undefined` when `ttl` is absent, zero, negative, or non-finite
* (meaning "no expiry").
*
* @param ttl - Time-to-live in milliseconds, or `undefined`
* @returns Absolute expiry timestamp (ms since epoch), or `undefined`
*/
function calculateExpires(ttl) {
	if (typeof ttl !== "number" || ttl <= 0 || !Number.isFinite(ttl)) return;
	return toStoreExpires(Date.now() + ttl);
}
/**
* Convert an absolute expiry into the form stores accept: whole milliseconds, rounded up so an
* entry never expires early, or `undefined` (no expiry) when it is not a finite number. Stores
* such as PostgreSQL's `BIGINT` column and Redis's `PXAT` reject a fractional timestamp.
*
* @param expires - Absolute expiry timestamp (ms since epoch), or `undefined`
* @returns The expiry in whole milliseconds, or `undefined` for no expiry
*/
function toStoreExpires(expires) {
	if (typeof expires !== "number" || !Number.isFinite(expires)) return;
	return Math.ceil(expires);
}
/**
* Resolve a TTL value by falling back to a default when none is given,
* then normalising zero, negative, or non-finite values to `undefined` (meaning "no expiry").
*
* @param ttl - Explicit TTL in milliseconds, or `undefined`
* @param defaultTtl - Fallback TTL (typically `Keyv._ttl`), or `undefined`
* @returns The resolved TTL in milliseconds, or `undefined` for no expiry
*/
function resolveTtl(ttl, defaultTtl) {
	const resolved = ttl ?? defaultTtl;
	if (resolved === void 0 || resolved <= 0 || !Number.isFinite(resolved)) return;
	return resolved;
}
/**
* Derive a store-level TTL from an absolute `expires` timestamp.
* Returns `undefined` when `expires` is absent, non-finite, or when the derived
* TTL is zero or negative (i.e. the entry has already expired).
*
* @param expires - Absolute expiry timestamp in milliseconds since epoch, or `undefined`
* @returns The remaining TTL in milliseconds, or `undefined`
*/
function ttlFromExpires(expires) {
	if (typeof expires !== "number" || !Number.isFinite(expires)) return;
	const remaining = expires - Date.now();
	return remaining > 0 ? remaining : void 0;
}
/**
* Calls a store's `has` once to see whether it returns a promise. A method that is not a native
* `async` function can still return one, such as an `async` method compiled to an older target.
* @param store - The store to check
* @returns `true` when `has` returned a promise
*/
function returnsPromise(store) {
	let result;
	try {
		result = store.has("keyv-probe");
	} catch {
		return false;
	}
	if (typeof result?.then !== "function") return false;
	Promise.resolve(result).catch(() => {});
	return true;
}
/**
* Scan parallel `keys` and `data` arrays, nullify any expired entries in
* `data`, and batch-delete the corresponding keys via `keyv.deleteMany()`.
*/
async function deleteExpiredKeys(keys, data, keyv) {
	const expiredKeys = [];
	for (const [index, row] of data.entries()) if (row !== void 0 && row !== null && isDataExpired(row)) {
		expiredKeys.push(keys[index]);
		data[index] = void 0;
	}
	if (expiredKeys.length > 0) await keyv.deleteMany(expiredKeys);
}
/**
* Maps new hook names to their deprecated equivalents so both fire during migration.
*/
const deprecatedHookAliases = /* @__PURE__ */ new Map([
	["before:set", "preSet"],
	["after:set", "postSet"],
	["before:get", "preGet"],
	["after:get", "postGet"],
	["before:getMany", "preGetMany"],
	["after:getMany", "postGetMany"],
	["before:getRaw", "preGetRaw"],
	["after:getRaw", "postGetRaw"],
	["before:getManyRaw", "preGetManyRaw"],
	["after:getManyRaw", "postGetManyRaw"],
	["before:setRaw", "preSetRaw"],
	["after:setRaw", "postSetRaw"],
	["before:setMany", "preSetMany"],
	["after:setMany", "postSetMany"],
	["before:setManyRaw", "preSetManyRaw"],
	["after:setManyRaw", "postSetManyRaw"],
	["before:delete", "preDelete"],
	["after:delete", "postDelete"],
	["before:deleteMany", "preDeleteMany"],
	["after:deleteMany", "postDeleteMany"]
]);
/**
* Build the deprecated-hooks map used by Hookified to warn when old PRE_/POST_ hook names are registered.
*/
function buildDeprecatedHooks() {
	return /* @__PURE__ */ new Map([
		["preSet", "Use KeyvHooks.BEFORE_SET ('before:set') instead"],
		["postSet", "Use KeyvHooks.AFTER_SET ('after:set') instead"],
		["preGet", "Use KeyvHooks.BEFORE_GET ('before:get') instead"],
		["postGet", "Use KeyvHooks.AFTER_GET ('after:get') instead"],
		["preGetMany", "Use KeyvHooks.BEFORE_GET_MANY ('before:getMany') instead"],
		["postGetMany", "Use KeyvHooks.AFTER_GET_MANY ('after:getMany') instead"],
		["preGetRaw", "Use KeyvHooks.BEFORE_GET_RAW ('before:getRaw') instead"],
		["postGetRaw", "Use KeyvHooks.AFTER_GET_RAW ('after:getRaw') instead"],
		["preGetManyRaw", "Use KeyvHooks.BEFORE_GET_MANY_RAW ('before:getManyRaw') instead"],
		["postGetManyRaw", "Use KeyvHooks.AFTER_GET_MANY_RAW ('after:getManyRaw') instead"],
		["preSetRaw", "Use KeyvHooks.BEFORE_SET_RAW ('before:setRaw') instead"],
		["postSetRaw", "Use KeyvHooks.AFTER_SET_RAW ('after:setRaw') instead"],
		["preSetMany", "Use KeyvHooks.BEFORE_SET_MANY ('before:setMany') instead"],
		["postSetMany", "Use KeyvHooks.AFTER_SET_MANY ('after:setMany') instead"],
		["preSetManyRaw", "Use KeyvHooks.BEFORE_SET_MANY_RAW ('before:setManyRaw') instead"],
		["postSetManyRaw", "Use KeyvHooks.AFTER_SET_MANY_RAW ('after:setManyRaw') instead"],
		["preDelete", "Use KeyvHooks.BEFORE_DELETE ('before:delete') instead"],
		["postDelete", "Use KeyvHooks.AFTER_DELETE ('after:delete') instead"],
		["preDeleteMany", "Use KeyvHooks.BEFORE_DELETE_MANY ('before:deleteMany') instead"],
		["postDeleteMany", "Use KeyvHooks.AFTER_DELETE_MANY ('after:deleteMany') instead"]
	]);
}
//#endregion
//#region src/adapters/bridge.ts
/**
* A bridge storage adapter for Keyv that wraps any promise-based store.
*
* This class provides a unified interface for using various async stores
* with Keyv, handling namespace prefixing, TTL-based expiration, and batch operations.
* If the underlying store implements optional methods (has, hasMany, getMany, etc.),
* the bridge will delegate to them. Otherwise, it falls back to using primitives.
*
* @example
* ```typescript
* // Using with a promise-based store
* const bridge = new KeyvBridgeAdapter(myAsyncStore, { namespace: 'cache' });
*
* // Using with Keyv
* const keyv = new Keyv({ store: new KeyvBridgeAdapter(myAsyncStore) });
* ```
*/
var KeyvBridgeAdapter = class extends Hookified {
	_store;
	_namespace;
	_namespaceSeparator = "::";
	_capabilities;
	/**
	* Whether the wrapped store manages its own namespace (exposes a `namespace` property).
	* When true the bridge propagates its namespace to the store and does not prefix keys,
	* so the store's native, namespace-scoped operations (notably `clear()`) are used directly.
	*/
	_storeHandlesNamespace;
	/**
	* Creates a new KeyvBridgeAdapter instance.
	* @param store - The underlying promise-based store to bridge
	* @param options - Configuration options for the adapter
	*/
	constructor(store, options) {
		super({ throwOnHookError: false });
		this._store = store;
		this._capabilities = detectKeyvStorage(store);
		this._storeHandlesNamespace = hasKeyvStorageMethods(this._capabilities) && "namespace" in store;
		if (options?.namespaceSeparator !== void 0) this._namespaceSeparator = options.namespaceSeparator;
		if (options?.namespace) this._namespace = options.namespace;
		if (this._storeHandlesNamespace) if (options?.namespace === void 0) this._namespace = this._store.namespace;
		else this._store.namespace = this._namespace;
		if (typeof store.on === "function") store.on("error", (error) => this.emit("error", error));
	}
	/**
	* Gets the underlying store instance.
	*/
	get store() {
		return this._store;
	}
	/**
	* Sets the underlying store instance.
	*/
	set store(store) {
		this._store = store;
	}
	/**
	* Gets the capabilities of the underlying store, with `expires: true` to declare that
	* the bridge accepts an absolute `expires` timestamp (which it converts to a ttl for the
	* wrapped legacy store).
	*/
	get capabilities() {
		return {
			...this._capabilities,
			expires: true
		};
	}
	/**
	* Gets the separator between the namespace and the key. Defaults to `::`.
	*/
	get namespaceSeparator() {
		return this._namespaceSeparator;
	}
	/**
	* Sets the separator between the namespace and the key.
	*/
	set namespaceSeparator(separator) {
		this._namespaceSeparator = separator;
	}
	/**
	* Gets the current namespace.
	*/
	get namespace() {
		return this._namespace;
	}
	/**
	* Sets the namespace. When the wrapped store manages its own namespace, the value is
	* propagated to it so its native scoped operations stay in sync.
	*/
	set namespace(namespace) {
		this._namespace = namespace;
		if (this._storeHandlesNamespace) this._store.namespace = namespace;
	}
	/**
	* Creates a prefixed key by combining the namespace and key with the separator.
	* @param key - The base key
	* @param namespace - Optional namespace to prefix the key with
	* @returns The prefixed key if namespace is provided, otherwise the original key
	*/
	getKeyPrefix(key, namespace) {
		if (this._storeHandlesNamespace) return key;
		if (namespace) return `${namespace}${this._namespaceSeparator}${key}`;
		return key;
	}
	/**
	* Parses a prefixed key to extract the namespace and original key.
	* @param key - The prefixed key to parse
	* @returns An object containing the namespace (if present) and the original key
	*/
	getKeyPrefixData(key) {
		if (this._namespace && key.startsWith(`${this._namespace}${this._namespaceSeparator}`)) return {
			namespace: this._namespace,
			key: key.slice(this._namespace.length + this._namespaceSeparator.length)
		};
		return { key };
	}
	/**
	* Retrieves a value from the store by key.
	* Automatically handles namespace prefixing and TTL expiration.
	* @param key - The key to retrieve
	* @returns The stored data, or undefined if not found or expired
	*/
	async get(key) {
		const keyPrefix = this.getKeyPrefix(key, this._namespace);
		const data = await this._store.get(keyPrefix);
		if (data === void 0 || data === null) return;
		if (isDataExpired(data)) {
			await this._store.delete(keyPrefix);
			return;
		}
		return data;
	}
	/**
	* Retrieves multiple values from the store by their keys.
	* Delegates to the store's native getMany if available.
	* @param keys - Array of keys to retrieve
	* @returns Array of stored data in the same order as the input keys
	*/
	async getMany(keys) {
		if (this._capabilities.methods.getMany.exists) {
			const prefixedKeys = keys.map((key) => this.getKeyPrefix(key, this._namespace));
			/* v8 ignore next -- @preserve */
			const results = await this._store.getMany?.(prefixedKeys) ?? [];
			const values = [];
			for (const [index, data] of results.entries()) {
				if (data === void 0 || data === null) {
					values.push(void 0);
					continue;
				}
				if (isDataExpired(data)) {
					await this._store.delete(prefixedKeys[index]);
					values.push(void 0);
					continue;
				}
				values.push(data);
			}
			return values;
		}
		const values = [];
		for (const key of keys) {
			const data = await this.get(key);
			values.push(data);
		}
		return values;
	}
	/**
	* Stores a value in the store with an optional absolute expiry.
	* The wrapped store's `set(key, value, ttl?)` expects a relative duration, so the absolute
	* `expires` is converted to a remaining ttl (`undefined` when already expired or absent).
	* The value is passed through unchanged — the bridge does not wrap it in an envelope — so
	* expiry is enforced by the wrapped legacy store's own ttl handling. (The read-side
	* {@link isDataExpired} check only fires when a caller stores a raw `{ value, expires }`
	* object directly; when Keyv core drives the bridge the value arrives already encoded.)
	* @param key - The key to store the value under
	* @param value - The value to store
	* @param expires - Optional absolute expiry as Unix ms since epoch
	* @returns Always returns true indicating success
	*/
	async set(key, value, expires) {
		const keyPrefix = this.getKeyPrefix(key, this._namespace);
		if (typeof expires === "number" && expires <= Date.now()) {
			await this._store.delete(keyPrefix);
			return true;
		}
		const result = await this._store.set(keyPrefix, value, ttlFromExpires(expires));
		if (typeof result === "boolean") return result;
		return true;
	}
	/**
	* Stores multiple entries in the store at once.
	* Delegates to the store's native setMany if available, otherwise loops over set.
	* @param entries - Array of entries containing key, value, and optional absolute `expires`
	* @returns One boolean per entry, `false` where the store reported that the write failed
	*/
	async setMany(entries) {
		if (this._capabilities.methods.setMany.exists) {
			const now = Date.now();
			const isExpired = (entry) => typeof entry.expires === "number" && entry.expires <= now;
			const live = entries.filter((entry) => !isExpired(entry));
			let liveResults;
			if (live.length > 0) liveResults = await this._store.setMany?.(live.map((entry) => ({
				key: this.getKeyPrefix(entry.key, this._namespace),
				value: entry.value,
				ttl: ttlFromExpires(entry.expires)
			})));
			for (const entry of entries) if (isExpired(entry)) await this._store.delete(this.getKeyPrefix(entry.key, this._namespace));
			let liveIndex = 0;
			return entries.map((entry) => {
				if (isExpired(entry)) return true;
				const result = Array.isArray(liveResults) ? liveResults[liveIndex] : liveResults;
				liveIndex++;
				return result !== false;
			});
		}
		const results = [];
		for (const entry of entries) results.push(await this.set(entry.key, entry.value, entry.expires));
		return results;
	}
	/**
	* Checks if a key exists in the store and is not expired.
	* Delegates to the store's native has if available.
	* @param key - The key to check
	* @returns True if the key exists and is not expired, false otherwise
	*/
	async has(key) {
		if (this._capabilities.methods.has.exists) {
			const keyPrefix = this.getKeyPrefix(key, this._namespace);
			/* v8 ignore next -- @preserve */
			return this._store.has?.(keyPrefix) ?? false;
		}
		return await this.get(key) !== void 0;
	}
	/**
	* Checks if multiple keys exist in the store and are not expired.
	* Delegates to the store's native hasMany if available.
	* @param keys - Array of keys to check
	* @returns Array of booleans indicating existence for each key
	*/
	async hasMany(keys) {
		if (this._capabilities.methods.hasMany.exists) {
			const prefixedKeys = keys.map((key) => this.getKeyPrefix(key, this._namespace));
			/* v8 ignore next -- @preserve */
			return this._store.hasMany?.(prefixedKeys) ?? [];
		}
		const results = [];
		for (const key of keys) results.push(await this.has(key));
		return results;
	}
	/**
	* Deletes a value from the store by key.
	* @param key - The key to delete
	* @returns True if the key was deleted, false otherwise
	*/
	async delete(key) {
		const keyPrefix = this.getKeyPrefix(key, this._namespace);
		return this._store.delete(keyPrefix);
	}
	/**
	* Deletes multiple keys from the store at once.
	* Delegates to the store's native deleteMany if available.
	* @param keys - Array of keys to delete
	* @returns Array of booleans indicating success for each key
	*/
	async deleteMany(keys) {
		if (this._capabilities.methods.deleteMany.exists) {
			const prefixedKeys = keys.map((key) => this.getKeyPrefix(key, this._namespace));
			/* v8 ignore next -- @preserve */
			const result = await this._store.deleteMany?.(prefixedKeys) ?? [];
			return Array.isArray(result) ? result : keys.map(() => result);
		}
		const results = [];
		for (const key of keys) try {
			const keyPrefix = this.getKeyPrefix(key, this._namespace);
			const result = await this._store.delete(keyPrefix);
			results.push(result);
		} catch (error) {
			this.emit("error", error);
			results.push(false);
		}
		return results;
	}
	/**
	* Clears entries from the store. With no namespace, the whole store is cleared. With a
	* namespace, only that namespace's entries are removed: a store that manages its own
	* namespace clears them itself, and otherwise the bridge finds them with the store's
	* `iterator()`. A store with neither can't tell namespaces apart, so this throws rather
	* than call the store's `clear()` and delete every namespace's entries.
	* @throws {Error} If a namespace is set and the store neither manages it nor has `iterator()`.
	*/
	async clear() {
		if (!this._namespace || this._storeHandlesNamespace) {
			await this._store.clear();
			return;
		}
		if (!this._capabilities.methods.iterator.exists) throw new Error(`Can't clear namespace "${this._namespace}": the store has no iterator() to find that namespace's keys, and its clear() would delete every namespace. Give the store an iterator(), or clear it without a namespace.`);
		const prefix = `${this._namespace}${this._namespaceSeparator}`;
		const keysToDelete = [];
		/* v8 ignore next -- @preserve */
		for await (const entry of this._store.iterator?.(this._namespace) ?? []) {
			const key = Array.isArray(entry) ? entry[0] : entry;
			if (typeof key === "string" && key.startsWith(prefix)) keysToDelete.push(key);
		}
		for (const key of keysToDelete) await this._store.delete(key);
	}
	/**
	* Creates an async iterator for iterating over store entries.
	* If the underlying store does not support iteration, returns an empty generator.
	* @returns An async generator yielding [key, value] pairs
	*/
	async *iterator() {
		if (!this._capabilities.methods.iterator.exists) return;
		const namespace = this._namespace;
		const prefix = namespace && !this._storeHandlesNamespace ? `${namespace}${this._namespaceSeparator}` : void 0;
		/* v8 ignore next -- @preserve */
		for await (const entry of this._store.iterator?.(this._namespace) ?? []) {
			const [key, data] = Array.isArray(entry) ? entry : [entry];
			if (prefix && typeof key === "string" && !key.startsWith(prefix)) continue;
			if (data && isDataExpired(data)) {
				await this._store.delete(key);
				continue;
			}
			yield [prefix && typeof key === "string" ? key.slice(prefix.length) : key, data];
		}
	}
	/**
	* Disconnects from the underlying store.
	* No-op if the store does not support disconnect.
	*/
	async disconnect() {
		if (this._capabilities.methods.disconnect.exists) await this._store.disconnect?.();
	}
};
//#endregion
//#region src/json-serializer.ts
function getGlobalBuffer() {
	return globalThis.Buffer;
}
function bytesToBase64(bytes) {
	const buffer = getGlobalBuffer();
	if (buffer) return buffer.from(bytes).toString("base64");
	let binary = "";
	for (const byte of bytes) binary += String.fromCharCode(byte);
	return btoa(binary);
}
function base64ToBytes(value) {
	const buffer = getGlobalBuffer();
	if (buffer) return buffer.from(value, "base64");
	const binary = atob(value);
	const bytes = new Uint8Array(binary.length);
	for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
	return bytes;
}
function isBinaryValue(value) {
	if (getGlobalBuffer()?.isBuffer(value)) return true;
	return value instanceof Uint8Array;
}
/**
* Pre-process a value tree, converting Buffer and BigInt to tagged strings
* before JSON.stringify can call toJSON() on them.
*/
function prepare(value) {
	if (value === null || value === void 0) return value;
	if (isBinaryValue(value)) return `:base64:${bytesToBase64(value)}`;
	if (typeof value === "bigint") return `:bigint:${value.toString()}`;
	if (typeof value === "string") return value.startsWith(":") ? `:${value}` : value;
	if (Array.isArray(value)) return value.map((item) => prepare(item));
	if (typeof value === "object") {
		if (typeof value.toJSON === "function") return prepare(value.toJSON());
		const result = Object.create(null);
		for (const key of Object.keys(value)) if (value[key] !== void 0) result[key] = prepare(value[key]);
		return result;
	}
	return value;
}
var KeyvJsonSerializer = class {
	stringify(object) {
		return JSON.stringify(prepare(object));
	}
	parse(data) {
		return JSON.parse(data, (_, value) => {
			if (typeof value === "string") {
				if (value.startsWith(":bigint:")) return BigInt(value.slice(8));
				if (value.startsWith(":base64:")) return base64ToBytes(value.slice(8));
				return value.startsWith(":") ? value.slice(1) : value;
			}
			return value;
		});
	}
};
const jsonSerializer = new KeyvJsonSerializer();
//#endregion
//#region src/sanitize.ts
const categoryPatterns = {
	sql: [
		/;/g,
		/--/g,
		/\/\*/g
	],
	mongo: [/^\$/g, /\{\s*\$/g],
	escape: [
		/\0/g,
		/\r/g,
		/\n/g
	],
	path: [/\.\.\//g, /\.\.\\/g]
};
/**
* Compile an array of RegExp patterns from the enabled categories.
* Returns `undefined` when nothing is enabled.
*/
function buildPatterns(options) {
	const patterns = [];
	for (const [category, regexes] of Object.entries(categoryPatterns)) if (options[category] !== false) patterns.push(...regexes);
	return patterns.length > 0 ? patterns : void 0;
}
/**
* Run all patterns against a string, stripping matched sequences. Stripping one match can form
* another, as `..././` becomes `../`, so when a match remains, {@link stripRemaining} finishes
* the job.
*/
function applyPatterns(value, patterns, options) {
	for (const pattern of patterns) {
		pattern.lastIndex = 0;
		value = value.replace(pattern, "");
	}
	if (patterns.some((pattern) => value.search(pattern) !== -1)) return stripRemaining(value, options);
	return value;
}
/**
* Strip the matches a single pass of {@link applyPatterns} left behind. It reads the string once,
* left to right, and drops a pattern as soon as its last character arrives, so a pattern formed by
* an earlier removal is caught without scanning the string again. The result contains no match.
*/
function stripRemaining(value, options) {
	const kept = [];
	for (const char of value) {
		const length = matchLength(kept, char, options);
		if (length === 0) kept.push(char);
		else kept.length -= length - 1;
	}
	return kept.join("");
}
const whitespace = /\s/;
/**
* The length of the pattern that `char` completes at the end of `kept`, counting `char` itself, or
* 0 when it completes none. `;`, `\0`, `\r` and `\n` need no check: the single pass before this
* removes every one of them, and removing characters never adds one back.
*/
function matchLength(kept, char, options) {
	const last = kept.length - 1;
	if (options.sql && (char === "-" && kept[last] === "-" || char === "*" && kept[last] === "/")) return 2;
	if (options.mongo && char === "$") {
		if (kept.length === 0) return 1;
		let start = kept.length;
		while (start > 0 && whitespace.test(kept[start - 1])) start--;
		if (kept[start - 1] === "{") return kept.length - start + 2;
	}
	if (options.path && (char === "/" || char === "\\") && kept[last] === "." && kept[last - 1] === ".") return 3;
	return 0;
}
const allOn = {
	escape: true,
	mongo: true,
	path: true,
	sql: true
};
const allOff = {
	escape: false,
	mongo: false,
	path: false,
	sql: false
};
/**
* Encapsulates key and namespace sanitization with an LRU result cache.
*/
var KeyvSanitize = class {
	_keys = { ...allOff };
	_namespace = { ...allOff };
	_keyPatterns;
	_namespacePatterns;
	_enabled = false;
	_cacheKeys = /* @__PURE__ */ new Map();
	_cacheNamespaces = /* @__PURE__ */ new Map();
	_cacheMax = 1e4;
	constructor(options) {
		if (options !== void 0) this.updateOptions(options);
	}
	/**
	* The key sanitization pattern configuration.
	*/
	get keys() {
		return this._keys;
	}
	/**
	* Whether any sanitization pattern (keys or namespace) is enabled.
	*/
	get enabled() {
		return this._enabled;
	}
	/**
	* The namespace sanitization pattern configuration.
	*/
	get namespace() {
		return this._namespace;
	}
	/**
	* Update the sanitization configuration. Recompiles patterns and clears the cache.
	*/
	updateOptions(options) {
		this._keys = this.resolvePatterns(options.keys);
		this._namespace = this.resolvePatterns(options.namespace);
		this._enabled = Object.values(this._keys).some(Boolean) || Object.values(this._namespace).some(Boolean);
		this._keyPatterns = buildPatterns(this._keys);
		this._namespacePatterns = buildPatterns(this._namespace);
		this._cacheKeys.clear();
		this._cacheNamespaces.clear();
	}
	/**
	* Sanitize a single key. Uses an LRU cache for repeated lookups.
	*/
	cleanKey(key) {
		if (!this._keyPatterns) return key;
		const cached = this._cacheKeys.get(key);
		if (cached !== void 0) {
			this._cacheKeys.delete(key);
			this._cacheKeys.set(key, cached);
			return cached;
		}
		const result = applyPatterns(key, this._keyPatterns, this._keys);
		this._cacheKeys.set(key, result);
		if (this._cacheKeys.size > this._cacheMax) {
			const first = this._cacheKeys.keys().next().value;
			if (first !== void 0) this._cacheKeys.delete(first);
		}
		return result;
	}
	/**
	* Sanitize an array of keys.
	*/
	cleanKeys(keys) {
		if (!this._keyPatterns) return keys;
		return keys.map((k) => this.cleanKey(k));
	}
	/**
	* Sanitize a namespace string. Uses an LRU cache for repeated lookups.
	*/
	cleanNamespace(ns) {
		if (!this._namespacePatterns) return ns;
		const cached = this._cacheNamespaces.get(ns);
		if (cached !== void 0) {
			this._cacheNamespaces.delete(ns);
			this._cacheNamespaces.set(ns, cached);
			return cached;
		}
		const result = applyPatterns(ns, this._namespacePatterns, this._namespace);
		this._cacheNamespaces.set(ns, result);
		if (this._cacheNamespaces.size > this._cacheMax) {
			const first = this._cacheNamespaces.keys().next().value;
			if (first !== void 0) this._cacheNamespaces.delete(first);
		}
		return result;
	}
	/**
	* Clear the LRU caches.
	*/
	clearCache() {
		this._cacheKeys.clear();
		this._cacheNamespaces.clear();
	}
	resolvePatterns(options) {
		if (options === false || options === void 0) return { ...allOff };
		if (options === true) return { ...allOn };
		return {
			sql: options.sql !== false,
			mongo: options.mongo !== false,
			escape: options.escape !== false,
			path: options.path !== false
		};
	}
};
//#endregion
//#region src/stats.ts
var KeyvStats = class {
	_hits = 0;
	_misses = 0;
	_sets = 0;
	_deletes = 0;
	_errors = 0;
	_maxEntries = 1e3;
	_enabled = false;
	hitKeysMap = /* @__PURE__ */ new Map();
	missKeysMap = /* @__PURE__ */ new Map();
	setKeysMap = /* @__PURE__ */ new Map();
	deleteKeysMap = /* @__PURE__ */ new Map();
	errorKeysMap = /* @__PURE__ */ new Map();
	_emitter;
	_listeners = /* @__PURE__ */ new Map();
	constructor(options) {
		if (options?.maxEntries !== void 0) this._maxEntries = options.maxEntries;
		if (options?.enabled !== void 0) this._enabled = options.enabled;
		this.reset();
		if (options?.emitter) if (this._enabled === true) this.subscribe(options.emitter);
		else this._emitter = options.emitter;
	}
	/**
	* Total number of cache hits.
	*/
	get hits() {
		return this._hits;
	}
	/**
	* Total number of cache misses.
	*/
	get misses() {
		return this._misses;
	}
	/**
	* Total number of cache sets.
	*/
	get sets() {
		return this._sets;
	}
	/**
	* Total number of cache deletes.
	*/
	get deletes() {
		return this._deletes;
	}
	/**
	* Total number of cache errors.
	*/
	get errors() {
		return this._errors;
	}
	/**
	* LRU-bounded map of key to hit count.
	*/
	get hitKeys() {
		return this.hitKeysMap;
	}
	/**
	* LRU-bounded map of key to miss count.
	*/
	get missKeys() {
		return this.missKeysMap;
	}
	/**
	* LRU-bounded map of key to set count.
	*/
	get setKeys() {
		return this.setKeysMap;
	}
	/**
	* LRU-bounded map of key to delete count.
	*/
	get deleteKeys() {
		return this.deleteKeysMap;
	}
	/**
	* LRU-bounded map of key to error count.
	*/
	get errorKeys() {
		return this.errorKeysMap;
	}
	/**
	* Maximum number of entries per event-type LRU map.
	* @default 1000
	*/
	get maxEntries() {
		return this._maxEntries;
	}
	/**
	* Set the maximum number of entries per event-type LRU map.
	* @param {number} value the new maximum entries
	*/
	/* v8 ignore next 3 -- @preserve */
	set maxEntries(value) {
		this._maxEntries = value;
	}
	/**
	* Whether stats tracking is enabled.
	* @default false
	*/
	get enabled() {
		return this._enabled;
	}
	/**
	* Enable or disable stats tracking. If false it will unsubscribe from the events
	* @param {boolean} value true to enable, false to disable
	*/
	set enabled(value) {
		this._enabled = value;
		if (this._enabled === false && this._listeners.size !== 0) {
			const emitter = this._emitter;
			this.unsubscribe();
			this._emitter = emitter;
		} else if (this._enabled === true && this._emitter && this._listeners.size === 0) this.subscribe(this._emitter);
	}
	/**
	* Build a composite key from a telemetry event.
	* Format: "namespace::key" if namespace is present, otherwise just "key".
	*/
	buildKeyEventName(event) {
		if (event.namespace && event.key) return `${event.namespace}::${event.key}`;
		return event.key ?? "";
	}
	/**
	* Increment the count for a key in an LRU-bounded map.
	* Deletes and re-inserts to maintain LRU order, evicts the oldest entry when full.
	*/
	incrementKeys(map, compositeKey) {
		if (this.maxEntries <= 0 || !compositeKey) return;
		const current = map.get(compositeKey) ?? 0;
		map.delete(compositeKey);
		map.set(compositeKey, current + 1);
		if (map.size > this.maxEntries) {
			const first = map.keys().next().value;
			if (first !== void 0) map.delete(first);
		}
	}
	/**
	* Subscribe to telemetry events from an emitter (e.g. a Keyv instance).
	* Automatically increments the corresponding stat counters and LRU key maps on each event.
	* @param {IEventEmitter} emitter the event emitter to subscribe to
	*/
	subscribe(emitter) {
		this.unsubscribe();
		this._emitter = emitter;
		const hitListener = (event) => {
			this._hits++;
			this.incrementKeys(this.hitKeysMap, this.buildKeyEventName(event));
		};
		const missListener = (event) => {
			this._misses++;
			this.incrementKeys(this.missKeysMap, this.buildKeyEventName(event));
		};
		const setListener = (event) => {
			this._sets++;
			this.incrementKeys(this.setKeysMap, this.buildKeyEventName(event));
		};
		const deleteListener = (event) => {
			this._deletes++;
			this.incrementKeys(this.deleteKeysMap, this.buildKeyEventName(event));
		};
		const errorListener = (event) => {
			this._errors++;
			this.incrementKeys(this.errorKeysMap, this.buildKeyEventName(event));
		};
		this._listeners.set("stat:hit", hitListener);
		this._listeners.set("stat:miss", missListener);
		this._listeners.set("stat:set", setListener);
		this._listeners.set("stat:delete", deleteListener);
		this._listeners.set("stat:error", errorListener);
		emitter.on("stat:hit", hitListener);
		emitter.on("stat:miss", missListener);
		emitter.on("stat:set", setListener);
		emitter.on("stat:delete", deleteListener);
		emitter.on("stat:error", errorListener);
	}
	/**
	* Unsubscribe from the currently subscribed emitter, removing all telemetry event listeners.
	*/
	unsubscribe() {
		if (!this._emitter) return;
		for (const [eventName, listener] of this._listeners) this._emitter.off(eventName, listener);
		this._listeners.clear();
		this._emitter = void 0;
	}
	/**
	* Reset all counters and LRU key maps to their initial state.
	*/
	reset() {
		this._hits = 0;
		this._misses = 0;
		this._sets = 0;
		this._deletes = 0;
		this._errors = 0;
		this.hitKeysMap.clear();
		this.missKeysMap.clear();
		this.setKeysMap.clear();
		this.deleteKeysMap.clear();
		this.errorKeysMap.clear();
	}
};
//#endregion
//#region src/keyv.ts
/**
* The namespace Keyv uses when sanitizing a namespace leaves nothing. An empty namespace would
* turn namespacing off, mixing the instance's keys with unrelated ones and letting `clear()`
* remove them.
*/
const emptiedNamespace = "keyv-sanitized";
var Keyv = class Keyv extends Hookified {
	/**
	* Keyv Constructor
	* @param {KeyvStorageAdapter | KeyvOptions} store
	* @param {Omit<KeyvOptions, 'store'>} [options] if you provide the store you can then provide the Keyv Options
	*/
	constructor(store, options) {
		const mergedOptions = Keyv.resolveOptions(store, options);
		super({
			throwOnHookError: false,
			throwOnEmptyListeners: true
		});
		this.deprecatedHooks = buildDeprecatedHooks();
		this._compression = mergedOptions.compression;
		this._encryption = mergedOptions.encryption;
		this.initSerialization(mergedOptions);
		this.initSanitize(mergedOptions);
		this.initNamespace(mergedOptions.namespace);
		this.initStats(mergedOptions);
		if (mergedOptions.store) this.setStore(mergedOptions.store);
		this.setTtl(mergedOptions.ttl);
		this._checkExpired = mergedOptions.checkExpired ?? true;
	}
	/**
	* Stats manager for tracking cache operation metrics (hits, misses, sets, deletes, errors).
	* @default this is disabled.
	*/
	_stats;
	/**
	* Default time to live in milliseconds. Can be overridden per-key via {@link set}.
	*/
	_ttl;
	/**
	* Key prefix namespace used to isolate keys across different Keyv instances sharing the same store.
	*/
	_namespace;
	/**
	* The underlying storage adapter. Defaults to an in-memory {@link Map}.
	*/
	_store = new KeyvMemoryAdapter(/* @__PURE__ */ new Map());
	/**
	* Pluggable serialization adapter with `stringify` and `parse` methods.
	* When `undefined`, the built-in {@link KeyvJsonSerializer} is used.
	*/
	_serialization;
	/**
	* Pluggable compression adapter with `compress` and `decompress` methods.
	*/
	_compression;
	/**
	* Pluggable encryption adapter with `encrypt` and `decrypt` methods.
	*/
	_encryption;
	/**
	* Sanitization handler for keys and namespaces. By default it is disabled.
	*/
	_sanitize;
	/**
	* When true, Keyv checks expiry at its layer on get/getMany/has/hasMany.
	* Defaults to true so reads stay millisecond-precise even on adapters whose
	* native expiry is coarse or lazily swept (e.g. Memcached, DynamoDB).
	*/
	_checkExpired = true;
	/**
	* Get the current storage adapter.
	* @returns {KeyvStorageAdapter} The current storage adapter.
	*/
	get store() {
		return this._store;
	}
	/**
	* Set the storage adapter.
	* @param {KeyvStorageAdapter | Map<any, any> | any} store The storage adapter to set.
	*/
	set store(store) {
		this.setStore(store);
	}
	/**
	* Get the current compression adapter.
	* @returns {KeyvCompressionAdapter | undefined} The current compression adapter.
	*/
	get compression() {
		return this._compression;
	}
	/**
	* Set the compression adapter.
	* @param {KeyvCompressionAdapter | undefined} compress The compression adapter to set.
	*/
	set compression(compress) {
		this._compression = compress;
	}
	/**
	* Get the current encryption adapter.
	* @returns {KeyvEncryptionAdapter | undefined} The current encryption adapter.
	*/
	get encryption() {
		return this._encryption;
	}
	/**
	* Set the encryption adapter.
	* @param {KeyvEncryptionAdapter | undefined} encryption The encryption adapter to set.
	*/
	set encryption(encryption) {
		this._encryption = encryption;
	}
	/**
	* Get the current namespace. When Keyv has no namespace of its own, this is the namespace
	* configured on the storage adapter.
	* @returns {string | undefined} The current namespace.
	*/
	get namespace() {
		return this._namespace ?? this._store.namespace;
	}
	/**
	* Set the current namespace.
	* @param {string | undefined} namespace The namespace to set.
	*/
	set namespace(namespace) {
		this._namespace = namespace && this._sanitize.enabled ? this.cleanNamespace(namespace) : namespace;
		this._store.namespace = this._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.setTtl(ttl);
	}
	/**
	* Get the current serialization adapter. If `undefined`, serialization is not enabled.
	* @returns {KeyvSerializationAdapter | undefined} The current serialization adapter.
	*/
	get serialization() {
		return this._serialization;
	}
	/**
	* Set the current serialization adapter. Pass a `KeyvSerializationAdapter` to enable
	* custom serialization, or `undefined` to disable serialization entirely.
	* @param {KeyvSerializationAdapter | undefined} serialization The serialization adapter to set.
	*/
	set serialization(serialization) {
		this._serialization = serialization === false ? void 0 : serialization;
	}
	/**
	* Get the current sanitize adapter. Sanitization is disabled by default. To
	* enable it `sanitize.keys` or `sanitize.namespace` to true or set KeyvSanitizePatterns
	* to each.
	* @returns {KeyvSanitizeAdapter} The current sanitize adapter.
	*/
	get sanitize() {
		return this._sanitize;
	}
	/**
	* Set the sanitize adapter directly and will run sanitization on namespace, including a
	* namespace kept from the storage adapter, and apply the result to the adapter.
	* @param {KeyvSanitizeAdapter} value The sanitize adapter to use.
	*/
	set sanitize(value) {
		this._sanitize = value;
		if (this._namespace && this._sanitize.enabled) {
			this._namespace = this.cleanNamespace(this._namespace);
			this._store.namespace = this._namespace;
		}
		this.sanitizeStoreNamespace();
	}
	/**
	* Get the stats. This is just for this instance
	* @returns {KeyvStats} The current stats.
	*/
	get stats() {
		return this._stats;
	}
	/**
	* Set the stats. When setting a new instance it will unsubscribe the old listeners
	* and subscribe the new instance.
	* @param {KeyvStats} stats The stats instance to set.
	*/
	set stats(stats) {
		this._stats.unsubscribe();
		this._stats = stats;
		this._stats.subscribe(this);
	}
	/**
	* Get whether Keyv checks expiry at its own layer on get/getMany/has/hasMany.
	* When true (default), expired entries are filtered (and deleted) by Keyv regardless of
	* the adapter. Set to false to trust the storage adapter to handle expiry on its own.
	* @returns {boolean} `true` if Keyv checks expiry at its layer.
	*/
	get checkExpired() {
		return this._checkExpired;
	}
	/**
	* Resolves a store to a fully-compliant KeyvStorageAdapter:
	* 1. If the store declares the v6 `capabilities.expires` contract, use it directly (this takes
	*    precedence over structural detection, so a full adapter whose async methods aren't written
	*    with the `async` keyword is not mis-bridged).
	* 2. If the store implements the full async storage interface (but doesn't declare `expires`),
	*    treat it as a legacy relative-`ttl` adapter and wrap it in KeyvBridgeAdapter.
	* 3. If the store is map-like (get/set/delete/has that aren't native `async` functions), wrap it
	*    in KeyvMemoryAdapter. A store other than a Map is first asked once, through `has`, whether
	*    it returns promises; if it does, wrap it in KeyvBridgeAdapter instead.
	* 4. If the store has async get/set/delete/clear, wrap it in KeyvBridgeAdapter.
	* 5. Otherwise, emit an error and fall back to a default in-memory KeyvMemoryAdapter.
	*
	* NOTE: this is used for internal but provided public for custom adapter testing
	* @param {unknown} store The store to resolve.
	* @returns {KeyvStorageAdapter} A fully-compliant storage adapter.
	*/
	resolveStore(store) {
		if (store?.capabilities?.expires === true) return store;
		const cap = detectKeyvStorage(store);
		if (cap.store === "keyvStorage") return new KeyvBridgeAdapter(store);
		if (cap.store === "mapLike") {
			if (!(store instanceof Map) && returnsPromise(store)) return new KeyvBridgeAdapter(store);
			return new KeyvMemoryAdapter(store);
		}
		if (cap.store === "asyncMap") return new KeyvBridgeAdapter(store);
		this.emit("error", /* @__PURE__ */ new Error("Could not use the provided storage adapter, falling back to KeyvMemoryAdapter with Map"));
		return new KeyvMemoryAdapter(/* @__PURE__ */ new Map());
	}
	/**
	* Sets the storage adapter by resolving it via {@link resolveStore}, then wires up
	* error forwarding and namespace propagation. Keyv's namespace, when set, is applied to the
	* adapter. Without one, the adapter keeps the namespace it was configured with.
	* @param {KeyvStorageAdapter | Map<any, any> | any} store The storage adapter to set.
	*/
	setStore(store) {
		this._store = this.resolveStore(store);
		if (typeof this._store.on === "function") this._store.on("error", (error) => this.emit("error", error));
		if (this._namespace !== void 0) this._store.namespace = this._namespace;
		else this.sanitizeStoreNamespace();
	}
	/**
	* Sets the TTL, treating zero and negative values as undefined (no TTL).
	* @param {number | undefined} ttl The TTL to set in milliseconds.
	*/
	setTtl(ttl) {
		if (typeof ttl === "number" && ttl <= 0) {
			this._ttl = void 0;
			return;
		}
		this._ttl = ttl;
	}
	async get(key) {
		if (Array.isArray(key)) return this.getMany(key);
		key = this._sanitize.enabled ? this._sanitize.cleanKey(key) : key;
		if (key === "") return;
		await this.hookWithDeprecated("before:get", { key });
		let rawData;
		try {
			rawData = await this._store.get(key);
		} catch (error) {
			this.emit("error", error);
			this.emitTelemetry("stat:error", key);
			await this.hookWithDeprecated("after:get", {
				key,
				value: void 0
			});
			return;
		}
		let data;
		if (this._checkExpired) [data] = await this.decodeWithExpire(key, rawData);
		else data = rawData === void 0 || rawData === null ? void 0 : typeof rawData === "string" ? await this.decode(rawData) : rawData;
		if (data === void 0) {
			await this.hookWithDeprecated("after:get", {
				key,
				value: void 0
			});
			this.emitTelemetry("stat:miss", key);
			return;
		}
		await this.hookWithDeprecated("after:get", {
			key,
			value: data
		});
		this.emitTelemetry("stat:hit", key);
		return data.value;
	}
	/**
	* Reads many keys from the store, preferring its native `getMany` and falling back to parallel
	* single `get`s when an adapter does not implement it. A directly-used v6 adapter is not
	* structurally required to provide `getMany` (the bridge and memory adapters always do), so
	* this keeps `getMany`/`getManyRaw` working regardless of the resolved adapter.
	* @param keys - the keys to read
	* @returns the raw store results in the same order as `keys`
	*/
	async storeGetMany(keys) {
		if (typeof this._store.getMany === "function") return this._store.getMany(keys);
		return Promise.all(keys.map(async (key) => this._store.get(key)));
	}
	/**
	* Runs a batch operation without the keys or entries whose key is empty, then returns its results
	* in the original order with `fallback` for each empty key. As with the single-key methods, an
	* empty key never reaches hooks, telemetry or the store.
	* @param items - the keys or entries of the batch
	* @param fallback - the result for an item whose key is empty
	* @param run - runs the batch operation on the items whose key is not empty
	* @returns one result per item, in the same order as `items`
	*/
	async skipEmptyKeys(items, fallback, run) {
		const isEmpty = (item) => (typeof item === "string" ? item : item.key) === "";
		const kept = items.filter((item) => !isEmpty(item));
		if (kept.length === items.length) return run(items);
		const results = kept.length > 0 ? await run(kept) : [];
		let next = 0;
		return items.map((item) => isEmpty(item) ? fallback : results[next++]);
	}
	/**
	* Get many values for an array of keys.
	* @param {string[]} keys the keys to get
	* @returns {Promise<Array<Value | undefined>>} an array of values in the same order as the
	* keys, with `undefined` for keys that do not exist or are expired.
	*/
	async getMany(keys) {
		keys = this._sanitize.enabled ? this._sanitize.cleanKeys(keys) : keys;
		return this.skipEmptyKeys(keys, void 0, async (keys) => {
			await this.hookWithDeprecated("before:getMany", { keys });
			let rawData;
			try {
				rawData = await this.storeGetMany(keys);
			} catch (error) {
				this.emit("error", error);
				this.emitTelemetry("stat:error", keys);
				const failed = keys.map(() => void 0);
				await this.hookWithDeprecated("after:getMany", failed);
				return failed;
			}
			let deserialized;
			if (this._checkExpired) deserialized = await this.decodeWithExpire(keys, rawData);
			else deserialized = await Promise.all(rawData.map(async (row) => {
				if (row === void 0 || row === null) return;
				return typeof row === "string" ? this.decode(row) : row;
			}));
			const result = deserialized.map((row) => row !== void 0 ? row.value : void 0);
			await this.hookWithDeprecated("after:getMany", result);
			for (let i = 0; i < result.length; i++) if (result[i] === void 0) this.emitTelemetry("stat:miss", keys[i]);
			else this.emitTelemetry("stat:hit", keys[i]);
			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<KeyvStorageGetResult<Value>>} will return a KeyvStorageGetResult<Value> or undefined
	* if the key does not exist or is expired.
	*/
	async getRaw(key) {
		key = this._sanitize.enabled ? this._sanitize.cleanKey(key) : key;
		if (key === "") return;
		await this.hookWithDeprecated("before:getRaw", { key });
		let rawData;
		try {
			rawData = await this._store.get(key);
		} catch (error) {
			this.emit("error", error);
			this.emitTelemetry("stat:error", key);
			await this.hookWithDeprecated("after:getRaw", {
				key,
				value: void 0
			});
			return;
		}
		let data;
		if (this._checkExpired) [data] = await this.decodeWithExpire(key, rawData);
		else data = rawData === void 0 || rawData === null ? void 0 : typeof rawData === "string" ? await this.decode(rawData) : rawData;
		if (data === void 0) {
			await this.hookWithDeprecated("after:getRaw", {
				key,
				value: void 0
			});
			this.emitTelemetry("stat:miss", key);
			return;
		}
		this.emitTelemetry("stat:hit", key);
		await this.hookWithDeprecated("after:getRaw", {
			key,
			value: data
		});
		return data;
	}
	/**
	* 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<KeyvStorageGetResult<Value>>>} will return an array of KeyvStorageGetResult<Value> or undefined if the key does not exist or is expired.
	*/
	async getManyRaw(keys) {
		/* v8 ignore next -- @preserve */
		keys = this._sanitize.enabled ? this._sanitize.cleanKeys(keys) : keys;
		return this.skipEmptyKeys(keys, void 0, async (keys) => {
			await this.hookWithDeprecated("before:getManyRaw", { keys });
			if (keys.length === 0) {
				const result = [];
				await this.hookWithDeprecated("after:getManyRaw", {
					keys,
					values: result
				});
				return result;
			}
			let rawData;
			try {
				rawData = await this.storeGetMany(keys);
			} catch (error) {
				this.emit("error", error);
				this.emitTelemetry("stat:error", keys);
				const failed = keys.map(() => void 0);
				await this.hookWithDeprecated("after:getManyRaw", {
					keys,
					values: failed
				});
				return failed;
			}
			let result;
			if (this._checkExpired) result = await this.decodeWithExpire(keys, rawData);
			else result = await Promise.all(rawData.map(async (row) => {
				if (row === void 0 || row === null) return;
				return typeof row === "string" ? this.decode(row) : row;
			}));
			for (let i = 0; i < result.length; i++) if (result[i] === void 0) this.emitTelemetry("stat:miss", keys[i]);
			else this.emitTelemetry("stat:hit", keys[i]);
			await this.hookWithDeprecated("after:getManyRaw", {
				keys,
				values: result
			});
			return result;
		});
	}
	/**
	* Set an item to the store
	* @param {string} key the key to use
	* @param {Value} value the value of the key
	* @param {number} [ttl] time to live in milliseconds. Overrides the instance-level `ttl`.
	* @returns {Promise<boolean>} `true` if it was set successfully, `false` on failure.
	*/
	async set(key, value, ttl) {
		key = this._sanitize.enabled ? this._sanitize.cleanKey(key) : key;
		if (key === "") return false;
		const data = {
			key,
			value,
			ttl
		};
		await this.hookWithDeprecated("before:set", data);
		data.ttl = resolveTtl(data.ttl, this._ttl);
		const expires = calculateExpires(data.ttl);
		if (typeof data.value === "symbol") {
			this.emit("error", "symbol cannot be serialized");
			this.emitTelemetry("stat:error", key);
			return false;
		}
		const formattedValue = {
			value: data.value,
			expires
		};
		let result = true;
		let encodedValue = formattedValue;
		try {
			encodedValue = await this.encode(formattedValue);
			result = await this._store.set(data.key, encodedValue, expires);
		} catch (error) {
			result = false;
			this.emit("error", error);
			this.emitTelemetry("stat:error", key);
		}
		await this.hookWithDeprecated("after:set", {
			key,
			value: encodedValue,
			ttl
		});
		if (result) this.emitTelemetry("stat:set", key);
		return result;
	}
	/**
	* Set many items to the store
	* @param {Array<KeyvEntry<Value>>} entries the entries to set
	* @returns {Promise<boolean[]>} an array of booleans, one per entry: `true` if set successfully, `false` on failure.
	*/
	async setMany(entries) {
		entries = entries.map((e) => ({
			...e,
			key: this._sanitize.enabled ? this._sanitize.cleanKey(e.key) : e.key
		}));
		return this.skipEmptyKeys(entries, false, async (entries) => {
			const data = { entries };
			await this.hookWithDeprecated("before:setMany", data);
			entries = data.entries;
			let results = [];
			try {
				const serializedEntries = await Promise.all(entries.map(async ({ key, value, ttl }) => {
					ttl = resolveTtl(ttl, this._ttl);
					/* v8 ignore next -- @preserve */
					const expires = calculateExpires(ttl);
					/* v8 ignore next -- @preserve */
					if (typeof value === "symbol") {
						this.emit("error", "symbol cannot be serialized");
						this.emitTelemetry("stat:error", key);
						throw new Error("symbol cannot be serialized");
					}
					const formattedValue = {
						value,
						expires
					};
					return {
						key,
						value: await this.encode(formattedValue),
						expires
					};
				}));
				const storeResult = await this._store.setMany(serializedEntries);
				/* v8 ignore next -- @preserve */
				results = Array.isArray(storeResult) ? storeResult : entries.map(() => true);
				this.emitTelemetry("stat:set", entries.map((e) => e.key));
			} catch (error) {
				this.emit("error", error);
				this.emitTelemetry("stat:error", entries.map((e) => e.key));
				results = entries.map(() => false);
			}
			await this.hookWithDeprecated("after:setMany", {
				entries,
				values: results
			});
			return results;
		});
	}
	/**
	* Set a raw value to the store without wrapping or serialization. This is the write-side counterpart to getRaw().
	* The value should be a KeyvValue object with { value, expires? }. If you need TTL-based expiration,
	* set `expires` on the value directly (e.g. `{ value: 'bar', expires: Date.now() + 60000 }`).
	* The store-level expiry is derived automatically from `value.expires`, rounded up to a whole millisecond
	* (a non-finite `expires` means no store-level expiry).
	* @param {string} key the key to set
	* @param {KeyvValue<Value>} value the raw value envelope to store
	* @returns {Promise<boolean>} `true` if it was set successfully, `false` on failure.
	*/
	async setRaw(key, value) {
		key = this._sanitize.enabled ? this._sanitize.cleanKey(key) : key;
		if (key === "") return false;
		const data = {
			key,
			value
		};
		await this.hookWithDeprecated("before:setRaw", data);
		const expires = toStoreExpires(data.value.expires);
		const ttl = ttlFromExpires(expires);
		let result = true;
		try {
			const encodedValue = await this.encode(data.value);
			const storeResult = await this._store.set(data.key, encodedValue, expires);
			if (typeof storeResult === "boolean") result = storeResult;
		} catch (error) {
			result = false;
			this.emit("error", error);
			this.emitTelemetry("stat:error", key);
		}
		await this.hookWithDeprecated("after:setRaw", {
			key,
			value: data.value,
			ttl
		});
		if (result) this.emitTelemetry("stat:set", key);
		return result;
	}
	/**
	* Set many raw values to the store without wrapping or serialization. This is the write-side counterpart to getManyRaw().
	* Each entry's value should be a KeyvValue object with { value, expires? }. If you need TTL-based expiration,
	* set `expires` on each value directly. The store-level expiry is derived automatically from `value.expires`,
	* rounded up to a whole millisecond (a non-finite `expires` means no store-level expiry).
	* @param {KeyvEntry<KeyvValue<Value>>[]} entries the raw entries to set
	* @returns {Promise<boolean[]>} an array of booleans, one per entry: `true` if set successfully, `false` on failure.
	*/
	async setManyRaw(entries) {
		entries = entries.map((e) => ({
			...e,
			/* v8 ignore next -- @preserve */
			key: this._sanitize.enabled ? this._sanitize.cleanKey(e.key) : e.key
		}));
		return this.skipEmptyKeys(entries, false, async (entries) => {
			let results = [];
			await this.hookWithDeprecated("before:setManyRaw", { entries });
			try {
				const rawEntries = await Promise.all(entries.map(async ({ key, value }) => {
					return {
						key,
						value: await this.encode(value),
						expires: toStoreExpires(value.expires)
					};
				}));
				const storeResult = await this._store.setMany(rawEntries);
				results = Array.isArray(storeResult) ? storeResult : entries.map(() => true);
				this.emitTelemetry("stat:set", entries.map((e) => e.key));
			} catch (error) {
				this.emit("error", error);
				this.emitTelemetry("stat:error", entries.map((e) => e.key));
				results = entries.map(() => false);
			}
			await this.hookWithDeprecated("after:setManyRaw", {
				entries,
				results
			});
			return results;
		});
	}
	async delete(key) {
		if (Array.isArray(key)) return this.deleteMany(key);
		key = this._sanitize.enabled ? this._sanitize.cleanKey(key) : key;
		if (key === "") return false;
		await this.hookWithDeprecated("before:delete", { key });
		let result = true;
		try {
			result = await this._store.delete(key);
		} catch (error) {
			result = false;
			this.emit("error", error);
			this.emitTelemetry("stat:error", key);
		}
		await this.hookWithDeprecated("after:delete", {
			key,
			value: result
		});
		this.emitTelemetry("stat:delete", key);
		return result;
	}
	/**
	* Delete many items from the store
	* @param {string[]} keys the keys to be deleted
	* @returns {Promise<boolean[]>} an array of booleans indicating success for each key.
	*/
	async deleteMany(keys) {
		/* v8 ignore next -- @preserve */
		keys = this._sanitize.enabled ? this._sanitize.cleanKeys(keys) : keys;
		return this.skipEmptyKeys(keys, false, async (keys) => {
			await this.hookWithDeprecated("before:deleteMany", { keys });
			await this.hookWithDeprecated("before:delete", { key: keys });
			let results;
			try {
				results = await this._store.deleteMany(keys);
				this.emitTelemetry("stat:delete", keys);
			} catch (error) {
				this.emit("error", error);
				this.emitTelemetry("stat:error", keys);
				results = keys.map(() => false);
			}
			await this.hookWithDeprecated("after:deleteMany", {
				keys,
				values: results
			});
			await this.hookWithDeprecated("after:delete", {
				key: keys,
				value: results
			});
			return results;
		});
	}
	async has(key) {
		if (Array.isArray(key)) return this.hasMany(key);
		key = this._sanitize.enabled ? this._sanitize.cleanKey(key) : key;
		if (key === "") return false;
		await this.hookWithDeprecated("before:has", { key });
		let result = false;
		try {
			if (this._checkExpired) {
				const rawData = await this._store.get(key);
				if (rawData !== void 0 && rawData !== null) {
					const [data] = await this.decodeWithExpire(key, rawData);
					result = data !== void 0;
				}
			} else result = await this._store.has(key);
		} catch (error) {
			this.emit("error", error);
			this.emitTelemetry("stat:error", key);
		}
		await this.hookWithDeprecated("after:has", {
			key,
			value: result
		});
		return result;
	}
	/**
	* Check if many keys exist
	* @param {string[]} keys the keys to check
	* @returns {Promise<boolean[]>} an array of booleans in the same order as the keys, `true` if the key exists.
	*/
	async hasMany(keys) {
		keys = this._sanitize.enabled ? this._sanitize.cleanKeys(keys) : keys;
		return this.skipEmptyKeys(keys, false, async (keys) => {
			await this.hookWithDeprecated("before:hasMany", { keys });
			let results = [];
			try {
				if (this._checkExpired) {
					const rawData = await this.storeGetMany(keys);
					results = (await this.decodeWithExpire(keys, rawData)).map((row) => row !== void 0);
				} else results = await this._store.hasMany(keys);
			} catch (error) {
				this.emit("error", error);
				this.emitTelemetry("stat:error", keys);
				results = keys.map(() => false);
			}
			await this.hookWithDeprecated("after:hasMany", {
				keys,
				values: results
			});
			return results;
		});
	}
	/**
	* Clear the store. If a namespace is set only entries in that namespace are removed. When the
	* store can't limit the removal to the namespace, nothing is removed and `error` is emitted.
	* Emits a `clear` event.
	* @returns {Promise<void>} resolves once the entries have been cleared.
	*/
	async clear() {
		this.emit("clear");
		await this.hook("before:clear", { namespace: this.namespace });
		try {
			await this._store.clear();
		} catch (error) {
			this.emit("error", error);
			this.emitTelemetry("stat:error");
		}
		await this.hook("after:clear", { namespace: this.namespace });
	}
	/**
	* Will disconnect the store. This is only available if the store has a disconnect method
	* @returns {Promise<void>}
	*/
	async disconnect() {
		this.emit("disconnect");
		await this.hook("before:disconnect", { namespace: this.namespace });
		try {
			if (this._store.disconnect) await this._store.disconnect();
		} catch (error) {
			this.emit("error", error);
		}
		await this.hook("after:disconnect", { namespace: this.namespace });
	}
	/**
	* Iterate over all key-value pairs in the store. Automatically deserializes values,
	* filters out expired entries, and deletes them from the store.
	* @returns {AsyncGenerator<Array<string | unknown>, void>} An async generator yielding `[key, value]` pairs.
	*/
	async *iterator() {
		/* v8 ignore next 3 -- @preserve */
		if (this._store.iterator === void 0) return;
		try {
			for await (const [key, raw] of this._store.iterator()) {
				const data = await this.decode(raw);
				if (this._checkExpired && data && isDataExpired(data)) {
					await this.delete(key);
					continue;
				}
				yield [key, data?.value];
			}
		} catch (error) {
			this.emit("error", error);
			this.emitTelemetry("stat:error");
		}
	}
	/**
	* Encodes a value for storage. Pipeline: serialize → compress → encrypt.
	* If serialization is not configured, returns the data as-is, skipping compression. With an
	* encryption adapter set, it throws instead, since the value can't be encrypted and storing it
	* unencrypted would defeat the adapter.
	* @param {KeyvValue<T>} data The value envelope to encode.
	* @returns {Promise<unknown>} The encoded value.
	* @throws {Error} If an encryption adapter is set while serialization is disabled.
	*/
	async encode(data) {
		if (!this._serialization) {
			if (this._encryption?.encrypt) throw new Error("Encryption needs serialization, which is disabled, so Keyv won't store the value unencrypted. Remove `serialization: false` or the encryption adapter.");
			return data;
		}
		let result = await this._serialization.stringify(data);
		if (this._compression?.compress) result = await this._compression.compress(result);
		if (this._encryption?.encrypt) result = await this._encryption.encrypt(result);
		return result;
	}
	/**
	* Decodes a stored value. Pipeline: decrypt → decompress → deserialize (reverse of encode).
	* If serialization is not configured, returns the data as a KeyvValue or undefined for strings.
	* @param {unknown} data The raw data from the store.
	* @returns {Promise<KeyvValue<T> | undefined>} The decoded value envelope, or undefined on failure.
	*/
	async decode(data) {
		if (data === void 0 || data === null) return;
		if (!this._serialization) return typeof data === "string" ? void 0 : data;
		try {
			let result = data;
			if (this._encryption?.decrypt) result = await this._encryption.decrypt(result);
			if (this._compression?.decompress) result = await this._compression.decompress(result);
			if (typeof result === "string") return await this._serialization.parse(result);
			return result;
		} catch (error) {
			this.emit("error", error);
			return;
		}
	}
	/**
	* Deserializes raw data from the store, checks for expiry, and deletes expired keys.
	* Accepts a single key/value or arrays. Returns an array of decoded KeyvValue objects
	* (undefined for missing or expired entries).
	* @param {string | string[]} keys the key(s) to process
	* @param {unknown | unknown[]} rawData the raw data from the store
	* @returns {Promise<Array<KeyvValue<Value> | undefined>>} decoded values with expired entries removed
	*/
	async decodeWithExpire(keys, rawData) {
		const keyArray = Array.isArray(keys) ? keys : [keys];
		const dataArray = Array.isArray(rawData) ? rawData : [rawData];
		const results = [];
		for (const row of dataArray) {
			if (row === void 0 || row === null) {
				results.push(void 0);
				continue;
			}
			const deserialized = typeof row === "string" ? await this.decode(row) : row;
			if (deserialized === void 0 || deserialized === null) {
				results.push(void 0);
				continue;
			}
			results.push(deserialized);
		}
		await deleteExpiredKeys(keyArray, results, this);
		return results;
	}
	/**
	* Fires a hook under its new name and also under the deprecated alias (if any),
	* so that integrations still subscribing to the old PRE_/POST_ names keep working.
	*/
	async hookWithDeprecated(event, ...args) {
		await this.hook(event, ...args);
		const deprecated = deprecatedHookAliases.get(event);
		if (deprecated && this.getHooks(deprecated)?.length) await this.hook(deprecated, ...args);
	}
	/**
	* Emit a telemetry event for cache operations.
	* @param {KeyvEvents} event the telemetry event type
	* @param {string | string[]} [key] the cache key or keys (emits one event per key)
	*/
	emitTelemetry(event, key) {
		if (key === void 0) {
			this.emit(event, {
				event: event.replace("stat:", ""),
				namespace: this.namespace,
				timestamp: Date.now()
			});
			return;
		}
		const keys = Array.isArray(key) ? key : [key];
		for (const k of keys) this.emit(event, {
			event: event.replace("stat:", ""),
			key: k,
			namespace: this.namespace,
			timestamp: Date.now()
		});
	}
	/**
	* Merges the overloaded constructor arguments into a single KeyvOptions object.
	*/
	static resolveOptions(store, options) {
		options ??= {};
		store ??= {};
		const merged = { ...options };
		if (store && store.get) merged.store = store;
		else Object.assign(merged, store);
		return merged;
	}
	/**
	* Initializes the serialization adapter from options.
	*/
	initSerialization(options) {
		if (options.serialization === false) this._serialization = void 0;
		else this._serialization = options.serialization ?? new KeyvJsonSerializer();
	}
	/**
	* Initializes the sanitization handler from options.
	*/
	initSanitize(options) {
		const sanitize = new KeyvSanitize();
		if (options.sanitize) sanitize.updateOptions(options.sanitize);
		this._sanitize = sanitize;
	}
	/**
	* Initializes the stats manager from options.
	*/
	initStats(options) {
		this._stats = new KeyvStats({
			emitter: this,
			enabled: options.stats ?? false
		});
	}
	/**
	* Initializes the namespace, applying sanitization if enabled.
	*/
	initNamespace(namespace) {
		this._namespace = namespace;
		if (this._namespace && this._sanitize.enabled) this._namespace = this.cleanNamespace(this._namespace);
	}
	/**
	* Sanitizes a namespace. When sanitizing leaves nothing, it returns {@link emptiedNamespace} so
	* the instance keeps a namespace of its own.
	*/
	cleanNamespace(namespace) {
		return this._sanitize.cleanNamespace(namespace) || emptiedNamespace;
	}
	/**
	* Sanitizes the namespace kept from the storage adapter when Keyv has none of its own.
	*/
	sanitizeStoreNamespace() {
		if (this._namespace === void 0 && this._store.namespace && this._sanitize.enabled) this._store.namespace = this.cleanNamespace(this._store.namespace);
	}
};
//#endregion
//#region src/adapters/memory.ts
/**
* An in-memory storage adapter for Keyv that wraps any Map-like object.
*
* This class provides a unified interface for using various Map-like stores
* with Keyv, handling namespace prefixing, TTL-based expiration, and batch operations.
*
* @example
* ```typescript
* // Using with a standard Map
* const store = new KeyvMemoryAdapter(new Map(), { namespace: 'cache' });
*
* // Using with a custom store
* const customStore = new KeyvMemoryAdapter(myCustomMapLikeStore, {
*   namespace: 'tenant-123',
*   namespaceSeparator: '::'
* });
* ```
*/
var KeyvMemoryAdapter = class extends Hookified {
	_store;
	_namespace;
	_namespaceSeparator = "::";
	_capabilities;
	/**
	* Creates a new KeyvMemoryAdapter instance.
	* @param store - The underlying Map or Map-like object to use for storage
	* @param options - Configuration options for the store
	*/
	constructor(store, options) {
		super({ throwOnHookError: false });
		this._store = store;
		this._capabilities = detectKeyvStorage(store);
		if (options?.namespaceSeparator !== void 0) this._namespaceSeparator = options.namespaceSeparator;
		if (options?.namespace) this._namespace = options?.namespace;
	}
	/**
	* Gets the capabilities of the underlying store, with `expires: true` to declare that
	* this adapter accepts an absolute `expires` timestamp (the v6 storage contract).
	*/
	get capabilities() {
		return {
			...this._capabilities,
			expires: true
		};
	}
	/**
	* Gets the underlying store instance.
	*/
	get store() {
		return this._store;
	}
	/**
	* Sets the underlying store instance.
	*/
	set store(store) {
		this._store = store;
	}
	/**
	* Gets the separator between the namespace and the key. Defaults to `::`.
	*/
	get namespaceSeparator() {
		return this._namespaceSeparator;
	}
	/**
	* Sets the separator between the namespace and the key.
	*/
	set namespaceSeparator(separator) {
		this._namespaceSeparator = separator;
	}
	/**
	* Gets the current namespace.
	*/
	get namespace() {
		return this._namespace;
	}
	/**
	* Sets the namespace.
	*/
	set namespace(namespace) {
		this._namespace = namespace;
	}
	/**
	* Creates a prefixed key by combining the namespace and key with the separator.
	* @param key - The base key
	* @param namespace - Optional namespace to prefix the key with
	* @returns The prefixed key if namespace is provided, otherwise the original key
	*/
	getKeyPrefix(key, namespace) {
		if (namespace) return `${namespace}${this._namespaceSeparator}${key}`;
		return key;
	}
	/**
	* Parses a prefixed key to extract the namespace and original key.
	* @param key - The prefixed key to parse
	* @returns An object containing the namespace (if present) and the original key
	*/
	getKeyPrefixData(key) {
		if (this._namespace && key.startsWith(`${this._namespace}${this._namespaceSeparator}`)) return {
			namespace: this._namespace,
			key: key.slice(this._namespace.length + this._namespaceSeparator.length)
		};
		return { key };
	}
	/**
	* Retrieves a value from the store by key.
	* Automatically handles namespace prefixing and TTL expiration.
	* @param key - The key to retrieve
	* @returns The stored data, or undefined if not found or expired
	*/
	async get(key) {
		const keyPrefix = this.getKeyPrefix(key, this._namespace);
		const entry = this._store.get(keyPrefix);
		if (entry === void 0 || entry === null) return;
		if (entry.expires !== void 0 && Date.now() > entry.expires) {
			this._store.delete(keyPrefix);
			return;
		}
		return entry.value;
	}
	/**
	* Stores a value in the store with an optional absolute expiry.
	* @param key - The key to store the value under
	* @param value - The value to store
	* @param expires - Optional absolute expiry as Unix ms since epoch
	* @returns Always returns true indicating success
	*/
	async set(key, value, expires) {
		const keyPrefix = this.getKeyPrefix(key, this._namespace);
		const entry = {
			value,
			expires: typeof expires === "number" ? expires : void 0
		};
		this._store.set(keyPrefix, entry, ttlFromExpires(expires));
		return true;
	}
	/**
	* Stores multiple entries in the store at once.
	* @param entries - Array of entries containing key, value, and optional absolute `expires`
	*/
	async setMany(entries) {
		const results = [];
		for (const entry of entries) {
			const keyPrefix = this.getKeyPrefix(entry.key, this._namespace);
			const memEntry = {
				value: entry.value,
				expires: typeof entry.expires === "number" ? entry.expires : void 0
			};
			this._store.set(keyPrefix, memEntry, ttlFromExpires(entry.expires));
			results.push(true);
		}
		return results;
	}
	/**
	* Deletes a value from the store by key.
	* @param key - The key to delete
	* @returns True if the key was deleted, false otherwise
	*/
	async delete(key) {
		const keyPrefix = this.getKeyPrefix(key, this._namespace);
		return this._store.delete(keyPrefix);
	}
	/**
	* Clears entries from the store. With no namespace, the whole store is cleared. With a
	* namespace, only that namespace's entries are removed, found through the store's `keys()`.
	* A store without `keys()` can't tell namespaces apart, so this throws rather than call the
	* store's `clear()` and delete every namespace's entries.
	* @throws {Error} If a namespace is set and the store has no `keys()`.
	*/
	async clear() {
		if (!this._namespace) {
			this._store.clear();
			return;
		}
		if (typeof this._store.keys !== "function") throw new Error(`Can't clear namespace "${this._namespace}": the store has no keys() to find that namespace's keys, and its clear() would delete every namespace. Use a store with keys(), such as a Map, or clear it without a namespace.`);
		const prefix = `${this._namespace}${this._namespaceSeparator}`;
		const keysToDelete = [];
		for (const key of this._store.keys()) if (key.startsWith(prefix)) keysToDelete.push(key);
		for (const key of keysToDelete) this._store.delete(key);
	}
	/**
	* Checks if a key exists in the store and is not expired.
	* @param key - The key to check
	* @returns True if the key exists and is not expired, false otherwise
	*/
	async has(key) {
		const keyPrefix = this.getKeyPrefix(key, this._namespace);
		const entry = this._store.get(keyPrefix);
		if (entry === void 0 || entry === null) return false;
		if (entry.expires !== void 0 && Date.now() > entry.expires) {
			this._store.delete(keyPrefix);
			return false;
		}
		return true;
	}
	/**
	* Checks if multiple keys exist in the store and are not expired.
	* @param keys - Array of keys to check
	* @returns Array of booleans indicating existence for each key
	*/
	async hasMany(keys) {
		const results = [];
		for (const key of keys) results.push(await this.has(key));
		return results;
	}
	/**
	* Retrieves multiple values from the store by their keys.
	* @param keys - Array of keys to retrieve
	* @returns Array of stored data in the same order as the input keys
	*/
	async getMany(keys) {
		const values = [];
		for (const key of keys) {
			const keyPrefix = this.getKeyPrefix(key, this._namespace);
			const entry = this._store.get(keyPrefix);
			if (entry === void 0 || entry === null) {
				values.push(void 0);
				continue;
			}
			if (entry.expires !== void 0 && Date.now() > entry.expires) {
				this._store.delete(keyPrefix);
				values.push(void 0);
				continue;
			}
			values.push(entry.value);
		}
		return values;
	}
	/**
	* Deletes multiple keys from the store at once.
	* @param keys - Array of keys to delete
	* @returns Array of booleans indicating success for each key
	*/
	async deleteMany(keys) {
		const results = [];
		for (const key of keys) try {
			const keyPrefix = this.getKeyPrefix(key, this._namespace);
			const existed = this._store.has(keyPrefix);
			this._store.delete(keyPrefix);
			results.push(existed);
		} catch (error) {
			this.emit("error", error);
			results.push(false);
		}
		return results;
	}
	/**
	* Creates an async iterator for iterating over store entries.
	* If the underlying store does not support iteration, returns an empty generator.
	* @returns {AsyncGenerator<Array<string | Awaited<Value> | undefined>, void>} An async generator yielding [key, value] pairs
	*/
	async *iterator() {
		if (typeof this._store.entries !== "function") return;
		const namespace = this._namespace;
		for (const [key, raw] of this._store.entries()) {
			if (namespace) {
				if (!key.startsWith(`${namespace}${this._namespaceSeparator}`)) continue;
			}
			const entry = raw;
			if (entry?.expires !== void 0 && Date.now() > entry.expires) {
				this._store.delete(key);
				continue;
			}
			yield [namespace ? key.slice(namespace.length + this._namespaceSeparator.length) : key, entry?.value];
		}
	}
	/**
	* No-op disconnect for in-memory stores.
	*/
	async disconnect() {}
};
/**
* Creates a Keyv instance with a memory adapter optimized for in-memory storage.
*
* This factory function configures Keyv to bypass serialization/deserialization
* and key prefixing, resulting in faster performance for in-memory use cases
* where data doesn't need to be persisted or transmitted.
*
* @param store - The underlying Map or Map-like object to use for storage
* @param options - Configuration options for the memory adapter
* @returns A configured Keyv instance with optimized settings for in-memory storage
*
* @example
* ```typescript
* // Create a simple in-memory cache
* const cache = createKeyv(new Map());
* await cache.set('user:1', { name: 'John' });
*
* // Create with namespace for multi-tenant scenarios
* const tenantCache = createKeyv(new Map(), {
*   namespace: 'tenant-123',
*   namespaceSeparator: '::'
* });
* ```
*/
function createKeyv(store, options) {
	const { namespace, ...adapterOptions } = options ?? {};
	return new Keyv({
		store: new KeyvMemoryAdapter(store, adapterOptions),
		serialization: false,
		namespace
	});
}
//#endregion
export { Keyv, Keyv as default, KeyvBridgeAdapter, KeyvEvents, KeyvHooks, KeyvJsonSerializer, KeyvMemoryAdapter, KeyvSanitize, KeyvStats, createKeyv, detectKeyv, detectKeyvCompression, detectKeyvEncryption, detectKeyvSerialization, detectKeyvStorage, jsonSerializer, keyvStorageCapability };