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.
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
context7for API reference
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 {
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:
// 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:
// 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
<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
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
<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:
// 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
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
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
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:
// 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:
// In WebSocket message handler
onMessage("WebhookChanged", (message) => {
invalidateWebhookQueries(queryClient, message);
});
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
foundatio-repositories
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.
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.
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.
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.
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.
Didn't find tool you were looking for?