Skip to content

feat: Svelte 5 API proposal #8852

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 24 commits into
base: svelte-5-adapter
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8c9ce99
WIP: Svelte 5 adapter (#6981)
zhihengGet Jul 26, 2024
647cbf9
Merge remote-tracking branch 'origin' into svelte-5-adapter-notes
elliott-with-the-longest-name-on-github Mar 23, 2025
3e60f15
feat: Draft proposal
elliott-with-the-longest-name-on-github Mar 25, 2025
cd2d81f
Merge remote-tracking branch 'origin' into svelte-5-adapter-derived-by
elliott-with-the-longest-name-on-github Mar 25, 2025
fec1a0d
chore: Improve reactive containers
elliott-with-the-longest-name-on-github Mar 25, 2025
5b359ed
ci: apply automated fixes
autofix-ci[bot] Mar 25, 2025
8325a7b
oops
elliott-with-the-longest-name-on-github Mar 25, 2025
a6917da
Merge branch 'svelte-5-adapter-derived-by' of github.com:elliott-with…
elliott-with-the-longest-name-on-github Mar 25, 2025
4c93461
fix: Update API, add a bunch of tests
elliott-with-the-longest-name-on-github Apr 2, 2025
07ab8dd
Merge remote-tracking branch 'origin' into svelte-5-adapter-derived-by
elliott-with-the-longest-name-on-github Apr 2, 2025
c35a54f
merge main
elliott-with-the-longest-name-on-github Apr 2, 2025
cb173e2
fix: use const
elliott-with-the-longest-name-on-github Apr 2, 2025
b7dda3d
more tests
elliott-with-the-longest-name-on-github Apr 2, 2025
cc54952
feat: More tests, back to thunks, fixed svelte-query-persist-client
elliott-with-the-longest-name-on-github Apr 3, 2025
6e73278
feat: More tests and examples!
elliott-with-the-longest-name-on-github Apr 8, 2025
9fdbcad
Merge remote-tracking branch 'upstream/main' into svelte-5-adapter-de…
elliott-with-the-longest-name-on-github Apr 8, 2025
14620d1
lockfile
elliott-with-the-longest-name-on-github Apr 15, 2025
7e542cd
Merge remote-tracking branch 'origin/svelte-5-adapter' into svelte-5-…
elliott-with-the-longest-name-on-github Apr 15, 2025
ba92c7f
fixes
elliott-with-the-longest-name-on-github Apr 15, 2025
dffbdc7
Fix current CI errors
lachlancollins Apr 20, 2025
2452ce9
More small fixes/tweaks
lachlancollins Apr 20, 2025
d0bbe8c
Remove test.only
lachlancollins Apr 20, 2025
fd399fb
ci: apply automated fixes
autofix-ci[bot] Apr 20, 2025
258d992
Fix pnpm-lock, fix import order
lachlancollins Apr 20, 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
26 changes: 18 additions & 8 deletions examples/svelte/auto-refetching/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

const client = useQueryClient()

const endpoint = 'http://localhost:5173/api/data'
const endpoint = '/api/data'

const todos = createQuery<{ items: string[] }>(() => ({
queryKey: ['refetch'],
Expand All @@ -21,7 +21,9 @@

const addMutation = createMutation(() => ({
mutationFn: (value: string) =>
fetch(`${endpoint}?add=${value}`).then((r) => r.json()),
fetch(`${endpoint}?add=${encodeURIComponent(value)}`).then((r) =>
r.json(),
),
onSuccess: () => client.invalidateQueries({ queryKey: ['refetch'] }),
}))

Expand All @@ -31,7 +33,7 @@
}))
</script>

<h1>Auto Refetch with stale-time set to 1s</h1>
<h1>Auto Refetch with stale-time set to {(intervalMs / 1000).toFixed(2)}s</h1>

<p>
This example is best experienced on your own machine, where you can open
Expand Down Expand Up @@ -86,14 +88,22 @@
<button onclick={() => clearMutation.mutate(undefined)}> Clear All </button>
</div>
{/if}
{#if todos.isFetching}
<div style="color:darkgreen; font-weight:700">
'Background Updating...' : ' '
</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>
18 changes: 12 additions & 6 deletions examples/svelte/basic/src/lib/Posts.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,9 @@
</article>
{/each}
</ul>
{#if posts.isFetching}
<div style="color:darkgreen; font-weight:700">
Background Updating...
</div>
{/if}
<pre
class={['updating-text', posts.isFetching && 'on']}
style="font-weight:700">Background Updating...</pre>
{/if}
</div>
</div>
Expand All @@ -53,8 +51,16 @@
}
a {
display: block;
color: white;
font-size: 1.5rem;
margin-bottom: 1rem;
}

.updating-text {
color: transparent;
transition: all 0.3s ease;
}
.updating-text.on {
color: green;
transition: none;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,6 @@
.card {
background-color: #111;
margin-bottom: 1rem;
color: rgba(255, 255, 255, 0.87);
}
</style>
2 changes: 1 addition & 1 deletion examples/svelte/optimistic-updates/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

const client = useQueryClient()

const endpoint = 'http://localhost:5173/api/data'
const endpoint = '/api/data'

const fetchTodos = async (): Promise<Todos> =>
await fetch(endpoint).then((r) => r.json())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type Todo = {
text: string
}

const items: Todo[] = []
const items: Array<Todo> = []

/** @type {import('./$types').RequestHandler} */
export const GET: RequestHandler = async (req) => {
Expand Down
3 changes: 1 addition & 2 deletions examples/svelte/playground/src/routes/AddTodo.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
let name = $state('')

const postTodo = async ({ name, notes }: Omit<Todo, 'id'>) => {
console.info('postTodo', { name, notes })
return new Promise((resolve, reject) => {
setTimeout(
() => {
Expand All @@ -31,7 +30,7 @@
}
const todo = { name, notes, id: id.value }
id.value = id.value + 1
list.value = [...list.value, todo]
list.value.push(todo)
resolve(todo)
},
queryTimeMin.value +
Expand Down
2 changes: 1 addition & 1 deletion examples/svelte/playground/src/routes/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

<button
onclick={() => {
views.value = [...views.value, '']
views.value.push('')
}}
>
Add Filter List
Expand Down
17 changes: 11 additions & 6 deletions examples/svelte/ssr/src/lib/Posts.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,9 @@
</article>
{/each}
</ul>
{#if posts.isFetching}
<div style="color:darkgreen; font-weight:700">
Background Updating...
</div>
{/if}
<pre
class={['updating-text', posts.isFetching && 'on']}
style="font-weight:700">Background Updating...</pre>
{/if}
</div>
</div>
Expand All @@ -53,8 +51,15 @@
}
a {
display: block;
color: white;
font-size: 1.5rem;
margin-bottom: 1rem;
}
.updating-text {
color: transparent;
transition: all 0.3s ease;
}
.updating-text.on {
color: green;
transition: none;
}
</style>
2 changes: 1 addition & 1 deletion examples/svelte/ssr/src/routes/+layout.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { browser } from '$app/environment'
import { QueryClient } from '@tanstack/svelte-query'
import type { LayoutLoad } from './$types'
import { browser } from '$app/environment'

export const load: LayoutLoad = () => {
const queryClient = new QueryClient({
Expand Down
2 changes: 1 addition & 1 deletion examples/svelte/ssr/src/routes/+page.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { api } from '$lib/api'
import type { PageLoad } from './$types'
import { api } from '$lib/api'

export const load: PageLoad = async ({ parent, fetch }) => {
const { queryClient } = await parent()
Expand Down
2 changes: 1 addition & 1 deletion examples/svelte/ssr/src/routes/[postId]/+page.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { api } from '$lib/api'
import type { PageLoad } from './$types'
import { api } from '$lib/api'

export const load: PageLoad = async ({ parent, fetch, params }) => {
const { queryClient } = await parent()
Expand Down
3 changes: 1 addition & 2 deletions examples/svelte/star-wars/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
<h2 class="text-4xl">React Query Demo</h2>
<p>Using the Star Wars API</p>
<p>
(Built by <a href="https://twitter.com/Brent_m_Clark">@Brent_m_Clark</a>
)
(Built by <a href="https://twitter.com/Brent_m_Clark">@Brent_m_Clark</a>)
</p>
<section>
<h5 class="text-2xl">Why React Query?</h5>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@
import { createQuery } from '@tanstack/svelte-query'
import Homeworld from './Homeworld.svelte'
import Film from './Film.svelte'

let { data } = $props()
import { page } from '$app/state'

const getCharacter = async () => {
const res = await fetch(
`https://swapi.dev/api/people/${data.params.characterId}/`,
`https://swapi.dev/api/people/${page.params.characterId}/`,
)
return await res.json()
}

const query = createQuery(() => ({
queryKey: ['character', data.params.characterId],
queryKey: ['character', page.params.characterId],
queryFn: getCharacter,
}))
</script>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
<script lang="ts">
import { createQuery } from '@tanstack/svelte-query'
import Character from './Character.svelte'

let { data } = $props()
import { page } from '$app/state'

const getFilm = async () => {
const res = await fetch(
`https://swapi.dev/api/films/${data.params.filmId}/`,
`https://swapi.dev/api/films/${page.params.filmId}/`,
)
return await res.json()
}

const query = createQuery(() => ({
queryKey: ['film', data.params.filmId],
queryKey: ['film', page.params.filmId],
queryFn: getFilm,
}))
</script>
Expand Down
5 changes: 0 additions & 5 deletions examples/svelte/star-wars/src/routes/films/[filmId]/+page.ts

This file was deleted.

4 changes: 2 additions & 2 deletions packages/svelte-query-persist-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"compile": "tsc --build",
"test:types": "svelte-check --tsconfig ./tsconfig.json",
"test:eslint": "eslint ./src",
"test:lib": "vitest",
"test:lib:dev": "pnpm run test:lib --watch",
"test:lib": "vitest run",
"test:lib:dev": "vitest",
"test:build": "publint --strict && attw --pack",
"build": "svelte-package --input ./src --output ./dist"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
QueryClientProvider,
setIsRestoringContext,
} from '@tanstack/svelte-query'
import { box } from './utils.svelte.js'
import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core'
import type {
OmitKeyof,
Expand All @@ -26,9 +27,9 @@
...props
}: PersistQueryClientProviderProps = $props()

let isRestoring = $state(true)
let isRestoring = box(true)

setIsRestoringContext(() => isRestoring)
setIsRestoringContext(isRestoring)

const options = $derived({
...persistOptions,
Expand All @@ -40,12 +41,12 @@
})

$effect(() => {
isRestoring = true
isRestoring.current = true
persistQueryClientRestore(options)
.then(() => props.onSuccess?.())
.catch(() => props.onError?.())
.finally(() => {
isRestoring = false
isRestoring.current = false
})
})
</script>
Expand Down
14 changes: 14 additions & 0 deletions packages/svelte-query-persist-client/src/utils.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
type Box<T> = { current: T }

export function box<T>(initial: T): Box<T> {
let current = $state(initial)

return {
get current() {
return current
},
set current(newValue) {
current = newValue
},
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<script lang="ts">
import { createQuery } from '@tanstack/svelte-query'
import { sleep } from '../utils.svelte.js'
import { sleep, StatelessRef } from '../utils.svelte.js'

let { states }: { states: Array<string> } = $props()
let { states }: { states: StatelessRef<Array<string>> } = $props()

const query = createQuery(() => ({
queryKey: ['test'],
queryFn: async () => {
states.push('fetching')
states.current.push('fetching')
await sleep(5)
states.push('fetched')
states.current.push('fetched')
return 'fetched'
},
}))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
import AwaitOnSuccess from './AwaitOnSuccess.svelte'
import type { OmitKeyof, QueryClient } from '@tanstack/svelte-query'
import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core'
import { StatelessRef } from '../utils.svelte'

interface Props {
queryClient: QueryClient
persistOptions: OmitKeyof<PersistQueryClientOptions, 'queryClient'>
onSuccess: () => Promise<void>
states: Array<string>
states: StatelessRef<Array<string>>
}

let { queryClient, persistOptions, onSuccess, states }: Props = $props()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,26 @@
<script lang="ts">
import { untrack } from 'svelte'
import { createQuery } from '@tanstack/svelte-query'
import { sleep } from '../utils.svelte.js'
import type { StatusResult } from '../utils.svelte.js'
import type { StatelessRef, StatusResult } from '../utils.svelte.js'

let {
states,
fetched,
}: {
states: { value: Array<StatusResult<string>> }
fetched: boolean
states: StatelessRef<Array<StatusResult<string>>>
} = $props()

const query = createQuery(() => ({
queryKey: ['test'],
queryFn: async () => {
fetched = true
await sleep(5)
return 'fetched'
},
queryFn: () => Promise.resolve('fetched'),

staleTime: Infinity,
}))

$effect(() => {
// svelte-ignore state_snapshot_uncloneable
states.value = [...untrack(() => states.value), $state.snapshot(query)]
const snapshot = $state.snapshot(query)
states.current.push(snapshot)
})
</script>

<div>data: {query.data ?? 'undefined'}</div>
<div>fetchStatus: {query.fetchStatus}</div>
<div>fetched: {fetched}</div>
Loading
Loading