Install
$ agentstack add skill-tanstack-skills-tanstack-skills-tanstack-db ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
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
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
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
// Eager (default): Load entire collection upfront. 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
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 {todos.map(todo => {todo.text})}
}
Query Builder API
const { data } = useLiveQuery((q) =>
q
.from({ t: todoCollection })
.where(({ t }) => eq(t.status, 'active'))
.orderBy(({ t }) => t.createdAt, 'desc')
.limit(10)
)
Joins
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
import { eq, lt, and } from '@tanstack/db'
// Equality
eq(field, value)
// Less than
lt(field, value)
// AND
and(eq(product.category, 'electronics'), lt(product.price, 100))
With Ordering and Limits
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
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 state | Automatic | | Manual onError rollback logic | Automatic | | Per-mutation cache invalidation | All live queries update automatically |
Query-Driven Sync (On-Demand)
Live queries automatically generate optimized network requests:
// 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
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
| Operation | Latency | |-----------|---------| | Single row update (100k sorted collection) | ~0.7 ms | | Subsequent queries (after sync) | 50k), progressive (collaborative)
- Use joins instead of view-specific APIs - load normalized collections once
- Let TanStack Query handle fetching - DB augments Query, doesn't replace it
- Use
parseLoadSubsetOptionsto map live query predicates to API params - Rely on automatic optimistic updates - don't manually manage optimistic state
- Use schemas for runtime validation and TypeScript inference
- 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 querywhereclauses - Not providing
getKeyfor proper normalization - Forgetting mutation handlers (
onInsert,onUpdate,onDelete) for server sync
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: tanstack-skills
- Source: tanstack-skills/tanstack-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.