Agent skill

tanstack-query

Use this skill when fetching data, managing server state, or handling API mutations in the Svelte frontend. Covers createQuery, createMutation, query keys, cache invalidation, optimistic updates, and WebSocket-driven refetching. Apply when adding API calls, managing loading/error states, or coordinating cache updates after mutations.

Stars 2,455
Forks 508

Install this agent skill to your Project

npx add-skill https://github.com/exceptionless/Exceptionless/tree/main/.agents/skills/tanstack-query

SKILL.md

TanStack Query

Documentation: tanstack.com/query | Use context7 for API reference

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

Query Basics

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

export function getOrganizationsQuery() {
    const client = useFetchClient();

    return createQuery(() => ({
        queryKey: ["organizations"],
        queryFn: async () => {
            const response =
                await client.getJSON<Organization[]>("/organizations");
            if (!response.ok) {
                throw response.problem;
            }
            return response.data!;
        },
    }));
}

Query Keys Convention

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

typescript
// From src/lib/features/webhooks/api.svelte.ts
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,
};

Common patterns:

typescript
// Resource list
["organizations"]["projects"][
    // Single resource
    ("organizations", organizationId)
][("projects", projectId)][
    // Nested resources
    ("organizations", organizationId, "projects")
][("projects", projectId, "events")][
    // Filtered queries
    ("events", { projectId, status: "open" })
];

Using Queries in Components

svelte
<script lang="ts">
    import { getOrganizationsQuery } from '$features/organizations/api.svelte';

    const organizationsQuery = getOrganizationsQuery();
</script>

{#if organizationsQuery.isPending}
    <LoadingSpinner />
{:else if organizationsQuery.isError}
    <ErrorMessage error={organizationsQuery.error} />
{:else}
    {#each organizationsQuery.data as org}
        <OrganizationCard {org} />
    {/each}
{/if}

Mutations

typescript
export function createOrganizationMutation() {
    const client = useFetchClient();
    const queryClient = useQueryClient();

    return createMutation(() => ({
        mutationFn: async (data: CreateOrganizationRequest) => {
            const response = await client.postJSON<Organization>(
                "/organizations",
                data,
            );
            if (!response.ok) {
                throw response.problem;
            }
            return response.data!;
        },
        onSuccess: () => {
            // Invalidate and refetch organizations list
            queryClient.invalidateQueries({ queryKey: ["organizations"] });
        },
    }));
}

Using Mutations

svelte
<script lang="ts">
    import { createOrganizationMutation } from '$features/organizations/api.svelte';

    const createMutation = createOrganizationMutation();

    async function handleCreate(data: CreateOrganizationRequest) {
        try {
            const org = await createMutation.mutateAsync(data);
            goto(`/organizations/${org.id}`);
        } catch (error) {
            // Error handled by form or toast
        }
    }
</script>

<Button
    onclick={() => handleCreate(formData)}
    disabled={createMutation.isPending}
>
    {createMutation.isPending ? 'Creating...' : 'Create'}
</Button>

Naming Conventions

Functions follow HTTP verb prefixes:

typescript
// Queries (GET)
export function getOrganizationsQuery() { ... }
export function getOrganizationQuery(id: string) { ... }
export function getProjectEventsQuery(projectId: string) { ... }

// Mutations
export function postOrganizationMutation() { ... }  // CREATE
export function patchOrganizationMutation() { ... } // UPDATE
export function deleteOrganizationMutation() { ... } // DELETE

Dependent Queries

typescript
export function getProjectQuery(projectId: string) {
    const client = useFetchClient();

    return createQuery(() => ({
        queryKey: ["projects", projectId],
        queryFn: async () => {
            const response = await client.getJSON<Project>(
                `/projects/${projectId}`,
            );
            if (!response.ok) throw response.problem;
            return response.data!;
        },
        enabled: !!projectId, // Only run when projectId is truthy
    }));
}

Optimistic Updates

typescript
export function updateOrganizationMutation() {
    const client = useFetchClient();
    const queryClient = useQueryClient();

    return createMutation(() => ({
        mutationFn: async ({
            id,
            data,
        }: {
            id: string;
            data: UpdateOrganizationRequest;
        }) => {
            const response = await client.patchJSON<Organization>(
                `/organizations/${id}`,
                data,
            );
            if (!response.ok) throw response.problem;
            return response.data!;
        },
        onMutate: async ({ id, data }) => {
            // Cancel in-flight queries
            await queryClient.cancelQueries({
                queryKey: ["organizations", id],
            });

            // Snapshot previous value
            const previous = queryClient.getQueryData<Organization>([
                "organizations",
                id,
            ]);

            // Optimistically update
            queryClient.setQueryData(
                ["organizations", id],
                (old: Organization) => ({
                    ...old,
                    ...data,
                }),
            );

            return { previous };
        },
        onError: (err, variables, context) => {
            // Rollback on error
            if (context?.previous) {
                queryClient.setQueryData(
                    ["organizations", variables.id],
                    context.previous,
                );
            }
        },
        onSettled: (data, error, { id }) => {
            // Always refetch after mutation
            queryClient.invalidateQueries({ queryKey: ["organizations", id] });
        },
    }));
}

Prefetching

typescript
export function prefetchOrganization(id: string) {
    const client = useFetchClient();
    const queryClient = useQueryClient();

    return queryClient.prefetchQuery({
        queryKey: ["organizations", id],
        queryFn: async () => {
            const response = await client.getJSON<Organization>(
                `/organizations/${id}`,
            );
            if (!response.ok) throw response.problem;
            return response.data!;
        },
    });
}

WebSocket-Driven Invalidation

Invalidate queries when WebSocket messages arrive:

typescript
// From src/lib/features/webhooks/api.svelte.ts
import type { WebSocketMessageValue } from "$features/websockets/models";
import { QueryClient } from "@tanstack/svelte-query";

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),
        });
    }

    // Fallback: invalidate all if no specific keys
    if (!id && !organization_id && !project_id) {
        await queryClient.invalidateQueries({ queryKey: queryKeys.type });
    }
}

