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-ormnpm install @rippledb/db-drizzle drizzle-ormyarn add @rippledb/db-drizzle drizzle-ormPlus your database driver:
# SQLite
pnpm add better-sqlite3
# PostgreSQL
pnpm add pg
# MySQL
pnpm add mysql2Basic 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:
| Driver | isSync Value |
|---|---|
better-sqlite3 | true |
pg (node-postgres) | false (default) |
mysql2 | false (default) |
@libsql/client | false (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: falsewith better-sqlite3: Error: "Transaction function cannot return a promise"isSync: truewith 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 keystream— Text, partition keychange_json— Text, serialized change
idempotencyTable
Drizzle table definition for idempotency tracking. Required columns:
stream— Text, partition keyidempotency_key— Textlast_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 migrateOr 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
| Feature | db-sqlite | db-drizzle |
|---|---|---|
| Database support | SQLite only | Any Drizzle-supported DB |
| Type-safe queries | No | Yes |
| ORM integration | Manual | Native |
| Custom materializer | SQL-based | Drizzle query builder |
Related
- materialize-drizzle — Type-safe materializer
- db-sqlite — Simpler SQLite-only adapter
- Server Reference — Full Db interface