mates-db
v0.1.1
Published
IndexedDB persistence for mates — DBAtom (reactive tables) and keyValueDB (opaque key/value).
Maintainers
Readme
mates-db
IndexedDB persistence for mates:
| API | Best for |
| --- | --- |
| DBAtom | Small reactive tables (rows with id) loaded into an atom |
| heavyDbAtom | Large tables — paged/sorted window driven by a query atom |
| keyValueDB | Opaque values by key — no list / query |
Web Storage helpers (LSAtom, SSAtom, LSList, SSList, lsStore, ssStore) live in mates itself.
Installation
npm install mates-db
npm install mates@>=0.5.1DBAtom(tableName)
Map-like reactive table. Mutators are async but update the in-memory atom
immediately, then persist to IndexedDB.
Component-scoped — each call creates a new handle (not a global singleton).
Open it when the page/component needs the table; call dispose() on unmount
(unsubscribes from xTabEvent). Prefer:
import { onCleanup, onMount } from "mates";
import { DBAtom, type DBAtomHandle } from "mates-db";
let sessions: DBAtomHandle<{ id?: string; js: string }> | null = null;
onCleanup(() => {
sessions?.dispose();
sessions = null;
});
onMount(async () => {
sessions = await DBAtom("sessions");
});(registerCleanup is also attempted inside DBAtom, but async onMount often
has no host context — so pair with onCleanup as above.)
const sessions = await DBAtom<{ id?: string; js: string }>("sessions");
const id = await sessions.add({ js: "console.log(1)" });
await sessions.add([{ js: "a" }, { id: "fixed", js: "b" }]);
await sessions.setItem(id, { js: "…" });
await sessions.setItem(id, (row) => ({ ...row, js: row.js + "!" }));
await sessions.set([{ id: "a", js: "x" }, { js: "y" }]);
await sessions.set((prev) => prev.filter((s) => s.id !== "a"));
await sessions.update((rows) => {
rows[0].js = "mutated";
rows.push({ js: "extra" });
});
await sessions.delete(id);
await sessions.deleteAll();
sessions.dispose();SSR / no IndexedDB → in-memory empty atom.
Cross-tab sync via mates xTabEvent (debounced 100ms): after a local write,
DBAtom calls xTabEvent.trigger("mates-db:sync", { store }). Other tabs
(and other handles) subscribe and dbGetAll / re-query.
heavyDbAtom(tableName, indexKeys, queryAtom)
For large tables. Declares IndexedDB indexes, then keeps a result atom in
sync with a query atom (search / filter / page / sort). Only the current page
is held in memory — the cursor walks the sortKey index.
Same lifecycle as DBAtom: new handle per call, dispose() on unmount.
sortKey must be one of indexKeys. sortOrder is "ASC" | "DSC".
import { atom, onCleanup, onMount } from "mates";
import { heavyDbAtom, type HeavyDbQuery, type HeavyDbAtomHandle } from "mates-db";
type Event = { id?: string; title: string; createdAt: number };
const query = atom<HeavyDbQuery<Event>>({
search: "",
page: 1,
sortKey: "createdAt",
sortOrder: "DSC",
itemsPerPage: 10,
filterFn: (row) => row.title.length > 0,
});
let list: HeavyDbAtomHandle<Event> | null = null;
onCleanup(() => {
list?.dispose();
list = null;
});
onMount(async () => {
list = await heavyDbAtom<Event>("events", ["createdAt", "title"], query);
});
// list() → { rows, page, itemsPerPage, hasMore, status }
query.set((q) => ({ ...q, page: 2, search: "launch" }));
await list?.refresh();Populate the table with DBAtom / dbPut, or your own writers. Cross-tab
sync pings re-run the current query (not a full table load).
keyValueDB(tableName)
const kv = await keyValueDB("cache");
await kv.add("user:1", { name: "Ada" });
await kv.get("user:1");
await kv.update("user:1", { name: "Ada Lovelace" });
await kv.delete("user:1");
await kv.clearAll();deleteTable(tableName)
Drops an IndexedDB object store used by DBAtom / keyValueDB.
Development
npm install
npm test
npm run test:coverage
npm run build