Teardown

Queries

Type-safe TanStack Query abstraction for server state management.

The @teardown/queries package provides a lightweight wrapper around TanStack Query (React Query) that simplifies creating type-safe, composable data fetching patterns. It abstracts key generation, provides utility methods for cache management, and enforces consistent patterns across the codebase.

Features

  • Type-safe query keys - Automatic key generation with resource/sub-resource hierarchies
  • Composable QueryClient - Organize domain-specific queries in classes
  • Cache utilities - Built-in fetch, prefetch, set, get, and refresh methods
  • Full query support - Standard queries, infinite queries, and mutations
  • Configurable defaults - Set staleTime and gcTime per resource domain

Installation

bun add @teardown/queries

Quick Start

import { QueryClient } from "@teardown/queries";
import { useQuery, useMutation } from "@tanstack/react-query";

// 1. Create a domain-specific query client
class ProjectsClient extends QueryClient<"projects"> {
  constructor(tanstackClient: TanstackQueryClient) {
    super(tanstackClient, "projects", {
      staleTime: 5 * 60 * 1000, // 5 minutes
    });
  }

  // 2. Define queries as methods
  getProjects(userId: string) {
    return this.query({
      key: ["list", userId],
      fn: async () => {
        const response = await fetch(`/api/projects?userId=${userId}`);
        return response.json();
      },
    });
  }

  // 3. Define mutations
  createProject() {
    return this.mutation({
      key: ["create"],
      fn: async (data: { name: string }) => {
        const response = await fetch("/api/projects", {
          method: "POST",
          body: JSON.stringify(data),
        });
        return response.json();
      },
      onSuccess: () => {
        this.refresh("list"); // Invalidate list queries
      },
    });
  }
}

// 4. Use in components
function ProjectsList({ userId }) {
  const projectsClient = useProjectsClient();
  const { data, isLoading } = useQuery(projectsClient.getProjects(userId));
  const { mutate } = useMutation(projectsClient.createProject());

  return (
    <div>
      {data?.map(project => <div key={project.id}>{project.name}</div>)}
      <button onClick={() => mutate({ name: "New Project" })}>Create</button>
    </div>
  );
}

Query Key Structure

All queries use a consistent key format for predictable cache management:

// Query keys
["@teardown", resource, "query", subResource, ...args]
// Example: ["@teardown", "projects", "query", "list", "user-123"]

// Mutation keys
["@teardown", resource, "mutation", mutationKey, ...args]
// Example: ["@teardown", "projects", "mutation", "create"]

Documentation