Tanstack Db

作者 tanstack-skills6f5521ecbdfb無授權條款35 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫8 個月前更新

Reactive client-first store for your API with collections, live queries, and optimistic mutations.

AI 產生的概覽

TanStack DB 參考指南:用戶端響應式儲存,包含集合、即時查詢與樂觀變更。

功能
此技能是 TanStack DB 的文件式參考,TanStack DB 是建構於差分資料流之上的用戶端嵌入式資料庫層。內容說明集合、同步模式、使用類 SQL 建構器的即時查詢、連接、篩選運算子、樂觀變更以及查詢驅動的同步,並附有 TypeScript 程式碼範例。此外也列出效能數據、支援的集合類型、最佳實務與常見陷阱。
適用情境
在前端專案中使用 TanStack DB,且需要了解如何建立集合、撰寫即時查詢或處理樂觀變更時使用。也適合用來選擇同步模式或將查詢述詞對應為 API 參數。
執行需求
不隨附指令碼,僅為說明性內容。實作範例需要 JavaScript/TypeScript 專案,並安裝 @tanstack/react-db、@tanstack/query-db-collection 與 @tanstack/db 套件,同時需要可同步的 API 或資料來源。

Overview

TanStack DB is a client-side embedded database layer built on differential dataflow. It maintains normalized collections, uses incremental computation for live queries, provides automatic optimistic mutations, and integrates with TanStack Query for data fetching. Sub-millisecond updates even with 100k+ rows.

Package: @tanstack/react-db Query Integration: @tanstack/query-db-collection Status: Beta (v0.5)

Installation

bash
npm install @tanstack/react-db @tanstack/query-db-collection

Core Concepts

  • Collections: Normalized data stores wrapping data sources (TanStack Query, Electric, etc.)
  • Live Queries: Reactive subscriptions with SQL-like query builder
  • Optimistic Mutations: Automatic instant UI updates with rollback on failure
  • Differential Dataflow: Only recomputes affected query results on changes

Collections

Creating a Collection

typescript
import { createCollection } from '@tanstack/react-db'import { queryCollectionOptions } from '@tanstack/query-db-collection'
const todoCollection = createCollection(  queryCollectionOptions({    queryKey: ['todos'],    queryFn: async () => api.todos.getAll(),    getKey: (item) => item.id,    schema: todoSchema,    onInsert: async ({ transaction }) => {      await Promise.all(        transaction.mutations.map((mutation) =>          api.todos.create(mutation.modified)        )      )    },    onUpdate: async ({ transaction }) => {      await Promise.all(        transaction.mutations.map((mutation) =>          api.todos.update(mutation.modified)        )      )    },    onDelete: async ({ transaction }) => {      await Promise.all(        transaction.mutations.map((mutation) =>          api.todos.delete(mutation.original.id)        )      )    },  }))

Sync Modes

typescript
// Eager (default): Load entire collection upfront. Best for <10k rows.const smallCollection = createCollection(  queryCollectionOptions({ syncMode: 'eager', /* ... */ }))
// On-Demand: Load only what queries request. Best for >50k rows, search.const largeCollection = createCollection(  queryCollectionOptions({    syncMode: 'on-demand',    queryFn: async (ctx) => {      const params = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions)      return api.getProducts(params)    },  }))
// Progressive: Load query subset immediately, full sync in background.const collaborativeCollection = createCollection(  queryCollectionOptions({ syncMode: 'progressive', /* ... */ }))

Live Queries

Basic Query

typescript
import { useLiveQuery } from '@tanstack/react-db'import { eq } from '@tanstack/db'
function TodoList() {  const { data: todos } = useLiveQuery((query) =>    query      .from({ todos: todoCollection })      .where(({ todos }) => eq(todos.completed, false))  )
  return <ul>{todos.map(todo => <li key={todo.id}>{todo.text}</li>)}</ul>}

Query Builder API

typescript
const { data } = useLiveQuery((q) =>  q    .from({ t: todoCollection })    .where(({ t }) => eq(t.status, 'active'))    .orderBy(({ t }) => t.createdAt, 'desc')    .limit(10))

