Skip to content

Conversation

lachlancollins
Copy link
Member

@lachlancollins lachlancollins commented Sep 26, 2025

🎯 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

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Breaking Changes

    • Requires Svelte 5.25+ (runes). Option objects must be provided as thunks (() => …). Templates now use direct query/mutation access (query.*) and many layouts render children programmatically instead of slots.
  • New Features

    • Added hooks for mutation/fetching state, boxed/ref reactive utilities, QueryClientProvider default export, and grouped Devtools options with cleanup.
  • Documentation

    • New "Migrate from v5 to v6" guide; legacy reactivity doc removed; Svelte docs updated.
  • Chores / Tests

    • Peer deps tightened to Svelte 5.25+, ESLint/parser updates, extensive test additions and refactors.

zhihengGet and others added 13 commits April 12, 2025 12:11
* 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>
Copy link

changeset-bot bot commented Sep 26, 2025

🦋 Changeset detected

Latest commit: 7049e4b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@tanstack/svelte-query-persist-client Major
@tanstack/svelte-query-devtools Major
@tanstack/svelte-query Major

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

Copy link
Contributor

coderabbitai bot commented Sep 26, 2025

Walkthrough

Migration 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

Cohort / File(s) Summary
Changeset
​.changeset/pink-pots-jam.md
Adds breaking changes changeset documenting major version bumps and migration guidance.
Docs & nav
docs/config.json, docs/framework/svelte/*.md, docs/framework/svelte/migrate-from-v5-to-v6.md
Add migration doc and nav entry; remove legacy reactivity doc; add examples showing thunked options and non-$ access.
Tooling / ESLint / package metadata
eslint.config.js, packages/*/eslint.config.js, packages/*/package.json, packages/svelte-query/package.json, packages/*-devtools/package.json, packages/*-persist-client/package.json
Add @typescript-eslint/parser, switch Svelte ESLint preset to recommended, include .svelte.ts/.svelte.js globs, add devDeps, and tighten peerDependency Svelte to ^5.25.0.
Project / Svelte config (runes)
examples/svelte/*/svelte.config.js, packages/*/svelte.config.js, packages/svelte-query-devtools/svelte.config.js
Enable compilerOptions.runes = true across examples/packages.
Layouts / children rendering
examples/svelte/**/src/routes/+layout.svelte, many layout files
Replace <slot /> with {@render children()} and extract children via const { children } = $props().
Examples — callsites & handlers
examples and tests under examples/svelte/**, packages/svelte-query/tests/**, many *.svelte files
Convert createQuery/createMutation/createInfiniteQuery/createQueries calls to thunk/accessor form () => ({ ... }), replace $store auto-subscriptions with direct query.* or .value, replace on:... with onclick, adopt $state and $props patterns.
Examples — stores migration
examples/svelte/playground/src/lib/stores.svelte.ts, removed examples/svelte/playground/src/lib/stores.ts
Replace writable store module with .svelte ref-style exports exposing .value; update consumers to use .value.
CSS
examples/svelte/load-more-infinite-scroll/src/app.css
Change element selectors from button to .button class selectors.
Core: types & exports
packages/svelte-query/src/types.ts, packages/svelte-query/src/index.ts, packages/svelte-query/src/context.ts
Introduce Accessor<T> and Box/ReactiveValue types, remove StoreOrVal/Readable wrappers, switch context keys to Symbols, re-export .svelte.js variants, add QueryClientProvider .svelte export.
Core: runes implementations (added)
packages/svelte-query/src/*.svelte.ts (createBaseQuery, createMutation, createQueries, useIsFetching, useIsMutating, useMutationState, utils.svelte.ts, watchChanges, etc.)
New runes-aware implementations accepting Accessor thunks, using watchChanges/untrack/$effect, returning ReactiveValue/Box/ref-style outputs.
Core: legacy removals
packages/svelte-query/src/*.ts (createBaseQuery.ts, createMutation.ts, useIsFetching.ts, useIsMutating.ts, useMutationState.ts, utils.ts, etc.)
Remove legacy store-based implementations and helpers replaced by .svelte variants.
Core components
packages/svelte-query/src/QueryClientProvider.svelte, packages/svelte-query/src/HydrationBoundary.svelte
Switch prop handling to $props() destructuring and render children with {@render children()}.
Containers & primitives
packages/svelte-query/src/containers.svelte.ts, packages/svelte-query/tests/containers.svelte.test.ts
Add createRawRef, ReactiveValue, Box types and comprehensive tests for raw-ref/reactivity behavior.
Persist-client provider & utils
packages/svelte-query-persist-client/src/PersistQueryClientProvider.svelte, packages/svelte-query-persist-client/src/utils.svelte.ts
Replace exported props with $props() destructuring, add box<T> util, switch restoration/subscription to effect-driven flow, render children via QueryClientProvider.
Devtools
packages/svelte-query-devtools/src/Devtools.svelte
Collapse many exported props into a DevtoolsOptions object via $props(), add onMount cleanup and $effect-based updates.
Tests & test utilities
packages/svelte-query/tests/**, packages/svelte-query/tests/utils.svelte.ts, packages/svelte-query-persist-client/tests/**
Add runes-based test utilities (ref, promiseWithResolvers, withEffectRoot), convert many tests/components to thunked options/ref-style state, add/remove updated test suites.
Vite / test config
packages/*/vite.config.ts
Adjust coverage/restoreMocks testing config (coverage toggles removed/disabled).

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
Loading
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
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • TkDodo

