mcpField
T
tanstack-query

tanstack-query

github.com/exceptionlessDevelopment2.5k53 installsUpdated 3d ago
Repository

About tanstack-query

TanStack Query data-fetching practices for Svelte application developers, covering centralized API calls, query-key conventions, mutations, and WebSocket-driven cache invalidation.

Key features

  1. 01Centralize API calls by feature
  2. 02Use query-key factories
  3. 03Follow query and mutation naming conventions
  4. 04Optimistic updates and rollback
  5. 05Invalidate cache from WebSocket messages

Use cases

  1. 01Organize data requests by feature
  2. 02Coordinate query keys and cache updates
  3. 03Implement optimistic mutations with rollback
  4. 04Refresh queries in response to WebSocket messages

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/exceptionless/exceptionless --skill tanstack-query

After installing, type/tanstack-queryin Claude Code to confirm it loaded.

Details

From the project README

TanStack Query

Documentation: tanstack.com/query. Use official docs when the local pattern is not enough.

Centralize API calls in api.svelte.ts per feature using TanStack Query with @exceptionless/fetchclient.

Query Basics

// src/lib/features/organizations/api.svelte.ts
import { createQuery, createMutation, useQueryClient } from "@tanstack/svelte-query";
import { type FetchClientResponse, type ProblemDetails, useFetchClient } from "@exceptionless/fetchclient";
import { accessToken } from "$features/auth/index.svelte";

const queryKeys = {
    type: ["Organization"] as const,
};

export function getOrganizationsQuery() {
    return createQuery<FetchClientResponse<Organization[]>, ProblemDetails>(() => ({
        enabled: () => !!accessToken.current,
        queryKey: queryKeys.type,
        queryFn: async ({ signal }: { signal: AbortSignal }) => {
            const client = useFetchClient();
            const response = await client.getJSON<Organization[]>("/organizations", { signal });
            return response;
        },
    }));
}

Query Keys Convention

Use a queryKeys factory per feature for type safety and consistency:

export const queryKeys = {
    type: ["Webhook"] as const,
    id: (id: string | undefined) => [...queryKeys.type, id] as const,
    ids: (ids: string[] | undefined) => [...queryKeys.type, ...(ids ?? [])] as const,
    project: (id: string | undefined) => [...queryKeys.type, "project", id] as const,
    deleteWebhook: (ids: string[] | undefined) => [...queryKeys.ids(ids), "delete"] as const,
    postWebhook: () => [...queryKeys.type, "post"] as const,
};

Prefer the feature's queryKeys factory over ad-hoc arrays so WebSocket invalidation and cache updates share the same keys.

Mutations

export function postOrganizationMutation() {
    const queryClient = useQueryClient();

    return createMutation(() => ({
        mutationFn: async (data: CreateOrganizationRequest) => {
            const client = useFetchClient();
            const response = await client.postJSON<Organization>("/organizations", data);
            return response.data!;
        },
        onSuccess: () => {
            queryClient.invalidateQueries({ queryKey: queryKeys.type });
        },
    }));
}

Naming Conventions

PatternNamingExample
Query (GET)get{Resource}QuerygetOrganizationsQuery()
Create (POST)post{Resource}MutationpostOrganizationMutation()
Update (PATCH)patch{Resource}MutationpatchOrganizationMutation()
Delete (DELETE)delete{Resource}MutationdeleteOrganizationMutation()

Dependent Queries

Use enabled to conditionally run queries: enabled: !!projectId.

Optimistic Updates

For mutations that update cached data optimistically: use onMutate to cancel in-flight queries, snapshot previous value via getQueryData, and apply optimistic update via setQueryData. Use onError to rollback from snapshot, and onSettled to always invalidateQueries for the final refetch.

WebSocket-Driven Invalidation

Invalidate queries when WebSocket messages arrive:

export async function invalidateWebhookQueries(
    queryClient: QueryClient,
    message: WebSocketMessageValue<"WebhookChanged">,
) {
    const { id, organization_id, project_id } = message;

    if (id) await queryClient.invalidateQueries({ queryKey: queryKeys.id(id) });
    if (project_id) await queryClient.invalidateQueries({ queryKey: queryKeys.project(project_id) });
    if (!id && !organization_id && !project_id)
        await queryClient.invalidateQueries({ queryKey: queryKeys.type });
}

Wire WebSocket messages from the app layout to the feature invalidation helper.

Comments

Markdown supported