Docs site recipe | Spacefast
The dish
What it makes.
v2.3 v2.4 · local-first state for TypeScript
Reactive data for web apps, in 4 kilobytes.
Local-first state for the web. Define typed collections, write reactive queries, and persist to IndexedDB — in 4 kB, with zero dependencies.
$npm i ledgerCopy
Why Ledger
A data layer that gets out of your way
Everything you need to model, query, and persist app state — without dragging in a database server or a megabyte of client code.
Type-safe by default
Collections are generic. Inserts, queries, and results are fully inferred — no codegen, no decorators.
Reactive queries
Call .live() on any query and subscribe. Ledger recomputes only what changed and emits the new result.
Local-first
Pluggable IndexedDB and localStorage adapters keep data on the device and survive reloads and offline use.
4 kB, zero deps
The whole core is 4 kB min+gzip with no runtime dependencies. Tree-shakeable down to what you import.
Framework bindings
First-party hooks for React, Vue, and Svelte. The core is framework-agnostic and works in plain JS too.
Safe migrations
Version your schema and ship migration steps. Ledger upgrades stored data on load, in order, exactly once.
Quick look
Define a collection, write a live query
Ledger queries are plain functions over typed records. Add .live() and you get a subscription that stays in sync as data changes — no manual invalidation.
- Full type inference from your record types
- Chainable
where/sort/limit - Fine-grained updates — subscribers fire only when their result changes
import { createLedger, collection } from 'ledger';
type Todo = { id: string; text: string; done: boolean };
const db = createLedger({
todos: collection<Todo>(),
});
// A live query re-runs whenever matching data changes.
const active = db.todos.where((t) => !t.done).sort('text').live();
active.subscribe((todos) => render(todos));
db.todos.insert({ id: 't1', text: 'Ship the docs', done: false });
// → subscriber fires with [{ id: 't1', text: 'Ship the docs', done: false }]
TodoList.tsx
import { useQuery } from 'ledger/react';
function TodoList() {
const todos = useQuery(() => db.todos.where((t) => !t.done).live());
return <ul>{todos.map((t) => <li key={t.id}>{t.text}</li>)}</ul>;
}
Framework bindings
One hook, zero boilerplate
The React binding turns any live query into reactive component state. The same query works untouched on the server, in a worker, or in plain JavaScript.
- Hooks for React, Vue, and Svelte
- Automatic subscribe / unsubscribe on mount
- SSR-safe — queries run anywhere
4 kB min + gzip
0 dependencies
100% typed API
MIT licensed
Start here
Pick a path
Whether you're wiring up your first store or migrating a production schema, there's a short guide for it.
- Getting started: Install, create a store, and render your first reactive list in five minutes. Read the guide → Getting started
- Reactive queries: Filters, sorting, derived queries, and how live subscriptions stay efficient. Learn queries → Reactive queries
- Persistence: Keep data on the device with the IndexedDB and localStorage adapters. Persist data → Persistence
- API reference: Every function, option, and return type — with copy-paste examples. Browse the API → API reference
Your ingredients
Your agent will ask.
Answer these once. The recipe uses your details instead of placeholder copy.
- What's the library/product and a one-line description of what it does?
- What are the top 4–5 doc sections (e.g. Getting started, Guides, API reference, Changelog)?
- Give me one real code example to feature on the landing page.
- (Optional) What's the brand accent color, and the GitHub / npm links?
The method
The whole prompt.
Paste this into Claude, ChatGPT, Codex, Cursor, or another coding agent. It builds the site and publishes it to Spacefast.