agentsclimarketplace

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

Install
npx -y skills add smicolon/ai-kit --skill db-patterns

Assembled 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

ScenarioSolution
Standard server dataTanStack Query
Offline-first appTanStack DB
Local-first with syncTanStack DB + sync
Real-time collaborationTanStack DB + WebSocket sync
Complex client stateTanStack DB or Store

Conventions

  1. Type your collections - Always define document interfaces
  2. Use transactions - For multi-document operations
  3. Handle offline - Design for offline-first
  4. Sync strategy - Define clear sync patterns
  5. 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())

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.