Poem

I thump my whiskers on the keys tonight,
Thunks and boxes make the code feel light.
No dollar signs in template song,
Children render, props move along.
A hopping rabbit—v6 takes flight! 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly describes the primary change—switching the Svelte adapter to runes syntax—and correctly uses the conventional commit “feat!” to indicate a breaking change. It is concise, focused on the main update, and clearly tied to the changeset scope.
Description Check ✅ Passed The pull request description matches the repository template, including the 🎯 Changes section clearly describing the breaking update, the completed ✅ Checklist items, and the 🚀 Release Impact section indicating the changeset generation and release scope.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch svelte-5-adapter

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05f0acd and 7049e4b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • packages/svelte-query-devtools/package.json (1 hunks)
  • packages/svelte-query-persist-client/package.json (1 hunks)
  • packages/svelte-query/package.json (2 hunks)
🧰 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
⏰ 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 (3)
packages/svelte-query-persist-client/package.json (2)

55-55: Parser addition unblocks lint config.

Thanks for adding the TypeScript ESLint parser here—this keeps the local package lint step self-contained with the new ESLint setup.


62-62: Confirm the Svelte peer floor matches the new features.

Our dev tooling now pulls in Svelte ^5.39.3 while the peer range bottoms out at ^5.25.0. Please double-check that none of the rune APIs or helpers you’re using were introduced after 5.25.0; if they were, we should raise the peer floor to avoid runtime mismatches.

packages/svelte-query/package.json (1)

60-60: Verify ESLint compatibility before merging.

@typescript-eslint/parser@^8.44.1 declares a peer dependency on eslint ^8.57.0 || ^9.0.0. Please double-check that the workspace already satisfies that range (or plan an accompanying bump), otherwise pnpm install will error out here. (app.unpkg.com)


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

nx-cloud bot commented Sep 26, 2025

View your CI Pipeline Execution ↗ for commit 7049e4b

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 2m 54s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 6s View ↗

☁️ Nx Cloud last updated this comment at 2025-09-30 05:56:33 UTC

Copy link

pkg-pr-new bot commented Sep 26, 2025

More templates

@tanstack/angular-query-experimental

npm i https://pkg.pr.new/@tanstack/angular-query-experimental@9694

@tanstack/eslint-plugin-query

npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@9694

@tanstack/query-async-storage-persister

npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@9694

@tanstack/query-broadcast-client-experimental

npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@9694

@tanstack/query-core

npm i https://pkg.pr.new/@tanstack/query-core@9694

@tanstack/query-devtools

npm i https://pkg.pr.new/@tanstack/query-devtools@9694

@tanstack/query-persist-client-core

