Electric 与 TanStack DB基于 Postgres 同步构建响应式客户端数据层【免费下载链接】electricThe agent platform built on sync.项目地址: https://gitcode.com/GitHub_Trending/el/electric本文围绕 Electric 官方文档中的 TanStack DB 产品页 展开讲解 TanStack DB 作为响应式客户端存储reactive client store的定位、三个核心原语collections、live queries、optimistic mutations、它在 Electric 数据流中的位置以及如何配合 Postgres Sync 实现查询驱动的渐进式同步。文中所有原理说明均结合仓库内的 tanstack-db-web-starter 示例工程源码展开读完你可以掌握Electric 负责实时同步 TanStack DB 负责本地查询 tRPC 负责写入的完整落地模式。TanStack DB 是什么TanStack DB 官方页给出的定义是TanStack DB 是一个响应式、客户端优先client-first的存储目标是让 UI 保持响应、一致且快得惊人——具备亚毫秒级的响应性sub-millisecond reactivity与即时本地写入instant local writes。它由 Electric 与 TanStack 联合打造是 Electric 生态中的客户端数据层组件。需要强调它解决的问题。传统每次交互都打 API的架构有三个典型痛点TanStack DB 的设计正是逐一回应避免端点蔓延endpoint sprawl与网络瀑布network waterfalls组件需要数据时不再各写各的 fetch 调用而是查询同一个本地存储跨集合的 join、聚合在本地完成优化客户端性能与重渲染查询结果是细粒度增量更新的组件只在真正变化的数据上重新渲染把网络移出交互路径take the network off the interaction path本地写入即时生效乐观更新同步在后台进行。其效果是数据加载被优化交互感觉即时后端保持简单——无论加载多少数据。适用场景文档列出的理想场景包括需要快速、响应式 UI 的现代应用多用户编辑共享数据的协作应用同时使用结构化数据via Postgres Sync与实时流via Durable Streams的应用实时同步与基于 API 的数据获取如 tRPC/REST 变更混合的应用任何需要一个响应式、可查询的客户端数据存储的应用。工作原理差分数据流与三个核心原语从 TanStack DB 文档页 的表述看TanStack DB 构建在 differential dataflow 的 TypeScript 实现d2ts之上对外暴露三个核心原语Collections集合统一的数据层入口。集合是可以镜像后端某张表或存放过滤视图如pendingTodos的强类型对象集合本质是普通 JavaScript 数据可随需加载Live Queries实时查询基于差分数据流的极速响应式查询支持跨集合的 join、filter、aggregate查询结果增量更新而非整体重跑Optimistic Mutations乐观变更与同步机制打通的事务原语——本地变更即时应用后台同步失败时自动回滚。下面用仓库中 tanstack-db-web-starter 的源码印证这三者如何落地。1. 用 Electric Shape 驱动的 Collection在 src/lib/collections.ts 中每个待同步的表对应一个 collection通过tanstack/electric-db-collection的electricCollectionOptions接入 Electric 的 Shape 流import { createCollection } from tanstack/react-db import { electricCollectionOptions } from tanstack/electric-db-collection export const todoCollection createCollection( electricCollectionOptions({ id: todos, shapeOptions: { url: new URL( /api/todos, typeof window ! undefined ? window.location.origin : http://localhost:5173 ).toString(), parser: { // 把 timestamptz 列解析为 JavaScript Date 对象 timestamptz: (date: string) new Date(date), }, }, schema: selectTodoSchema, getKey: (item) item.id, onInsert: async ({ transaction }) { const { modified: newTodo } transaction.mutations[0] const result await trpc.todos.create.mutate({ user_id: newTodo.user_id, text: newTodo.text, completed: newTodo.completed, project_id: newTodo.project_id, user_ids: newTodo.user_ids, }) return { txid: result.txid } }, onUpdate: async ({ transaction }) { /* 调 trpc.todos.update */ }, onDelete: async ({ transaction }) { /* 调 trpc.todos.delete */ }, }) )几个关键参数的作用以 starter 源码 为准shapeOptions.url指向 Shape 端点。starter 指向的是自家服务器代理路由/api/todos而非 Electric 直连地址原因见下文Shape Proxy一节parser声明式的类型解析器把 Electric 传回的字符串timestamptz转成Date无需手动转换schemaDrizzle 生成的 Zod select schema让集合中的每一项都是强类型记录getKey指定主键字段是集合内部规范化normalized存储的依据onInsert/onUpdate/onDelete本地乐观写入后同步到后端的钩子。注意transaction.mutations中modified变更后与original变更前的区分onDelete用的是original返回值{ txid }后端 tRPC mutation 返回的 PostgreSQL 事务 ID用于把本次写入与后续 Shape 流回来的变更对上账避免乐观状态与真实数据冲突。2. 跨集合的 Live Query在组件里用useLiveQuery做响应式读取支持跨集合 joinimport { useLiveQuery, eq } from tanstack/react-db const { data: todos } useLiveQuery((q) q .from({ todo: todoCollection }) .join({ list: listCollection }, ({ list, todo }) eq(list.id, todo.list_id) ) .where(({ list }) eq(list.active, true)) .select(({ list, todo }) ({ id: todo.id, text: todo.text, list_name: list.name, })) )这是客户端优先架构的核心收益原本要在后端手写 JOIN 查询、拆成多个端点的数据现在在本地存储上一次查出来且当任意一侧数据变化时结果增量更新、亚毫秒级重算而不必重新发请求。3. 乐观写入写入不直接调用 API而是操作 collection 本身todoCollection.insert({ id: crypto.randomUUID(), text: Make app faster, completed: false, })本地集合立刻更新UI 即时响应onInsert钩子后台调用 tRPC 写入 Postgres 并返回txid若写入失败乐观状态会被回滚。starter 的 README 把这三条总结为核心架构规则读走 Electric用useLiveQuery collections而不是 tRPC query写走 collection 操作调collection.insert()而不是直接调trpc.create.mutate()在路由 loader 中预加载集合防止加载闪烁保证组件渲染前数据可用。数据流TanStack DB 在 Electric 生态中的位置TanStack DB 文档页 描述了它的整体数据流TanStack DB 充当 Electric 生态中的客户端数据层数据从后端经由 Electric 的同步原语流入 TanStack DB再由它驱动响应式 UI 组件。┌─────────────────────────────────────────────────────────────┐ │ Client (Browser) │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ TanStack DB │ │ Electric │ │ tRPC │ │ │ │ Collection │───▶│ Shape Client │ │ Client │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ (reads via shapes) │ (writes via tRPC) ▼ ▼ ┌──────────────────┐ ┌─────────────────────────────────┐ │ Electric Server│─▶│ Postgres │ └──────────────────┘ └─────────────────────────────────┘图示摘自 starter 的认证架构图简化后即上表中的读写分工数据可以来自多个源包括你自己的 API、Postgres SyncShape 部分复制、Durable Streams实时事件流。TanStack DB 把这些来源统一成同一个响应式接口——这也是 starter 集成文档 强调它类型安全、声明式、可渐进采用的原因你可以先只同步一两张表逐步把更多数据源接进来。查询驱动的同步Query-Driven Sync这是 TanStack DB 文档页 重点介绍、也是 TanStack DB 与 Postgres Sync 组合后最有特色的能力。配合 Postgres Sync 时TanStack DB 利用 Electric 的渐进式数据加载progressive data loading实现了查询驱动同步你不必预先决定同步哪些表、哪些行而是直接在本地客户端存储上定义 live query——当组件因为用户导航、输入或事件需要某份数据时对应的查询会触发渐进式的 Shape 订阅数据随查询按需用量地流入应用。换句话说同步的边界由 UI 的查询需求决定而不是由前端工程师手工维护的一堆端点决定。这与上面避免端点蔓延的动机首尾呼应查询即订阅数据即视图。实战tanstack-db-web-starter 的完整落地仓库提供了完整的 Web starterexamples/tanstack-db-web-starter。它基于 TanStack Start TanStack DB Electric Drizzle tRPC Better Auth是理解本文所有概念的标准答案。这里继承 starter README 的关键实操内容。环境与快速开始前置依赖Docker跑 Postgres 与 Electric 服务、Caddy本地 HTTPS/HTTP/2、Node pnpm。# 1. 创建项目 npx gitpick electric-sql/electric/tree/main/examples/tanstack-db-web-starter my-tanstack-db-project cd my-tanstack-db-project # 2. 准备环境变量默认为本地 Docker改 DATABASE_URL / ELECTRIC_URL 可指向 Electric Cloud cp .env.example .env # 3. 安装依赖 pnpm install # 4. 启动后端服务Postgres Electric pnpm backend:up # 5. 应用数据库迁移 pnpm migrate # 6. 启动开发服务器 pnpm dev然后打开https://localhost:5173。另外官方 Quickstart 提供了一条更快捷的入口命令npx electric-sql/start my-electric-app默认走 Electric Cloud适合快速体验。为什么要 Caddy这是本 starter 最容易踩的坑。Electric 的 Shape 投递显著受益于HTTP/2 多路复用没有 HTTP/2 时每个 Shape 订阅各占一条 HTTP/1.1 连接而浏览器对同域限制 6 条并发连接多表同时同步就会排队变慢。Caddy 以反向代理方式提供自动 HTTPS 与 HTTP/2starter 通过 Vite 插件src/vite-plugin-caddy.ts在pnpm dev时自动生成Caddyfile并拉起 Caddy。本地需先信任其根证书caddy trust # 可能需要 sudo直连http://localhost:5173仍然可用但 Shape 加载会较慢HTTP/1.1 限制。Shape Proxy让持久同步连接也带身份与每个请求都单独鉴权的 REST 不同Electric 维护的是持久同步连接鉴权模型必须不同。starter 采用Shape Proxy 模式用户通过 Better Auth 登录会话 Cookie服务器的 API 路由如/api/todos先验证会话再代理转发给 Electric代理在转发时注入行级过滤的where子句参数化保证用户只能看到自己的数据写入走 tRPC mutation服务端再次校验所有权。以/api/todos路由为例见 routes/api/todos.ts 与 READMEconst serve async ({ request }: { request: Request }) { // 1. 验证会话 const session await auth.api.getSession({ headers: request.headers }) if (!session) { return new Response(JSON.stringify({ error: Unauthorized }), { status: 401 }) } // 2. 构造带行级过滤的 Electric URL const originUrl prepareElectricUrl(request.url) originUrl.searchParams.set(table, todos) originUrl.searchParams.set(where, $1 ANY(user_ids)) originUrl.searchParams.set(params[1], session.user.id) // 3. 代理转发到 Electric return proxyElectricRequest(originUrl) }这一模式保证了Electric 永远不会收到未鉴权请求行级权限由数据库层的WHERE子句强制会话 Cookie 自动生效客户端无需额外配置。新增一张表的七步流程starter README 给出了标准化的扩展流程以新增categories表为例也是查询驱动同步 读写分离模式的可复制清单定义 Drizzle schemasrc/db/schema.ts表结构 Zod select/insert/update schema生成并应用迁移pnpm migrate:generate然后pnpm migrate暴露 Shape 代理路由在src/routes/api/下建categories.ts验证会话并注入where: user_id $1行级过滤注意列命名统一用snake_case与 Postgres/Electric 保持一致如需前端 camelCase可用 Electric TypeScript 客户端的snakeCamelMapper列映射添加 tRPC router每个 mutation 在事务内通过generateTxId(tx)拿到事务 ID 一并返回供 collection 钩子回填txid接线 tRPC把新 router 挂到appRouter添加 TanStack DB collectionelectricCollectionOptions配好id、shapeOptions、schema、getKey与onInsert/onUpdate/onDelete在路由中使用loader 里await categoriesCollection.preload()组件里用useLiveQuery读取。命名与常见故障命名数据库列一律snake_casePostgres 惯例且 Electric 按数据库中的确切列名同步Drizzle schema、Zod schema 与 TypeScript 类型保持一致端口冲突Postgres 54321 / Electric 30000 被占用时改 docker-compose.yaml 中的端口调试命令docker compose ps、docker compose logs -f electric postgres、psql $DATABASE_URL -c SELECT 1生产部署需设置BETTER_AUTH_SECRET≥32 位强随机、ELECTRIC_SOURCE_ID/ELECTRIC_SOURCE_SECRET如用托管 Electric、生产DATABASE_URL并注意 dev 环境任意邮箱密码可登录的行为在生产的处理方式见 starter README 的部署清单。小结与延伸阅读回到 TanStack DB 文档页 的主线TanStack DB 用差分数据流提供 collections、live queries、optimistic mutations 三个原语Electric 提供 Shape 部分复制与 Durable Streams二者组合出的查询驱动同步让同步范围由 UI 查询决定写入则由 collection 乐观更新 服务端校验的 tRPC 完成。仓库中可直接对照的材料examples/tanstack-db-web-starterWeb 端完整 starter本文代码出处examples/tanstack-db-expo-starterExpo 移动端 starterwebsite/docs/sync/integrations/tanstack.mdTanStack 集成总览与项目链接website/docs/sync/quickstart.md五分钟内跑通同步的 Quickstartwebsite/docs/sync/guides/shapes.mdShape 与渐进式数据加载的机制细节website/sync/postgres-sync.md 与 website/streams/index.mdTanStack DB 背后两类数据源的官方说明。【免费下载链接】electricThe agent platform built on sync.项目地址: https://gitcode.com/GitHub_Trending/el/electric创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
