electric-orm

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
This skill builds on electric-shapes and electric-schema-shapes. Read those first.
本技能基于electric-shapes和electric-schema-shapes相关内容,请先阅读上述内容。

Electric — ORM Integration

Electric — ORM集成

Setup

环境配置

Drizzle ORM

Drizzle ORM

ts
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) }
  })
}
ts
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

Prisma

ts
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) }
  })
}
ts
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

带REPLICA IDENTITY配置的Drizzle迁移

ts
// 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`)
}
ts
// 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

带REPLICA IDENTITY配置的Prisma迁移

sql
-- 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;
sql
-- 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

ORM配合Collection的onInsert使用

ts
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 }
    },
  })
)
ts
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

高优先级:ORM写入操作未返回txid

Wrong:
ts
// Drizzle — no txid returned
const [todo] = await db.insert(todos).values({ text: 'New' }).returning()
return { id: todo.id }
Correct:
ts
// 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
错误示例:
ts
// Drizzle — no txid returned
const [todo] = await db.insert(todos).values({ text: 'New' }).returning()
return { id: todo.id }
正确示例:
ts
// 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) }
})
ORM默认不会返回
pg_current_xact_id()
,你需要在同一个事务内添加原生SQL查询来获取txid。如果缺少该操作,乐观状态可能会在同步版本抵达前失效,导致UI闪烁。
来源:
AGENTS.md:116-119

MEDIUM Running migrations that drop replica identity

中优先级:运行迁移时丢失了replica identity配置

Wrong:
ts
// 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:
ts
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.
错误示例:
ts
// 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
正确示例:
ts
await db.execute(sql`DROP TABLE todos`)
await db.execute(sql`CREATE TABLE todos (...)`)
await db.execute(sql`ALTER TABLE todos REPLICA IDENTITY FULL`)
部分迁移工具会重置表属性,每次重建表后都务必确认设置了
REPLICA IDENTITY FULL
。缺少该配置的话,Electric无法正确流式传输更新和删除操作。
来源:
website/docs/guides/troubleshooting.md:373
另请参阅:electric-new-feature/SKILL.md — 包含txid握手的完整写入路径流程。 另请参阅:electric-schema-shapes/SKILL.md — Schema设计会同时影响shapes和ORM查询。

Version

版本

Targets @electric-sql/client v1.5.10.
适配@electric-sql/client v1.5.10。