-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
feat!(svelte-query): Update svelte adapter to use runes #9694
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
base: main
Are you sure you want to change the base?
Conversation
* feat: Draft proposal * chore: Improve reactive containers * ci: apply automated fixes * oops * fix: Update API, add a bunch of tests * merge main * fix: use const * more tests * feat: More tests, back to thunks, fixed svelte-query-persist-client * feat: More tests and examples! * lockfile * fixes * Fix current CI errors * More small fixes/tweaks * Remove test.only * ci: apply automated fixes * Fix pnpm-lock, fix import order * update main docs * feat: More tests * ci: apply automated fixes * add back old tests * Cleanup * Fix persist client * Fix useMutationState --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Lachlan Collins <1667261+lachlancollins@users.noreply.github.com>
🦋 Changeset detectedLatest commit: 7049e4b The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
WalkthroughMigration of the Svelte adapter and examples to "runes" (Svelte v5+): option objects become thunk/accessor functions, store auto-subscriptions are replaced with ref/Box/ReactiveValue patterns, many .ts implementations replaced by .svelte variants, props consumed via $props(), and peer deps/tooling updated for Svelte ^5.25.0. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant C as Component
participant A as Options Accessor
participant QC as QueryClient Accessor
participant O as QueryObserver
participant R as Reactive Result
C->>A: invoke () => options
C->>QC: resolve query client (if accessor)
C->>O: instantiate QueryObserver(client, resolvedOptions)
O-->>R: provide initial (optimistic) result
loop runtime updates
O-->>R: notify -> update reactive result
C->>C: template reads result.* (no $ prefix)
A->>O: options change -> pre-effect setOptions
end
sequenceDiagram
autonumber
participant P as PersistQueryClientProvider
participant B as Box<boolean> (isRestoring)
participant Rf as restore()
participant Sb as subscribe()
participant QP as QueryClientProvider
P->>B: set isRestoring.current = true
P->>Rf: call persistQueryClientRestore(client, opts)
alt success
Rf-->>P: resolved
P->>B: set isRestoring.current = false
P->>Sb: call persistQueryClientSubscribe(client, opts)
else failure
Rf-->>P: rejected
P->>B: set isRestoring.current = false
end
P->>QP: render children via QueryClientProvider
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (3)
🧰 Additional context used🧠 Learnings (1)📚 Learning: 2025-09-02T17:57:33.184Z
Applied to files:
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
🔇 Additional comments (3)
Comment |
View your CI Pipeline Execution ↗ for commit 7049e4b
☁️ Nx Cloud last updated this comment at |
Sizes for commit 7049e4b:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 23
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/svelte-query-devtools/src/Devtools.svelte (1)
66-89
: Fix race: async import may resolve after unmount, causing stray mountIf the component unmounts before the dynamic import resolves, devtools can still instantiate/mount into a detached node. Add a cancellation guard and unmount the local instance safely.
Apply:
- onMount(() => { - import('@tanstack/query-devtools').then((m) => { - const QueryDevtools = m.TanstackQueryDevtools - - devtools = new QueryDevtools({ - client, - queryFlavor: 'Svelte Query', - version: '5', - onlineManager, - buttonPosition, - position, - initialIsOpen, - errorTypes, - styleNonce, - shadowDOMTarget, - hideDisabledQueries, - }) - - devtools.mount(ref) - }) - return () => devtools?.unmount() - }) + onMount(() => { + let cancelled = false + let instance: TanstackQueryDevtools | undefined + + ;(async () => { + const m = await import('@tanstack/query-devtools') + if (cancelled) return + const QueryDevtools = m.TanstackQueryDevtools + + instance = new QueryDevtools({ + client, + queryFlavor: 'Svelte Query', + version: '5', + onlineManager, + buttonPosition, + position, + initialIsOpen, + errorTypes, + styleNonce, + shadowDOMTarget, + hideDisabledQueries, + }) + + devtools = instance + instance.mount(ref) + })() + + return () => { + cancelled = true + instance?.unmount() + } + })packages/svelte-query/src/createQuery.ts (1)
15-38
: Adjust all overloads to accept a nullable queryClient accessor.For consistency with the implementation (and
createBaseQuery
), the overload signatures should acceptAccessor<QueryClient | undefined>
rather thanAccessor<QueryClient>
. Without this, TypeScript will complain when an accessor conditionally returnsundefined
—despite the runtime handling it. Update each overload’squeryClient
parameter accordingly.- queryClient?: Accessor<QueryClient>, + queryClient?: Accessor<QueryClient | undefined>,(Apply the same change to every overload that declares
queryClient
.)
🧹 Nitpick comments (67)
packages/svelte-query-devtools/package.json (1)
57-58
: Keep Svelte peer ranges aligned across packages.To reduce confusion for consumers, let’s mirror the adapter’s
svelte
peer range (^5.7.0
). It keeps the expectation consistent and avoids scenarios where the devtools appear to accept lower minors than the core package actually supports.packages/svelte-query-persist-client/eslint.config.js (1)
20-24
: Limit the blanketsvelte/no-unused-svelte-ignore
disable.Turning this rule off repo-wide removes our guardrail against stale
@svelte-ignore
comments. If the disable is only here to dodge current eslint-plugin-svelte false positives with runes, please either scope it to the specific files that hit the issue or leave an inline TODO pointing to the upstream tracker so we remember to re-enable it. Right now it’s a silent, permanent relax.examples/svelte/star-wars/src/routes/films/[filmId]/Character.svelte (2)
11-14
: Pass AbortSignal, guard when id is empty, and avoid subtle closure mismatchImprove cancellation and robustness by using the QueryFunctionContext (provides
signal
andqueryKey
) and guarding on a falsycharacterId
. This also avoids relying on a closure for id resolution.Apply this diff:
- const query = createQuery(() => ({ - queryKey: ['character', characterId], - queryFn: getCharacter, - })) + const query = createQuery(() => ({ + queryKey: ['character', characterId] as const, + enabled: !!characterId, + queryFn: async ({ signal, queryKey }) => { + const [, id] = queryKey as ['character', string] + const res = await fetch(`https://swapi.dev/api/people/${id}/`, { signal }) + if (!res.ok) throw new Error(`Failed to fetch character ${id}`) + return res.json() + }, + }))Based on learnings
17-17
: Prefer boolean flag over string comparisonUse the built-in boolean for readability and to avoid typos:
-{#if query.status === 'success'} +{#if query.isSuccess}packages/svelte-query-devtools/src/Devtools.svelte (1)
90-105
: Guard setter calls with optional call chainingIf any setter isn’t present on the queried devtools version, current calls can throw at runtime. Use optional call chaining on the method.
Apply:
- $effect(() => { - devtools?.setButtonPosition(buttonPosition) - }) + $effect(() => { + devtools?.setButtonPosition?.(buttonPosition) + }) - $effect(() => { - devtools?.setPosition(position) - }) + $effect(() => { + devtools?.setPosition?.(position) + }) - $effect(() => { - devtools?.setInitialIsOpen(initialIsOpen) - }) + $effect(() => { + devtools?.setInitialIsOpen?.(initialIsOpen) + }) - $effect(() => { - devtools?.setErrorTypes(errorTypes) - }) + $effect(() => { + devtools?.setErrorTypes?.(errorTypes) + }).changeset/pink-pots-jam.md (1)
7-7
: Make the changeset note more actionable (add concrete migration pointers).Consider briefly calling out key breaking changes and the new invocation pattern to reduce upgrade friction.
Apply this diff to enrich the note:
-BREAKING: Migrate to svelte runes (svelte v5+). Please see documentation for migration guide. +BREAKING: Migrate to Svelte runes (Svelte v5+). See the migration guide in the docs. + +- Queries now use factory-wrapped options, e.g. `createQuery(() => ({ ... }))`, `createInfiniteQuery(() => ({ ... }))`. +- Components adopt `$props()` and render children via `{@render children?.()}`. +- Requires Svelte 5+ (ensure `compilerOptions.runes` is enabled).examples/svelte/load-more-infinite-scroll/src/routes/+layout.svelte (1)
7-8
: Guard children render to avoid runtime errors when no slot content.If children are absent, calling
children()
throws. Use optional chaining.Apply this diff:
- {@render children()} + {@render children?.()}Also applies to: 20-20
examples/svelte/star-wars/src/routes/+layout.svelte (1)
7-8
: Use optional chaining when rendering children.Prevents a crash if children are missing.
Apply this diff:
- {@render children()} + {@render children?.()}Also applies to: 30-30
examples/svelte/basic/src/routes/+layout.svelte (1)
9-10
: Use optional chaining for resilient children render.Avoids invoking an undefined render function.
Apply this diff:
- {@render children()} + {@render children?.()}Also applies to: 26-27
packages/svelte-query/tests/infiniteQueryOptions/infiniteQueryOptions.test-d.ts (1)
41-44
: Fix stale comment reference.The comment still mentions
useInfiniteQuery
.Apply this diff:
- // known issue: type of pageParams is unknown when returned from useInfiniteQuery + // known issue: type of pageParams is unknown when returned from createInfiniteQueryexamples/svelte/basic/src/routes/[postId]/+page.svelte (1)
4-4
: Preserve types by annotating $props() extraction.Without the prop export,
data
loses itsPageData
type. Add the type import and annotate the destructure.Apply this diff:
- const { data } = $props() + const { data }: { data: PageData } = $props()Add this import at the top of the script block:
import type { PageData } from './$types'examples/svelte/optimistic-updates/src/routes/+layout.svelte (1)
7-8
: Optional-chain children render.Prevents errors if no children are provided.
Apply this diff:
- {@render children()} + {@render children?.()}Also applies to: 20-20
examples/svelte/ssr/src/routes/[postId]/+page.svelte (1)
4-4
: Optional: add type to props destructure for better DX.Type the data prop inline to keep editor types without extra imports.
Apply:
- const { data } = $props() + const { data }: { data: import('./$types').PageData } = $props()examples/svelte/star-wars/src/routes/films/[filmId]/+page.svelte (1)
6-16
: Inline queryFn with AbortSignal and capture id once.Improves cancellation and avoids implicit dependency on page in a separate function.
Apply:
- const getFilm = async () => { - const res = await fetch( - `https://swapi.dev/api/films/${page.params.filmId}/`, - ) - return await res.json() - } - - const query = createQuery(() => ({ - queryKey: ['film', page.params.filmId], - queryFn: getFilm, - })) + const query = createQuery(() => { + const id = page.params.filmId + return { + queryKey: ['film', id], + queryFn: async ({ signal }) => + fetch(`https://swapi.dev/api/films/${id}/`, { signal }).then((r) => + r.json(), + ), + } + })examples/svelte/simple/src/lib/Simple.svelte (1)
12-18
: Use AbortSignal in queryFn to support cancellation.Prevents in-flight fetches from completing after unmount/param change.
Apply:
- const query = createQuery<Repo>(() => ({ + const query = createQuery<Repo>(() => ({ queryKey: ['repoData'], - queryFn: async () => - await fetch('https://api.github.com/repos/TanStack/query').then((r) => - r.json(), - ), + queryFn: async ({ signal }) => + fetch('https://api.github.com/repos/TanStack/query', { signal }).then((r) => + r.json(), + ), }))examples/svelte/optimistic-updates/src/routes/+page.svelte (2)
39-43
: Strengthen error typing or guard for messagetodos.error defaults to unknown; accessing message may fail typing. Either annotate Error as the second generic or guard in template.
Option A (type-safe generics):
-const todos = createQuery<Todos>(() => ({ +const todos = createQuery<Todos, Error>(() => ({ queryKey: ['optimistic'], queryFn: fetchTodos, }))Option B (template-safe guard):
-{#if todos.error} - An error has occurred: - {todos.error.message} +{#if todos.error} + An error has occurred: + {todos.error instanceof Error ? todos.error.message : String(todos.error)} {/if}Also applies to: 102-108
44-78
: Optional: add mutation generics for better type safetyYou can tighten types for err/variables/context to remove any casts and improve DX.
Example:
type Ctx = { previousTodos?: Todos } const addTodoMutation = createMutation<Todo, Error, string, Ctx>(() => ({ // ... onError: (_err, _variables, context) => { if (context?.previousTodos) { client.setQueryData(['optimistic'], context.previousTodos) } }, }))examples/svelte/load-more-infinite-scroll/src/lib/LoadMore.svelte (2)
26-31
: Guard error message for unknown error typesquery.error may not be an Error; guard to avoid type/runtime issues.
-{#if query.error} - <span>Error: {query.error.message}</span> +{#if query.error} + <span>Error: {query.error instanceof Error ? query.error.message : String(query.error)}</span> {/if}
50-55
: Prefer isFetchingNextPage for the button labelUsing isFetching can show “Loading more...” during other refetches. isFetchingNextPage better reflects the button action.
- {#if query.isFetching} + {#if query.isFetchingNextPage} Loading more... {:else if query.hasNextPage} Load More {:else}Nothing more to load{/if}packages/svelte-query/tests/utils.svelte.ts (1)
14-22
: Tighten promiseWithResolvers typesExpose standard PromiseLike resolution and unknown for reject reason.
-export function promiseWithResolvers<T>() { - let resolve: (value: T) => void - let reject: (reason?: any) => void +export function promiseWithResolvers<T>() { + let resolve!: (value: T | PromiseLike<T>) => void + let reject!: (reason?: unknown) => void const promise = new Promise<T>((res, rej) => { resolve = res reject = rej }) - return { promise, resolve: resolve!, reject: reject! } + return { promise, resolve, reject } }examples/svelte/basic/src/lib/Posts.svelte (1)
7-7
: Use const for non-mutated valuelimit is never reassigned; prefer const.
-let limit = 10 +const limit = 10packages/svelte-query/src/createMutation.svelte.ts (2)
48-52
: Batch updates with notifyManager.batch, not batchCalls invoked immediatelybatchCalls returns a wrapper for callbacks; for inline updates, batch is the simpler/clearer choice.
Apply this diff:
- const unsubscribe = observer.subscribe((val) => { - notifyManager.batchCalls(() => { - Object.assign(result, val) - })() - }) + const unsubscribe = observer.subscribe((val) => { + notifyManager.batch(() => { + Object.assign(result, val) + }) + })
60-69
: Avoid spreading result on every get; it over‑tracks and allocatesSpreading result in the get trap reads all fields (over‑tracking) and allocates a new object for each property access. Return only the requested property.
Apply this diff:
- get: (_, prop) => { - const r = { - ...result, - mutate, - mutateAsync: result.mutate, - } - if (prop == 'value') return r - // @ts-expect-error - return r[prop] - }, + get: (_, prop) => { + if (prop === 'value') { + return { ...result, mutate, mutateAsync: result.mutate } + } + if (prop === 'mutate') return mutate + if (prop === 'mutateAsync') return result.mutate + // @ts-expect-error + return (result as any)[prop] + },examples/svelte/ssr/src/lib/Post.svelte (1)
8-11
: Guard the query with enabled to avoid fetching when postId is falsyPrevents unnecessary fetches and aligns with the template’s conditional.
Apply this diff:
const post = createQuery<Post>(() => ({ queryKey: ['post', postId], queryFn: () => api().getPostById(postId), + enabled: !!postId, }))
packages/svelte-query-persist-client/src/PersistQueryClientProvider.svelte (4)
23-29
: Avoid passing onSuccess/onError through to QueryClientProviderExtract them explicitly so they aren’t spread into .
Apply this diff:
- let { - client, - children, - persistOptions, - ...props - }: PersistQueryClientProviderProps = $props() + let { + client, + children, + persistOptions, + onSuccess, + onError, + ...restProps + }: PersistQueryClientProviderProps = $props()
39-41
: Return undefined during restore instead of a noop cleanupSlightly cleaner effect cleanup semantics.
Apply this diff:
- $effect(() => { - return isRestoring.current ? () => {} : persistQueryClientSubscribe(options) - }) + $effect(() => + isRestoring.current ? undefined : persistQueryClientSubscribe(options), + )
46-47
: Reference extracted callbacks directlyAvoid reading from the spread props bag.
Apply this diff:
- .then(() => props.onSuccess?.()) - .catch(() => props.onError?.()) + .then(() => onSuccess?.()) + .catch(() => onError?.())
54-56
: Spread only remaining provider propsPrevents leaking unrelated props to the provider.
Apply this diff:
-<QueryClientProvider {client} {...props}> +<QueryClientProvider {client} {...restProps}>packages/svelte-query/tests/useIsMutating/BaseExample.svelte (1)
18-18
: Use Svelte event directive on:click instead of onclickonclick may not attach as an event handler in Svelte templates; on:click is the idiomatic directive.
Apply this diff:
-<button onclick={() => mutation.mutate()}>Trigger</button> +<button on:click={() => mutation.mutate()}>Trigger</button>If you intentionally rely on a Svelte 5 change that supports onclick, please confirm; otherwise switch to on:click.
packages/svelte-query/tests/createQuery.svelte.test.ts (1)
228-236
: Avoid wrapping createQuery in $derived$derived here would recreate the query instance when dependencies change. Use the query result directly.
Apply this diff:
- const query = $derived( - createQuery<string, Error>( - () => ({ - queryKey: key, - queryFn: () => promise, - }), - () => queryClient, - ), - ) + const query = createQuery<string, Error>( + () => ({ + queryKey: key, + queryFn: () => promise, + }), + () => queryClient, + )packages/svelte-query/tests/createQueries.svelte.test.ts (3)
370-379
: Avoid sparse arrays with Array(50).map in runtime paths.Array(50).map produces a sparse array (map won’t run). Use Array.from to avoid accidental runtime surprises even in type-centric tests.
Apply:
- queries: Array(50).map((_, i) => ({ + queries: Array.from({ length: 50 }, (_, i) => ({ queryKey: ['key', i] as const, queryFn: () => i + 10, - })), + })),
486-495
: Same sparse array issue (with select).Apply:
- queries: Array(50).map((_, i) => ({ + queries: Array.from({ length: 50 }, (_, i) => ({ queryKey: ['key', i] as const, queryFn: () => i + 10, select: (data: number) => data.toString(), - })), + })),
583-593
: Same sparse array issue (fetch/Promise).Apply:
- queries: Array(50).map((_, i) => ({ + queries: Array.from({ length: 50 }, (_, i) => ({ queryKey: ['key', i] as const, queryFn: () => fetch('return Promise<any>').then((resp) => resp.json()), - })), + })),examples/svelte/ssr/src/routes/[postId]/+page.ts (1)
7-7
: Specify radix for parseInt.Avoid implicit radix to prevent edge-case parsing.
Apply:
- const postId = parseInt(params.postId) + const postId = parseInt(params.postId, 10)examples/svelte/auto-refetching/src/routes/+layout.svelte (2)
7-8
: Type the children prop for TS (Svelte 5 Snippet).For better DX and to avoid implicit any, type children as Snippet.
<script lang="ts"> + import type { Snippet } from 'svelte' import '../app.css' import { browser } from '$app/environment' import { QueryClientProvider, QueryClient } from '@tanstack/svelte-query' import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools' - const { children } = $props() + const { children }: { children: Snippet } = $props()
18-23
: Avoid SSR mismatch: render Devtools only in the browser.SvelteQueryDevtools should be gated by browser to prevent SSR/CSR markup mismatch and unnecessary server work.
Suggested change:
<QueryClientProvider client={queryClient}> <main> {@render children()} </main> {#if browser} <SvelteQueryDevtools /> {/if} </QueryClientProvider>examples/svelte/star-wars/src/routes/films/+page.svelte (1)
9-41
: Rune migration looks good; consider adding a keyed each for stability.
- The function-wrapped createQuery and direct query.* access align with the runes adapter.
- Add a stable key to the each block to minimize DOM churn:
- {#each query.data.results as film} + {#each query.data.results as film (film.url)}packages/svelte-query/src/useIsRestoring.ts (1)
2-6
: Type alignment check: Box shape vs context return.Confirm getIsRestoringContext() returns a Box with the same shape you export (e.g., current vs value). If consumers expect isRestoring.current, returning a mismatched shape will break them. If there’s a possible “no provider” path, consider a safe default.
examples/svelte/playground/src/routes/+page.svelte (1)
32-32
: Optional: ensure numeric coercion on inputs if needed.bind:value on number inputs should yield numbers, but if any path treats them as strings, coerce explicitly (e.g., bind:value={staleTime.value} and validate downstream) to avoid accidental string math.
Also applies to: 42-42, 55-55, 65-65, 74-74
packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts (1)
18-33
: LGTM on ref migration; you can make states const.You mutate states.value but don’t reassign states; prefer const for clarity:
- let states = ref<Array<QueryObserverResult>>([]) + const states = ref<Array<QueryObserverResult>>([])examples/svelte/star-wars/src/routes/characters/+page.svelte (1)
9-36
: Rune migration looks good; add keyed each for better diffing.Same as films example, key the each block:
- {#each query.data.results as person} + {#each query.data.results as person (person.url)}packages/svelte-query/tests/QueryClientProvider/ParentComponent.svelte (1)
4-7
: LGTM; optional ergonomic tweak for props typing.You can type $props directly to avoid inline annotation:
- let { queryClient }: { queryClient: QueryClient } = $props() + const { queryClient } = $props<{ queryClient: QueryClient }>()packages/svelte-query/tests/createInfiniteQuery/BaseExample.svelte (1)
12-20
: Rune migration looks correct; consider avoiding ts-ignore with a typed snapshot helper.The new Accessor-based call and client factory are consistent. The $effect + untrack pattern is fine. To avoid ts-expect-error/svelte-ignore, you can cast the snapshot once via a helper.
Example:
+ const snapshot = () => $state.snapshot(query) as unknown as QueryObserverResult $effect(() => { - // @ts-expect-error - // svelte-ignore state_snapshot_uncloneable - states.value = [...untrack(() => states.value), $state.snapshot(query)] + states.value = [...untrack(() => states.value), snapshot()] })Also applies to: 22-26
packages/svelte-query/src/useIsMutating.svelte.ts (2)
12-15
: Batch cache notifications to reduce redundant recomputations.Wrap the subscriber with notifyManager.batchCalls to coalesce updates (mirrors prior store-based patterns and React/Solid adapters).
Apply:
+import { notifyManager } from '@tanstack/query-core' export function useIsMutating( filters?: MutationFilters, queryClient?: QueryClient, ): ReactiveValue<number> { const client = useQueryClient(queryClient) const cache = client.getMutationCache() return new ReactiveValue( () => client.isMutating(filters), - (update) => cache.subscribe(update), + (update) => cache.subscribe(notifyManager.batchCalls(() => update())), ) }
5-8
: Non-reactive filters/client params.Passing plain filters/queryClient means changes to those won’t trigger recomputation until the cache emits an event. If that’s intentional, fine; otherwise consider Accessor<MutationFilters | QueryClient> for reactive args (aligns with other Accessor-based APIs).
Can you confirm the intended semantics for filters reactivity here?
packages/svelte-query/src/useIsFetching.svelte.ts (2)
12-15
: Batch query cache notifications.Use notifyManager.batchCalls to avoid excessive updates during bursts.
Apply:
+import { notifyManager } from '@tanstack/query-core' export function useIsFetching( filters?: QueryFilters, queryClient?: QueryClient, ): ReactiveValue<number> { const client = useQueryClient(queryClient) const queryCache = client.getQueryCache() return new ReactiveValue( () => client.isFetching(filters), - (update) => queryCache.subscribe(update), + (update) => queryCache.subscribe(notifyManager.batchCalls(() => update())), ) }
5-8
: Filters/client are non-reactive.Same note as useIsMutating: if consumers expect filters changes to reflect immediately (without a cache event), consider Accessor parameters.
Is this API intentionally non-reactive for filters?
packages/svelte-query/src/HydrationBoundary.svelte (1)
10-15
: Tighten Props typing and defaults.Prefer optional props over unions-with-undefined for options/queryClient for readability; keep required children/state.
Apply:
- type Props = { - children: Snippet - state: DehydratedState - options: HydrateOptions | undefined - queryClient: QueryClient | undefined - } + type Props = { + children: Snippet + state: DehydratedState + options?: HydrateOptions + queryClient?: QueryClient + } const { children, state, - options = undefined, - queryClient = undefined, + options, + queryClient, }: Props = $props()Also applies to: 17-22, 27-27
packages/svelte-query/tests/QueryClientProvider/QueryClientProvider.svelte.test.ts (1)
15-29
: Clean up QueryClient after test to prevent cross-test contamination.Clear the client to avoid retained queries between tests.
Apply:
test('Sets a specific cache for all queries to use', async () => { const queryClient = new QueryClient() const queryCache = queryClient.getQueryCache() @@ expect(queryCache.find({ queryKey: ['hello'] })).toBeDefined() + queryClient.clear() })
packages/svelte-query/tests/createInfiniteQuery/SelectExample.svelte (1)
12-24
: Consistent runes migration; typings nit optional.The Accessor-based call and untrack + snapshot pattern are consistent. Optionally avoid ts-ignore by casting a snapshot helper as noted in BaseExample.
Also applies to: 26-30, 33-33
packages/svelte-query-persist-client/tests/RemoveCache/Provider.svelte (1)
7-14
: Widen onSuccess/onError to support async handlersinterface Props { queryClient: QueryClient persistOptions: OmitKeyof<PersistQueryClientOptions, 'queryClient'> - onSuccess: () => void - onError: () => void + onSuccess: () => void | Promise<void> + onError: () => void | Promise<void> }
QueryClient
andOmitKeyof
are correctly re-exported from@tanstack/svelte-query
.packages/svelte-query/src/useMutationState.svelte.ts (2)
1-1
: Batch cache notifications to avoid update thrashingWrap the subscription callback with notifyManager.batchCalls like the store-based variant to coalesce updates.
Apply this diff:
-import { replaceEqualDeep } from '@tanstack/query-core' +import { notifyManager, replaceEqualDeep } from '@tanstack/query-core' @@ - $effect(() => { - const unsubscribe = mutationCache.subscribe(() => { + $effect(() => { + const unsubscribe = mutationCache.subscribe( + notifyManager.batchCalls(() => { const nextResult = replaceEqualDeep( result, getResult(mutationCache, options), ) if (result !== nextResult) { - Object.assign(result, nextResult) + result.splice(0, result.length, ...nextResult) } - }) + }), + )Based on learnings.
Also applies to: 30-38
43-54
: Remove dead/commented blockThe alternative $effect block is outdated (references result.current/optionsRef) and can be removed.
Apply this diff:
- /* $effect(() => { - mutationCache.subscribe(() => { - const nextResult = replaceEqualDeep( - result.current, - getResult(mutationCache, optionsRef), - ) - if (result.current !== nextResult) { - result = nextResult - //notifyManager.schedule(onStoreChange) - } - }) - }) */packages/svelte-query/src/context.ts (1)
27-35
: Optional: return a stable default Box instanceReturning a new { current: false } each call can defeat identity checks. A module-level singleton default avoids churn.
Apply this diff:
+const _defaultIsRestoring: Box<boolean> = { current: false } @@ - return isRestoring ?? { current: false } + return isRestoring ?? _defaultIsRestoring } catch (error) { - return { current: false } + return _defaultIsRestoring }packages/svelte-query-persist-client/tests/AwaitOnSuccess/Provider.svelte (1)
6-6
: Align import path extension for NodeNext/ESM consistencyOther files import from utils.svelte.js. Consider using the .js extension here to avoid resolution issues in strict ESM setups.
Apply this diff:
-import { StatelessRef } from '../utils.svelte' +import { StatelessRef } from '../utils.svelte.js'If your tooling resolves extensionless paths reliably, feel free to keep as-is.
examples/svelte/playground/src/routes/TodosList.svelte (1)
33-36
: Minor: use object shorthand in optionsSmall readability tweak; no behavior change.
- const query = createQuery(() => ({ - queryKey: ['todos', { filter: filter }], - queryFn: () => fetchTodos({ filter: filter }), - })) + const query = createQuery(() => ({ + queryKey: ['todos', { filter }], + queryFn: () => fetchTodos({ filter }), + }))packages/svelte-query/tests/containers.svelte.test.ts (1)
1-219
: Solid coverage of proxy traps and reactivityGreat suite validating set/has/ownKeys/getOwnPropertyDescriptor traps, lazy access, arrays, and effect behavior.
Consider adding:
- A test for symbol keys in objects (ensuring ownKeys filtering works with PropertyKey beyond strings).
- An array test deleting a non-terminal index (e.g., delete ref[1]) to confirm length handling and visibility are as intended.
packages/svelte-query/src/createBaseQuery.svelte.ts (1)
60-66
: Consider memory leak prevention for subscriptionsThe effect correctly returns an unsubscribe function, but there's a potential issue: if
isRestoring.current
changes fromtrue
tofalse
while the effect is running, the subscription won't be created until the next effect run.Consider restructuring to handle the subscription more robustly:
$effect(() => { - const unsubscribe = isRestoring.current - ? () => undefined - : observer.subscribe(() => update(createResult())) - observer.updateResult() - return unsubscribe + let unsubscribe: (() => void) | undefined + + if (!isRestoring.current) { + unsubscribe = observer.subscribe(() => update(createResult())) + } + observer.updateResult() + + return () => unsubscribe?.() })examples/svelte/auto-refetching/src/routes/+page.svelte (2)
63-69
: Prefer modern event handler syntaxWhile
onsubmit
works, consider using Svelte 5's event handler syntax for consistency.- onsubmit={(e) => { + onsubmit={(e: SubmitEvent) => {
88-88
: Missing type safety for mutation parameterThe
undefined
parameter toclearMutation.mutate
could be more explicit about intent.Consider adding a comment or using
void
to make the intent clearer:- <button onclick={() => clearMutation.mutate(undefined)}> Clear All </button> + <button onclick={() => clearMutation.mutate(undefined /* no parameters required */)}> Clear All </button>packages/svelte-query-persist-client/tests/RestoreCache/RestoreCache.svelte (1)
13-17
: Unbounded state accumulation in tests$effect pushes a new snapshot on every reactive update and never clears it. If the test expects only specific lifecycle points, consider constraining or cleaning up to avoid unbounded growth.
Example options:
- Guard to push only when fetchStatus/data change meaningfully.
- Add cleanup to reset states on destroy.
$effect(() => { // svelte-ignore state_snapshot_uncloneable const snapshot = $state.snapshot(query) states.current.push(snapshot) + return () => { + // keep array instance but clear entries to avoid leaks across mounts + states.current.length = 0 + } })examples/svelte/playground/src/lib/stores.svelte.ts (1)
1-12
: Type the setter parameter for better inferenceAnnotate newValue so ref preserves types in setters.
export function ref<T>(initial: T) { let value = $state(initial) return { get value() { return value }, - set value(newValue) { + set value(newValue: T) { value = newValue }, } }packages/svelte-query-persist-client/tests/FreshData/Provider.svelte (1)
8-14
: Validate availability of OmitKeyofOmitKeyof may not be exported by @tanstack/svelte-query. If it isn’t, switch to the built-in Omit utility type.
- import type { OmitKeyof, QueryClient } from '@tanstack/svelte-query' + import type { QueryClient } from '@tanstack/svelte-query' import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core' ... - persistOptions: OmitKeyof<PersistQueryClientOptions, 'queryClient'> + persistOptions: Omit<PersistQueryClientOptions, 'queryClient'>examples/svelte/playground/src/routes/EditTodo.svelte (1)
18-39
: Minor: order existence check before snapshotAvoid snapshotting a possibly undefined value; check first, then snapshot to keep intent clear.
- const todo = $state.snapshot(list.value.find((d) => d.id === id)) - if (!todo) { + const found = list.value.find((d) => d.id === id) + if (!found) { return reject( new Error(JSON.stringify({ fetchTodoById: { id } }, null, 2)), ) } - resolve(todo) + resolve($state.snapshot(found))packages/svelte-query/src/containers.svelte.ts (2)
87-103
: Align array length to new value in update()After removing/setting keys, explicitly set the array length to match the new array to avoid stale trailing entries.
function update(newValue: T) { const existingKeys = Object.keys(out) const newKeys = Object.keys(newValue) const keysToRemove = existingKeys.filter((key) => !newKeys.includes(key)) for (const key of keysToRemove) { // @ts-expect-error delete out[key] } for (const key of newKeys) { // @ts-expect-error // This craziness is required because Tanstack Query defines getters for all of the keys on the object. // These getters track property access, so if we access all of them here, we'll end up tracking everything. // So we wrap the property access in a special function that we can identify later to lazily access the value. // (See above) out[key] = brand(() => newValue[key]) } + if (Array.isArray(out) && Array.isArray(newValue)) { + // ensure visible length matches new array size + // @ts-expect-error + out.length = newValue.length + } }Also applies to: 105-110
41-53
: Preserve reactivity on direct assignmentsWhen assigning to existing keys, the setter replaces state with a non-reactive value. Wrap non-branded values in $state.raw so future reads stay reactive; keep branded values as-is to retain laziness.
Object.defineProperty(target, prop, { configurable: true, enumerable: true, get: () => { // If this is a lazy value, we need to call it. // We can't do something like typeof state === 'function' // because the value could actually be a function that we don't want to call. return state && isBranded(state) ? state() : state }, - set: (v) => { - state = v - }, + set: (v) => { + // keep laziness if branded; otherwise make it reactive + // @ts-expect-error + state = isBranded(v) ? v : $state.raw(v) + }, })Please re-run the affected containers tests to ensure this doesn’t inadvertently over-track properties (the branding path should still avoid eager access). Based on learnings
examples/svelte/star-wars/src/routes/characters/[characterId]/Film.svelte (1)
17-23
: Prefer boolean flags over status string checksFor readability and resilience to status value changes, consider
isSuccess
.Apply this diff:
-{#if query.status === 'success'} +{#if query.isSuccess}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (131)
.changeset/pink-pots-jam.md
(1 hunks)docs/config.json
(0 hunks)docs/framework/svelte/installation.md
(0 hunks)docs/framework/svelte/overview.md
(3 hunks)docs/framework/svelte/reactivity.md
(0 hunks)docs/framework/svelte/ssr.md
(2 hunks)eslint.config.js
(1 hunks)examples/svelte/auto-refetching/src/routes/+layout.svelte
(2 hunks)examples/svelte/auto-refetching/src/routes/+page.svelte
(2 hunks)examples/svelte/auto-refetching/svelte.config.js
(1 hunks)examples/svelte/basic/src/lib/Post.svelte
(1 hunks)examples/svelte/basic/src/lib/Posts.svelte
(3 hunks)examples/svelte/basic/src/routes/+layout.svelte
(2 hunks)examples/svelte/basic/src/routes/[postId]/+page.svelte
(1 hunks)examples/svelte/basic/svelte.config.js
(1 hunks)examples/svelte/load-more-infinite-scroll/src/app.css
(3 hunks)examples/svelte/load-more-infinite-scroll/src/lib/LoadMore.svelte
(4 hunks)examples/svelte/load-more-infinite-scroll/src/routes/+layout.svelte
(2 hunks)examples/svelte/load-more-infinite-scroll/svelte.config.js
(1 hunks)examples/svelte/optimistic-updates/src/routes/+layout.svelte
(2 hunks)examples/svelte/optimistic-updates/src/routes/+page.svelte
(4 hunks)examples/svelte/optimistic-updates/svelte.config.js
(1 hunks)examples/svelte/playground/src/lib/stores.svelte.ts
(1 hunks)examples/svelte/playground/src/lib/stores.ts
(0 hunks)examples/svelte/playground/src/routes/+layout.svelte
(2 hunks)examples/svelte/playground/src/routes/+page.svelte
(6 hunks)examples/svelte/playground/src/routes/AddTodo.svelte
(1 hunks)examples/svelte/playground/src/routes/App.svelte
(1 hunks)examples/svelte/playground/src/routes/EditTodo.svelte
(4 hunks)examples/svelte/playground/src/routes/TodosList.svelte
(1 hunks)examples/svelte/playground/svelte.config.js
(1 hunks)examples/svelte/simple/src/lib/Simple.svelte
(1 hunks)examples/svelte/simple/src/main.ts
(1 hunks)examples/svelte/simple/svelte.config.js
(1 hunks)examples/svelte/ssr/src/lib/Post.svelte
(1 hunks)examples/svelte/ssr/src/lib/Posts.svelte
(3 hunks)examples/svelte/ssr/src/routes/+layout.svelte
(1 hunks)examples/svelte/ssr/src/routes/+layout.ts
(1 hunks)examples/svelte/ssr/src/routes/+page.ts
(1 hunks)examples/svelte/ssr/src/routes/[postId]/+page.svelte
(1 hunks)examples/svelte/ssr/src/routes/[postId]/+page.ts
(1 hunks)examples/svelte/ssr/svelte.config.js
(1 hunks)examples/svelte/star-wars/src/routes/+layout.svelte
(2 hunks)examples/svelte/star-wars/src/routes/characters/+page.svelte
(1 hunks)examples/svelte/star-wars/src/routes/characters/[characterId]/+page.svelte
(3 hunks)examples/svelte/star-wars/src/routes/characters/[characterId]/+page.ts
(0 hunks)examples/svelte/star-wars/src/routes/characters/[characterId]/Film.svelte
(1 hunks)examples/svelte/star-wars/src/routes/characters/[characterId]/Homeworld.svelte
(1 hunks)examples/svelte/star-wars/src/routes/films/+page.svelte
(1 hunks)examples/svelte/star-wars/src/routes/films/[filmId]/+page.svelte
(1 hunks)examples/svelte/star-wars/src/routes/films/[filmId]/+page.ts
(0 hunks)examples/svelte/star-wars/src/routes/films/[filmId]/Character.svelte
(1 hunks)examples/svelte/star-wars/svelte.config.js
(1 hunks)packages/svelte-query-devtools/package.json
(1 hunks)packages/svelte-query-devtools/src/Devtools.svelte
(2 hunks)packages/svelte-query-persist-client/eslint.config.js
(1 hunks)packages/svelte-query-persist-client/package.json
(1 hunks)packages/svelte-query-persist-client/src/PersistQueryClientProvider.svelte
(1 hunks)packages/svelte-query-persist-client/src/utils.svelte.ts
(1 hunks)packages/svelte-query-persist-client/tests/AwaitOnSuccess/AwaitOnSuccess.svelte
(1 hunks)packages/svelte-query-persist-client/tests/AwaitOnSuccess/Provider.svelte
(1 hunks)packages/svelte-query-persist-client/tests/FreshData/FreshData.svelte
(1 hunks)packages/svelte-query-persist-client/tests/FreshData/Provider.svelte
(1 hunks)packages/svelte-query-persist-client/tests/InitialData/InitialData.svelte
(2 hunks)packages/svelte-query-persist-client/tests/InitialData/Provider.svelte
(1 hunks)packages/svelte-query-persist-client/tests/OnSuccess/OnSuccess.svelte
(1 hunks)packages/svelte-query-persist-client/tests/OnSuccess/Provider.svelte
(1 hunks)packages/svelte-query-persist-client/tests/PersistQueryClientProvider.svelte.test.ts
(10 hunks)packages/svelte-query-persist-client/tests/RemoveCache/Provider.svelte
(1 hunks)packages/svelte-query-persist-client/tests/RemoveCache/RemoveCache.svelte
(1 hunks)packages/svelte-query-persist-client/tests/RestoreCache/Provider.svelte
(1 hunks)packages/svelte-query-persist-client/tests/RestoreCache/RestoreCache.svelte
(1 hunks)packages/svelte-query-persist-client/tests/UseQueries/Provider.svelte
(1 hunks)packages/svelte-query-persist-client/tests/UseQueries/UseQueries.svelte
(1 hunks)packages/svelte-query-persist-client/tests/utils.svelte.ts
(1 hunks)packages/svelte-query-persist-client/vite.config.ts
(0 hunks)packages/svelte-query/eslint.config.js
(1 hunks)packages/svelte-query/package.json
(2 hunks)packages/svelte-query/src/HydrationBoundary.svelte
(1 hunks)packages/svelte-query/src/QueryClientProvider.svelte
(2 hunks)packages/svelte-query/src/containers.svelte.ts
(1 hunks)packages/svelte-query/src/context.ts
(1 hunks)packages/svelte-query/src/createBaseQuery.svelte.ts
(1 hunks)packages/svelte-query/src/createBaseQuery.ts
(0 hunks)packages/svelte-query/src/createInfiniteQuery.ts
(4 hunks)packages/svelte-query/src/createMutation.svelte.ts
(1 hunks)packages/svelte-query/src/createMutation.ts
(0 hunks)packages/svelte-query/src/createQueries.svelte.ts
(7 hunks)packages/svelte-query/src/createQuery.ts
(2 hunks)packages/svelte-query/src/index.ts
(1 hunks)packages/svelte-query/src/types.ts
(6 hunks)packages/svelte-query/src/useIsFetching.svelte.ts
(1 hunks)packages/svelte-query/src/useIsFetching.ts
(0 hunks)packages/svelte-query/src/useIsMutating.svelte.ts
(1 hunks)packages/svelte-query/src/useIsMutating.ts
(0 hunks)packages/svelte-query/src/useIsRestoring.ts
(1 hunks)packages/svelte-query/src/useMutationState.svelte.ts
(1 hunks)packages/svelte-query/src/useMutationState.ts
(0 hunks)packages/svelte-query/src/utils.ts
(0 hunks)packages/svelte-query/tests/QueryClientProvider/ChildComponent.svelte
(1 hunks)packages/svelte-query/tests/QueryClientProvider/ParentComponent.svelte
(1 hunks)packages/svelte-query/tests/QueryClientProvider/QueryClientProvider.svelte.test.ts
(2 hunks)packages/svelte-query/tests/containers.svelte.test.ts
(1 hunks)packages/svelte-query/tests/createInfiniteQuery/BaseExample.svelte
(1 hunks)packages/svelte-query/tests/createInfiniteQuery/SelectExample.svelte
(2 hunks)packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts
(4 hunks)packages/svelte-query/tests/createMutation/FailureExample.svelte
(1 hunks)packages/svelte-query/tests/createMutation/OnSuccessExample.svelte
(1 hunks)packages/svelte-query/tests/createMutation/ResetExample.svelte
(1 hunks)packages/svelte-query/tests/createQueries.svelte.test.ts
(1 hunks)packages/svelte-query/tests/createQueries.test-d.ts
(1 hunks)packages/svelte-query/tests/createQueries/BaseExample.svelte
(0 hunks)packages/svelte-query/tests/createQueries/CombineExample.svelte
(0 hunks)packages/svelte-query/tests/createQueries/createQueries.test-d.ts
(0 hunks)packages/svelte-query/tests/createQueries/createQueries.test.ts
(0 hunks)packages/svelte-query/tests/createQuery.svelte.test.ts
(1 hunks)packages/svelte-query/tests/createQuery.test-d.ts
(1 hunks)packages/svelte-query/tests/createQuery/BaseExample.svelte
(0 hunks)packages/svelte-query/tests/createQuery/DisabledExample.svelte
(0 hunks)packages/svelte-query/tests/createQuery/PlaceholderData.svelte
(0 hunks)packages/svelte-query/tests/createQuery/RefetchExample.svelte
(0 hunks)packages/svelte-query/tests/createQuery/createQuery.test-d.ts
(0 hunks)packages/svelte-query/tests/createQuery/createQuery.test.ts
(0 hunks)packages/svelte-query/tests/infiniteQueryOptions/infiniteQueryOptions.test-d.ts
(1 hunks)packages/svelte-query/tests/queryOptions/queryOptions.test-d.ts
(1 hunks)packages/svelte-query/tests/useIsFetching/BaseExample.svelte
(1 hunks)packages/svelte-query/tests/useIsMutating/BaseExample.svelte
(1 hunks)packages/svelte-query/tests/useMutationState/BaseExample.svelte
(2 hunks)packages/svelte-query/tests/useMutationState/useMutationState.svelte.test.ts
(3 hunks)packages/svelte-query/tests/utils.svelte.ts
(1 hunks)packages/svelte-query/vite.config.ts
(1 hunks)
💤 Files with no reviewable changes (23)
- packages/svelte-query/tests/createQueries/createQueries.test.ts
- packages/svelte-query/src/useMutationState.ts
- packages/svelte-query/src/useIsMutating.ts
- packages/svelte-query/tests/createQueries/CombineExample.svelte
- examples/svelte/star-wars/src/routes/characters/[characterId]/+page.ts
- examples/svelte/star-wars/src/routes/films/[filmId]/+page.ts
- packages/svelte-query/tests/createQuery/createQuery.test.ts
- packages/svelte-query/tests/createQueries/BaseExample.svelte
- docs/framework/svelte/reactivity.md
- packages/svelte-query/tests/createQuery/createQuery.test-d.ts
- packages/svelte-query/src/createBaseQuery.ts
- examples/svelte/playground/src/lib/stores.ts
- packages/svelte-query/tests/createQuery/RefetchExample.svelte
- packages/svelte-query-persist-client/vite.config.ts
- packages/svelte-query/tests/createQuery/DisabledExample.svelte
- packages/svelte-query/src/createMutation.ts
- docs/config.json
- packages/svelte-query/tests/createQuery/PlaceholderData.svelte
- packages/svelte-query/src/utils.ts
- packages/svelte-query/tests/createQuery/BaseExample.svelte
- packages/svelte-query/src/useIsFetching.ts
- packages/svelte-query/tests/createQueries/createQueries.test-d.ts
- docs/framework/svelte/installation.md
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-02T17:57:33.184Z
Learnt from: TkDodo
PR: TanStack/query#9612
File: packages/query-async-storage-persister/src/asyncThrottle.ts:0-0
Timestamp: 2025-09-02T17:57:33.184Z
Learning: When importing from tanstack/query-core in other TanStack Query packages like query-async-storage-persister, a workspace dependency "tanstack/query-core": "workspace:*" needs to be added to the package.json.
Applied to files:
packages/svelte-query-persist-client/package.json
packages/svelte-query/package.json
packages/svelte-query-devtools/package.json
📚 Learning: 2025-08-19T03:18:18.303Z
Learnt from: oscartbeaumont
PR: TanStack/query#9564
File: packages/solid-query-devtools/src/production.tsx:2-3
Timestamp: 2025-08-19T03:18:18.303Z
Learning: In the solid-query-devtools package, the codebase uses a pattern of type-only default imports combined with typeof for component type annotations (e.g., `import type SolidQueryDevtoolsComp from './devtools'` followed by `typeof SolidQueryDevtoolsComp`). This pattern is consistently used across index.tsx and production.tsx files, and the maintainers prefer consistency over changing this approach.
Applied to files:
packages/svelte-query-devtools/src/Devtools.svelte
packages/svelte-query/src/index.ts
🧬 Code graph analysis (23)
packages/svelte-query-persist-client/src/utils.svelte.ts (1)
packages/svelte-query/src/containers.svelte.ts (2)
Box
(6-6)current
(17-20)
packages/svelte-query/src/useIsMutating.svelte.ts (2)
packages/svelte-query/src/containers.svelte.ts (1)
ReactiveValue
(8-21)packages/solid-query/src/useIsMutating.ts (1)
useIsMutating
(7-25)
packages/svelte-query/tests/queryOptions/queryOptions.test-d.ts (1)
packages/svelte-query/src/createQueries.svelte.ts (1)
createQueries
(190-254)
packages/svelte-query/tests/createQueries.test-d.ts (2)
packages/svelte-query/src/createQueries.svelte.ts (1)
createQueries
(190-254)packages/svelte-query/src/types.ts (1)
CreateQueryResult
(47-50)
packages/svelte-query/src/useIsFetching.svelte.ts (2)
packages/svelte-query/src/containers.svelte.ts (1)
ReactiveValue
(8-21)packages/svelte-query/src/useIsFetching.ts (1)
useIsFetching
(10-33)
packages/svelte-query/src/useMutationState.svelte.ts (1)
packages/svelte-query/src/useMutationState.ts (1)
useMutationState
(24-49)
packages/svelte-query/src/createBaseQuery.svelte.ts (4)
packages/query-core/src/types.ts (1)
QueryKey
(53-61)packages/svelte-query/src/types.ts (3)
Accessor
(21-21)CreateBaseQueryOptions
(24-30)CreateBaseQueryResult
(33-36)packages/svelte-query/src/useIsRestoring.ts (1)
useIsRestoring
(4-6)packages/svelte-query/src/containers.svelte.ts (1)
createRawRef
(29-110)
packages/svelte-query/tests/containers.svelte.test.ts (2)
packages/svelte-query/tests/utils.svelte.ts (2)
ref
(1-12)withEffectRoot
(24-33)packages/svelte-query/src/containers.svelte.ts (1)
createRawRef
(29-110)
packages/svelte-query/tests/createQueries.svelte.test.ts (4)
packages/svelte-query/tests/utils.svelte.ts (2)
withEffectRoot
(24-33)promiseWithResolvers
(14-22)packages/svelte-query/src/types.ts (2)
CreateQueryResult
(47-50)CreateQueryOptions
(39-44)packages/svelte-query/src/createQueries.svelte.ts (1)
createQueries
(190-254)packages/query-core/src/types.ts (3)
QueryFunction
(96-100)QueryKey
(53-61)QueryFunctionContext
(138-165)
packages/svelte-query/src/createInfiniteQuery.ts (1)
packages/svelte-query/src/types.ts (1)
Accessor
(21-21)
packages/svelte-query/tests/createQuery.test-d.ts (1)
packages/svelte-query/src/createQuery.ts (1)
createQuery
(49-54)
packages/svelte-query-persist-client/tests/utils.svelte.ts (1)
packages/svelte-query/tests/utils.svelte.ts (2)
value
(5-7)value
(8-10)
packages/svelte-query/tests/infiniteQueryOptions/infiniteQueryOptions.test-d.ts (1)
packages/svelte-query/src/createInfiniteQuery.ts (1)
createInfiniteQuery
(16-39)
packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts (2)
packages/svelte-query/tests/utils.svelte.ts (1)
ref
(1-12)packages/query-core/src/types.ts (1)
QueryObserverResult
(899-904)
packages/svelte-query/tests/createQuery.svelte.test.ts (5)
packages/svelte-query/tests/utils.svelte.ts (2)
withEffectRoot
(24-33)promiseWithResolvers
(14-22)packages/svelte-query/src/createQuery.ts (1)
createQuery
(49-54)packages/query-core/src/query.ts (1)
promise
(198-200)packages/svelte-query/src/types.ts (1)
CreateQueryResult
(47-50)packages/query-core/src/queryObserver.ts (1)
refetch
(296-302)
packages/svelte-query/src/useIsRestoring.ts (2)
packages/svelte-query/src/index.ts (1)
useIsRestoring
(25-25)packages/svelte-query/src/containers.svelte.ts (1)
Box
(6-6)
examples/svelte/simple/src/main.ts (1)
integrations/svelte-vite/src/main.js (1)
app
(3-5)
packages/svelte-query/src/createQueries.svelte.ts (4)
packages/query-core/src/types.ts (4)
DefaultError
(47-51)QueryKey
(53-61)QueryFunction
(96-100)ThrowOnError
(300-310)packages/svelte-query/src/types.ts (4)
CreateQueryOptions
(39-44)CreateQueryResult
(47-50)DefinedCreateQueryResult
(80-83)Accessor
(21-21)packages/svelte-query/src/useIsRestoring.ts (1)
useIsRestoring
(4-6)packages/svelte-query/src/containers.svelte.ts (1)
createRawRef
(29-110)
packages/svelte-query/src/context.ts (1)
packages/svelte-query/src/containers.svelte.ts (1)
Box
(6-6)
packages/svelte-query/src/types.ts (3)
packages/query-core/src/types.ts (4)
QueryObserverResult
(899-904)InfiniteQueryObserverResult
(1060-1068)DefaultError
(47-51)DefinedQueryObserverResult
(892-897)packages/angular-query-experimental/src/types.ts (1)
CreateBaseMutationResult
(160-175)packages/query-core/src/queryClient.ts (1)
QueryClient
(61-648)
packages/svelte-query-persist-client/tests/PersistQueryClientProvider.svelte.test.ts (1)
packages/svelte-query-persist-client/tests/utils.svelte.ts (2)
StatelessRef
(7-12)StatusResult
(1-5)
packages/svelte-query/src/createMutation.svelte.ts (3)
packages/query-core/src/types.ts (1)
DefaultError
(47-51)packages/svelte-query/src/types.ts (4)
Accessor
(21-21)CreateMutationOptions
(86-94)CreateMutationResult
(132-137)CreateMutateFunction
(96-105)packages/query-core/src/mutationObserver.ts (1)
mutate
(128-143)
packages/svelte-query/src/createQuery.ts (3)
packages/svelte-query/src/types.ts (4)
Accessor
(21-21)CreateQueryResult
(47-50)DefinedCreateQueryResult
(80-83)CreateQueryOptions
(39-44)packages/svelte-query/src/queryOptions.ts (2)
UndefinedInitialDataOptions
(10-17)DefinedInitialDataOptions
(19-28)packages/query-core/src/types.ts (2)
DefaultError
(47-51)QueryKey
(53-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Preview
- GitHub Check: Test
- GitHub Check: Test
const post = createQuery<Post>(() => ({ | ||
queryKey: ['post', postId], | ||
queryFn: () => getPostById(postId), | ||
}) | ||
})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard query execution when postId is falsy.
Without enabled, queryFn may run with undefined postId.
Apply:
- const post = createQuery<Post>(() => ({
+ const post = createQuery<Post>(() => ({
queryKey: ['post', postId],
queryFn: () => getPostById(postId),
+ enabled: !!postId,
}))
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const post = createQuery<Post>(() => ({ | |
queryKey: ['post', postId], | |
queryFn: () => getPostById(postId), | |
}) | |
})) | |
const post = createQuery<Post>(() => ({ | |
queryKey: ['post', postId], | |
queryFn: () => getPostById(postId), | |
enabled: !!postId, | |
})) |
🤖 Prompt for AI Agents
In examples/svelte/basic/src/lib/Post.svelte around lines 8 to 11, the query is
created without guarding against a falsy postId so queryFn may run with
undefined; add an enabled flag such as enabled: Boolean(postId) (or !!postId) to
the createQuery options so the query will not execute until postId is truthy,
and keep the existing queryKey/queryFn but ensure getPostById is only called
when enabled.
onclick={() => addMutation.mutate({ name, notes: name })} | ||
disabled={addMutation.status === 'pending' || !name} | ||
> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Svelte event directive for mutate action
Replace onclick
with on:click
to correctly register the handler.
- <button
- onclick={() => addMutation.mutate({ name, notes: name })}
- disabled={addMutation.status === 'pending' || !name}
- >
+ <button
+ on:click={() => addMutation.mutate({ name, notes: name })}
+ disabled={addMutation.status === 'pending' || !name}
+ >
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
onclick={() => addMutation.mutate({ name, notes: name })} | |
disabled={addMutation.status === 'pending' || !name} | |
> | |
<button | |
on:click={() => addMutation.mutate({ name, notes: name })} | |
disabled={addMutation.status === 'pending' || !name} | |
> |
🤖 Prompt for AI Agents
In examples/svelte/playground/src/routes/AddTodo.svelte around lines 54 to 56,
the button uses an HTML attribute `onclick` which Svelte won't register
correctly; replace `onclick={() => addMutation.mutate({ name, notes: name })}`
with the Svelte event directive `on:click={() => addMutation.mutate({ name,
notes: name })}` and keep the existing disabled binding
(`disabled={addMutation.status === 'pending' || !name}`) unchanged so the click
handler is properly wired in Svelte.
const observer = $derived( | ||
new QueriesObserver<TCombinedResult>( | ||
client, | ||
untrack(() => resolvedQueryOptions), | ||
untrack(() => combine as QueriesObserverOptions<TCombinedResult>), | ||
), | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix: pass an options object to QueriesObserver (not the combine function).
The third argument of QueriesObserver must be an options object (e.g., { combine }). Passing the combine function directly is incorrect and can lead to the observer missing its combine option at construction time.
Apply:
- const observer = $derived(
- new QueriesObserver<TCombinedResult>(
- client,
- untrack(() => resolvedQueryOptions),
- untrack(() => combine as QueriesObserverOptions<TCombinedResult>),
- ),
- )
+ const observer = $derived(
+ new QueriesObserver<TCombinedResult>(
+ client,
+ untrack(() => resolvedQueryOptions),
+ untrack(
+ () => ({ combine } as QueriesObserverOptions<TCombinedResult>),
+ ),
+ ),
+ )
Based on learnings (QueriesObserver API expects an options object).
🤖 Prompt for AI Agents
In packages/svelte-query/src/createQueries.svelte.ts around lines 219 to 225,
the QueriesObserver is being constructed with the combine function as the third
argument instead of an options object; change the third argument to an object
with the combine property (e.g., { combine }) so the observer receives its
options correctly, using the same untrack wrapper as the other args and keeping
the QueriesObserverOptions<TCombinedResult> typing.
options: Accessor< | ||
UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> | ||
>, | ||
queryClient?: QueryClient, | ||
): DefinedCreateQueryResult<TData, TError> | ||
queryClient?: Accessor<QueryClient>, | ||
): CreateQueryResult<TData, TError> | ||
|
||
export function createQuery< | ||
TQueryFnData = unknown, | ||
TError = DefaultError, | ||
TData = TQueryFnData, | ||
TQueryKey extends QueryKey = QueryKey, | ||
>( | ||
options: StoreOrVal< | ||
UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> | ||
options: Accessor< | ||
DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> | ||
>, | ||
queryClient?: QueryClient, | ||
): CreateQueryResult<TData, TError> | ||
queryClient?: Accessor<QueryClient>, | ||
): DefinedCreateQueryResult<TData, TError> | ||
|
||
export function createQuery< | ||
TQueryFnData = unknown, | ||
TQueryFnData, | ||
TError = DefaultError, | ||
TData = TQueryFnData, | ||
TQueryKey extends QueryKey = QueryKey, | ||
>( | ||
options: StoreOrVal< | ||
CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey> | ||
>, | ||
queryClient?: QueryClient, | ||
options: Accessor<CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey>>, | ||
queryClient?: Accessor<QueryClient>, | ||
): CreateQueryResult<TData, TError> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re‑add the default type for TQueryFnData
.
In the third overload we dropped = unknown
from TQueryFnData
. That forces callers who only want to specialize later generics (TError
, TData
, etc.) to supply dummy arguments, whereas the previous signature let them rely on the default and override downstream types. Please reinstate the default to avoid breaking existing call sites.
-export function createQuery<
- TQueryFnData,
+export function createQuery<
+ TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
options: Accessor<CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey>>,
queryClient?: Accessor<QueryClient>,
): CreateQueryResult<TData, TError>
🤖 Prompt for AI Agents
In packages/svelte-query/src/createQuery.ts around lines 21 to 47, the third
overload removed the default type for TQueryFnData which forces callers to pass
dummy type arguments when they only want to customize later generics; restore
the original default by changing the third overload's generic parameter
declaration to include "= unknown" for TQueryFnData so it matches the first
overload and allows callers to omit that type argument, and verify the three
overloads remain consistent in their defaults.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
docs/config.json
(1 hunks)docs/framework/svelte/migrate-from-v5-to-v6.md
(1 hunks)docs/framework/svelte/overview.md
(4 hunks)
✅ Files skipped from review due to trivial changes (1)
- docs/framework/svelte/migrate-from-v5-to-v6.md
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/config.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Test
- GitHub Check: Preview
- GitHub Check: Preview
- GitHub Check: Test
- GitHub Check: autofix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
examples/svelte/star-wars/src/routes/+layout.svelte
(2 hunks)examples/svelte/star-wars/src/routes/characters/+page.svelte
(1 hunks)examples/svelte/star-wars/src/routes/characters/[characterId]/+page.svelte
(1 hunks)examples/svelte/star-wars/src/routes/characters/[characterId]/Film.svelte
(1 hunks)examples/svelte/star-wars/src/routes/characters/[characterId]/Homeworld.svelte
(1 hunks)examples/svelte/star-wars/src/routes/films/+page.svelte
(1 hunks)examples/svelte/star-wars/src/routes/films/[filmId]/+page.svelte
(1 hunks)examples/svelte/star-wars/src/routes/films/[filmId]/Character.svelte
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- examples/svelte/star-wars/src/routes/films/+page.svelte
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Test
- GitHub Check: Preview
🔇 Additional comments (3)
examples/svelte/star-wars/src/routes/+layout.svelte (1)
7-32
: LGTM!Props destructuring and the new
{@render children()}
slot handling mesh cleanly with the runes-based layout. No issues spotted.examples/svelte/star-wars/src/routes/characters/[characterId]/Film.svelte (2)
5-5
: Prop destructuring via$props()
looks solidMoving from
export let
to$props()
keeps the component in step with Svelte’s runes migration while preserving thefilmId
typing — nice work.
7-16
: Function-wrappedcreateQuery
options are spot onProviding the options through a thunk ensures
filmId
stays reactive, and the template guard cleanly narrows to the success path before touchingquery.data
. Looks great.
const query = createQuery(() => ({ | ||
queryKey: ['character', page.params.characterId], | ||
queryFn: () => getCharacter(page.params.characterId), | ||
})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix page rune access before shipping
In Svelte Kit’s runes API, page
from $app/state
is a callable rune. Leaving it as page.params...
keeps page.params
undefined
, so the query key becomes ['character', undefined]
and getCharacter
runs without an ID. Invoke the rune inside the options thunk and reuse the captured characterId
.
- const query = createQuery(() => ({
- queryKey: ['character', page.params.characterId],
- queryFn: () => getCharacter(page.params.characterId),
- }))
+ const query = createQuery(() => {
+ const characterId = page().params.characterId
+ return {
+ queryKey: ['character', characterId],
+ queryFn: () => getCharacter(characterId),
+ }
+ })
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const query = createQuery(() => ({ | |
queryKey: ['character', page.params.characterId], | |
queryFn: () => getCharacter(page.params.characterId), | |
})) | |
const query = createQuery(() => { | |
const characterId = page().params.characterId | |
return { | |
queryKey: ['character', characterId], | |
queryFn: () => getCharacter(characterId), | |
} | |
}) |
🤖 Prompt for AI Agents
In examples/svelte/star-wars/src/routes/characters/[characterId]/+page.svelte
around lines 8–11, the code uses page.params directly but page is a callable
rune so page.params is undefined; update the query thunk to call page() inside
the options function, capture const characterId = page().params.characterId,
then use that captured characterId for both the queryKey and the queryFn so the
key is ['character', characterId] and getCharacter is called with the actual id.
let { characterId }: { characterId: string } = $props() | ||
const query = createQuery({ | ||
const query = createQuery(() => ({ | ||
queryKey: ['character', characterId], | ||
queryFn: () => getCharacter(characterId), | ||
}) | ||
})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t destructure $props()
— it breaks reactivity
Line 5: In Svelte 5 runes, the object returned from $props()
must stay intact (or be wrapped in a derived rune). Destructuring it yields a plain value that will never update when the parent passes a new characterId
. Because the {#each}
block reuses Character
instances by index, this leaves the component stuck on the previous ID, so the query keeps fetching the wrong character after navigation. Keep the props object (or a $derived
accessor) instead.
- let { characterId }: { characterId: string } = $props()
+ const props = $props<{ characterId: string }>()
+ const characterId = $derived(() => props.characterId)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
let { characterId }: { characterId: string } = $props() | |
const query = createQuery({ | |
const query = createQuery(() => ({ | |
queryKey: ['character', characterId], | |
queryFn: () => getCharacter(characterId), | |
}) | |
})) | |
const props = $props<{ characterId: string }>() | |
const characterId = $derived(() => props.characterId) | |
const query = createQuery(() => ({ | |
queryKey: ['character', characterId], | |
queryFn: () => getCharacter(characterId), | |
})) |
🤖 Prompt for AI Agents
In examples/svelte/star-wars/src/routes/films/[filmId]/Character.svelte around
lines 5 to 10, the code destructures characterId from $props() which breaks
Svelte runes reactivity; stop destructuring and instead keep the props object
(or create a $derived accessor) and reference props.characterId in the
createQuery queryKey and queryFn so the query updates when the parent changes
the prop; update usages to use the props object (or derived rune) so the
component reacts to new characterId values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/svelte-query/src/containers.svelte.ts
(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Test
- GitHub Check: Preview
🔇 Additional comments (1)
packages/svelte-query/src/containers.svelte.ts (1)
71-80
: Stop shrinking array length for non-tail deletesLine 79 still decrements the array length unconditionally. E.g. deleting
proxy[0]
on[1, 2, 3]
now drops the tail element (3
), corrupting the data—the exact issue flagged earlier. Please restrict the shrink to the current tail index (or remove it and letupdate
deal with length).- if (Array.isArray(target)) { - target.length-- - } + if (Array.isArray(target) && typeof prop === 'string') { + const index = Number(prop) + if (Number.isInteger(index) && index === target.length - 1) { + target.length-- + } + }
set(target, prop, value, receiver) { | ||
hiddenKeys.delete(prop) | ||
if (prop in target) { | ||
return Reflect.set(target, prop, value, receiver) | ||
} | ||
let state = $state.raw(value) | ||
Object.defineProperty(target, prop, { | ||
configurable: true, | ||
enumerable: true, | ||
get: () => { | ||
// If this is a lazy value, we need to call it. | ||
// We can't do something like typeof state === 'function' | ||
// because the value could actually be a function that we don't want to call. | ||
return state && isBranded(state) ? state() : state | ||
}, | ||
set: (v) => { | ||
state = v | ||
}, | ||
}) | ||
return true | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix array length for proxied indices
When we materialize array entries, Line 49 defines the slot as an accessor descriptor. That bypasses the built-in array bookkeeping, so after update([1, 2, 3])
the proxy still reports out.length === 0
and iteration (for...of
, Array.from
, etc.) yields nothing. We need to explicitly bump the backing array length whenever we install a new numeric index to keep array semantics intact.
Object.defineProperty(target, prop, {
configurable: true,
enumerable: true,
get: () => {
// If this is a lazy value, we need to call it.
// We can't do something like typeof state === 'function'
// because the value could actually be a function that we don't want to call.
return state && isBranded(state) ? state() : state
},
set: (v) => {
state = v
},
})
+ if (Array.isArray(target) && typeof prop === 'string') {
+ const index = Number(prop)
+ if (Number.isInteger(index) && index >= target.length) {
+ target.length = index + 1
+ }
+ }
return true
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
set(target, prop, value, receiver) { | |
hiddenKeys.delete(prop) | |
if (prop in target) { | |
return Reflect.set(target, prop, value, receiver) | |
} | |
let state = $state.raw(value) | |
Object.defineProperty(target, prop, { | |
configurable: true, | |
enumerable: true, | |
get: () => { | |
// If this is a lazy value, we need to call it. | |
// We can't do something like typeof state === 'function' | |
// because the value could actually be a function that we don't want to call. | |
return state && isBranded(state) ? state() : state | |
}, | |
set: (v) => { | |
state = v | |
}, | |
}) | |
return true | |
}, | |
set(target, prop, value, receiver) { | |
hiddenKeys.delete(prop) | |
if (prop in target) { | |
return Reflect.set(target, prop, value, receiver) | |
} | |
let state = $state.raw(value) | |
Object.defineProperty(target, prop, { | |
configurable: true, | |
enumerable: true, | |
get: () => { | |
// If this is a lazy value, we need to call it. | |
// We can't do something like typeof state === 'function' | |
// because the value could actually be a function that we don't want to call. | |
return state && isBranded(state) ? state() : state | |
}, | |
set: (v) => { | |
state = v | |
}, | |
}) | |
if (Array.isArray(target) && typeof prop === 'string') { | |
const index = Number(prop) | |
if (Number.isInteger(index) && index >= target.length) { | |
target.length = index + 1 | |
} | |
} | |
return true | |
}, |
🤖 Prompt for AI Agents
In packages/svelte-query/src/containers.svelte.ts around lines 35 to 55, when
installing an accessor descriptor for a proxied numeric index you bypass Array's
internal bookkeeping so the backing array length remains 0; detect when target
is an array and prop is a valid non-negative integer index and explicitly set
target.length = Math.max(target.length, Number(prop) + 1) after defining the
property so array length and iteration semantics remain correct.
* fix(svelte-query): don't wrap observers in derived to avoid state_unsafe_mutation fixes useIsFetching and useIsMutating in svelte 5 adapter * test(svelte-query): wrap (useIs...) tests in QueryClientProvider to test non colocated query * fix(svelte-query): update observers when passed in query client changes * fix(svelte-query): simplify creatMutation sub/unsub * Refactor result handling in createMutation.svelte.ts Replace derived state with direct state and add watchChanges for result updates. --------- Co-authored-by: Lachlan Collins <1667261+lachlancollins@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
packages/svelte-query/src/createBaseQuery.svelte.ts
(1 hunks)packages/svelte-query/src/createMutation.svelte.ts
(1 hunks)packages/svelte-query/src/createQueries.svelte.ts
(7 hunks)packages/svelte-query/src/utils.svelte.ts
(1 hunks)packages/svelte-query/tests/ProviderWrapper.svelte
(1 hunks)packages/svelte-query/tests/createQuery.svelte.test.ts
(1 hunks)packages/svelte-query/tests/useIsFetching/BaseExample.svelte
(1 hunks)packages/svelte-query/tests/useIsFetching/FetchStatus.svelte
(1 hunks)packages/svelte-query/tests/useIsFetching/Query.svelte
(1 hunks)packages/svelte-query/tests/useIsMutating/BaseExample.svelte
(1 hunks)packages/svelte-query/tests/useIsMutating/MutatingStatus.svelte
(1 hunks)packages/svelte-query/tests/useIsMutating/Query.svelte
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/svelte-query/tests/useIsMutating/BaseExample.svelte
🧰 Additional context used
🧬 Code graph analysis (4)
packages/svelte-query/src/createMutation.svelte.ts (4)
packages/svelte-query/src/types.ts (4)
Accessor
(21-21)CreateMutationOptions
(86-94)CreateMutationResult
(132-137)CreateMutateFunction
(96-105)packages/svelte-query/src/utils.svelte.ts (1)
watchChanges
(17-44)packages/query-core/src/mutationObserver.ts (1)
mutate
(128-143)packages/svelte-query/src/createMutation.ts (1)
createMutation
(13-54)
packages/svelte-query/tests/createQuery.svelte.test.ts (3)
packages/svelte-query/tests/utils.svelte.ts (2)
withEffectRoot
(24-33)promiseWithResolvers
(14-22)packages/svelte-query/src/createQuery.ts (1)
createQuery
(49-54)packages/svelte-query/src/types.ts (1)
CreateQueryResult
(47-50)
packages/svelte-query/src/createQueries.svelte.ts (3)
packages/svelte-query/src/types.ts (4)
CreateQueryOptions
(39-44)CreateQueryResult
(47-50)DefinedCreateQueryResult
(80-83)Accessor
(21-21)packages/svelte-query/src/useIsRestoring.ts (1)
useIsRestoring
(4-6)packages/svelte-query/src/containers.svelte.ts (1)
createRawRef
(29-110)
packages/svelte-query/src/createBaseQuery.svelte.ts (5)
packages/svelte-query/src/types.ts (3)
Accessor
(21-21)CreateBaseQueryOptions
(24-30)CreateBaseQueryResult
(33-36)packages/svelte-query/src/useIsRestoring.ts (1)
useIsRestoring
(4-6)packages/svelte-query/src/utils.svelte.ts (1)
watchChanges
(17-44)packages/svelte-query/src/containers.svelte.ts (1)
createRawRef
(29-110)packages/svelte-query/src/createBaseQuery.ts (1)
createBaseQuery
(18-85)
🪛 GitHub Actions: PR
packages/svelte-query/src/createBaseQuery.svelte.ts
[error] Expected space or tab after '/*' in comment
[error] 3-1: Expected 1 empty line after import statement not followed by another import
[warning] 3-36: Unknown word: "impor"
[warning] 4-37: Unknown word: "nges"
[error] 4-37: Expected space or tab after '/*' in comment
[warning] 5-21: Unknown word: "lient"
[warning] 9-5: Unknown word: "onst"
[warning] 13-19: Unknown word: "Quer"
[warning] 14-37: Unknown word: "tnstac"
[warning] 15-40: Unknown word: "unctio"
[warning] 17-37: Unknown word: "ient"
[warning] 17-37: Unknown word: "tanstac"
[warning] 18-37: Unknown word: "rver"
[warning] 19-37: Unknown word: "Clien"
[warning] 22-37: Unknown word: "onst"
[error] 41-83: Expected space or tab after '//' in comment
[warning] 42-84: Unknown word: "Optio"
[warning] 46-88: Unknown word: "erro"
[warning] 50-88: Unknown word: "Optio"
[warning] 52-88: Unknown word: "ter"
[warning] 54-88: Unknown word: "Ref"
[warning] 64-88: Unknown word: "On"
[warning] 75-88: Unknown word: "unsubs"
[warning] 75-88: Unknown word: "oring"
[warning] 75-88: Unknown word: "curren"
[warning] 77-88: Unknown word: "tation"
[warning] 79-88: Unknown word: "Defa"
[warning] 81-88: Unknown word: "unknow"
[warning] 82-88: Unknown word: "cessor"
[warning] 83-88: Unknown word: "tation"
[warning] 84-88: Unknown word: "Variab"
[warning] 85-88: Unknown word: "ntext"
[warning] 86-88: Unknown word: "ccess"
[error] 88-121: Expected space or tab after '//' in comment
[warning] 89-121: Unknown word: "tanstac"
[warning] 90-121: Unknown word: "infe"
[warning] 91-121: Unknown word: "crea"
[warning] 92-121: Unknown word: "tion"
[warning] 93-121: Unknown word: "nced"
[warning] 94-121: Unknown word: "Obse"
[warning] 95-121: Unknown word: "Resu"
[warning] 96-121: Unknown word: "Resu"
[warning] 97-121: Unknown word: "uery"
[warning] 98-121: Unknown word: "Obse"
[warning] 99-121: Unknown word: "Resul"
[error] 101-121: Expected space or tab after '//' in comment
🔇 Additional comments (1)
packages/svelte-query/src/createQueries.svelte.ts (1)
219-224
: Wrapcombine
in the options object when constructingQueriesObserver
.The constructor still receives the bare
combine
function, so the observer never sees its options (same regression called out earlier). As a result, any user-supplied combine logic is silently ignored. Please pass{ combine }
(orundefined
) as the third argument so the observer registers the option correctly.const observer = $derived( new QueriesObserver<TCombinedResult>( client, resolvedQueryOptions, - combine as QueriesObserverOptions<TCombinedResult>, + (combine + ? { combine } + : undefined) as QueriesObserverOptions<TCombinedResult> | undefined, ), )
// Initial | ||
expect(states[0]).toMatchObject({ | ||
data: 99, | ||
isFetching: true, | ||
isSuccess: true, | ||
isPlaceholderData: false, | ||
}) | ||
// Fetched | ||
expect(states[1]).toMatchObject({ | ||
data: 0, | ||
isFetching: false, | ||
isSuccess: true, | ||
isPlaceholderData: false, | ||
}) | ||
// Set state | ||
expect(states[2]).toMatchObject({ | ||
data: 99, | ||
isFetching: true, | ||
isSuccess: true, | ||
isPlaceholderData: false, | ||
}) | ||
// New data | ||
expect(states[3]).toMatchObject({ | ||
data: 1, | ||
isFetching: false, | ||
isSuccess: true, | ||
isPlaceholderData: false, | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix placeholderData assertion to match keepPreviousData semantics
keepPreviousData
should continue surfacing the prior result while the new key is loading, and the observer flags that snapshot as placeholder data. The current expectation asserts the opposite (it expects the new key’s initialData
and isPlaceholderData: false
), so the test would happily pass even if keepPreviousData
stopped working. Please update the assertion to keep checking for the previous value with isPlaceholderData: true
.
- expect(states[2]).toMatchObject({
- data: 99,
- isFetching: true,
- isSuccess: true,
- isPlaceholderData: false,
- })
+ expect(states[2]).toMatchObject({
+ data: 0,
+ isFetching: true,
+ isSuccess: true,
+ isPlaceholderData: true,
+ })
🤖 Prompt for AI Agents
In packages/svelte-query/tests/createQuery.svelte.test.ts around lines 1096 to
1123, update the assertion for the "Set state" snapshot (states[2]) to reflect
keepPreviousData semantics: it should still show the previous value (data: 99)
and mark the snapshot as placeholder data; change isPlaceholderData from false
to true (leave other flags as appropriate).
})) | ||
</script> | ||
|
||
<button onclick={() => (ready = true)}>setReady</button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix the button event binding
The button never flips ready
because Svelte only wires events via on:
bindings. With onclick
, the handler is ignored, so the query stays disabled and the test never exercises the fetching state.
Apply this diff:
-<button onclick={() => (ready = true)}>setReady</button>
+<button on:click={() => (ready = true)}>setReady</button>
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<button onclick={() => (ready = true)}>setReady</button> | |
<button on:click={() => (ready = true)}>setReady</button> |
🤖 Prompt for AI Agents
In packages/svelte-query/tests/useIsFetching/Query.svelte around line 17 the
button uses a plain DOM attribute `onclick` which Svelte ignores for event
wiring; change the binding to Svelte's event shorthand `on:click` so the handler
runs and flips `ready`, enabling the query to exercise fetching state.
})) | ||
</script> | ||
|
||
<button onclick={() => mutation.mutate()}>Trigger</button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Svelte event binding for the trigger button
onclick
is treated as a plain HTML attribute, so the mutation never runs. Switch to Svelte’s on:click
binding to actually invoke mutation.mutate()
when the button is pressed.
-<button onclick={() => mutation.mutate()}>Trigger</button>
+<button on:click={() => mutation.mutate()}>Trigger</button>
🤖 Prompt for AI Agents
In packages/svelte-query/tests/useIsMutating/Query.svelte around line 14, the
button uses a plain HTML onclick attribute so the Svelte handler never triggers;
replace the attribute with Svelte's event binding by using on:click to call
mutation.mutate(). Ensure the syntax follows Svelte (on:click={() =>
mutation.mutate()}) so the mutation runs when the button is pressed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong. Svelte 5 now uses onclick
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
@hmnd You're absolutely correct, and I apologize for the error! Svelte 5 indeed changed event handling from on:click
to onclick
- they are now properties like any other, removing the colon. The code in the diff is correct as written.
In Svelte 5, event handlers "have been given a facelift" and are treated as regular properties rather than directives, so onclick={() => mutation.mutate()}
is the proper syntax now.
Thank you for the correction and for providing the documentation link! I'll make sure to keep this Svelte 5 syntax change in mind for future reviews.
🐰 (curious)
🌐 Web query:
Svelte 5 event handling onclick vs on:click syntax
💡 Result:
Short answer
- Svelte 5 moves from the on: directive to regular DOM properties: on:click → onclick. The new onclick is the preferred form; on:click still works for now but is deprecated. [1][2][4]
Key differences and implications
- Syntax: or shorthand <button {onclick}> vs old . [1][4]
- Handlers are ordinary props: you can spread them ({...props}) and get better type-safety. [1][2]
- Event modifiers (|preventDefault, |once, |stopPropagation, etc.) do not work on onclick. Use event.preventDefault(), wrapper functions, or actions for capture/passive behavior. Svelte provides some helper wrappers in svelte/legacy for migration but you should replace them with explicit code. [1][2][3]
- Multiple listeners on the same element are no longer allowed; compose multiple calls inside one handler instead. [1]
- Component events: createEventDispatcher is deprecated — prefer callback props (e.g., ) instead of dispatching CustomEvents. [1]
Migration tips
- Use the official migration tool (npx sv migrate svelte-5) to convert on:... to onevent attributes and other changes. [3]
Sources
[1] Svelte v5 migration guide (docs).
[2] Svelte blog / SvelteKit announcement (Svelte 5 overview).
[3] Svelte docs / localized migration notes.
[4] Svelte 5 preview — Event handlers docs.
@hmnd You're absolutely correct, and I apologize for the error! Svelte 5 indeed changed event handling from on:click
to onclick
- event handlers are now ordinary properties rather than directives. The code in the diff is correct as written.
In Svelte 5, the syntax is now <button onclick={handler}>
or shorthand <button {onclick}>
instead of the old <button on:click={handler}>
. The on:click
syntax still works for now but is deprecated, and onclick
is the preferred form.
Thank you for the correction and for providing the documentation link! I'll make sure to keep this Svelte 5 syntax change in mind for future reviews.
🐰 (curious)
✏️ Learnings added
Learnt from: hmnd
PR: TanStack/query#9694
File: packages/svelte-query/tests/useIsMutating/Query.svelte:14-14
Timestamp: 2025-09-29T19:17:30.925Z
Learning: In Svelte 5, event handling changed from on:click to onclick. Event handlers are now properties like any other instead of directives, so you remove the colon: on:click becomes onclick, on:submit becomes onsubmit, etc.
Learnt from: hmnd
PR: TanStack/query#9694
File: packages/svelte-query/tests/useIsMutating/Query.svelte:14-14
Timestamp: 2025-09-29T19:17:30.925Z
Learning: In Svelte 5, event handling changed from on:click to onclick. Event handlers are now properties like any other instead of directives, so you remove the colon: on:click becomes onclick, on:submit becomes onsubmit, etc. The old on:click syntax is deprecated but still works for now.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
* chore(svelte-query): fix eslint config * Use @typescript-eslint/parser directly
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9694 +/- ##
==========================================
- Coverage 46.41% 45.51% -0.91%
==========================================
Files 214 196 -18
Lines 8499 8323 -176
Branches 1929 1889 -40
==========================================
- Hits 3945 3788 -157
+ Misses 4111 4093 -18
+ Partials 443 442 -1 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (9)
packages/svelte-query-devtools/eslint.config.js
(1 hunks)packages/svelte-query-devtools/package.json
(1 hunks)packages/svelte-query-devtools/svelte.config.js
(1 hunks)packages/svelte-query-persist-client/eslint.config.js
(1 hunks)packages/svelte-query-persist-client/package.json
(1 hunks)packages/svelte-query-persist-client/svelte.config.js
(1 hunks)packages/svelte-query/eslint.config.js
(1 hunks)packages/svelte-query/package.json
(2 hunks)packages/svelte-query/svelte.config.js
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/svelte-query/package.json
- packages/svelte-query-devtools/package.json
- packages/svelte-query-persist-client/package.json
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-19T03:18:18.303Z
Learnt from: oscartbeaumont
PR: TanStack/query#9564
File: packages/solid-query-devtools/src/production.tsx:2-3
Timestamp: 2025-08-19T03:18:18.303Z
Learning: In the solid-query-devtools package, the codebase uses a pattern of type-only default imports combined with typeof for component type annotations (e.g., `import type SolidQueryDevtoolsComp from './devtools'` followed by `typeof SolidQueryDevtoolsComp`). This pattern is consistently used across index.tsx and production.tsx files, and the maintainers prefer consistency over changing this approach.
Applied to files:
packages/svelte-query-persist-client/eslint.config.js
packages/svelte-query/eslint.config.js
packages/svelte-query-devtools/eslint.config.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Test
🔇 Additional comments (2)
packages/svelte-query/svelte.config.js (1)
5-7
: Enable runes in compiler options looks goodSetting
compilerOptions.runes = true
here aligns the adapter with the rest of the PR’s Svelte 5 migration work. No issues spotted.packages/svelte-query-devtools/svelte.config.js (1)
5-7
: Looks good: enabling runes in devtools config.Setting
compilerOptions.runes = true
aligns this package with the rest of the Svelte 5 migration, so runes-based components in the devtools will compile correctly.
5d7869e
to
18a12ca
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (4)
packages/svelte-query-devtools/package.json
(1 hunks)packages/svelte-query-persist-client/package.json
(1 hunks)packages/svelte-query/package.json
(2 hunks)packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts
(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/svelte-query-devtools/package.json
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-02T17:57:33.184Z
Learnt from: TkDodo
PR: TanStack/query#9612
File: packages/query-async-storage-persister/src/asyncThrottle.ts:0-0
Timestamp: 2025-09-02T17:57:33.184Z
Learning: When importing from tanstack/query-core in other TanStack Query packages like query-async-storage-persister, a workspace dependency "tanstack/query-core": "workspace:*" needs to be added to the package.json.
Applied to files:
packages/svelte-query-persist-client/package.json
🧬 Code graph analysis (1)
packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts (2)
packages/svelte-query/tests/utils.svelte.ts (1)
ref
(1-12)packages/query-core/src/types.ts (1)
QueryObserverResult
(899-904)
🪛 Biome (2.1.2)
packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts
[error] 3-3: Shouldn't redeclare 'render'. Consider to delete it or rename it.
'render' is defined here:
(lint/suspicious/noRedeclare)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Test
- GitHub Check: Preview
🔇 Additional comments (2)
packages/svelte-query-persist-client/package.json (1)
62-63
: Clarify the new Svelte peer floor.Line 62 narrows the peer range from all of Svelte 5 to only
^5.25.0
. That drops support for 5.0–5.24, so we should only do this if the adapter now depends on APIs introduced at or after 5.25. Can you confirm the minimum version we truly need and, if possible, loosen the range accordingly?packages/svelte-query/package.json (1)
56-63
: No action required for ESLint version
Root-level ESLint (^9.36.0) satisfies the ≥8.57.0 peer dependency needed by@typescript-eslint/parser
v8.
packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts
Outdated
Show resolved
Hide resolved
a69fb76
to
c6d0b92
Compare
🎯 Changes
Breaking update to the Svelte adapter, which switches from stores to runes syntax.
Tremendous thanks to @zhihengGet , @elliott-with-the-longest-name-on-github , and everyone else who has contributed or tested this adapter!
✅ Checklist
pnpm test:pr
.🚀 Release Impact
Summary by CodeRabbit
Breaking Changes
New Features
Documentation
Chores / Tests