One API. Every Runtime. Zero Headaches.
@rabbx/kv is a cross-platform, type-safe Key-Value store abstraction. It provides a unified, modern async API that works identically across Node.js, Bun, Cloudflare Workers, Vercel KV, and the Browser. Stop rewriting your storage logic every time you switch runtimes or deploy targets.
- Universal API: Standard
get,set,del,has,clear+ batch operations (getMany,setMany,delMany). - Multi-Runtime: Node.js, Bun, Cloudflare Workers, Vercel (Upstash), and Browser.
- Pluggable Node Backends: In-memory, File-backed (JSON), LevelDB, and SQLite.
- Binary & Serialization: Internal
Uint8Arraycontract. Choose betweenjsonormsgpack(natively handles Dates, Maps, Sets, and binary data). - Per-Key TTL: Granular expiration control, fully supported inside batch operations.
- Zero Core Dependencies: The core is dependency-free. You only install the specific database drivers you actually use.
Install the core package:
npm install @rabbx/kvOnly install the drivers for the backends you intend to use:
# For LevelDB backend
npm install level
# For SQLite backend
npm install better-sqlite3
# For Browser backend (if not already in your project)
npm install localforageimport { createKV } from '@rabbx/kv';
// 1. Initialize (Defaults to in-memory Node backend)
const kv = createKV({ platform: 'node' });
// 2. Use it
await kv.set('user:1', { name: 'Alice', role: 'admin' }, { ttl: 3600 });
const user = await kv.get<{ name: string; role: string }>('user:1');
console.log(user); // { name: 'Alice', role: 'admin' }Persists data to a local JSON file. Great for local dev, CLI tools, or small caches.
const kv = createKV({
platform: 'node',
backend: 'file',
filePath: './data/cache.json',
serializer: 'msgpack' // Optional: defaults to 'msgpack'
});High-performance, persistent local storage. Uses better-sqlite3 under the hood.
const kv = createKV({
platform: 'node',
backend: 'sqlite',
dbPath: './data/local.db'
});Fast, ordered, persistent key-value store.
const kv = createKV({
platform: 'node',
backend: 'level',
dbPath: './data/level-db'
});Bun ships with bun:sqlite built-in. The Bun adapter uses it natively, meaning zero extra dependencies and zero configuration required. It defaults to a rabbx-kv.sqlite file in your current directory.
const kv = createKV({
platform: 'bun'
// dbPath: './custom-path.sqlite' // Optional: override default path
});
await kv.set('runtime', 'bun');Automatically uses IndexedDB, WebSQL, or LocalStorage depending on browser support.
import localforage from 'localforage';
const kv = createKV({
platform: 'browser',
localforageInstance: localforage.createInstance({
name: 'my-app',
storeName: 'kv_store'
})
});Wraps a Cloudflare KV Namespace.
export default {
async fetch(request, env) {
const kv = createKV({
platform: 'cf',
cfNamespace: env.MY_KV_NAMESPACE
});
await kv.set('edge:config', { featureFlags: true });
return new Response('OK');
}
};Uses @upstash/redis directly β the same engine that powers @vercel/kv, but with better typing and broader runtime support (Edge, CF Workers, Deno, etc.).
npm install @upstash/redisimport { Redis } from '@upstash/redis';
import { createKV } from '@rabbx/kv';
const redis = new Redis({
url: process.env.UPSTASH_URL!,
token: process.env.UPSTASH_TOKEN!,
});
const kv = createKV({
platform: 'vercel',
upstashClient: redis
});
await kv.set('session:abc', { userId: '123' }, { ttl: 3600 });Why @upstash/redis over @vercel/kv?
@vercel/kvis just a thin wrapper around@upstash/rediswith some Vercel-specific env var auto-detection- Using
@upstash/redisdirectly gives you access to the full Redis API (pipelines, transactions, Lua scripts) if you ever need to drop down - Works identically in Vercel Edge, Cloudflare Workers, Node, Bun, and Deno
- Better TypeScript support and more predictable typing
| Method | Signature | Description |
|---|---|---|
get |
<T>(key: string): Promise<T | null> |
Retrieves a value. Returns null if missing or expired. |
set |
<T>(key: string, value: T, opts?: { ttl?: number }): Promise<void> |
Sets a value. ttl is in seconds. |
del |
(key: string): Promise<void> |
Deletes a key. |
has |
(key: string): Promise<boolean> |
Checks if a key exists and is not expired. |
clear |
(): Promise<void> |
Wipes all keys. (Throws on Cloudflare Workers). |
| Method | Signature | Description |
|---|---|---|
getMany |
<T>(keys: string[]): Promise<Record<string, T | null>> |
Fetches multiple keys. Returns a Record mapping keys to values (or null). |
setMany |
<T>(entries: BatchEntry<T>[]): Promise<void> |
Sets multiple keys. Supports per-key TTL. |
delMany |
(keys: string[]): Promise<void> |
Deletes multiple keys. |
Batch Entry Shape:
interface BatchEntry<T> {
key: string;
value: T;
ttl?: number; // Optional per-key TTL in seconds
}| Property | Type | Description |
|---|---|---|
platform |
'node' | 'bun' | 'cf' | 'vercel' | 'browser' |
Required. The target runtime. |
serializer |
'json' | 'msgpack' |
Defaults to 'msgpack'. Use 'json' if you only store basic primitives and want to save bandwidth on remote KV. |
backend |
'memory' | 'file' | 'level' | 'sqlite' |
Node only. Defaults to 'memory'. |
filePath |
string |
Node + File backend. Path to the JSON file. |
dbPath |
string |
Node + Level/SQLite & Bun. Path to the database directory/file. |
cfNamespace |
any |
CF. The Cloudflare KV namespace binding. |
vercelClient |
any |
Vercel. The @vercel/kv client instance. |
localforageInstance |
any |
Browser. Optional custom LocalForage instance. |
- The
Uint8ArrayContract: Internally, all adapters speakUint8Array. This decouples serialization from storage. LevelDB and SQLite store raw bytes natively (zero overhead), while File and Browser adapters handle Base64 translation seamlessly. - SQLite Event Loop:
better-sqlite3(Node) andbun:sqlite(Bun) are strictly synchronous. For v1, we wrap them in Promises to satisfy the async interface. If you are doing massive batch writes (10k+ keys), be aware it will briefly block the event loop. - LevelDB TTLs: LevelDB lacks native TTL support. We use a "shadow key" pattern (
__meta__${key}) to store expiration timestamps. Expired keys are lazily cleaned up on read. - Cloudflare
clear(): CF KV does not support bulk deletes natively without listing keys first (which is rate-limited and expensive). Callingclear()on the CF adapter throws an error by design to prevent accidental production blowups. - Vercel/Upstash Binary: Upstash's REST API expects strings. When using
msgpack, we Base64 encode the payload. This adds ~33% overhead. If you only store JSON-serializable data on Vercel, switch toserializer: 'json'.
If @rabbx/kv saves you time or helps you ship faster, consider buying me a coffee or sponsoring the project. It keeps the lights on and the dependencies updated.
- GitHub Sponsors: github.com/sponsors/rabbxdev
- Ko-fi: ko-fi.com/rabbxdev
MIT Β© rabbxdev