agentsclimarketplace

Pacer patterns

Skill smicolon/ai-kit/packs/tanstack-router/skills/pacer-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 pacer-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 Pacer patterns for rate limiting, debouncing, and throttling. Activates when implementing search inputs, API rate limiting, or performance optimization. NOTE: Beta library - API may change.

SKILL.md

7.9 KB, ~2.0k tokens by cl100k_base, as published. Nobody here has run it

TanStack Pacer Patterns (Beta)

Beta Library: TanStack Pacer is in beta. APIs may change between versions.

TanStack Pacer provides utilities for rate limiting, debouncing, throttling, and async queuing.

Debounce

Delay execution until input stops for a specified time.

Basic Debounce

import { debounce } from '@tanstack/pacer'

const debouncedSearch = debounce((query: string) => {
  console.log('Searching:', query)
  return fetch(`/api/search?q=${query}`)
}, 300)

// Only executes after 300ms of no calls
debouncedSearch('h')
debouncedSearch('he')
debouncedSearch('hel')
debouncedSearch('hell')
debouncedSearch('hello') // Only this executes

Debounce in React

import { useDebounce } from '@tanstack/pacer-react'
import { useState } from 'react'

function SearchInput() {
  const [query, setQuery] = useState('')

  const debouncedQuery = useDebounce(query, 300)

  // Use debouncedQuery for API calls
  const { data } = useQuery({
    queryKey: ['search', debouncedQuery],
    queryFn: () => searchApi.search(debouncedQuery),
    enabled: debouncedQuery.length > 0,
  })

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search..."
      />
      {data && <SearchResults results={data} />}
    </div>
  )
}

Debounced Callback

import { useDebouncedCallback } from '@tanstack/pacer-react'

function AutoSaveEditor({ postId }: { postId: string }) {
  const [content, setContent] = useState('')
  const updatePost = useUpdatePost()

  const saveContent = useDebouncedCallback(
    async (content: string) => {
      await updatePost.mutateAsync({ id: postId, content })
    },
    1000, // Save after 1s of no typing
    { maxWait: 5000 } // But at least every 5s
  )

  const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    const newContent = e.target.value
    setContent(newContent)
    saveContent(newContent)
  }

  return (
    <textarea value={content} onChange={handleChange} />
  )
}

Throttle

Limit execution to at most once per interval.

Basic Throttle

import { throttle } from '@tanstack/pacer'

const throttledScroll = throttle((position: number) => {
  console.log('Scroll position:', position)
}, 100)

// Executes at most once every 100ms
window.addEventListener('scroll', () => {
  throttledScroll(window.scrollY)
})

Throttle in React

import { useThrottledCallback } from '@tanstack/pacer-react'
import { useEffect } from 'react'

function ScrollTracker() {
  const trackScroll = useThrottledCallback(
    (position: number) => {
      analytics.track('scroll', { position })
    },
    500
  )

  useEffect(() => {
    const handleScroll = () => trackScroll(window.scrollY)
    window.addEventListener('scroll', handleScroll)
    return () => window.removeEventListener('scroll', handleScroll)
  }, [trackScroll])

  return null
}

Throttled API Calls

import { useThrottledCallback } from '@tanstack/pacer-react'

function LiveSearch() {
  const [results, setResults] = useState<Result[]>([])

  const search = useThrottledCallback(
    async (query: string) => {
      const data = await searchApi.search(query)
      setResults(data)
    },
    200, // At most 5 requests per second
    { leading: true, trailing: true }
  )

  return (
    <input
      onChange={(e) => search(e.target.value)}
      placeholder="Search..."
    />
  )
}

Rate Limiting

Control the rate of operations.

Rate Limiter

import { createRateLimiter } from '@tanstack/pacer'

const apiLimiter = createRateLimiter({
  limit: 10,      // 10 requests
  interval: 1000, // per second
})

async function fetchData(id: string) {
  await apiLimiter.acquire() // Wait if rate limited
  return fetch(`/api/data/${id}`)
}

// Safe to call rapidly - will be rate limited
await Promise.all(
  ids.map(id => fetchData(id))
)

Per-Key Rate Limiting

import { createKeyedRateLimiter } from '@tanstack/pacer'

const userLimiter = createKeyedRateLimiter({
  limit: 5,
  interval: 60000, // 5 requests per minute per user
})

async function handleUserAction(userId: string, action: Action) {
  const limiter = userLimiter.get(userId)
  if (!limiter.tryAcquire()) {
    throw new Error('Rate limited. Please try again later.')
  }
  return processAction(action)
}

Async Queue

