Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ee9346a
WIP: Svelte 5 adapter (#6981)
zhihengGet Jul 26, 2024
b5f5fd4
feat(svelte-query): Improve svelte runes API (#8852)
elliott-with-the-longest-name-on-github May 12, 2025
594e0bf
Merge branch 'main' into svelte-5-adapter
lachlancollins May 12, 2025
e3f7484
Use sleep from query-test-utils
lachlancollins May 12, 2025
859d9b7
Simplify test reset logic
lachlancollins May 12, 2025
746b9c5
Merge branch 'main' into svelte-5-adapter
lachlancollins May 12, 2025
6e65a4a
Merge branch 'main' into svelte-5-adapter
lachlancollins Sep 26, 2025
540ce06
Fix some merge conflicts
lachlancollins Sep 26, 2025
f0a9ebe
More fixes
lachlancollins Sep 26, 2025
cc5a571
A few more fixes
lachlancollins Sep 26, 2025
d484552
Merge branch 'main' into svelte-5-adapter
lachlancollins Sep 26, 2025
686db47
Fix useMutationState
lachlancollins Sep 26, 2025
e3bba4a
Add changeset
lachlancollins Sep 26, 2025
373e24b
Add migration docs
lachlancollins Sep 26, 2025
d0beb44
Merge branch 'main' into svelte-5-adapter
lachlancollins Sep 27, 2025
bd8737f
Replace Set with SvelteSet
lachlancollins Sep 27, 2025
e07e895
Update minimum svelte version
lachlancollins Sep 27, 2025
9f5c7e8
Bump svelte-eslint-parser
lachlancollins Sep 27, 2025
eea078e
Unwrap createQuery test
lachlancollins Sep 27, 2025
968d6a8
fix(svelte-query): `state_unsafe_mutation` error with `useIs...` (#9493)
hmnd Sep 27, 2025
62be7d5
chore(svelte-query): fix eslint config (#9699)
lachlancollins Sep 27, 2025
c1e67ed
Merge branch 'main' into svelte-5-adapter
lachlancollins Sep 27, 2025
c6d0b92
Merge branch 'main' into svelte-5-adapter
lachlancollins Sep 30, 2025
05f0acd
ci: apply automated fixes
autofix-ci[bot] Sep 30, 2025
7049e4b
Fix sherif
lachlancollins Sep 30, 2025
0c44073
Update docs and changeset
lachlancollins Sep 30, 2025
f3fc6a2
Update keywords
lachlancollins Sep 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/pink-pots-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@tanstack/svelte-query-persist-client': major
'@tanstack/svelte-query-devtools': major
'@tanstack/svelte-query': major
---

BREAKING: Migrate to svelte runes (signals). Requires [Svelte v5.25.0](https://github.com/sveltejs/svelte/releases/tag/svelte%405.25.0) or newer. Please see the [migration guide](https://tanstack.com/query/latest/docs/framework/svelte/migrate-from-v5-to-v6).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Confirm Svelte minimum version and align peerDeps; consider adding a brief TL;DR migration snippet.

  • Please verify that all three packages declare a consistent peerDependencies.svelte range (e.g., ^5.25.0 or >=5.25.0) and that 5.25.0 is the true minimum required by the new runes usage. If only specific APIs introduced after 5.0 are needed, we might relax the floor.
  • Sanity-check that both links resolve (Svelte release tag and the TanStack migration guide).
  • Consider adding a tiny code before/after in the changeset body to help readers in the changelog.

Example tweak (optional):

 BREAKING: Migrate to svelte runes (signals). Requires [Svelte v5.25.0](https://github.com/sveltejs/svelte/releases/tag/svelte%405.25.0) or newer. Please see the [migration guide](https://tanstack.com/query/latest/docs/framework/svelte/migrate-from-v5-to-v6).
+
+TL;DR:
+- Svelte: >=5.25.0
+- Query APIs now take thunks:
+  - Before: `createQuery({ queryKey, queryFn })`
+  - After:  `createQuery(() => ({ queryKey, queryFn }))`

Run this script to check peerDependency alignment across the repo:


🏁 Script executed:

#!/bin/bash
# Find all package.json files and print any svelte peerDependency declarations
fd -a package.json | while read -r f; do
  jq -r --arg file "$f" '
    if has("peerDependencies") and .peerDependencies | has("svelte") then
      "\($file): " + .peerDependencies.svelte
    else empty end
  ' "$f"
done | rg -n "svelte"

Length of output: 11450


🏁 Script executed:

#!/bin/bash
echo "PeerDependencies.svelte in svelte packages:"
for f in packages/svelte-query*/package.json; do
  if [[ -f "$f" ]]; then
    version=$(jq -r '.peerDependencies.svelte // empty' "$f")
    echo "$f: $version"
  fi
done

echo
echo "Checking URL status codes:"
for url in "https://github.com/sveltejs/svelte/releases/tag/svelte@5.25.0" "https://tanstack.com/query/latest/docs/framework/svelte/migrate-from-v5-to-v6"; do
  code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
  echo "$url => $code"
done

Length of output: 381


PeerDeps aligned and release tag valid; migration guide link broken

  • All three packages/svelte-query* declare peerDependencies.svelte as ^5.25.0.
  • Svelte v5.25.0 release tag link resolves (HTTP 200).
  • Migration guide URL returns 404—update the https://tanstack.com/query/latest/docs/framework/svelte/migrate-from-v5-to-v6 link to its correct path.
  • Optional: include a brief TL;DR snippet in the changeset for quick migration reference.
🤖 Prompt for AI Agents
.changeset/pink-pots-jam.md around line 7: the migration guide URL currently
points to
https://tanstack.com/query/latest/docs/framework/svelte/migrate-from-v5-to-v6
which returns 404; update that link to the correct migration guide path (replace
with the current valid URL on tanstack.com for migrating Svelte v5→v6) and
confirm the anchor resolves (HTTP 200); optionally add a one-line TL;DR under
the BREAKING note summarizing the key migration step (e.g., "replace stores with
svelte runes (signals) and update to Svelte >= v5.25.0") to help quick
reference.

4 changes: 2 additions & 2 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@
"to": "framework/svelte/ssr"
},
{
"label": "Reactivity",
"to": "framework/svelte/reactivity"
"label": "Migrate from v5 to v6",
"to": "framework/svelte/migrate-from-v5-to-v6"
}
]
},
Expand Down
2 changes: 0 additions & 2 deletions docs/framework/svelte/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ title: Installation

You can install Svelte Query via [NPM](https://npmjs.com).

> v5 is currently available as a release-candidate. We don't anticipate any major API changes from here on out. We encourage you to try it out and report any issues you find.
### NPM

```bash
Expand Down
77 changes: 77 additions & 0 deletions docs/framework/svelte/migrate-from-v5-to-v6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
## Overview

While Svelte v5 has legacy compatibility with the stores syntax from Svelte v3/v4, it has been somewhat buggy and unreliable for this adapter. The `@tanstack/svelte-query` v6 adapter fully migrates to the runes syntax, which relies on signals. This rewrite should also simplify the code required to ensure your query inputs remain reactive.

## Installation

Please ensure your project has [Svelte v5.25.0](https://github.com/sveltejs/svelte/releases/tag/svelte%405.25.0) or newer.

Run `pnpm add @tanstack/svelte-query@latest` (or your package manager's equivalent).

> Note that `@tanstack/svelte-query` v6 depends on `@tanstack/query-core` v5.

## Thunks

Like the Solid adapter, most functions for the Svelte adapter now require options to be provided as a "thunk" (`() => options`) to provide reactivity.

```diff
-const query = createQuery({
+const query = createQuery(() => ({
queryKey: ['todos'],
queryFn: () => fetchTodos(),
-})
+}))
```

## Accessing Properties

Given the adapter no longer uses stores, it is no longer necessary to prefix with `$`.

```diff
-{#if $todos.isSuccess}
+{#if todos.isSuccess}
<ul>
- {#each $todos.data.items as item}
+ {#each todos.data.items as item}
<li>{item}</li>
{/each}
</ul>
{/if}
```

## Reactivity

You previously needed to do some funky things with stores to achieve reactivity for inputs. This is no longer the case! You don't even need to wrap your query in a `$derived`.

```diff
-const intervalMs = writable(1000)
+let intervalMs = $state(1000)

-const query = createQuery(derived(intervalMs, ($intervalMs) => ({
+const query = createQuery(() => ({
queryKey: ['refetch'],
queryFn: async () => await fetch('/api/data').then((r) => r.json()),
refetchInterval: $intervalMs,
-})))
+}))
```

## Disabling Legacy Mode

If your component has any stores, it might not properly switch to runes mode. You can ensure your application is using runes in two ways:

### On a per-file basis

In each `.svelte` file, once you have migrated to runes, add `<svelte:options runes={true} />`. This is better for large applications requiring gradual migration.

### On an project-wide basis

In your `svelte.config.js`, add the following to config:

```json
compilerOptions: {
runes: true,
},
```

This can be added once you've 100% eradicated stores syntax from your app.
21 changes: 12 additions & 9 deletions docs/framework/svelte/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ title: Overview

The `@tanstack/svelte-query` package offers a 1st-class API for using TanStack Query via Svelte.

> Migrating from stores to the runes syntax? See the [migration guide](../migrate-from-v5-to-v6).

## Example

Include the QueryClientProvider near the root of your project:
Expand All @@ -28,19 +30,19 @@ Then call any function (e.g. createQuery) from any component:
<script lang="ts">
import { createQuery } from '@tanstack/svelte-query'

const query = createQuery({
const query = createQuery(() => ({
queryKey: ['todos'],
queryFn: () => fetchTodos(),
})
}))
</script>

<div>
{#if $query.isLoading}
{#if query.isLoading}
<p>Loading...</p>
{:else if $query.isError}
<p>Error: {$query.error.message}</p>
{:else if $query.isSuccess}
{#each $query.data as todo}
{:else if query.isError}
<p>Error: {query.error.message}</p>
{:else if query.isSuccess}
{#each query.data as todo}
<p>{todo.title}</p>
{/each}
{/if}
Expand All @@ -62,6 +64,8 @@ Svelte Query offers useful functions and components that will make managing serv
- `useQueryClient`
- `useIsFetching`
- `useIsMutating`
- `useMutationState`
- `useIsRestoring`
- `useHydrate`
- `<QueryClientProvider>`
- `<HydrationBoundary>`
Expand All @@ -70,5 +74,4 @@ Svelte Query offers useful functions and components that will make managing serv

Svelte Query offers an API similar to React Query, but there are some key differences to be mindful of.

- Many of the functions in Svelte Query return a Svelte store. To access values on these stores reactively, you need to prefix the store with a `$`. You can learn more about Svelte stores [here](https://learn.svelte.dev/tutorial/writable-stores).
- If your query or mutation depends on variables, you must use a store for the options. You can read more about this [here](../reactivity).
- The arguments to the `create*` functions must be wrapped in a function to preserve reactivity.
45 changes: 0 additions & 45 deletions docs/framework/svelte/reactivity.md

This file was deleted.

8 changes: 4 additions & 4 deletions docs/framework/svelte/ssr.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ export async function load() {
export let data: PageData
const query = createQuery({
const query = createQuery(() => ({
queryKey: ['posts'],
queryFn: getPosts,
initialData: data.posts,
})
}))
</script>
```

Expand Down Expand Up @@ -136,10 +136,10 @@ export async function load({ parent, fetch }) {
import { createQuery } from '@tanstack/svelte-query'
// This data is cached by prefetchQuery in +page.ts so no fetch actually happens here
const query = createQuery({
const query = createQuery(() => ({
queryKey: ['posts'],
queryFn: async () => (await fetch('/api/posts')).json(),
})
}))
</script>
```

Expand Down
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default [
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-unsafe-function-type': 'off',
'no-case-declarations': 'off',
'prefer-const': 'off',
},
},
{
Expand Down
4 changes: 3 additions & 1 deletion examples/svelte/auto-refetching/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import { QueryClientProvider, QueryClient } from '@tanstack/svelte-query'
import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'
const { children } = $props()
const queryClient = new QueryClient({
defaultOptions: {
queries: {
Expand All @@ -15,7 +17,7 @@

<QueryClientProvider client={queryClient}>
<main>
<slot />
{@render children()}
</main>
<SvelteQueryDevtools />
</QueryClientProvider>
56 changes: 31 additions & 25 deletions examples/svelte/auto-refetching/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,32 @@
createMutation,
} from '@tanstack/svelte-query'
let intervalMs = 1000
let value = ''
let intervalMs = $state(1000)
let value = $state<string>('')
const client = useQueryClient()
const endpoint = '/api/data'
$: todos = createQuery<{ items: string[] }>({
const todos = createQuery<{ items: string[] }>(() => ({
queryKey: ['refetch'],
queryFn: async () => await fetch(endpoint).then((r) => r.json()),
// Refetch the data every second
refetchInterval: intervalMs,
})
}))
const addMutation = createMutation({
const addMutation = createMutation(() => ({
mutationFn: (value: string) =>
fetch(`${endpoint}?add=${encodeURIComponent(value)}`).then((r) =>
r.json(),
),
onSuccess: () => client.invalidateQueries({ queryKey: ['refetch'] }),
})
}))
const clearMutation = createMutation({
const clearMutation = createMutation(() => ({
mutationFn: () => fetch(`${endpoint}?clear=1`).then((r) => r.json()),
onSuccess: () => client.invalidateQueries({ queryKey: ['refetch'] }),
})
}))
</script>

<h1>Auto Refetch with stale-time set to {intervalMs}ms</h1>
Expand All @@ -51,53 +51,59 @@
margin-left:.5rem;
width:.75rem;
height:.75rem;
background: {$todos.isFetching ? 'green' : 'transparent'};
transition: {!$todos.isFetching ? 'all .3s ease' : 'none'};
background: {todos.isFetching ? 'green' : 'transparent'};
transition: {!todos.isFetching ? 'all .3s ease' : 'none'};
border-radius: 100%;
transform: scale(1.5)"
></span>
</div>
</label>
<h2>Todo List</h2>
<form
on:submit={(e) => {
onsubmit={(e) => {
e.preventDefault()
e.stopPropagation()
$addMutation.mutate(value, {
addMutation.mutate(value, {
onSuccess: () => (value = ''),
})
}}
>
<input placeholder="enter something" bind:value />
</form>

{#if $todos.isPending}
{#if todos.isPending}
Loading...
{/if}
{#if $todos.error}
{#if todos.error}
An error has occurred:
{$todos.error.message}
{todos.error.message}
{/if}
{#if $todos.isSuccess}
{#if todos.isSuccess}
<ul>
{#each $todos.data.items as item}
{#each todos.data.items as item}
<li>{item}</li>
{/each}
</ul>
<div>
<button on:click={() => $clearMutation.mutate(undefined)}>
Clear All
</button>
</div>
{/if}
{#if $todos.isFetching}
<div style="color:darkgreen; font-weight:700">
'Background Updating...' : ' '
<button onclick={() => clearMutation.mutate(undefined)}> Clear All </button>
</div>
{/if}

<pre
class={['updating-text', todos.isFetching && 'on']}
style="font-weight:700">Background Updating...</pre>

<style>
li {
text-align: left;
}
.updating-text {
color: transparent;
transition: all 0.3s ease;
}
.updating-text.on {
color: green;
transition: none;
}
</style>
3 changes: 3 additions & 0 deletions examples/svelte/auto-refetching/svelte.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ const config = {
kit: {
adapter: adapter(),
},
compilerOptions: {
runes: true,
},
}

export default config
Loading
Loading