Thank you for considering a contribution. LibreDB is a small, readable, embeddable database, and the whole point is that you can open the source, understand it, and improve it. This guide explains how to get set up, what the bar for a change is, and how the trust model shapes where contributions land fastest.
Please also read MANIFESTO.md and docs/DESIGN.md — they
explain what LibreDB is, what it deliberately refuses to be, and which decisions are locked.
This project follows the Contributor Covenant. By participating, you agree to uphold it.
LibreDB is open at the edges, guarded at the core. The file boundary is the trust boundary:
- The edges — lenses (
src/lens/), the query surface (src/query/), adapters (src/adapter/), tooling, and docs — are open to fast contribution. Bring a test and a clear rationale and it moves quickly. - The durability core (
src/core.ts) — storage, transactions, recovery — is open for everyone to read, but every line written into it passes heavy review and deterministic testing. Changes here are slower by design, because the core holds your data. Expect deeper review, and expect to back the change with the deterministic simulation suite (src/sim/).
If you are new, the edges are the best place to start.
LibreDB uses Bun as its runtime, package manager, and test runner. The version is
pinned in .bun-version.
bun install
bun run gatebun run gate is the single command that must pass before anything is merged. It runs, in order:
typecheck -> format -> lint -> knip -> build -> size -> test
- typecheck —
tsc --noEmit. - format — Biome (formatter only). Run
bun run format:fixto apply. - lint — Oxlint plus a narrow type-aware typescript-eslint pass.
- knip — no unused files, exports, or dependencies.
- build —
tscemitsdist/with isolated declarations. - size — byte budgets on the shipped entries, public and browser (
.size-limit.json); a heavy or non-tree-shakeable import fails it. - test —
bun test --coverage. Coverage is held at 100% line/function/statement (bunfig.toml); a change that drops coverage fails the gate.
- Tests are the truth. Every change is backed by tests. No stubs, no placeholder assertions, no "TODO: test later". A feature without tests is not done.
- Readability is a design constraint. We open code up to make it clear; we do not compress it to
look clever.
core.tsstays small because it is genuinely minimal — never because complexity was pushed elsewhere. The metric is comprehension time, not line count. - Nothing hidden. Errors surface; they are not swallowed. The query, the schema, and the plan are visible.
- Conventional Commits are enforced locally by a
commit-msggit hook (commitlint). Examples:feat(lens): add prefix scan to the kv lens,fix(core): fsync the directory on first file create,docs: clarify the recovery invariant. - English only, and no emoji anywhere — code, comments, commits, docs.
- PRs are squash-merged: the individual commit messages are discarded and the PR title becomes the single commit, so keep the PR title conventional and descriptive. (The changelog comes from changesets, not commit messages — see below.)
- If your change is user-facing, add a changeset:
bun run changeset. This is what generates the changelog and version bump at release time. Semver policy for the bump: runtime behavior changes and new runtime capabilities are minor; documentation, tooling, and type-only additions (new exported types, widened annotations — anything with no runtime effect) are patch; anything that breaks an existing consumer is major (and, pre-1.0, lands with loud changelog warnings, as the 0.2.0 on-disk format change did). - The CI gate mirrors
bun run gateand runs on every PR, including forks.
Use the issue templates. For bugs, a minimal reproduction is worth more than anything else. For
security vulnerabilities, do not open a public issue — see SECURITY.md.
By contributing, you agree that your contributions are licensed under the MIT License that covers this project.