experimental@0xsarwagya/durable-local
Why does an object need a database to survive a refresh?
I refuse to build a database because an object should survive a refresh.
The browser already has storage. You have a value. You want to come back tomorrow and find it there. That does not need a query language.
pnpm add @0xsarwagya/durable-local# or: npm install @0xsarwagya/durable-localThe whole API
import { createDurable } from "@0xsarwagya/durable-local";
const durable = createDurable();
const workspace = await durable.open("workspace", {
initial: {
title: "Untitled",
blocks: [],
},
});
workspace.value; // { title: "Untitled", blocks: [] }
await workspace.update((current) => ({
...current,
title: "Something",
}));
// Close the tab. Come back tomorrow. Still there.The boundary
Not a database
What it guarantees
| Invariant | Meaning |
|---|---|
| Commit | set() resolves → reload → same value. |
| Failure | Commit fails → previous committed value survives. |
| Concurrency | Two updates → no silent lost write. IDB serializes them. |
| Cross-tab | Tab A commits revision N → Tab B eventually observes N. |
| Migration | Migration fails → previous committed state remains recoverable. |
| Validation | Invalid stored value → never returned as T. Typed error instead. |
Storage is not confidentiality
The API surface
const durable = createDurable({ namespace?: string });
const slot = await durable.open<T>(name, {
initial: T,
version?: number,
migrations?: { [n: number]: (value) => value },
validate?: (value: unknown) => T,
});
slot.value;
slot.revision;
await slot.set(next);
await slot.update((current) => ({ ...current }));
const unsubscribe = slot.subscribe((value, event) => { ... });
await slot.reset();
await slot.destroy();
const status = await durable.storage();
const granted = await durable.requestPersistence();Used in Local
Local uses durable-local to keep each
peer's chat history alive across reloads. One slot per peer, one
update() per received message, atomic commits, no database. When a
peer's public key changes on reconnect, the slot refuses to attach —
that pinning is the only thing between recovered identity and silent
peer substitution. Source:
github.com/0xsarwagya/local.