CouchDB, but for SQLite. A local, embeddable document store with CouchDB semantics — revision trees, optimistic concurrency, deterministic conflict-winner selection, per-type JSONSchema validation, and a changes feed that supports replication from/to other slouchdb instances.
Built on node:sqlite (Node 25+). Synchronous API, no external runtime
dependencies beyond zod for schema validation.
v0 / early work. The core document store, revision model, validator, and replication primitives are implemented. An HTTP surface and a network replicator are not — slouchdb is a library today, not a server.
npm install slouchdbRequires Node.js 25+ (for native .ts execution and built-in
node:sqlite).
import { openStore, put, get, remove } from "slouchdb";
import { validate } from "slouchdb/validate";
const store = openStore("./my.db", { validate });
// Register a schema. Schemas are documents under `_schema/<type>`.
put(store, {
_id: "_schema/note",
_type: "_schema",
type: "object",
properties: { title: { type: "string" } },
required: ["title"],
});
// Genesis write.
const a = put(store, { _id: "n1", _type: "note", title: "hello" });
// Extension — must pass the current leaf as `_parent`.
const b = put(store, {
_id: "n1",
_type: "note",
_parent: a._rev,
title: "hello, world",
});
get(store, "n1"); // -> b (the winning revision)
remove(store, "n1", b._rev); // tombstoneEvery write produces a new row. A _rev is "<gen>-<hash>":
genstarts at1for a genesis write, increments by one per extension.hashis a content hash over{_id, _gen, _parent, _type, _deleted, ...data}.
Identical edits on two replicas converge to the same _rev. Identical content
at different generations hashes differently.
Among the leaves of an _id (revisions with no child), the winner is picked
deterministically:
- Prefer non-deleted over deleted.
- Then highest generation (compared numerically — gen
11beats gen2). - Then lexicographic tiebreak on the hash.
Identical on every replica, so no coordination is required.
Schemas live in the same documents table as everything else:
_type = "_schema",_id = "_schema/<type-name>".- User data is a JSONSchema object.
- Schemas participate in MVCC and replicate like any other document.
- When a write has
_type = "foo", the validator looks up_schema/foo, compiles it withz.fromJSONSchema, and validates the user-data portion. The compiled schema is cached by the schema doc's_rev— content-addressed, so updates invalidate automatically.
If no schema exists for a type, validation is skipped. Rationale: a freshly replicated database may receive typed documents before their schema has replicated in. Enforcement kicks in once the schema is known.
bulkInsert is the replication ingress. It bypasses the leaf check (forks are
intentional), recomputes each revision's hash and rejects tampered rows, and
permits missing ancestors. Schema validation is skipped here — a batch may
carry the schema alongside documents that depend on it, and ingest must not
stall on order.
changesSince(seq) is the egress. It returns every revision with
_local_seq > seq in order — CouchDB's style=all_docs shape. Callers pick
their own checkpoint.
All functions are synchronous, matching node:sqlite.
openStore(path, { validate? }): Store
closeStore(store): void
put(store, { _id, _type?, _parent?, _deleted?, ...data }): Document
remove(store, id, parentRev): Document
get(store, id): Document | undefined // winning leaf
getRevision(store, rev): Document | undefined // any revision by _rev
getLeaves(store, id): Document[] // all current leaves
getResolved(store, id): { winner, conflicts } | undefined
bulkInsert(store, docs): { inserted, skipped, rejected }
changesSince(store, since): Document[]
formatRev(gen, hash): string
parseRev(rev): { gen, hash }Errors:
ConflictError—putwas given a stale or missing_parent.ValidationError—putfailed schema validation.IntegrityError—bulkInsertrecomputed a different hash than the one carried in_rev.
See docs/spec.md for the full specification.
npm run check # tsc --noEmit
npm test # node --test src/**/*.test.ts
npm run fmt # prettier --write
npm run lint # eslint .- Revision-tree pruning / stemming (slouchdb keeps every revision).
- Compaction and tombstone purging.
- Attachments.
- View / MapReduce indexes.
- A meta-schema for
_schemadocuments. - HTTP surface and a network replicator.
MIT © Gordon Brander