npm i https://pkg.pr.new/@tanstack/query-persist-client-core@9694

@tanstack/query-sync-storage-persister

npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@9694

@tanstack/react-query

npm i https://pkg.pr.new/@tanstack/react-query@9694

@tanstack/react-query-devtools

npm i https://pkg.pr.new/@tanstack/react-query-devtools@9694

@tanstack/react-query-next-experimental

npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@9694

@tanstack/react-query-persist-client

npm i https://pkg.pr.new/@tanstack/react-query-persist-client@9694

@tanstack/solid-query

npm i https://pkg.pr.new/@tanstack/solid-query@9694

@tanstack/solid-query-devtools

npm i https://pkg.pr.new/@tanstack/solid-query-devtools@9694

@tanstack/solid-query-persist-client

npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@9694

@tanstack/svelte-query

npm i https://pkg.pr.new/@tanstack/svelte-query@9694

@tanstack/svelte-query-devtools

npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@9694

@tanstack/svelte-query-persist-client

npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@9694

@tanstack/vue-query

npm i https://pkg.pr.new/@tanstack/vue-query@9694

@tanstack/vue-query-devtools

npm i https://pkg.pr.new/@tanstack/vue-query-devtools@9694

commit: 7049e4b

Copy link
Contributor

github-actions bot commented Sep 26, 2025

Sizes for commit 7049e4b:

Branch Bundle Size
Main
This PR

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 mount

If 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 accept Accessor<QueryClient | undefined> rather than Accessor<QueryClient>. Without this, TypeScript will complain when an accessor conditionally returns undefined—despite the runtime handling it. Update each overload’s queryClient 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 blanket svelte/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 mismatch

Improve cancellation and robustness by using the QueryFunctionContext (provides signal and queryKey) and guarding on a falsy characterId. 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 comparison

Use 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 chaining

If 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 createInfiniteQuery
examples/svelte/basic/src/routes/[postId]/+page.svelte (1)

4-4: Preserve types by annotating $props() extraction.

Without the prop export, data loses its PageData 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 message

todos.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 safety

You 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 types

query.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 label

Using 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 types

Expose 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 value

limit is never reassigned; prefer const.

-let limit = 10
+const limit = 10
packages/svelte-query/src/createMutation.svelte.ts (2)

48-52: Batch updates with notifyManager.batch, not batchCalls invoked immediately

batchCalls 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 allocates

Spreading 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 falsy

Prevents 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 QueryClientProvider

Extract 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 cleanup

Slightly cleaner effect cleanup semantics.

Apply this diff:

-  $effect(() => {
-    return isRestoring.current ? () => {} : persistQueryClientSubscribe(options)
-  })
+  $effect(() =>
+    isRestoring.current ? undefined : persistQueryClientSubscribe(options),
+  )

46-47: Reference extracted callbacks directly

Avoid 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 props

Prevents 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 onclick

onclick 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 handlers

 interface Props {
   queryClient: QueryClient
   persistOptions: OmitKeyof<PersistQueryClientOptions, 'queryClient'>
-  onSuccess: () => void
-  onError: () => void
+  onSuccess: () => void | Promise<void>
+  onError: () => void | Promise<void>
 }

QueryClient and OmitKeyof are correctly re-exported from @tanstack/svelte-query.

packages/svelte-query/src/useMutationState.svelte.ts (2)

1-1: Batch cache notifications to avoid update thrashing

Wrap 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 block

The 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 instance

Returning 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 consistency

Other 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 options

Small 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 reactivity

Great 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 subscriptions

The effect correctly returns an unsubscribe function, but there's a potential issue: if isRestoring.current changes from true to false 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 syntax

While onsubmit works, consider using Svelte 5's event handler syntax for consistency.

