Ship persistence with your package. Use the database the app already owns.
CapsuleDB lets an Effect library own its tables, typed queries and migrations inside the host app's existing database. The host keeps its client. The package keeps its schema private.
npm i capsuledb effectexport const capsule = Capsule.make({
id: "reference.tokens",
tables: [tokens, tokenAudit],
migrations: [
Migration.make({
id: 1,
name: "create-tokens",
risk: "additive",
steps: [Migration.createTable(tokens)],
}),
Migration.make({
id: 2,
name: "add-token-audit",
risk: "additive",
steps: [Migration.createTable(tokenAudit)],
}),
],
layer: tokenLayer,
});example_app=> \dt
| Name | Owned by |
|---|---|
| accounts | app |
| sessions | app |
| capsule_reference_2e_ | reference.tokens |
| capsule_reference_2e_ | reference.tokens |
| capsuledb_registry_ledger | capsuledb |
| capsuledb_registry_metadata | capsuledb |
example_app=> select capsule_id, migration_id, name from capsuledb_registry_ledger;
| capsule_id | migration_id | name |
|---|---|---|
| reference.tokens | 1 | create-tokens |
| reference.tokens | 2 | add-token-audit |
The reference capsule prefixes its tables with the namespace CapsuleDB derives from its ID. The host cannot rename it, and the tables stay out of the package's public API.
Three steps from package to host
Each block is a region of examples/quick-start.ts, which CI typechecks and runs against the built package.
Declare the table
The package declares its table once. CapsuleDB renders it for PostgreSQL and SQLite.
const visits = Schema.table("capsule_example_2e_visits", { columns: { path: Schema.text(), hits: Schema.integer(), }, primaryKey: ["path"], });Export one capsule
Tables, an append-only migration history and an Effect service, bundled as one module constant the host can import without running anything.
export const capsule = Capsule.make({ id: "example.visits", tables: [visits], migrations: [ Migration.make({ id: 1, name: "create-visits", risk: "additive", steps: [Migration.createTable(visits)], }), ], layer: visitsLayer, });Compose it over the host's client
The host lists the capsules it trusts.
Run the whole quick startRegistry.layerapplies pending migrations before any service answers, and still requires the host'sSqlClient.export const CapsulesLive = Registry.layer({ provider: SqliteBun.profile, capsules: [capsule], });
In use: DomainKit ships its PostgreSQL storage as one CapsuleDB capsule, @domainkit/capsuledb.
The boundary in one minute
A silent film on what the package owns, what the host owns, and how preparation reaches Ready.
Read the transcriptOne migration history, four providers
Pick the profile that matches the host's client. The logical history stays the same.
| Profile | Dialect | Execution | Notes |
|---|---|---|---|
Pg.profile | postgres | Transactional | Preparation serializes with an advisory lock. |
SqliteBun.profile | sqlite | Transactional | Host-owned Bun SQLite client. |
Libsql.profile | sqlite | Transactional | Host-owned libSQL client. |
D1.profile | sqlite | Atomic batch | Bounded batches. No interactive transactions or Effect steps. |
Where it stops
CapsuleDB coordinates migrations and provides a capsule's service over the host's client. It does nothing past that line.
- Not an ORM
- No public tables, rows or query builders. Persistence details stay inside the capsule.
- Not a deployment service
- It does not invoke Wrangler or manage provider resources. The optional Alchemy resource prepares a database the host already runs.
- Not a second connection
- The host's client stays the host's, from open to close.
- Not a sandbox
- Namespace collisions are rejected, but trusted capsule SQL is not isolated. The host composes only what it trusts.
Guides
The full contract, from both sides of the boundary.
- Capsule authorsIDs, private migrations, services and provider bodies.
- Host applicationsClient ownership, preparation, readiness and serverless deploys.
- ProvidersThe capability matrix and D1's atomic-batch contract.
- Migrations and recoveryAppend-only changes and what to do when preparation fails.
- Design principlesThe contract and the boundaries it keeps.
- ChangelogEvery release, with the Effect versions it requires.