Joins

typescript
const { data } = useLiveQuery((q) =>  q    .from({ t: todoCollection })    .innerJoin(      { u: userCollection },      ({ t, u }) => eq(t.userId, u.id)    )    .innerJoin(      { p: projectCollection },      ({ u, p }) => eq(u.projectId, p.id)    )    .where(({ p }) => eq(p.id, currentProject.id)))

Filter Operators

typescript
import { eq, lt, and } from '@tanstack/db'
// Equalityeq(field, value)
// Less thanlt(field, value)
// ANDand(eq(product.category, 'electronics'), lt(product.price, 100))

With Ordering and Limits

typescript
const { data } = useLiveQuery((q) =>  q    .from({ product: productsCollection })    .where(({ product }) =>      and(eq(product.category, 'electronics'), lt(product.price, 100))    )    .orderBy(({ product }) => product.price, 'asc')    .limit(10))

Optimistic Mutations

Insert

typescript
todoCollection.insert({  id: uuid(),  text: 'New todo',  completed: false,})// Immediately: updates all live queries referencing this collection// Background: calls onInsert handler to sync with server// On failure: automatic rollback

No Manual Boilerplate

Before (TanStack Query only)After (TanStack DB)
Manual onMutate for optimistic stateAutomatic
Manual onError rollback logicAutomatic
Per-mutation cache invalidationAll live queries update automatically

Query-Driven Sync (On-Demand)

Live queries automatically generate optimized network requests:

typescript
// This live query...useLiveQuery((q) =>  q.from({ product: productsCollection })    .where(({ product }) => and(eq(product.category, 'electronics'), lt(product.price, 100)))    .orderBy(({ product }) => product.price, 'asc')    .limit(10))
// ...automatically generates:// GET /api/products?category=electronics&price_lt=100&sort=price:asc&limit=10

Predicate Mapping

typescript
queryFn: async (ctx) => {  const { filters, sorts, limit } = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions)
  const params = new URLSearchParams()  filters.forEach(({ field, operator, value }) => {    if (operator === 'eq') params.set(field.join('.'), String(value))    else if (operator === 'lt') params.set(`${field.join('.')}_lt`, String(value))  })  if (limit) params.set('limit', String(limit))
  return fetch(`/api/products?${params}`).then(r => r.json())}

Performance

OperationLatency
Single row update (100k sorted collection)~0.7 ms
Subsequent queries (after sync)<1 ms
Join across collectionsSub-millisecond

Supported Collection Types

  • Query Collection - TanStack Query integration
  • Electric Collection - Electric SQL real-time sync
  • TrailBase Collection - TrailBase backend
  • RxDB Collection - RxDB integration
  • PowerSync Collection - PowerSync sync
  • LocalStorage Collection - Browser persistence
  • LocalOnly Collection - In-memory only

API Summary

typescript
import { createCollection, useLiveQuery } from '@tanstack/react-db'import { queryCollectionOptions } from '@tanstack/query-db-collection'import { eq, lt, and, parseLoadSubsetOptions } from '@tanstack/db'

Best Practices

  1. Define collections at module level - they're singletons
  2. Choose the right sync mode: eager (<10k), on-demand (>50k), progressive (collaborative)
  3. Use joins instead of view-specific APIs - load normalized collections once
  4. Let TanStack Query handle fetching - DB augments Query, doesn't replace it
  5. Use parseLoadSubsetOptions to map live query predicates to API params
  6. Rely on automatic optimistic updates - don't manually manage optimistic state
  7. Use schemas for runtime validation and TypeScript inference
  8. Leverage incremental computation - let the engine handle filtering vs manual .filter()

Common Pitfalls

  • Creating collections inside components (should be module-level)
  • Trying to replace TanStack Query entirely (DB builds on top of it)
  • Using manual .filter() in render instead of live query where clauses
  • Not providing getKey for proper normalization
  • Forgetting mutation handlers (onInsert, onUpdate, onDelete) for server sync

來源與署名

來源:tanstack-skills/tanstack-skills位於plugins/tanstack-db/skills/tanstack-db提交6f5521e

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架