A namespace isolates one Keyv instance's keys from another when they share a backend. Clearing users does not touch cache.
import Keyv from "keyv";
import KeyvRedis from "@keyv/redis";
const redis = "redis://localhost:6379";
const users = new Keyv(new KeyvRedis(redis), { namespace: "users" });
const cache = new Keyv(new KeyvRedis(redis), { namespace: "cache" });
await users.set("foo", "users");
await cache.set("foo", "cache");
await users.get("foo"); // 'users'
await cache.get("foo"); // 'cache'
await users.clear();
await users.get("foo"); // undefined
await cache.get("foo"); // 'cache'
Table of Contents
- How it works in v6
- Memory and Map stores
- Bridge / legacy adapters
- Namespaces that share a prefix
- Embedding Keyv in a library
How it works in v6
Keyv core does not prefix keys itself. It sets store.namespace. Official adapters apply that namespace with their own prefixing (Redis namespace::key, SQL WHERE namespace = …, and so on).
You can set the namespace on Keyv or on the adapter:
- A namespace passed to Keyv wins. Keyv writes it to the adapter, replacing any namespace the adapter had.
- When Keyv has no namespace, it keeps the adapter's and
keyv.namespacereturns it.
const store = new KeyvRedis(redis, { namespace: "users" });
const users = new Keyv(store);
users.namespace; // 'users'
That is why useKeyPrefix / keyPrefix from v5 are gone. See the v5 → v6 Migration guide.
Set or clear the namespace later via the property:
keyv.namespace = "tenant-42";
keyv.namespace = undefined; // no isolation
If sanitization is enabled, the namespace is cleaned on construct and on the setter. A namespace that Keyv keeps from the adapter is cleaned too. A namespace that cleaning leaves empty becomes keyv-sanitized, so the instance still has a namespace of its own.
Memory and Map stores
KeyvMemoryAdapter (the default Map / LRU wrapper) prefixes keys as namespace::key (customizable namespaceSeparator). A namespaced clear() removes only those keys, which it finds with the underlying store's keys() (a standard Map has one). A minimal Map-like object without keys() can't tell namespaces apart, so a namespaced clear() throws instead of wiping the entire store.
Bridge / legacy adapters
KeyvBridgeAdapter does one of two things:
- If the wrapped store already has a
namespaceproperty (a full adapter), the bridge propagates namespace and does not prefix again. When the bridge has no namespace of its own, it keeps the one the store was configured with. - Otherwise it prefixes keys itself so one shared async store can host multiple namespaces. A namespaced
clear()finds those keys with the store'siterator(). A store without one can't tell namespaces apart, soclear()throws instead of wiping every namespace.
Namespaces that share a prefix
Most adapters store a key as <namespace><separator><key>: the memory adapter, the bridge adapter when it prefixes keys itself, Redis, Valkey, Etcd, DynamoDB, Cloudflare KV, and Memcache. The separator is :: by default, and each of these adapters takes a namespaceSeparator option to change it.
const store = new KeyvRedis(redis, { namespace: "users", namespaceSeparator: "/" });
await store.set("foo", "bar"); // stored as users/foo
A namespace that extends another with the separator isn't kept apart from it:
- Clearing or iterating
usersalso reaches the entries ofusers::archive, because these adapters find a namespace's entries by its prefix. Memcache is the exception: it clears each namespace on its own and has no iterator. - The two can build the same stored key.
archive::xinusersandxinusers::archiveare one entry, so writing either replaces the other.
Keep the separator out of namespace names when namespaces have to stay apart. With the default ::, names that contain a single :, such as users:archive, stay apart from users. Valkey's useSets: true doesn't change this: it builds keys the same way, and its iterator() still finds entries by prefix.
SQLite, PostgreSQL, MySQL, and MongoDB keep the namespace in its own column or field, so their namespaces never overlap. A bridge around an adapter that handles its own namespace leaves the keys to that adapter, so it behaves like the adapter it wraps.
Embedding Keyv in a library
Always set a namespace when you wrap Keyv inside another module so callers can .clear() without destroying unrelated app data.
class AwesomeModule {
constructor(opts) {
this.cache = new Keyv({
store: typeof opts.cache === "string" ? undefined : opts.cache,
namespace: "awesome-module",
});
}
}