-  onsubmit={(e) => {
+  onsubmit={(e: SubmitEvent) => {

88-88: Missing type safety for mutation parameter

The undefined parameter to clearMutation.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 inference

Annotate 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 OmitKeyof

OmitKeyof 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 snapshot

Avoid 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 assignments

When 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 checks

For 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8e42926 and e3bba4a.

📒 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

Comment on lines +8 to +11
const post = createQuery<Post>(() => ({
queryKey: ['post', postId],
queryFn: () => getPostById(postId),
})
}))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Comment on lines +54 to 56
onclick={() => addMutation.mutate({ name, notes: name })}
disabled={addMutation.status === 'pending' || !name}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

Comment on lines 219 to 225
const observer = $derived(
new QueriesObserver<TCombinedResult>(
client,
untrack(() => resolvedQueryOptions),
untrack(() => combine as QueriesObserverOptions<TCombinedResult>),
),
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Comment on lines +21 to 47
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>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between e3bba4a and 373e24b.

📒 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

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between 373e24b and d0beb44.

📒 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 solid

Moving from export let to $props() keeps the component in step with Svelte’s runes migration while preserving the filmId typing — nice work.


7-16: Function-wrapped createQuery options are spot on

Providing the options through a thunk ensures filmId stays reactive, and the template guard cleanly narrows to the success path before touching query.data. Looks great.

Comment on lines +8 to +11
const query = createQuery(() => ({
queryKey: ['character', page.params.characterId],
queryFn: () => getCharacter(page.params.characterId),
}))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

Comment on lines +5 to +10
let { characterId }: { characterId: string } = $props()
const query = createQuery({
const query = createQuery(() => ({
queryKey: ['character', characterId],
queryFn: () => getCharacter(characterId),
})
}))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between d0beb44 and bd8737f.

📒 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 deletes

Line 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 let update 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--
+          }
+        }

Comment on lines +35 to +55
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
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

lachlancollins and others added 3 commits September 27, 2025 17:46
* 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>
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between eea078e and 968d6a8.

📒 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: Wrap combine in the options object when constructing QueriesObserver.

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 } (or undefined) 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,
     ),
   )

Comment on lines +1096 to +1123
// 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,
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
<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>
Copy link
Contributor

@coderabbitai coderabbitai bot Sep 27, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Copy link

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

Copy link
Contributor

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
Copy link

codecov bot commented Sep 27, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 45.51%. Comparing base (a242f98) to head (7049e4b).

Additional details and impacted files

Impacted file tree graph

@@            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     
Components Coverage Δ
@tanstack/angular-query-experimental 93.85% <ø> (ø)
@tanstack/eslint-plugin-query 83.03% <ø> (ø)
@tanstack/query-async-storage-persister 43.85% <ø> (ø)
@tanstack/query-broadcast-client-experimental 24.39% <ø> (ø)
@tanstack/query-codemods 0.00% <ø> (ø)
@tanstack/query-core 97.48% <ø> (ø)
@tanstack/query-devtools 3.48% <ø> (ø)
@tanstack/query-persist-client-core 79.60% <ø> (ø)
@tanstack/query-sync-storage-persister 84.61% <ø> (ø)
@tanstack/query-test-utils 77.77% <ø> (ø)
@tanstack/react-query 96.00% <ø> (ø)
@tanstack/react-query-devtools 10.00% <ø> (ø)
@tanstack/react-query-next-experimental ∅ <ø> (∅)
@tanstack/react-query-persist-client 100.00% <ø> (ø)
@tanstack/solid-query 78.06% <ø> (ø)
@tanstack/solid-query-devtools ∅ <ø> (∅)
@tanstack/solid-query-persist-client 100.00% <ø> (ø)
@tanstack/svelte-query ∅ <ø> (∅)
@tanstack/svelte-query-devtools ∅ <ø> (∅)
@tanstack/svelte-query-persist-client ∅ <ø> (∅)
@tanstack/vue-query 71.10% <ø> (ø)
@tanstack/vue-query-devtools ∅ <ø> (∅)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between 968d6a8 and 62be7d5.

⛔ 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 good

Setting 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.

@lachlancollins lachlancollins changed the title WIP: Update svelte adapter to use runes BREAKING: Update svelte adapter to use runes Sep 27, 2025
@lachlancollins lachlancollins changed the title BREAKING: Update svelte adapter to use runes feat!(svelte-query): Update svelte adapter to use runes Sep 29, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between 62be7d5 and a69fb76.

⛔ 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants