Local-first database
for the browser
Encrypted sync. Built-in collaboration. Zero backend.
Device A
Device B
Buy coffee beans
Fix login bug
Buy coffee beans
Fix login bug
Nostr Relay
Every record is gift-wrapped with NIP-59 encryption. The relay stores and forwards encrypted blobs — it never sees your data.
Everything you need
A complete toolkit for local-first applications
Local First
Your app works offline. All data lives on your device in IndexedDB. No server required, no loading spinners.
Encrypted Sync
Data syncs to Nostr relays, encrypted end-to-end with NIP-59 gift wrapping. Relay operators cannot read your data.
Built-in Collaboration
Share databases with invite links. Key rotation on member removal — like changing locks when a roommate moves out.
Typed Collections
Define schemas with collection() and field.*() builders. Full TypeScript inference from schema to query results.
Svelte 5 Integration
First-class reactive bindings using async runes. Queries inside $derived(await ...) auto-update when data changes.
Configurable Logging
Effect-based structured logging with timing spans. From full debug output to complete silence in one config line.
Two APIs, one database
Use the Effect API for full control, or the Svelte API for reactive simplicity
src/lib/db.ts
import { Tablinum, collection, field } from "tablinum/svelte";
const schema = {
todos: collection("todos", {
title: field.string(),
done: field.boolean(),
}, { indices: ["done"] }),
};
export const db = new Tablinum({
schema,
relays: ["wss://relay.example.com"],
});
export const todos = db.collection("todos");src/routes/+page.svelte
<script lang="ts">
import { todos } from "$lib/db";
let title = $state("");
let pending = $derived(await todos.where("done").equals(false).get());
async function addTodo() {
await todos.add({ title, done: false });
title = "";
}
</script>
<form onsubmit={addTodo}>
<input bind:value={title} placeholder="Add a todo..." />
<button type="submit">Add</button>
</form>
{#each pending as todo (todo.id)}
<div>{todo.title}</div>
{/each}How it works
Store locally
Data lives in IndexedDB on your device. Reads are instant, writes never block.
Encrypt
Every record is wrapped in NIP-59 gift wrapping. Relays see encrypted blobs, nothing more.
Sync via relays
NIP-77 negentropy figures out what's missing and only transfers the difference.
Available everywhere
Your data arrives on every device and every collaborator, fully decrypted and ready to use.