Adapters Overview
Choose the right database adapter and materializer for your use case
Adapters
RippleDB uses adapters to integrate with different runtimes, databases, and ORMs. There are four types:
- Database Adapters (
db-*) — Store the change log and handle append/pull - Materializers (
materialize-*) — Project changes into domain tables - Stores (
store-*) — Client-side local truth store (what the UI reads from) - Bindings (
bind-*) — Wire DbEvents to UI cache invalidation (e.g. TanStack Query)
Decision Tree
Which Database Adapter?
Which Materializer?
Quick Reference
| Adapter | Best For | Sync/Async | Notes |
|---|---|---|---|
db-sqlite | Local servers, Electron, tests | Sync | Uses better-sqlite3 |
db-turso | Edge deployments, serverless | Async | Uses @libsql/client |
db-drizzle | Existing Drizzle projects | Both | Database-agnostic |
db-memory | Unit tests | Sync | No persistence |
store-memory | Unit tests | Sync | Client truth store, no persistence |
store-sqlite | Production client apps | Sync | Persistent, SQL WHERE clauses |
materialize-db | Raw SQL projects | Both | Works with db-sqlite, db-turso |
materialize-drizzle | Drizzle projects | Both | Type-safe queries |
Database Adapters
db-sqlite
Production-ready SQLite adapter using better-sqlite3
db-turso
Edge-ready adapter for Turso and libSQL
db-drizzle
Database-agnostic adapter using Drizzle ORM
db-memory
In-memory adapter for testing
Materializers
materialize-db
SQL-based materializer for raw SQL projects
materialize-drizzle
Type-safe materializer using Drizzle schemas
Materialization
Materialization is how you, the app developer, get a fast “current state” view of your data at all times.
RippleDB stores an append-only change log. That log is great for sync, but you usually don’t want to replay changes on every read. A materializer incrementally applies incoming changes into your domain tables (a live snapshot), so your application can query current rows efficiently.
Who is this for?
- You want normal reads:
SELECT * FROM todos WHERE ...(or Drizzle queries) against current state. - You need a live snapshot: UI and APIs read domain tables that always reflect the latest applied changes.
- You care about performance: reads stay fast as history grows.
RippleDB does not need materialization
RippleDB does not require materialization. You can use the change log directly if you want. All our adapters on the client side consume changes directly from the change log.
When this might change
A Snapshot is a powerful tool to keep new clients in sync with the latest state before moving them to the change log. But this is not implemented yet. And will be optional in the future yet recommended for production use.
When you might skip it
- Prototypes/tests where you only care about the log
- You already maintain your own projection layer and want RippleDB only for replication history
Stores
Stores implement the client-side Store contract (@rippledb/client). They are optional but recommended if you want a consistent local "truth DB" that emits DbEvents for UI reactivity.
store-memory
In-memory Store implementation for tests and examples
store-sqlite
SQLite-based Store with persistent storage and SQL WHERE clauses
Bindings
Bindings wire RippleDB's DbEvents to UI cache invalidation. When your Store applies changes, it emits events like { entity: 'todos', kind: 'update', id: '1' }. A binding translates these into cache invalidations for your UI framework.
Pairing Guide
These are the supported pairings we test and guarantee (especially around atomicity/transactions). Other combinations can be made to work, but you may need custom wiring to ensure the Db adapter and materializer share the same transaction/connection semantics.
| Database Adapter | Recommended Materializer |
|---|---|
db-sqlite | materialize-db |
db-turso | materialize-db |
db-drizzle | materialize-drizzle |
db-memory | Custom or none |
Why These Pairings?
- db-sqlite + materialize-db: Both speak raw SQL and can share the same connection/transaction.
- db-turso + materialize-db: Both use the libSQL driver and transaction model.
- db-drizzle + materialize-drizzle: Both use Drizzle’s schema + query builder, keeping things type-safe and consistent.
Can I mix-and-match?
Yes — the limitation is usually support/guarantees, not a hard technical block.
For example, db-sqlite + materialize-drizzle could work if you:
- create a Drizzle instance on top of the same SQLite connection used by
db-sqlite - ensure materialization runs inside the same
db.append()transaction boundary
If you don’t share the transaction/connection, you can break the atomicity guarantee (log written but materialization fails, or vice versa).
Atomicity
All adapters guarantee atomicity within db.append():
- Changes are written to the log
- Materialization happens in the same transaction
- If anything fails, everything rolls back
Why is this important?
Atomicity prevents “half-applied” state, which is one of the hardest classes of bugs in sync systems:
- If the log write succeeds but materialization fails, reads from your domain tables won’t match what replication history says happened.
- If materialization succeeds but the log write fails/rolls back, clients may never receive the change again (history is missing) even though your tables changed.
Keeping log + materialized state in one transaction makes failures safe: either the whole change is visible everywhere, or it’s as if it never happened.
With db-drizzle, set isSync: true for synchronous drivers like
better-sqlite3 to ensure proper transaction handling. See the db-drizzle
docs for details.
Custom Adapters
You can implement your own adapters by following the Db interface:
interface Db<S extends RippleSchema> {
append(stream: string, changes: Change<S>[]): Promise<{ accepted: number }>;
pull(
stream: string,
cursor: number | null,
): Promise<{ changes: Change<S>[]; cursor: number }>;
close(): void;
}See the Server Reference for the full interface definition.