RippleDB
RippleDB
Adapters

db-drizzle

Database-agnostic adapter using Drizzle ORM

@rippledb/db-drizzle

A database-agnostic adapter that uses Drizzle ORM for all database operations. Works with any Drizzle-supported database: SQLite, PostgreSQL, MySQL, and more.

Installation

pnpm add @rippledb/db-drizzle drizzle-orm
npm install @rippledb/db-drizzle drizzle-orm
yarn add @rippledb/db-drizzle drizzle-orm

Plus your database driver:

# SQLite
pnpm add better-sqlite3

# PostgreSQL
pnpm add pg

# MySQL
pnpm add mysql2

Basic Usage (SQLite)

import { defineSchema, s } from "@rippledb/core";
import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
import { getTableConfig } from "drizzle-orm/sqlite-core";
import { DrizzleDb } from "@rippledb/db-drizzle";
import { createDrizzleSyncMaterializer } from "@rippledb/materialize-drizzle";

const schema = defineSchema({
  todos: { id: s.string(), title: s.string(), done: s.boolean() },
});

// Define Drizzle table schemas for internal tables
const changesTable = sqliteTable("ripple_changes", {
  seq: integer("seq").primaryKey({ autoIncrement: true }),
  stream: text("stream").notNull(),
  change_json: text("change_json").notNull(),
});

const idempotencyTable = sqliteTable("ripple_idempotency", {
  stream: text("stream").notNull(),
  idempotency_key: text("idempotency_key").notNull(),
  last_seq: integer("last_seq").notNull(),
});

// Create the database
const sqlite = new Database("./data.db");
const drizzleDb = drizzle(sqlite);

// Create tables (use Drizzle migrations in production)
sqlite.exec(`
  CREATE TABLE IF NOT EXISTS ripple_changes (
    seq INTEGER PRIMARY KEY AUTOINCREMENT,
    stream TEXT NOT NULL,
    change_json TEXT NOT NULL
  );
  CREATE TABLE IF NOT EXISTS ripple_idempotency (
    stream TEXT NOT NULL,
    idempotency_key TEXT NOT NULL,
    last_seq INTEGER NOT NULL,
    PRIMARY KEY (stream, idempotency_key)
  );
`);

// Create the RippleDB adapter
const db = new DrizzleDb({
  db: drizzleDb,
  changesTable,
  idempotencyTable,
  getTableConfig,
  isSync: true, // Required for better-sqlite3
});

Basic Usage (PostgreSQL)

import { defineSchema, s } from "@rippledb/core";
import { Client } from "pg";
import { drizzle } from "drizzle-orm/node-postgres";
import { pgTable, text, serial, integer } from "drizzle-orm/pg-core";
import { getTableConfig } from "drizzle-orm/pg-core";
import { DrizzleDb } from "@rippledb/db-drizzle";

const schema = defineSchema({
  todos: { id: s.string(), title: s.string(), done: s.boolean() },
});

const changesTable = pgTable("ripple_changes", {
  seq: serial("seq").primaryKey(),
  stream: text("stream").notNull(),
  change_json: text("change_json").notNull(),
});

const idempotencyTable = pgTable("ripple_idempotency", {
  stream: text("stream").notNull(),
  idempotency_key: text("idempotency_key").notNull(),
  last_seq: integer("last_seq").notNull(),
});

const client = new Client({ connectionString: process.env.DATABASE_URL });
await client.connect();
const drizzleDb = drizzle(client);

const db = new DrizzleDb({
  db: drizzleDb,
  changesTable,
  idempotencyTable,
  getTableConfig,
  // isSync defaults to false for async drivers
  schema,
});

The isSync Flag

Critical for SQLite with better-sqlite3: You must set isSync: true.

The isSync flag tells RippleDB whether the underlying Drizzle driver is synchronous or asynchronous:

DriverisSync Value
better-sqlite3true
pg (node-postgres)false (default)
mysql2false (default)
@libsql/clientfalse (default)

Why Is This Needed?

Drizzle drivers have different transaction APIs:

// Synchronous (better-sqlite3)
db.transaction((tx) => {
  tx.insert(...).run();  // Synchronous
});

// Asynchronous (pg, mysql2)
await db.transaction(async (tx) => {
  await tx.insert(...).execute();  // Returns Promise
});

RippleDB needs to know which style to use for proper transaction handling.

What Happens If I Get It Wrong?

  • isSync: false with better-sqlite3: Error: "Transaction function cannot return a promise"
  • isSync: true with async driver: Queries won't await properly, causing data corruption

With Materialization

import { DrizzleDb } from "@rippledb/db-drizzle";
import { and, eq } from "drizzle-orm";

// Tags table for materialization
const tagsTable = sqliteTable("ripple_tags", {
  entity: text("entity").notNull(),
  id: text("id").notNull(),
  data: text("data").notNull(),
  tags: text("tags").notNull(),
  deleted: integer("deleted").notNull().default(0),
  deleted_tag: text("deleted_tag"),
});

// Your domain table
const todosTable = sqliteTable("todos", {
  id: text("id").primaryKey(),
  title: text("title"),
  done: integer("done"),
});

const db = new DrizzleDb<MySchema, typeof drizzleDb>({
  db: drizzleDb,
  changesTable,
  idempotencyTable,
  getTableConfig,
  isSync: true,
  schema,
  materializer: ({ schema }) =>
    createDrizzleSyncMaterializer({
      schema,
      tableMap: { todos: todosTable },
      tagsTableDef: tagsTable,
      getTableConfig,
      fieldMap: { todos: { id: "id", title: "title", done: "done" } },
    }),
});

For a simpler materializer setup, consider using materialize-drizzle which provides sensible defaults.

Options

db

The Drizzle database instance. Works with any Drizzle-supported database.

changesTable

Drizzle table definition for the changes log. Required columns:

  • seq — Auto-increment primary key
  • stream — Text, partition key
  • change_json — Text, serialized change

idempotencyTable

Drizzle table definition for idempotency tracking. Required columns:

  • stream — Text, partition key
  • idempotency_key — Text
  • last_seq — Integer

Primary key: (stream, idempotency_key)

getTableConfig

Import from your Drizzle dialect:

import { getTableConfig } from "drizzle-orm/sqlite-core";
import { getTableConfig } from "drizzle-orm/pg-core";
import { getTableConfig } from "drizzle-orm/mysql-core";

isSync?

Set to true for synchronous drivers (better-sqlite3). Default: false.

schema

Schema descriptor used for entity/field discovery and type inference.

materializer?

Optional factory function for materialization. Receives { db, schema } and returns a materializer adapter (typically from materialize-drizzle).

Table Creation

DrizzleDb does not create tables automatically. Use Drizzle Kit for migrations:

npx drizzle-kit generate
npx drizzle-kit migrate

Or create tables manually:

-- SQLite
CREATE TABLE ripple_changes (
  seq INTEGER PRIMARY KEY AUTOINCREMENT,
  stream TEXT NOT NULL,
  change_json TEXT NOT NULL
);

CREATE TABLE ripple_idempotency (
  stream TEXT NOT NULL,
  idempotency_key TEXT NOT NULL,
  last_seq INTEGER NOT NULL,
  PRIMARY KEY (stream, idempotency_key)
);

Benefits Over db-sqlite

Featuredb-sqlitedb-drizzle
Database supportSQLite onlyAny Drizzle-supported DB
Type-safe queriesNoYes
ORM integrationManualNative
Custom materializerSQL-basedDrizzle query builder

On this page