Process async operations sequentially or with concurrency limits.

Sequential Queue

import { createAsyncQueue } from '@tanstack/pacer'

const uploadQueue = createAsyncQueue({
  concurrency: 1, // One at a time
})

async function uploadFiles(files: File[]) {
  const results = await Promise.all(
    files.map(file =>
      uploadQueue.add(() => uploadFile(file))
    )
  )
  return results
}

Concurrent Queue with Limit

import { createAsyncQueue } from '@tanstack/pacer'

const processQueue = createAsyncQueue({
  concurrency: 3, // Max 3 concurrent
})

function ProcessingStatus() {
  const { pending, active, completed } = processQueue.status

  return (
    <div>
      <span>Pending: {pending}</span>
      <span>Active: {active}</span>
      <span>Completed: {completed}</span>
    </div>
  )
}

Queue with Priority

import { createAsyncQueue } from '@tanstack/pacer'

const taskQueue = createAsyncQueue({
  concurrency: 2,
})

// Higher priority tasks run first
taskQueue.add(() => processTask('low'), { priority: 1 })
taskQueue.add(() => processTask('high'), { priority: 10 })
taskQueue.add(() => processTask('urgent'), { priority: 100 })

Integration with TanStack Query

import { useDebounce } from '@tanstack/pacer-react'
import { useQuery } from '@tanstack/react-query'

function SearchWithDebounce() {
  const [input, setInput] = useState('')
  const debouncedInput = useDebounce(input, 300)

  const { data, isLoading } = useQuery({
    queryKey: ['search', debouncedInput],
    queryFn: () => searchApi.search(debouncedInput),
    enabled: debouncedInput.length >= 2,
  })

  return (
    <div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="Search (min 2 chars)..."
      />
      {isLoading && <Spinner />}
      {data && <Results items={data} />}
    </div>
  )
}

Common Use Cases

ScenarioSolution
Search inputDebounce 300ms
Auto-saveDebounce 1s with maxWait
Scroll trackingThrottle 100-200ms
API rate limitsRate limiter
File uploadsAsync queue with concurrency
Resize handlerThrottle 100ms

Conventions

  1. Debounce for inputs - Always debounce search/filter inputs
  2. Throttle for events - Use for scroll, resize, mousemove
  3. Rate limit APIs - Protect against abuse
  4. Queue heavy operations - Use for file uploads, bulk operations
  5. Cleanup - Cancel pending operations on unmount

Anti-Patterns

// ❌ WRONG: No debounce on search
function Search() {
  const [query, setQuery] = useState('')
  const { data } = useQuery({
    queryKey: ['search', query], // Fires on every keystroke!
    queryFn: () => search(query),
  })
}

// ✅ CORRECT: Debounced search
function Search() {
  const [query, setQuery] = useState('')
  const debouncedQuery = useDebounce(query, 300)
  const { data } = useQuery({
    queryKey: ['search', debouncedQuery],
    queryFn: () => search(debouncedQuery),
    enabled: debouncedQuery.length > 0,
  })
}

// ❌ WRONG: Creating debounce inside render
function Component() {
  const search = debounce(() => {}, 300) // New instance every render!
}

// ✅ CORRECT: Use hook
function Component() {
  const search = useDebouncedCallback(() => {}, 300)
}

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Gives 0 of the 12 instructions most performance cost skills give in ~2.0k tokens

Counted across 803 of the 1,058 authors here whose files we hold, read 2026-08-07

  • Keep skill files under 500 lines or tokensin 82 of 803, across 16 files
  • Use imperative form in instructionsin 80 of 803, across 9 files
  • Draft assertions while test runs are in progressin 75 of 803, across 9 files
  • Create two to three realistic test promptsin 74 of 803, across 9 files
  • Write skill descriptions to be pushyin 72 of 803, across 7 files
  • Save test cases to evals JSONin 72 of 803, across 6 files
  • Ask questions about edge cases and input formatsin 72 of 803, across 7 files
  • Save timing data immediately when runs completein 70 of 803, across 5 files
  • Include all trigger conditions in the skill descriptionin 69 of 803, across 3 files
  • Launch all test runs in a single turn or simultaneouslyin 69 of 803, across 3 files
  • Capture intent before writing a skillin 67 of 803, across 1 file
  • Import directly instead of barrel filesin 52 of 803, across 15 files

Said here and by no other author read

  • Use maxWait option for autosave debouncing
  • Throttle high-frequency events
  • Rate limit API operations
  • Use async queues for heavy operations
  • Cancel pending operations on unmount
  • Use pacer hooks inside React components

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 327,167. 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.