oss.sarwagya.wtf

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.

Install
pnpm add @0xsarwagya/durable-local# or: npm install @0xsarwagya/durable-local

The 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

InvariantMeaning
Commitset() resolves → reload → same value.
FailureCommit fails → previous committed value survives.
ConcurrencyTwo updates → no silent lost write. IDB serializes them.
Cross-tabTab A commits revision N → Tab B eventually observes N.
MigrationMigration fails → previous committed state remains recoverable.
ValidationInvalid 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.