RippleDB
RippleDB
Adapters

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:

  1. Database Adapters (db-*) — Store the change log and handle append/pull
  2. Materializers (materialize-*) — Project changes into domain tables
  3. Stores (store-*) — Client-side local truth store (what the UI reads from)
  4. Bindings (bind-*) — Wire DbEvents to UI cache invalidation (e.g. TanStack Query)

Decision Tree

Which Database Adapter?

YesNoSQLiteTursoIn-memoryAre you using Drizzle ORM?db-drizzleWhat database?db-sqlitedb-tursodb-memory

Which Materializer?

YesNoUsing db-drizzle?materialize-drizzlematerialize-db

Quick Reference

AdapterBest ForSync/AsyncNotes
db-sqliteLocal servers, Electron, testsSyncUses better-sqlite3
db-tursoEdge deployments, serverlessAsyncUses @libsql/client
db-drizzleExisting Drizzle projectsBothDatabase-agnostic
db-memoryUnit testsSyncNo persistence
store-memoryUnit testsSyncClient truth store, no persistence
store-sqliteProduction client appsSyncPersistent, SQL WHERE clauses
materialize-dbRaw SQL projectsBothWorks with db-sqlite, db-turso
materialize-drizzleDrizzle projectsBothType-safe queries

Database Adapters

Materializers

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.

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 AdapterRecommended Materializer
db-sqlitematerialize-db
db-tursomaterialize-db
db-drizzlematerialize-drizzle
db-memoryCustom 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.

On this page