Wire up in WebSocket handler:

typescript
// In WebSocket message handler
onMessage("WebhookChanged", (message) => {
    invalidateWebhookQueries(queryClient, message);
});

Expand your agent's capabilities with these related and highly-rated skills.

exceptionless/Exceptionless

foundatio-repositories

2,455 508
Explore
exceptionless/Exceptionless

releasenotes

Generate formatted changelogs from git history since the last release tag. Use when preparing release notes that categorize changes into breaking changes, features, fixes, and other sections.

2,455 508
Explore
exceptionless/Exceptionless

e2e-testing

Use this skill when writing or running end-to-end browser tests with Playwright. Covers Page Object Model patterns, selector strategies (data-testid, getByRole, getByLabel), fixtures, and accessibility audits with axe-playwright. Apply when adding E2E test coverage, debugging flaky tests, or testing user flows through the browser.

2,455 508
Explore
exceptionless/Exceptionless

dogfood

Systematically explore and test a web application to find bugs, UX issues, and other problems. Use when asked to "dogfood", "QA", "exploratory test", "find issues", "bug hunt", "test this app/site/platform", or review the quality of a web application. Produces a structured report with full reproduction evidence -- step-by-step screenshots, repro videos, and detailed repro steps for every issue -- so findings can be handed directly to the responsible teams.

2,455 508
Explore
exceptionless/Exceptionless

storybook

Use this skill when creating or updating Storybook stories for Svelte components. Covers Svelte CSF story format, defineMeta, argTypes, snippet-based customization, and autodocs. Apply when adding visual documentation for components, setting up story files, or running Storybook for development.

2,455 508
Explore
exceptionless/Exceptionless

frontend-testing

Use this skill when writing or running frontend unit and component tests with Vitest and Testing Library. Covers render/screen/fireEvent patterns, vi.mock for mocking, and the AAA (Arrange-Act-Assert) test structure. Apply when adding test coverage for Svelte components, debugging test failures, or setting up test utilities.

2,455 508
Explore

Didn't find tool you were looking for?

Be as detailed as possible for better results