mcpField
E
electric-orm

electric-orm

github.com/electric-sqlData & databases10.4k48 installsUpdated 3w ago
Repository

About electric-orm

A skill for developers integrating Electric with ORMs, describing write operations and migration considerations with Drizzle ORM or Prisma. It emphasizes transaction-scoped txid, REPLICA IDENTITY, and TanStack DB collections.

Key features

  1. 01Integrate Drizzle ORM
  2. 02Integrate Prisma
  3. 03Set REPLICA IDENTITY in migrations
  4. 04Reconcile writes using txid
  5. 05Configure TanStack DB collections

Use cases

  1. 01Write through an ORM and reconcile Electric sync
  2. 02Create migrations that support streamed updates and deletes
  3. 03Connect ORM writes to TanStack DB collections

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/electric-sql/electric --skill electric-orm

After installing, type/electric-ormin Claude Code to confirm it loaded.

Details

From the project README

This skill builds on electric-shapes and electric-schema-shapes. Read those first.

Electric — ORM Integration

Setup

Drizzle ORM
import { drizzle } from 'drizzle-orm/node-postgres'
import { sql } from 'drizzle-orm'
import { todos } from './schema'

const db = drizzle(pool)

// Write with txid for Electric reconciliation
async function createTodo(text: string, userId: string) {
  return await db.transaction(async (tx) => {
    const [row] = await tx
      .insert(todos)
      .values({
        id: crypto.randomUUID(),
        text,
        userId,
      })
      .returning()

    const [{ txid }] = await tx.execute<{ txid: string }>(
      sql`SELECT pg_current_xact_id()::xid::text AS txid`
    )

    return { id: row.id, txid: parseInt(txid) }
  })
}
Prisma
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function createTodo(text: string, userId: string) {
  return await prisma.$transaction(async (tx) => {
    const todo = await tx.todo.create({
      data: { id: crypto.randomUUID(), text, userId },
    })

    const [{ txid }] = await tx.$queryRaw<[{ txid: string }]>`
      SELECT pg_current_xact_id()::xid::text AS txid
    `

    return { id: todo.id, txid: parseInt(txid) }
  })
}

Core Patterns

Drizzle migration with REPLICA IDENTITY
// In migration file
import { sql } from 'drizzle-orm'

export async function up(db) {
  await db.execute(sql`
    CREATE TABLE todos (
      id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
      text TEXT NOT NULL,
      completed BOOLEAN DEFAULT false
    )
  `)
  await db.execute(sql`ALTER TABLE todos REPLICA IDENTITY FULL`)
}
Prisma migration with REPLICA IDENTITY
-- prisma/migrations/001_init/migration.sql
CREATE TABLE "todos" (
  "id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  "text" TEXT NOT NULL,
  "completed" BOOLEAN DEFAULT false
);

ALTER TABLE "todos" REPLICA IDENTITY FULL;
Collection onInsert with ORM
import { createCollection } from '@tanstack/react-db'
import { electricCollectionOptions } from '@tanstack/electric-db-collection'

export const todoCollection = createCollection(
  electricCollectionOptions({
    id: 'todos',
    schema: todoSchema,
    getKey: (row) => row.id,
    shapeOptions: { url: '/api/todos' },
    onInsert: async ({ transaction }) => {
      const newTodo = transaction.mutations[0].modified
      const { txid } = await createTodo(newTodo.text, newTodo.userId)
      return { txid }
    },
  })
)

Common Mistakes

HIGH Not returning txid from ORM write operations

Wrong:

// Drizzle — no txid returned
const [todo] = await db.insert(todos).values({ text: 'New' }).returning()
return { id: todo.id }

Correct:

// Drizzle — txid in same transaction
const result = await db.transaction(async (tx) => {
  const [row] = await tx.insert(todos).values({ text: 'New' }).returning()
  const [{ txid }] = await tx.execute<{ txid: string }>(
    sql`SELECT pg_current_xact_id()::xid::text AS txid`
  )
  return { id: row.id, txid: parseInt(txid) }
})

ORMs do not return pg_current_xact_id() by default. Add a raw SQL query for txid within the same transaction. Without it, optimistic state may drop before the synced version arrives, causing UI flicker.

Source: AGENTS.md:116-119

MEDIUM Running migrations that drop replica identity

Wrong:

// ORM migration recreates table without REPLICA IDENTITY
await db.execute(sql`DROP TABLE todos`)
await db.execute(sql`CREATE TABLE todos (...)`)
// Missing: ALTER TABLE todos REPLICA IDENTITY FULL

Correct:

await db.execute(sql`DROP TABLE todos`)
await db.execute(sql`CREATE TABLE todos (...)`)
await db.execute(sql`ALTER TABLE todos REPLICA IDENTITY FULL`)

Some migration tools reset table properties. Always ensure REPLICA IDENTITY FULL is set after table recreation. Without it, Electric cannot stream updates and deletes correctly.

Source: website/docs/guides/troubleshooting.md:373

See also: electric-new-feature/SKILL.md — Full write-path journey including txid handshake. See also: electric-schema-shapes/SKILL.md — Schema design affects both shapes and ORM queries.

Version

Targets @electric-sql/client v1.5.10.

Comments

Markdown supported