Db patterns
Skill smicolon/ai-kit/packs/tanstack-router/skills/db-patterns
Convention packs for any AI coding tool - agents, skills, commands, and rules for 15 tools including Claude Code, Cursor, Windsurf, and Copilot
npx -y skills add smicolon/ai-kit --skill db-patternsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 6 stars6 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
TanStack DB patterns for client-first reactive data stores. Activates when implementing offline-first apps, local-first data, or reactive client databases. NOTE: Beta library - API may change.
SKILL.md
7.6 KB, as published. Nobody here has run it
TanStack DB Patterns (Beta)
Beta Library: TanStack DB is in beta. APIs may change between versions.
TanStack DB provides a client-first reactive data store with optional sync to remote sources.
Core Concepts
- Collections: Named groups of documents (like tables)
- Documents: Individual records with unique IDs
- Queries: Reactive queries that update when data changes
- Transactions: Atomic operations across multiple documents
- Sync: Optional sync to remote backends
Basic Setup
// lib/db.ts
import { createDB, createCollection } from '@tanstack/db'
// Define document types
interface Post {
id: string
title: string
content: string
authorId: string
published: boolean
createdAt: number
updatedAt: number
}
interface User {
id: string
name: string
email: string
}
// Create database
export const db = createDB({
collections: {
posts: createCollection<Post>(),
users: createCollection<User>(),
},
})
CRUD Operations
Create
import { db } from '@/lib/db'
// Insert a single document
const newPost = await db.posts.insert({
id: crypto.randomUUID(),
title: 'My Post',
content: 'Post content...',
authorId: 'user-1',
published: false,
createdAt: Date.now(),
updatedAt: Date.now(),
})
// Insert multiple documents
await db.posts.insertMany([
{ id: '1', title: 'Post 1', ... },
{ id: '2', title: 'Post 2', ... },
])
Read
// Get by ID
const post = await db.posts.get('post-id')
// Query with filters
const publishedPosts = await db.posts.findMany({
where: { published: true },
orderBy: { createdAt: 'desc' },
limit: 10,
})
// Query with complex filters
const userPosts = await db.posts.findMany({
where: {
authorId: 'user-1',
published: true,
},
})
Update
// Update by ID
await db.posts.update('post-id', {
title: 'Updated Title',
updatedAt: Date.now(),
})
// Update with function
await db.posts.update('post-id', (post) => ({
...post,
viewCount: post.viewCount + 1,
updatedAt: Date.now(),
}))
// Update many
await db.posts.updateMany(
{ where: { authorId: 'user-1' } },
{ published: false }
)
Delete
// Delete by ID
await db.posts.delete('post-id')
// Delete many
await db.posts.deleteMany({
where: { published: false },
})
Reactive Queries in React
import { useQuery } from '@tanstack/db-react'
import { db } from '@/lib/db'
function PostList() {
// Reactive query - updates when data changes
const posts = useQuery(
db.posts.query({
where: { published: true },
orderBy: { createdAt: 'desc' },
})
)
return (
<ul>
{posts.map((post) => (
<PostCard key={post.id} post={post} />
))}
</ul>
)
}
function PostDetail({ postId }: { postId: string }) {
// Single document query
const post = useQuery(db.posts.get(postId))
if (!post) return <NotFound />
return <article>{post.title}</article>
}
Transactions
import { db } from '@/lib/db'
async function transferPost(postId: string, newAuthorId: string) {
await db.transaction(async (tx) => {
// Get current post
const post = await tx.posts.get(postId)
if (!post) throw new Error('Post not found')
// Update post author
await tx.posts.update(postId, {
authorId: newAuthorId,
updatedAt: Date.now(),
})
// Update user post counts (if tracking)
await tx.users.update(post.authorId, (user) => ({
...user,
postCount: user.postCount - 1,
}))
await tx.users.update(newAuthorId, (user) => ({
...user,
postCount: user.postCount + 1,
}))
})
}
Sync with Remote Backend
// lib/db.ts
import { createDB, createCollection, createSyncProvider } from '@tanstack/db'
const syncProvider = createSyncProvider({
// Pull changes from server
pull: async (collection, lastSync) => {
const response = await fetch(`/api/${collection}/sync?since=${lastSync}`)
return response.json()
},
// Push changes to server
push: async (collection, changes) => {
await fetch(`/api/${collection}/sync`, {
method: 'POST',
body: JSON.stringify(changes),
})
},
})
export const db = createDB({
collections: {
posts: createCollection<Post>(),
users: createCollection<User>(),
},
sync: syncProvider,
})
// Trigger sync
await db.sync()
// Auto-sync on interval
setInterval(() => db.sync(), 30000)
Offline-First Pattern
import { useQuery, useMutation } from '@tanstack/db-react'
import { db } from '@/lib/db'
function CreatePostForm() {
const createPost = useMutation(db.posts.insert)
const handleSubmit = async (data: PostInput) => {
// Immediately available locally
await createPost.mutate({
id: crypto.randomUUID(),
...data,
createdAt: Date.now(),
updatedAt: Date.now(),
_pending: true, // Mark as pending sync
})
// Sync will happen in background when online
if (navigator.onLine) {
db.sync()
}
}
return <form onSubmit={handleSubmit}>{/* form fields */}</form>
}
// Show pending items differently
function PostCard({ post }: { post: Post }) {
return (
<div className={post._pending ? 'opacity-50' : ''}>
{post.title}
{post._pending && <span>Syncing...</span>}
</div>
)
}
Integration with TanStack Query
// Hybrid approach: DB for offline, Query for server sync
import { useQuery as useReactQuery } from '@tanstack/react-query'
import { useQuery as useDBQuery } from '@tanstack/db-react'
import { db } from '@/lib/db'
function PostList() {
// Local DB for immediate data
const localPosts = useDBQuery(
db.posts.query({ where: { published: true } })
)
// Server query for sync
const { data: serverPosts } = useReactQuery({
queryKey: ['posts', 'published'],
queryFn: () => postApi.getPosts({ published: true }),
onSuccess: (posts) => {
// Update local DB with server data
db.posts.upsertMany(posts)
},
})
// Use local data (always available, even offline)
return (
<ul>
{localPosts.map((post) => (
<PostCard key={post.id} post={post} />
))}
</ul>
)
}
When to Use TanStack DB
| Scenario | Solution |
|---|---|
| Standard server data | TanStack Query |
| Offline-first app | TanStack DB |
| Local-first with sync | TanStack DB + sync |
| Real-time collaboration | TanStack DB + WebSocket sync |
| Complex client state | TanStack DB or Store |
Conventions
- Type your collections - Always define document interfaces
- Use transactions - For multi-document operations
- Handle offline - Design for offline-first
- Sync strategy - Define clear sync patterns
- Combine with Query - Use Query for pure server data
Anti-Patterns
// ❌ WRONG: Not using transactions for related updates
await db.posts.delete(postId)
await db.comments.deleteMany({ where: { postId } }) // Could fail leaving orphans
// ✅ CORRECT: Use transaction
await db.transaction(async (tx) => {
await tx.posts.delete(postId)
await tx.comments.deleteMany({ where: { postId } })
})
// ❌ WRONG: Using DB for pure server data without offline need
const posts = useDBQuery(db.posts.query({}))
// ✅ CORRECT: Use Query for server data
const { data: posts } = useQuery(postsQueryOptions())