kevy
kevy 6.3.0 · Ship it inside

Put the store
inside the thing

No server, no socket, no network. The engine is a struct you call, a 481 KB WebAssembly module, or a no_std library on a chip with no operating system — and it is the same engine, with the same commands, in all three.

Why this fits

Every application that has to work offline ends up writing a storage layer. A desktop app gets SQLite and a schema nobody wanted. A web app gets localStorage, discovers the 5 MB ceiling and the fact that it is synchronous and string-only, and then gets IndexedDB and an abstraction over it. A device gets a hand-rolled ring buffer in flash.

All three are the same problem, and they can be the same solution. kevy embeds with no process boundary, ships to a browser with real TTLs and pub/sub, and boots on a Cortex-M with a fixed arena and no allocator at all. CI proves the last one on every push.

Inside a Rust program

The store is a struct you call — no socket, no serialisation, no second process. Durable, and it replays its log on open.

Add it
# Cargo.toml
kevy-embedded = "4.0"
Open, write, read
let db = Db::open("data/")?;
db.set(b"session:7f3a", b"{\"user\":\"ada\"}", Some(Duration::from_secs(3600)))?;
assert_eq!(db.get(b"session:7f3a")?.is_some(), true);
Need redis-cli later? Open the listener
db.listen("127.0.0.1:6379")?;

Other processes reach the same store over RESP, without changing any of the above.

Cost & limits An embedded store is not shared. One process owns the data directory; if a second process needs the data, that is what the listener above — or the full server — is for. The store can also take a RAM budget in-process (with_tier_budget): cold values spill to disk and page back inside your process — with the honest note that an in-process cold read holds the store's lock for the read's duration, which is why the largest spillable value is capped at 256 KiB by default. The tiering guide has the details.

In a browser tab

481 KB gzipped. Persists to the browser's own filesystem, survives a reload, and speaks pub/sub across tabs.

Open it, persistent
import { open } from "@goliapkg/kevy";

const db = await open({ persist: { name: "app" } });
Write with a real TTL, read after a reload
db.set("cart:u881", JSON.stringify(items), { ttlMs: 86_400_000 });
db.get("cart:u881");        // still there after a reload
db.pttl("cart:u881");       // the engine expires it, not your code
Hear the other tabs
db.subscribe("sync", (payload) => merge(payload));

Cost & limits localStorage is faster for a small synchronous read — it is a map in the page's own address space, and nothing built on OPFS will beat it at that. kevy wins on everything that makes localStorage a bad idea anyway: real TTLs, no 5 MB ceiling, byte values rather than strings, and writes that do not block the main thread.

On a microcontroller

no_std, no allocator, no operating system: the store lives in a fixed arena you size yourself, and CI boots it on every push.

Strip it down
# Cargo.toml
kevy-store = { version = "4.0", default-features = false }
Give it memory, use it
let mut arena = [0u8; 64 * 1024];
let mut store = Store::new_in(&mut arena);
store.set(b"temp", b"21.4")?;

Cost & limits The arena is fixed. There is no growing it at runtime — that is what "no allocator" means, and sizing it is your design decision, not the engine's. The feature tiers and what each one costs in bytes are in the IoT guide.

Next

Guide

kevy on WebAssembly

The browser build, OPFS persistence, and the size budget.

Read it →

Guide

The embedded listener

Embed the engine and still speak RESP on a socket.

Read it →

Guide

IoT and bare metal

no_std, the arena, and the feature tiers.

Read it →