RippleDB
RippleDB
Getting Started

Quick Start

Build a sync-enabled todo app in 5 minutes

Quick Start

Let's build a simple todo backend with RippleDB. By the end, you'll have a server that:

  • Stores changes in an append-only log
  • Materializes changes into a todos table
  • Supports cursor-based sync for clients

Prerequisites

  • Node.js 18+
  • A new project directory

Install Dependencies

mkdir rippledb-demo && cd rippledb-demo
npm init -y
npm install @rippledb/core @rippledb/db-sqlite @rippledb/materialize-db better-sqlite3
npm install -D typescript @types/better-sqlite3 tsx

Define Your Schema

Create src/schema.ts to define the shape of your data:

src/schema.ts
import { defineSchema, s, type InferSchema } from "@rippledb/core";

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

export type TodosSchema = InferSchema<typeof schema>;

Set Up the Database

Create src/db.ts to configure your RippleDB database:

src/db.ts
import { SqliteDb } from "@rippledb/db-sqlite";
import { createSyncMaterializer } from "@rippledb/materialize-db";
import type { TodosSchema } from "./schema";
import { schema } from "./schema";

// Create the database with a materializer
export const db = new SqliteDb<TodosSchema>({
  filename: "./data.db",
  schema,
  materializer: ({ db, schema }) =>
    createSyncMaterializer({
      schema,
      db,
      dialect: "sqlite",
      tableMap: { todos: "todos" },
      fieldMap: { todos: { id: "id", title: "title", done: "done" } },
    }),
});

Create HLC Generator

Create src/hlc.ts to generate timestamps:

src/hlc.ts
import { createHlcState, tickHlc } from "@rippledb/core";

// Each node needs a unique ID
const nodeId = `server-${Math.random().toString(36).slice(2, 8)}`;
const hlcState = createHlcState(nodeId);

export function hlc() {
  return tickHlc(hlcState, Date.now());
}

Write Your First Change

Create src/index.ts:

src/index.ts
import { makeUpsert, makeDelete } from "@rippledb/core";
import { db } from "./db";
import { hlc } from "./hlc";
import type { TodosSchema } from "./schema";

async function main() {
  const stream = "user-123"; // Partition key (e.g., user ID)

  // Create a todo
  const createTodo = makeUpsert<TodosSchema>({
    stream,
    entity: "todos",
    entityId: "todo-1",
    patch: { id: "todo-1", title: "Buy milk", done: false },
    hlc: hlc(),
  });

  await db.append({ stream, changes: [createTodo] });
  console.log("Created todo:", createTodo);

  // Update the todo
  const updateTodo = makeUpsert<TodosSchema>({
    stream,
    entity: "todos",
    entityId: "todo-1",
    patch: { done: true },
    hlc: hlc(),
  });

  await db.append({ stream, changes: [updateTodo] });
  console.log("Updated todo:", updateTodo);

  // Pull all changes
  const { changes, nextCursor } = await db.pull({ stream, cursor: null });
  console.log("All changes:", changes);
  console.log("Cursor for next pull:", nextCursor);

  // Delete the todo
  const deleteTodo = makeDelete<TodosSchema>({
    stream,
    entity: "todos",
    entityId: "todo-1",
    hlc: hlc(),
  });

  await db.append({ stream, changes: [deleteTodo] });
  console.log("Deleted todo");

  db.close();
}

main();

Run It

npx tsx src/index.ts

You should see output like:

Created todo: { stream: 'user-123', entity: 'todos', entityId: 'todo-1', kind: 'upsert', ... }
Updated todo: { stream: 'user-123', entity: 'todos', entityId: 'todo-1', kind: 'upsert', ... }
All changes: [{ ... }, { ... }]
Cursor for next pull: 2
Deleted todo

What Just Happened?

  1. makeUpsert created a Change object with:

    • kind: 'upsert' — insert or update semantics
    • patch — the fields being changed
    • tags — auto-generated HLC timestamps per field
    • hlc — the change-level timestamp
  2. db.append stored the change and materialized it:

    • Appended to the ripple_changes table
    • Applied field-level LWW to the todos table
    • Updated the ripple_tags table with field timestamps
  3. db.pull returned all changes since cursor null (the beginning).

  4. makeDelete marked the entity as deleted using a tombstone.

Next Steps

Now that you understand the basics, dive deeper into the core concepts.

On this page