materialize-drizzle
Type-safe materializer using Drizzle ORM schemas
@rippledb/materialize-drizzle
A type-safe materializer that uses Drizzle ORM table definitions. No raw SQL needed.
Installation
pnpm add @rippledb/materialize-drizzle drizzle-ormnpm install @rippledb/materialize-drizzle drizzle-ormyarn add @rippledb/materialize-drizzle drizzle-ormWhy Use This?
| Feature | materialize-db | materialize-drizzle |
|---|---|---|
| Type safety | No | Yes |
| Raw SQL | Yes | No |
| Drizzle integration | Manual | Native |
| Multi-database | Via dialects | Via Drizzle |
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 { SqliteDb } from "@rippledb/db-sqlite";
import { createDrizzleSyncMaterializer } from "@rippledb/materialize-drizzle";
const schema = defineSchema({
todos: { id: s.string(), title: s.string(), done: s.boolean() },
});
// Define your domain table
const todosTable = sqliteTable("todos", {
id: text("id").primaryKey(),
title: text("title"),
done: integer("done"),
});
// Define the tags table (required)
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"),
});
const db = new SqliteDb<MySchema>({
filename: "./data.db",
schema,
materializer: ({ db, schema }) => {
const drizzleDb = drizzle(db);
return createDrizzleSyncMaterializer({
schema,
tableMap: { todos: todosTable },
tagsTableDef: tagsTable,
getTableConfig,
});
},
});Sync vs Async
Use the right function based on your database driver:
| Driver | Function |
|---|---|
better-sqlite3 | createDrizzleSyncMaterializer |
pg, mysql2, @libsql/client | createDrizzleMaterializer |
// Synchronous (SQLite with better-sqlite3)
import { createDrizzleSyncMaterializer } from "@rippledb/materialize-drizzle";
// Asynchronous (PostgreSQL, MySQL, Turso)
import { createDrizzleMaterializer } from "@rippledb/materialize-drizzle";Configuration
schema
Schema descriptor used for field discovery and type inference.
tableMap
Maps entity names to Drizzle table definitions.
createDrizzleSyncMaterializer({
schema,
tableMap: {
todos: todosTable,
users: usersTable,
},
// ...
});tagsTableDef
Drizzle table definition for the tags table. Required columns:
entity(text, not null)id(text, not null)data(text, not null)tags(text, not null)deleted(integer, not null, default 0)deleted_tag(text, nullable)
Primary key: (entity, id)
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";fieldMap
Optional mapping from schema fields to column names. If omitted, field names are used as-is.
createDrizzleSyncMaterializer({
schema,
// ...
fieldMap: {
todos: {
id: "id",
title: "title",
done: "done",
},
},
});Without fieldMap, only the ripple_tags table is updated. Add fieldMap to
also write to domain tables.
normalizeValue
Transform values before writing to the database. Useful for SQLite boolean conversion:
createDrizzleSyncMaterializer({
schema,
// ...
normalizeValue: (value, { tableName, columnName }) => {
// Convert booleans to integers for SQLite
if (typeof value === "boolean") {
return value ? 1 : 0;
}
return value;
},
});ensureTagsTable
Hook to create the tags table if it doesn't exist:
createDrizzleSyncMaterializer({
schema,
// ...
ensureTagsTable: () => {
db.exec(`
CREATE TABLE IF NOT EXISTS ripple_tags (
entity TEXT NOT NULL,
id TEXT NOT NULL,
data TEXT NOT NULL,
tags TEXT NOT NULL,
deleted INTEGER NOT NULL DEFAULT 0,
deleted_tag TEXT,
PRIMARY KEY (entity, id)
)
`);
},
});Full Example (PostgreSQL)
import { Client } from "pg";
import { drizzle } from "drizzle-orm/node-postgres";
import { pgTable, text, integer, primaryKey } from "drizzle-orm/pg-core";
import { getTableConfig } from "drizzle-orm/pg-core";
import { createDrizzleMaterializer } from "@rippledb/materialize-drizzle";
const todosTable = pgTable("todos", {
id: text("id").primaryKey(),
title: text("title"),
done: integer("done"),
});
const tagsTable = pgTable(
"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"),
},
(t) => ({
pk: primaryKey({ columns: [t.entity, t.id] }),
}),
);
const client = new Client({ connectionString: process.env.DATABASE_URL });
await client.connect();
const drizzleDb = drizzle(client);
// Use with db-drizzle
const rippleDb = new DrizzleDb({
db: drizzleDb,
changesTable,
idempotencyTable,
getTableConfig,
materializer: ({ schema }) =>
createDrizzleMaterializer({
schema,
tableMap: { todos: todosTable },
tagsTableDef: tagsTable,
getTableConfig,
fieldMap: { todos: { id: "id", title: "title", done: "done" } },
}),
});Using with db-sqlite
When using db-sqlite, you must use the sync version:
import { SqliteDb } from "@rippledb/db-sqlite";
import { createDrizzleSyncMaterializer } from "@rippledb/materialize-drizzle";
const db = new SqliteDb<MySchema>({
filename: "./data.db",
schema,
materializer: ({ db, schema }) => {
const drizzleDb = drizzle(db);
return createDrizzleSyncMaterializer({
schema,
tableMap: { todos: todosTable },
tagsTableDef: tagsTable,
getTableConfig,
fieldMap: { todos: { id: "id", title: "title", done: "done" } },
});
},
});Using with db-drizzle
For the most integrated experience, use both db-drizzle and materialize-drizzle:
import { DrizzleDb } from "@rippledb/db-drizzle";
import { createDrizzleMaterializer } from "@rippledb/materialize-drizzle";
const db = new DrizzleDb<MySchema, typeof drizzleDb>({
db: drizzleDb,
changesTable,
idempotencyTable,
getTableConfig,
isSync: true,
schema,
materializer: ({ schema }) =>
createDrizzleMaterializer({
schema,
tableMap: { todos: todosTable },
tagsTableDef: tagsTable,
getTableConfig,
fieldMap: { todos: { id: "id", title: "title", done: "done" } },
}),
});Related
- db-drizzle — Drizzle database adapter
- db-sqlite — SQLite database adapter
- materialize-db — SQL-based alternative
- Core Concepts — Understand field-level LWW