TanStack DB binding: typed, live-synced collections and a durable offline outbox over the Lunora client
Daniel Bannert's open source work is supported by the community on GitHub Sponsors
A TanStack DB binding for the Lunora client. defineCollections wires your Lunora queries and mutations into live, indexed client collections (reads) and a durable, retried offline-transactions outbox (writes) — so a write renders instantly, survives reloads and offline windows, is superseded by the real server row on acknowledgement, and rolls back if the server rejects it. Peer-depends on @tanstack/db and @tanstack/offline-transactions.
Part of the Lunora framework — a type-safe, real-time backend on Cloudflare Workers + Durable Objects with a Vite-first DX.
npm install @lunora/dbyarn add @lunora/dbpnpm add @lunora/dbimport { defineCollections } from "@lunora/db";
import type { LunoraClient } from "@lunora/react";
import { api } from "./_generated/api";
import type { Doc, Id } from "./_generated/dataModel";
export const createCollections = (client: LunoraClient) =>
defineCollections(client, {
messages: {
list: api.messages.list,
scopeBy: "channelId", // sharded — re-point with db.scope.messages({ channelId })
insert: {
mutation: api.messages.send,
optimistic: (input: Omit<Doc<"messages">, "_id" | "_creationTime">, id) => ({
_id: id as Id<"messages">,
_creationTime: Date.now(),
...input,
}),
toArgs: (row) => ({ channelId: row.channelId, id: row._id, text: row.text }),
},
},
users: { list: api.users.list }, // read-only
});
// → db.collections.* (feed useLiveQuery), db.actions.* (optimistic writes),
// db.scope.* (re-point sharded collections), db.executor (the outbox)
vis generate lunora-collectionsscaffolds this from yourschema.ts+ functions.
Keep a single db instance, and treat its collections as the one source of
truth per table — don't mirror rows into your own store. A derived index (a tree,
a search index, an undo capture) built from a copy of the rows can silently read
stale data while the UI renders through useLiveQuery. See One source of truth
per table.
Beyond whole-table collections, the @lunora/db/collections and
@lunora/db/mutators subpaths expose the local-first sync engine:
lunoraCollectionOptions({ shape }) syncs a partial replication shape (only
the rows a client needs, scoped by a server-resolved predicate) over the poke
diff protocol, and defineMutator + bindMutators run optimistic custom
mutators (a local body first, a server-authoritative impl second, rebased on
every sync tick). The framework adapters add a useMutator / createMutator /
mutator hook over a bound handle. See the
local-first guide.
This README covers the basics. For the full API, options, and guides, see the documentation.
@lunora/client— the browser SDK this layer syncs over.@lunora/react— React hooks for Lunora queries and mutations.@lunora/server— server primitives that define the queries and mutations you bind.
Libraries in this ecosystem make the best effort to track Node.js' release schedule. Here's a post on why we think this is important.
If you would like to help take a look at the list of issues and check our Contributing guidelines.
Note: please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.
This is an open source project and will always remain free to use. If you think it's cool, please star it 🌟. Anolilab is a Development and AI Studio. Contact us at hello@anolilab.com if you need any help with these technologies or just want to say hi!
The Lunora db package is open-sourced software licensed under the FSL-1.1-Apache-2.0.