Loading...
{/if}
-{#if $query.status === 'error'}
+{#if query.status === 'error'}
-
{$query.data.title}
-
{$query.data.opening_crawl}
+
{query.data.title}
+
{query.data.opening_crawl}
Characters
- {#each $query.data.characters as character}
+ {#each query.data.characters as character}
{@const characterUrlParts = character.split('/').filter(Boolean)}
{@const characterId = characterUrlParts[characterUrlParts.length - 1]}
diff --git a/examples/svelte/star-wars/src/routes/films/[filmId]/+page.ts b/examples/svelte/star-wars/src/routes/films/[filmId]/+page.ts
deleted file mode 100644
index dbfde8eb56..0000000000
--- a/examples/svelte/star-wars/src/routes/films/[filmId]/+page.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import type { PageLoad } from './$types'
-
-export const load: PageLoad = ({ params }) => {
- return { params }
-}
diff --git a/examples/svelte/star-wars/src/routes/films/[filmId]/Character.svelte b/examples/svelte/star-wars/src/routes/films/[filmId]/Character.svelte
index 14e439d9a6..17d2eac398 100644
--- a/examples/svelte/star-wars/src/routes/films/[filmId]/Character.svelte
+++ b/examples/svelte/star-wars/src/routes/films/[filmId]/Character.svelte
@@ -2,21 +2,21 @@
import { getCharacter } from '$lib/api'
import { createQuery } from '@tanstack/svelte-query'
- export let characterId: string
+ let { characterId }: { characterId: string } = $props()
- const query = createQuery({
+ const query = createQuery(() => ({
queryKey: ['character', characterId],
queryFn: () => getCharacter(characterId),
- })
+ }))
-{#if $query.status === 'success'}
+{#if query.status === 'success'}
- {$query.data.name}
+ {query.data.name}
{/if}
diff --git a/examples/svelte/star-wars/svelte.config.js b/examples/svelte/star-wars/svelte.config.js
index 3ddb57f8c4..d6b43b0085 100644
--- a/examples/svelte/star-wars/svelte.config.js
+++ b/examples/svelte/star-wars/svelte.config.js
@@ -7,6 +7,9 @@ const config = {
kit: {
adapter: adapter(),
},
+ compilerOptions: {
+ runes: true,
+ },
}
export default config
diff --git a/packages/svelte-query-devtools/eslint.config.js b/packages/svelte-query-devtools/eslint.config.js
index b657d69d66..e102c19ee9 100644
--- a/packages/svelte-query-devtools/eslint.config.js
+++ b/packages/svelte-query-devtools/eslint.config.js
@@ -1,17 +1,19 @@
// @ts-check
+import tsParser from '@typescript-eslint/parser'
import pluginSvelte from 'eslint-plugin-svelte'
import rootConfig from './root.eslint.config.js'
import svelteConfig from './svelte.config.js'
export default [
...rootConfig,
- ...pluginSvelte.configs['flat/recommended'],
+ ...pluginSvelte.configs['recommended'],
{
- files: ['**/*.svelte'],
+ files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
languageOptions: {
parserOptions: {
- parser: '@typescript-eslint/parser',
+ parser: tsParser,
+ extraFileExtensions: ['.svelte'],
svelteConfig,
},
},
diff --git a/packages/svelte-query-devtools/package.json b/packages/svelte-query-devtools/package.json
index fb43371d02..0b06f684b9 100644
--- a/packages/svelte-query-devtools/package.json
+++ b/packages/svelte-query-devtools/package.json
@@ -14,6 +14,11 @@
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
+ "keywords": [
+ "tanstack",
+ "query",
+ "svelte"
+ ],
"scripts": {
"clean": "premove ./dist ./coverage ./.svelte-kit ./dist-ts",
"compile": "tsc --build",
@@ -48,12 +53,13 @@
"@sveltejs/package": "^2.4.0",
"@sveltejs/vite-plugin-svelte": "^5.1.1",
"@tanstack/svelte-query": "workspace:*",
+ "@typescript-eslint/parser": "^8.44.1",
"eslint-plugin-svelte": "^3.11.0",
"svelte": "^5.39.3",
"svelte-check": "^4.3.1"
},
"peerDependencies": {
"@tanstack/svelte-query": "workspace:^",
- "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0"
+ "svelte": "^5.25.0"
}
}
diff --git a/packages/svelte-query-devtools/src/Devtools.svelte b/packages/svelte-query-devtools/src/Devtools.svelte
index ea0de0dfd0..e80064de68 100644
--- a/packages/svelte-query-devtools/src/Devtools.svelte
+++ b/packages/svelte-query-devtools/src/Devtools.svelte
@@ -10,14 +10,55 @@
TanstackQueryDevtools,
} from '@tanstack/query-devtools'
- export let initialIsOpen = false
- export let buttonPosition: DevtoolsButtonPosition = 'bottom-right'
- export let position: DevtoolsPosition = 'bottom'
- export let client: QueryClient = useQueryClient()
- export let errorTypes: Array
= []
- export let styleNonce: string | undefined = undefined
- export let shadowDOMTarget: ShadowRoot | undefined = undefined
- export let hideDisabledQueries: boolean = false
+ interface DevtoolsOptions {
+ /**
+ * Set this true if you want the dev tools to default to being open
+ */
+ initialIsOpen?: boolean
+ /**
+ * The position of the TanStack Query logo to open and close the devtools panel.
+ * 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
+ * Defaults to 'bottom-right'.
+ */
+ buttonPosition?: DevtoolsButtonPosition
+ /**
+ * The position of the TanStack Query devtools panel.
+ * 'top' | 'bottom' | 'left' | 'right'
+ * Defaults to 'bottom'.
+ */
+ position?: DevtoolsPosition
+ /**
+ * Custom instance of QueryClient
+ */
+ client?: QueryClient
+ /**
+ * Use this so you can define custom errors that can be shown in the devtools.
+ */
+ errorTypes?: Array
+ /**
+ * Use this to pass a nonce to the style tag that is added to the document head. This is useful if you are using a Content Security Policy (CSP) nonce to allow inline styles.
+ */
+ styleNonce?: string
+ /**
+ * Use this so you can attach the devtool's styles to specific element in the DOM.
+ */
+ shadowDOMTarget?: ShadowRoot
+ /**
+ * Set this to true to hide disabled queries from the devtools panel.
+ */
+ hideDisabledQueries?: boolean
+ }
+
+ let {
+ initialIsOpen = false,
+ buttonPosition = 'bottom-right',
+ position = 'bottom',
+ client = useQueryClient(),
+ errorTypes = [],
+ styleNonce = undefined,
+ shadowDOMTarget = undefined,
+ hideDisabledQueries = false,
+ }: DevtoolsOptions = $props()
let ref: HTMLDivElement
let devtools: TanstackQueryDevtools | undefined
@@ -43,20 +84,24 @@
devtools.mount(ref)
})
+ return () => devtools?.unmount()
+ })
- return () => {
- devtools?.unmount()
- }
+ $effect(() => {
+ devtools?.setButtonPosition(buttonPosition)
})
- }
- $: {
- if (devtools) {
- devtools.setButtonPosition(buttonPosition)
- devtools.setPosition(position)
- devtools.setInitialIsOpen(initialIsOpen)
- devtools.setErrorTypes(errorTypes)
- }
+ $effect(() => {
+ devtools?.setPosition(position)
+ })
+
+ $effect(() => {
+ devtools?.setInitialIsOpen(initialIsOpen)
+ })
+
+ $effect(() => {
+ devtools?.setErrorTypes(errorTypes)
+ })
}
diff --git a/packages/svelte-query-devtools/svelte.config.js b/packages/svelte-query-devtools/svelte.config.js
index 94ca454ac7..076d2dcd50 100644
--- a/packages/svelte-query-devtools/svelte.config.js
+++ b/packages/svelte-query-devtools/svelte.config.js
@@ -2,6 +2,9 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
const config = {
preprocess: vitePreprocess(),
+ compilerOptions: {
+ runes: true,
+ },
}
export default config
diff --git a/packages/svelte-query-persist-client/eslint.config.js b/packages/svelte-query-persist-client/eslint.config.js
index b657d69d66..e102c19ee9 100644
--- a/packages/svelte-query-persist-client/eslint.config.js
+++ b/packages/svelte-query-persist-client/eslint.config.js
@@ -1,17 +1,19 @@
// @ts-check
+import tsParser from '@typescript-eslint/parser'
import pluginSvelte from 'eslint-plugin-svelte'
import rootConfig from './root.eslint.config.js'
import svelteConfig from './svelte.config.js'
export default [
...rootConfig,
- ...pluginSvelte.configs['flat/recommended'],
+ ...pluginSvelte.configs['recommended'],
{
- files: ['**/*.svelte'],
+ files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
languageOptions: {
parserOptions: {
- parser: '@typescript-eslint/parser',
+ parser: tsParser,
+ extraFileExtensions: ['.svelte'],
svelteConfig,
},
},
diff --git a/packages/svelte-query-persist-client/package.json b/packages/svelte-query-persist-client/package.json
index 84bc7b96cc..5dd977f52c 100644
--- a/packages/svelte-query-persist-client/package.json
+++ b/packages/svelte-query-persist-client/package.json
@@ -14,6 +14,11 @@
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
+ "keywords": [
+ "tanstack",
+ "query",
+ "svelte"
+ ],
"scripts": {
"clean": "premove ./dist ./coverage ./.svelte-kit ./dist-ts",
"compile": "tsc --build",
@@ -52,12 +57,13 @@
"@tanstack/query-test-utils": "workspace:*",
"@tanstack/svelte-query": "workspace:*",
"@testing-library/svelte": "^5.2.8",
+ "@typescript-eslint/parser": "^8.44.1",
"eslint-plugin-svelte": "^3.11.0",
"svelte": "^5.39.3",
"svelte-check": "^4.3.1"
},
"peerDependencies": {
"@tanstack/svelte-query": "workspace:^",
- "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0"
+ "svelte": "^5.25.0"
}
}
diff --git a/packages/svelte-query-persist-client/src/PersistQueryClientProvider.svelte b/packages/svelte-query-persist-client/src/PersistQueryClientProvider.svelte
index b0ba375b04..d94c4fbe20 100644
--- a/packages/svelte-query-persist-client/src/PersistQueryClientProvider.svelte
+++ b/packages/svelte-query-persist-client/src/PersistQueryClientProvider.svelte
@@ -1,51 +1,56 @@
-
-
+
+ {@render children()}
diff --git a/packages/svelte-query-persist-client/src/utils.svelte.ts b/packages/svelte-query-persist-client/src/utils.svelte.ts
new file mode 100644
index 0000000000..7760eded8c
--- /dev/null
+++ b/packages/svelte-query-persist-client/src/utils.svelte.ts
@@ -0,0 +1,14 @@
+type Box = { current: T }
+
+export function box(initial: T): Box {
+ let current = $state(initial)
+
+ return {
+ get current() {
+ return current
+ },
+ set current(newValue) {
+ current = newValue
+ },
+ }
+}
diff --git a/packages/svelte-query-persist-client/svelte.config.js b/packages/svelte-query-persist-client/svelte.config.js
index 94ca454ac7..076d2dcd50 100644
--- a/packages/svelte-query-persist-client/svelte.config.js
+++ b/packages/svelte-query-persist-client/svelte.config.js
@@ -2,6 +2,9 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
const config = {
preprocess: vitePreprocess(),
+ compilerOptions: {
+ runes: true,
+ },
}
export default config
diff --git a/packages/svelte-query-persist-client/tests/AwaitOnSuccess/AwaitOnSuccess.svelte b/packages/svelte-query-persist-client/tests/AwaitOnSuccess/AwaitOnSuccess.svelte
index 74f09accd8..4835489c1e 100644
--- a/packages/svelte-query-persist-client/tests/AwaitOnSuccess/AwaitOnSuccess.svelte
+++ b/packages/svelte-query-persist-client/tests/AwaitOnSuccess/AwaitOnSuccess.svelte
@@ -1,20 +1,20 @@
-{$query.data}
-fetchStatus: {$query.fetchStatus}
+{query.data}
+fetchStatus: {query.fetchStatus}
diff --git a/packages/svelte-query-persist-client/tests/AwaitOnSuccess/Provider.svelte b/packages/svelte-query-persist-client/tests/AwaitOnSuccess/Provider.svelte
index c3087ac2fa..79126c724b 100644
--- a/packages/svelte-query-persist-client/tests/AwaitOnSuccess/Provider.svelte
+++ b/packages/svelte-query-persist-client/tests/AwaitOnSuccess/Provider.svelte
@@ -3,12 +3,16 @@
import AwaitOnSuccess from './AwaitOnSuccess.svelte'
import type { OmitKeyof, QueryClient } from '@tanstack/svelte-query'
import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core'
- import type { Writable } from 'svelte/store'
+ import { StatelessRef } from '../utils.svelte'
- export let queryClient: QueryClient
- export let persistOptions: OmitKeyof
- export let onSuccess: () => Promise
- export let states: Writable>
+ interface Props {
+ queryClient: QueryClient
+ persistOptions: OmitKeyof
+ onSuccess: () => Promise
+ states: StatelessRef>
+ }
+
+ let { queryClient, persistOptions, onSuccess, states }: Props = $props()
diff --git a/packages/svelte-query-persist-client/tests/FreshData/FreshData.svelte b/packages/svelte-query-persist-client/tests/FreshData/FreshData.svelte
index 451a2a98f2..fff8ba820d 100644
--- a/packages/svelte-query-persist-client/tests/FreshData/FreshData.svelte
+++ b/packages/svelte-query-persist-client/tests/FreshData/FreshData.svelte
@@ -1,25 +1,26 @@
-data: {$query.data ?? 'undefined'}
-fetchStatus: {$query.fetchStatus}
+data: {query.data ?? 'undefined'}
+fetchStatus: {query.fetchStatus}
diff --git a/packages/svelte-query-persist-client/tests/FreshData/Provider.svelte b/packages/svelte-query-persist-client/tests/FreshData/Provider.svelte
index 75a9c1aefa..70a9ea483f 100644
--- a/packages/svelte-query-persist-client/tests/FreshData/Provider.svelte
+++ b/packages/svelte-query-persist-client/tests/FreshData/Provider.svelte
@@ -3,15 +3,17 @@
import FreshData from './FreshData.svelte'
import type { OmitKeyof, QueryClient } from '@tanstack/svelte-query'
import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core'
- import type { Writable } from 'svelte/store'
- import type { StatusResult } from '../utils.js'
+ import type { StatelessRef, StatusResult } from '../utils.svelte.js'
- export let queryClient: QueryClient
- export let persistOptions: OmitKeyof
- export let states: Writable>>
- export let fetched: Writable
+ interface Props {
+ queryClient: QueryClient
+ persistOptions: OmitKeyof
+ states: StatelessRef>>
+ }
+
+ let { queryClient, persistOptions, states }: Props = $props()
-
+
diff --git a/packages/svelte-query-persist-client/tests/InitialData/InitialData.svelte b/packages/svelte-query-persist-client/tests/InitialData/InitialData.svelte
index 7e661dbe71..a4df9132f8 100644
--- a/packages/svelte-query-persist-client/tests/InitialData/InitialData.svelte
+++ b/packages/svelte-query-persist-client/tests/InitialData/InitialData.svelte
@@ -1,12 +1,12 @@
-{$query.data}
-fetchStatus: {$query.fetchStatus}
+{query.data}
+fetchStatus: {query.fetchStatus}
diff --git a/packages/svelte-query-persist-client/tests/InitialData/Provider.svelte b/packages/svelte-query-persist-client/tests/InitialData/Provider.svelte
index e5e17f712e..a50338006a 100644
--- a/packages/svelte-query-persist-client/tests/InitialData/Provider.svelte
+++ b/packages/svelte-query-persist-client/tests/InitialData/Provider.svelte
@@ -3,12 +3,15 @@
import InitialData from './InitialData.svelte'
import type { OmitKeyof, QueryClient } from '@tanstack/svelte-query'
import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core'
- import type { Writable } from 'svelte/store'
- import type { StatusResult } from '../utils.js'
+ import type { StatelessRef, StatusResult } from '../utils.svelte.js'
- export let queryClient: QueryClient
- export let persistOptions: OmitKeyof
- export let states: Writable>>
+ interface Props {
+ queryClient: QueryClient
+ persistOptions: OmitKeyof
+ states: StatelessRef>>
+ }
+
+ let { queryClient, persistOptions, states }: Props = $props()
diff --git a/packages/svelte-query-persist-client/tests/OnSuccess/OnSuccess.svelte b/packages/svelte-query-persist-client/tests/OnSuccess/OnSuccess.svelte
index 40dd1d90be..a6ef7b3214 100644
--- a/packages/svelte-query-persist-client/tests/OnSuccess/OnSuccess.svelte
+++ b/packages/svelte-query-persist-client/tests/OnSuccess/OnSuccess.svelte
@@ -1,15 +1,11 @@
-{$query.data}
-fetchStatus: {$query.fetchStatus}
+{query.data}
+fetchStatus: {query.fetchStatus}
diff --git a/packages/svelte-query-persist-client/tests/OnSuccess/Provider.svelte b/packages/svelte-query-persist-client/tests/OnSuccess/Provider.svelte
index c0d2792771..0b280ca570 100644
--- a/packages/svelte-query-persist-client/tests/OnSuccess/Provider.svelte
+++ b/packages/svelte-query-persist-client/tests/OnSuccess/Provider.svelte
@@ -4,9 +4,13 @@
import type { OmitKeyof, QueryClient } from '@tanstack/svelte-query'
import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core'
- export let queryClient: QueryClient
- export let persistOptions: OmitKeyof
- export let onSuccess: () => void
+ interface Props {
+ queryClient: QueryClient
+ persistOptions: OmitKeyof
+ onSuccess: () => void
+ }
+
+ let { queryClient, persistOptions, onSuccess }: Props = $props()
diff --git a/packages/svelte-query-persist-client/tests/PersistQueryClientProvider.test.ts b/packages/svelte-query-persist-client/tests/PersistQueryClientProvider.svelte.test.ts
similarity index 74%
rename from packages/svelte-query-persist-client/tests/PersistQueryClientProvider.test.ts
rename to packages/svelte-query-persist-client/tests/PersistQueryClientProvider.svelte.test.ts
index a6f3105c63..668a848607 100644
--- a/packages/svelte-query-persist-client/tests/PersistQueryClientProvider.test.ts
+++ b/packages/svelte-query-persist-client/tests/PersistQueryClientProvider.svelte.test.ts
@@ -1,9 +1,8 @@
import { render, waitFor } from '@testing-library/svelte'
import { describe, expect, test, vi } from 'vitest'
+import { QueryClient } from '@tanstack/svelte-query'
import { persistQueryClientSave } from '@tanstack/query-persist-client-core'
-import { get, writable } from 'svelte/store'
import { sleep } from '@tanstack/query-test-utils'
-import { QueryClient } from '@tanstack/svelte-query'
import AwaitOnSuccess from './AwaitOnSuccess/Provider.svelte'
import FreshData from './FreshData/Provider.svelte'
import OnSuccess from './OnSuccess/Provider.svelte'
@@ -11,13 +10,12 @@ import InitialData from './InitialData/Provider.svelte'
import RemoveCache from './RemoveCache/Provider.svelte'
import RestoreCache from './RestoreCache/Provider.svelte'
import UseQueries from './UseQueries/Provider.svelte'
-
+import { StatelessRef } from './utils.svelte.js'
import type {
PersistedClient,
Persister,
} from '@tanstack/query-persist-client-core'
-import type { Writable } from 'svelte/store'
-import type { StatusResult } from './utils.js'
+import type { StatusResult } from './utils.svelte.js'
const createMockPersister = (): Persister => {
let storedState: PersistedClient | undefined
@@ -27,8 +25,7 @@ const createMockPersister = (): Persister => {
storedState = persistClient
},
async restoreClient() {
- await sleep(5)
- return storedState
+ return Promise.resolve(storedState)
},
removeClient() {
storedState = undefined
@@ -47,8 +44,7 @@ const createMockErrorPersister = (
// noop
},
async restoreClient() {
- await sleep(5)
- throw error
+ return Promise.reject(error)
},
removeClient,
},
@@ -57,7 +53,7 @@ const createMockErrorPersister = (
describe('PersistQueryClientProvider', () => {
test('restores cache from persister', async () => {
- const statesStore: Writable>> = writable([])
+ const states = new StatelessRef>>([])
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
@@ -75,7 +71,7 @@ describe('PersistQueryClientProvider', () => {
props: {
queryClient,
persistOptions: { persister },
- states: statesStore,
+ states,
},
})
@@ -83,34 +79,21 @@ describe('PersistQueryClientProvider', () => {
await waitFor(() => rendered.getByText('hydrated'))
await waitFor(() => rendered.getByText('fetched'))
- const states = get(statesStore)
- expect(states).toHaveLength(5)
+ expect(states.current).toHaveLength(3)
- expect(states[0]).toMatchObject({
+ expect(states.current[0]).toMatchObject({
status: 'pending',
fetchStatus: 'idle',
data: undefined,
})
- expect(states[1]).toMatchObject({
- status: 'success',
- fetchStatus: 'fetching',
- data: 'hydrated',
- })
-
- expect(states[2]).toMatchObject({
- status: 'success',
- fetchStatus: 'fetching',
- data: 'hydrated',
- })
-
- expect(states[3]).toMatchObject({
+ expect(states.current[1]).toMatchObject({
status: 'success',
fetchStatus: 'fetching',
data: 'hydrated',
})
- expect(states[4]).toMatchObject({
+ expect(states.current[2]).toMatchObject({
status: 'success',
fetchStatus: 'idle',
data: 'fetched',
@@ -118,7 +101,7 @@ describe('PersistQueryClientProvider', () => {
})
test('should also put useQueries into idle state', async () => {
- const statesStore: Writable>> = writable([])
+ const states = new StatelessRef>>([])
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
@@ -136,7 +119,7 @@ describe('PersistQueryClientProvider', () => {
props: {
queryClient,
persistOptions: { persister },
- states: statesStore,
+ states,
},
})
@@ -144,35 +127,21 @@ describe('PersistQueryClientProvider', () => {
await waitFor(() => rendered.getByText('hydrated'))
await waitFor(() => rendered.getByText('fetched'))
- const states = get(statesStore)
+ expect(states.current).toHaveLength(3)
- expect(states).toHaveLength(5)
-
- expect(states[0]).toMatchObject({
+ expect(states.current[0]).toMatchObject({
status: 'pending',
fetchStatus: 'idle',
data: undefined,
})
- expect(states[1]).toMatchObject({
- status: 'success',
- fetchStatus: 'fetching',
- data: 'hydrated',
- })
-
- expect(states[2]).toMatchObject({
+ expect(states.current[1]).toMatchObject({
status: 'success',
fetchStatus: 'fetching',
data: 'hydrated',
})
- expect(states[3]).toMatchObject({
- status: 'success',
- fetchStatus: 'fetching',
- data: 'hydrated',
- })
-
- expect(states[4]).toMatchObject({
+ expect(states.current[2]).toMatchObject({
status: 'success',
fetchStatus: 'idle',
data: 'fetched',
@@ -180,7 +149,7 @@ describe('PersistQueryClientProvider', () => {
})
test('should show initialData while restoring', async () => {
- const statesStore: Writable>> = writable([])
+ const states = new StatelessRef>>([])
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
@@ -198,7 +167,7 @@ describe('PersistQueryClientProvider', () => {
props: {
queryClient,
persistOptions: { persister },
- states: statesStore,
+ states,
},
})
@@ -206,34 +175,21 @@ describe('PersistQueryClientProvider', () => {
await waitFor(() => rendered.getByText('hydrated'))
await waitFor(() => rendered.getByText('fetched'))
- const states = get(statesStore)
- expect(states).toHaveLength(5)
+ expect(states.current).toHaveLength(3)
- expect(states[0]).toMatchObject({
+ expect(states.current[0]).toMatchObject({
status: 'success',
fetchStatus: 'idle',
data: 'initial',
})
- expect(states[1]).toMatchObject({
- status: 'success',
- fetchStatus: 'fetching',
- data: 'hydrated',
- })
-
- expect(states[2]).toMatchObject({
+ expect(states.current[1]).toMatchObject({
status: 'success',
fetchStatus: 'fetching',
data: 'hydrated',
})
- expect(states[3]).toMatchObject({
- status: 'success',
- fetchStatus: 'fetching',
- data: 'hydrated',
- })
-
- expect(states[4]).toMatchObject({
+ expect(states.current[2]).toMatchObject({
status: 'success',
fetchStatus: 'idle',
data: 'fetched',
@@ -241,7 +197,7 @@ describe('PersistQueryClientProvider', () => {
})
test('should not refetch after restoring when data is fresh', async () => {
- const statesStore: Writable>> = writable([])
+ const states = new StatelessRef>>([])
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
@@ -255,32 +211,31 @@ describe('PersistQueryClientProvider', () => {
queryClient.clear()
- const fetched = writable(false)
-
const rendered = render(FreshData, {
props: {
queryClient,
persistOptions: { persister },
- states: statesStore,
- fetched,
+ states,
},
})
await waitFor(() => rendered.getByText('data: undefined'))
await waitFor(() => rendered.getByText('data: hydrated'))
+ await expect(
+ waitFor(() => rendered.getByText('data: fetched'), {
+ timeout: 100,
+ }),
+ ).rejects.toThrowError()
- const states = get(statesStore)
- expect(states).toHaveLength(2)
+ expect(states.current).toHaveLength(2)
- expect(get(fetched)).toBe(false)
-
- expect(states[0]).toMatchObject({
+ expect(states.current[0]).toMatchObject({
status: 'pending',
fetchStatus: 'idle',
data: undefined,
})
- expect(states[1]).toMatchObject({
+ expect(states.current[1]).toMatchObject({
status: 'success',
fetchStatus: 'idle',
data: 'hydrated',
@@ -311,7 +266,6 @@ describe('PersistQueryClientProvider', () => {
})
expect(onSuccess).toHaveBeenCalledTimes(0)
-
await waitFor(() => rendered.getByText('hydrated'))
expect(onSuccess).toHaveBeenCalledTimes(1)
await waitFor(() => rendered.getByText('fetched'))
@@ -330,17 +284,17 @@ describe('PersistQueryClientProvider', () => {
queryClient.clear()
- const statesStore: Writable> = writable([])
+ const states = new StatelessRef>([])
const rendered = render(AwaitOnSuccess, {
props: {
queryClient,
persistOptions: { persister },
- states: statesStore,
+ states,
onSuccess: async () => {
- statesStore.update((s) => [...s, 'onSuccess'])
- await sleep(20)
- statesStore.update((s) => [...s, 'onSuccess done'])
+ states.current.push('onSuccess')
+ await sleep(5)
+ states.current.push('onSuccess done')
},
},
})
@@ -348,9 +302,7 @@ describe('PersistQueryClientProvider', () => {
await waitFor(() => rendered.getByText('hydrated'))
await waitFor(() => rendered.getByText('fetched'))
- const states = get(statesStore)
-
- expect(states).toEqual([
+ expect(states.current).toEqual([
'onSuccess',
'onSuccess done',
'fetching',
@@ -359,11 +311,12 @@ describe('PersistQueryClientProvider', () => {
})
test('should remove cache after non-successful restoring', async () => {
- const consoleMock = vi.spyOn(console, 'error')
+ const consoleMock = vi
+ .spyOn(console, 'error')
+ .mockImplementation(() => undefined)
const consoleWarn = vi
.spyOn(console, 'warn')
.mockImplementation(() => undefined)
- consoleMock.mockImplementation(() => undefined)
const queryClient = new QueryClient()
const removeClient = vi.fn()
diff --git a/packages/svelte-query-persist-client/tests/RemoveCache/Provider.svelte b/packages/svelte-query-persist-client/tests/RemoveCache/Provider.svelte
index 7edd6cc903..c6410d912e 100644
--- a/packages/svelte-query-persist-client/tests/RemoveCache/Provider.svelte
+++ b/packages/svelte-query-persist-client/tests/RemoveCache/Provider.svelte
@@ -4,10 +4,14 @@
import type { OmitKeyof, QueryClient } from '@tanstack/svelte-query'
import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core'
- export let queryClient: QueryClient
- export let persistOptions: OmitKeyof
- export let onSuccess: () => void
- export let onError: () => void
+ interface Props {
+ queryClient: QueryClient
+ persistOptions: OmitKeyof
+ onSuccess: () => void
+ onError: () => void
+ }
+
+ let { queryClient, persistOptions, onError, onSuccess }: Props = $props()
import { createQuery } from '@tanstack/svelte-query'
- import { sleep } from '@tanstack/query-test-utils'
- const query = createQuery({
+ const query = createQuery(() => ({
queryKey: ['test'],
- queryFn: async () => {
- await sleep(5)
- return 'fetched'
- },
- })
+ queryFn: () => Promise.resolve('fetched'),
+ }))
-{$query.data}
-fetchStatus: {$query.fetchStatus}
+{query.data}
+fetchStatus: {query.fetchStatus}
diff --git a/packages/svelte-query-persist-client/tests/RestoreCache/Provider.svelte b/packages/svelte-query-persist-client/tests/RestoreCache/Provider.svelte
index 531aae8c3f..e89cdbafef 100644
--- a/packages/svelte-query-persist-client/tests/RestoreCache/Provider.svelte
+++ b/packages/svelte-query-persist-client/tests/RestoreCache/Provider.svelte
@@ -3,12 +3,15 @@
import RestoreCache from './RestoreCache.svelte'
import type { OmitKeyof, QueryClient } from '@tanstack/svelte-query'
import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core'
- import type { Writable } from 'svelte/store'
- import type { StatusResult } from '../utils.js'
+ import type { StatelessRef, StatusResult } from '../utils.svelte.js'
- export let queryClient: QueryClient
- export let persistOptions: OmitKeyof
- export let states: Writable>>
+ interface Props {
+ queryClient: QueryClient
+ persistOptions: OmitKeyof
+ states: StatelessRef>>
+ }
+
+ let { queryClient, persistOptions, states }: Props = $props()
diff --git a/packages/svelte-query-persist-client/tests/RestoreCache/RestoreCache.svelte b/packages/svelte-query-persist-client/tests/RestoreCache/RestoreCache.svelte
index de78c98bfd..79b9b6add5 100644
--- a/packages/svelte-query-persist-client/tests/RestoreCache/RestoreCache.svelte
+++ b/packages/svelte-query-persist-client/tests/RestoreCache/RestoreCache.svelte
@@ -1,21 +1,21 @@
-{$query.data}
-fetchStatus: {$query.fetchStatus}
+{query.data}
+fetchStatus: {query.fetchStatus}
diff --git a/packages/svelte-query-persist-client/tests/UseQueries/Provider.svelte b/packages/svelte-query-persist-client/tests/UseQueries/Provider.svelte
index 56429b7d81..b5a3857bf7 100644
--- a/packages/svelte-query-persist-client/tests/UseQueries/Provider.svelte
+++ b/packages/svelte-query-persist-client/tests/UseQueries/Provider.svelte
@@ -3,12 +3,15 @@
import UseQueries from './UseQueries.svelte'
import type { OmitKeyof, QueryClient } from '@tanstack/svelte-query'
import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core'
- import type { Writable } from 'svelte/store'
- import type { StatusResult } from '../utils.js'
+ import type { StatelessRef, StatusResult } from '../utils.svelte.js'
- export let queryClient: QueryClient
- export let persistOptions: OmitKeyof
- export let states: Writable>>
+ interface Props {
+ queryClient: QueryClient
+ persistOptions: OmitKeyof
+ states: StatelessRef>>
+ }
+
+ let { queryClient, persistOptions, states }: Props = $props()
diff --git a/packages/svelte-query-persist-client/tests/UseQueries/UseQueries.svelte b/packages/svelte-query-persist-client/tests/UseQueries/UseQueries.svelte
index 15b5ab46ff..4d646ac8cf 100644
--- a/packages/svelte-query-persist-client/tests/UseQueries/UseQueries.svelte
+++ b/packages/svelte-query-persist-client/tests/UseQueries/UseQueries.svelte
@@ -1,25 +1,25 @@
-{$queries[0].data}
-fetchStatus: {$queries[0].fetchStatus}
+{queries[0].data}
+fetchStatus: {queries[0].fetchStatus}
diff --git a/packages/svelte-query-persist-client/tests/utils.ts b/packages/svelte-query-persist-client/tests/utils.svelte.ts
similarity index 50%
rename from packages/svelte-query-persist-client/tests/utils.ts
rename to packages/svelte-query-persist-client/tests/utils.svelte.ts
index bf757da058..47162efdea 100644
--- a/packages/svelte-query-persist-client/tests/utils.ts
+++ b/packages/svelte-query-persist-client/tests/utils.svelte.ts
@@ -3,3 +3,10 @@ export type StatusResult = {
fetchStatus: string
data: T | undefined
}
+
+export class StatelessRef {
+ current: T
+ constructor(value: T) {
+ this.current = value
+ }
+}
diff --git a/packages/svelte-query-persist-client/vite.config.ts b/packages/svelte-query-persist-client/vite.config.ts
index 1249fcc5a8..fd7a429728 100644
--- a/packages/svelte-query-persist-client/vite.config.ts
+++ b/packages/svelte-query-persist-client/vite.config.ts
@@ -23,7 +23,6 @@ export default defineConfig({
watch: false,
environment: 'jsdom',
setupFiles: ['./tests/test-setup.ts'],
- coverage: { enabled: true, provider: 'istanbul', include: ['src/**/*'] },
typecheck: { enabled: true },
restoreMocks: true,
},
diff --git a/packages/svelte-query/eslint.config.js b/packages/svelte-query/eslint.config.js
index b657d69d66..e102c19ee9 100644
--- a/packages/svelte-query/eslint.config.js
+++ b/packages/svelte-query/eslint.config.js
@@ -1,17 +1,19 @@
// @ts-check
+import tsParser from '@typescript-eslint/parser'
import pluginSvelte from 'eslint-plugin-svelte'
import rootConfig from './root.eslint.config.js'
import svelteConfig from './svelte.config.js'
export default [
...rootConfig,
- ...pluginSvelte.configs['flat/recommended'],
+ ...pluginSvelte.configs['recommended'],
{
- files: ['**/*.svelte'],
+ files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
languageOptions: {
parserOptions: {
- parser: '@typescript-eslint/parser',
+ parser: tsParser,
+ extraFileExtensions: ['.svelte'],
svelteConfig,
},
},
diff --git a/packages/svelte-query/package.json b/packages/svelte-query/package.json
index 1a6d8b8755..e6b2027bbe 100644
--- a/packages/svelte-query/package.json
+++ b/packages/svelte-query/package.json
@@ -14,6 +14,11 @@
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
+ "keywords": [
+ "tanstack",
+ "query",
+ "svelte"
+ ],
"scripts": {
"clean": "premove ./dist ./coverage ./.svelte-kit ./dist-ts",
"compile": "tsc --build",
@@ -51,11 +56,12 @@
"@sveltejs/vite-plugin-svelte": "^5.1.1",
"@tanstack/query-test-utils": "workspace:*",
"@testing-library/svelte": "^5.2.8",
+ "@typescript-eslint/parser": "^8.44.1",
"eslint-plugin-svelte": "^3.11.0",
"svelte": "^5.39.3",
"svelte-check": "^4.3.1"
},
"peerDependencies": {
- "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0"
+ "svelte": "^5.25.0"
}
}
diff --git a/packages/svelte-query/src/HydrationBoundary.svelte b/packages/svelte-query/src/HydrationBoundary.svelte
index 330984311f..96c26068a2 100644
--- a/packages/svelte-query/src/HydrationBoundary.svelte
+++ b/packages/svelte-query/src/HydrationBoundary.svelte
@@ -1,16 +1,27 @@
-
+{@render children()}
diff --git a/packages/svelte-query/src/QueryClientProvider.svelte b/packages/svelte-query/src/QueryClientProvider.svelte
index 9f43e49093..36f1df43b1 100644
--- a/packages/svelte-query/src/QueryClientProvider.svelte
+++ b/packages/svelte-query/src/QueryClientProvider.svelte
@@ -2,8 +2,10 @@
import { onDestroy, onMount } from 'svelte'
import { QueryClient } from '@tanstack/query-core'
import { setQueryClientContext } from './context.js'
+ import type { QueryClientProviderProps } from './types.js'
- export let client = new QueryClient()
+ const { client = new QueryClient(), children }: QueryClientProviderProps =
+ $props()
onMount(() => {
client.mount()
@@ -16,4 +18,4 @@
})
-
+{@render children()}
diff --git a/packages/svelte-query/src/containers.svelte.ts b/packages/svelte-query/src/containers.svelte.ts
new file mode 100644
index 0000000000..60d27c6843
--- /dev/null
+++ b/packages/svelte-query/src/containers.svelte.ts
@@ -0,0 +1,123 @@
+import { SvelteSet, createSubscriber } from 'svelte/reactivity'
+
+type VoidFn = () => void
+type Subscriber = (update: VoidFn) => void | VoidFn
+
+export type Box = { current: T }
+
+export class ReactiveValue implements Box {
+ #fn
+ #subscribe
+
+ constructor(fn: () => T, onSubscribe: Subscriber) {
+ this.#fn = fn
+ this.#subscribe = createSubscriber((update) => onSubscribe(update))
+ }
+
+ get current() {
+ this.#subscribe()
+ return this.#fn()
+ }
+}
+
+/**
+ * Makes all of the top-level keys of an object into $state.raw fields whose initial values
+ * are the same as in the original object. Does not mutate the original object. Provides an `update`
+ * function that _can_ (but does not have to be) be used to replace all of the object's top-level keys
+ * with the values of the new object, while maintaining the original root object's reference.
+ */
+export function createRawRef>(
+ init: T,
+): [T, (newValue: T) => void] {
+ const refObj = (Array.isArray(init) ? [] : {}) as T
+ const hiddenKeys = new SvelteSet()
+ const out = new Proxy(refObj, {
+ 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
+ },
+ has: (target, prop) => {
+ if (hiddenKeys.has(prop)) {
+ return false
+ }
+ return prop in target
+ },
+ ownKeys(target) {
+ return Reflect.ownKeys(target).filter((key) => !hiddenKeys.has(key))
+ },
+ getOwnPropertyDescriptor(target, prop) {
+ if (hiddenKeys.has(prop)) {
+ return undefined
+ }
+ return Reflect.getOwnPropertyDescriptor(target, prop)
+ },
+ deleteProperty(target, prop) {
+ if (prop in target) {
+ // @ts-expect-error
+ // We need to set the value to undefined to signal to the listeners that the value has changed.
+ // If we just deleted it, the reactivity system wouldn't have any idea that the value was gone.
+ target[prop] = undefined
+ hiddenKeys.add(prop)
+ if (Array.isArray(target)) {
+ target.length--
+ }
+ return true
+ }
+ return false
+ },
+ })
+
+ 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])
+ }
+ }
+
+ // we can't pass `init` directly into the proxy because it'll never set the state fields
+ // (because (prop in target) will always be true)
+ update(init)
+
+ return [out, update]
+}
+
+const lazyBrand = Symbol('LazyValue')
+type Branded unknown> = T & { [lazyBrand]: true }
+
+function brand unknown>(fn: T): Branded {
+ // @ts-expect-error
+ fn[lazyBrand] = true
+ return fn as Branded
+}
+
+function isBranded unknown>(fn: T): fn is Branded {
+ return Boolean((fn as Branded)[lazyBrand])
+}
diff --git a/packages/svelte-query/src/context.ts b/packages/svelte-query/src/context.ts
index 962451b232..27595517f5 100644
--- a/packages/svelte-query/src/context.ts
+++ b/packages/svelte-query/src/context.ts
@@ -1,20 +1,19 @@
import { getContext, setContext } from 'svelte'
-import { readable } from 'svelte/store'
import type { QueryClient } from '@tanstack/query-core'
-import type { Readable } from 'svelte/store'
+import type { Box } from './containers.svelte'
-const _contextKey = '$$_queryClient'
+const _contextKey = Symbol('QueryClient')
/** Retrieves a Client from Svelte's context */
export const getQueryClientContext = (): QueryClient => {
- const client = getContext(_contextKey)
+ const client = getContext(_contextKey)
if (!client) {
throw new Error(
'No QueryClient was found in Svelte context. Did you forget to wrap your component with QueryClientProvider?',
)
}
- return client as QueryClient
+ return client
}
/** Sets a QueryClient on Svelte's context */
@@ -22,21 +21,21 @@ export const setQueryClientContext = (client: QueryClient): void => {
setContext(_contextKey, client)
}
-const _isRestoringContextKey = '$$_isRestoring'
+const _isRestoringContextKey = Symbol('isRestoring')
/** Retrieves a `isRestoring` from Svelte's context */
-export const getIsRestoringContext = (): Readable => {
+export const getIsRestoringContext = (): Box => {
try {
- const isRestoring = getContext | undefined>(
+ const isRestoring = getContext | undefined>(
_isRestoringContextKey,
)
- return isRestoring ? isRestoring : readable(false)
+ return isRestoring ?? { current: false }
} catch (error) {
- return readable(false)
+ return { current: false }
}
}
/** Sets a `isRestoring` on Svelte's context */
-export const setIsRestoringContext = (isRestoring: Readable): void => {
+export const setIsRestoringContext = (isRestoring: Box): void => {
setContext(_isRestoringContextKey, isRestoring)
}
diff --git a/packages/svelte-query/src/createBaseQuery.svelte.ts b/packages/svelte-query/src/createBaseQuery.svelte.ts
new file mode 100644
index 0000000000..03fc6b28db
--- /dev/null
+++ b/packages/svelte-query/src/createBaseQuery.svelte.ts
@@ -0,0 +1,107 @@
+import { useIsRestoring } from './useIsRestoring.js'
+import { useQueryClient } from './useQueryClient.js'
+import { createRawRef } from './containers.svelte.js'
+import { watchChanges } from './utils.svelte.js'
+import type { QueryClient, QueryKey, QueryObserver } from '@tanstack/query-core'
+import type {
+ Accessor,
+ CreateBaseQueryOptions,
+ CreateBaseQueryResult,
+} from './types.js'
+
+/**
+ * Base implementation for `createQuery` and `createInfiniteQuery`
+ * @param options - A function that returns query options
+ * @param Observer - The observer from query-core
+ * @param queryClient - Custom query client which overrides provider
+ */
+export function createBaseQuery<
+ TQueryFnData,
+ TError,
+ TData,
+ TQueryData,
+ TQueryKey extends QueryKey,
+>(
+ options: Accessor<
+ CreateBaseQueryOptions
+ >,
+ Observer: typeof QueryObserver,
+ queryClient?: Accessor,
+): CreateBaseQueryResult {
+ /** Load query client */
+ const client = $derived(useQueryClient(queryClient?.()))
+ const isRestoring = useIsRestoring()
+
+ const resolvedOptions = $derived.by(() => {
+ const opts = client.defaultQueryOptions(options())
+ opts._optimisticResults = isRestoring.current ? 'isRestoring' : 'optimistic'
+ return opts
+ })
+
+ /** Creates the observer */
+ // svelte-ignore state_referenced_locally - intentional, initial value
+ let observer = $state(
+ new Observer(
+ client,
+ resolvedOptions,
+ ),
+ )
+ watchChanges(
+ () => client,
+ 'pre',
+ () => {
+ observer = new Observer<
+ TQueryFnData,
+ TError,
+ TData,
+ TQueryData,
+ TQueryKey
+ >(client, resolvedOptions)
+ },
+ )
+
+ function createResult() {
+ const result = observer.getOptimisticResult(resolvedOptions)
+ return !resolvedOptions.notifyOnChangeProps
+ ? observer.trackResult(result)
+ : result
+ }
+ const [query, update] = createRawRef(
+ // svelte-ignore state_referenced_locally - intentional, initial value
+ createResult(),
+ )
+
+ $effect(() => {
+ const unsubscribe = isRestoring.current
+ ? () => undefined
+ : observer.subscribe(() => update(createResult()))
+ observer.updateResult()
+ return unsubscribe
+ })
+
+ watchChanges(
+ () => resolvedOptions,
+ 'pre',
+ () => {
+ observer.setOptions(resolvedOptions)
+ },
+ )
+ watchChanges(
+ () => [resolvedOptions, observer],
+ 'pre',
+ () => {
+ // The only reason this is necessary is because of `isRestoring`.
+ // Because we don't subscribe while restoring, the following can occur:
+ // - `isRestoring` is true
+ // - `isRestoring` becomes false
+ // - `observer.subscribe` and `observer.updateResult` is called in the above effect,
+ // but the subsequent `fetch` has already completed
+ // - `result` misses the intermediate restored-but-not-fetched state
+ //
+ // this could technically be its own effect but that doesn't seem necessary
+ update(createResult())
+ },
+ )
+
+ return query
+}
diff --git a/packages/svelte-query/src/createBaseQuery.ts b/packages/svelte-query/src/createBaseQuery.ts
deleted file mode 100644
index df45764bca..0000000000
--- a/packages/svelte-query/src/createBaseQuery.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import { derived, get, readable } from 'svelte/store'
-import { noop, notifyManager } from '@tanstack/query-core'
-import { useIsRestoring } from './useIsRestoring.js'
-import { useQueryClient } from './useQueryClient.js'
-import { isSvelteStore } from './utils.js'
-import type {
- QueryClient,
- QueryKey,
- QueryObserver,
- QueryObserverResult,
-} from '@tanstack/query-core'
-import type {
- CreateBaseQueryOptions,
- CreateBaseQueryResult,
- StoreOrVal,
-} from './types.js'
-
-export function createBaseQuery<
- TQueryFnData,
- TError,
- TData,
- TQueryData,
- TQueryKey extends QueryKey,
->(
- options: StoreOrVal<
- CreateBaseQueryOptions
- >,
- Observer: typeof QueryObserver,
- queryClient?: QueryClient,
-): CreateBaseQueryResult {
- /** Load query client */
- const client = useQueryClient(queryClient)
- const isRestoring = useIsRestoring()
- /** Converts options to a svelte store if not already a store object */
- const optionsStore = isSvelteStore(options) ? options : readable(options)
-
- /** Creates a store that has the default options applied */
- const defaultedOptionsStore = derived(
- [optionsStore, isRestoring],
- ([$optionsStore, $isRestoring]) => {
- const defaultedOptions = client.defaultQueryOptions($optionsStore)
- defaultedOptions._optimisticResults = $isRestoring
- ? 'isRestoring'
- : 'optimistic'
- return defaultedOptions
- },
- )
-
- /** Creates the observer */
- const observer = new Observer<
- TQueryFnData,
- TError,
- TData,
- TQueryData,
- TQueryKey
- >(client, get(defaultedOptionsStore))
-
- defaultedOptionsStore.subscribe(($defaultedOptions) => {
- observer.setOptions($defaultedOptions)
- })
-
- const result = derived<
- typeof isRestoring,
- QueryObserverResult
- >(isRestoring, ($isRestoring, set) => {
- const unsubscribe = $isRestoring
- ? noop
- : observer.subscribe(notifyManager.batchCalls(set))
- observer.updateResult()
- return unsubscribe
- })
-
- /** Subscribe to changes in result and defaultedOptionsStore */
- const { subscribe } = derived(
- [result, defaultedOptionsStore],
- ([$result, $defaultedOptionsStore]) => {
- $result = observer.getOptimisticResult($defaultedOptionsStore)
- return !$defaultedOptionsStore.notifyOnChangeProps
- ? observer.trackResult($result)
- : $result
- },
- )
-
- return { subscribe }
-}
diff --git a/packages/svelte-query/src/createInfiniteQuery.ts b/packages/svelte-query/src/createInfiniteQuery.ts
index 5ebb82213a..e8fe794890 100644
--- a/packages/svelte-query/src/createInfiniteQuery.ts
+++ b/packages/svelte-query/src/createInfiniteQuery.ts
@@ -1,5 +1,5 @@
import { InfiniteQueryObserver } from '@tanstack/query-core'
-import { createBaseQuery } from './createBaseQuery.js'
+import { createBaseQuery } from './createBaseQuery.svelte.js'
import type {
DefaultError,
InfiniteData,
@@ -8,9 +8,9 @@ import type {
QueryObserver,
} from '@tanstack/query-core'
import type {
+ Accessor,
CreateInfiniteQueryOptions,
CreateInfiniteQueryResult,
- StoreOrVal,
} from './types.js'
export function createInfiniteQuery<
@@ -20,7 +20,7 @@ export function createInfiniteQuery<
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
>(
- options: StoreOrVal<
+ options: Accessor<
CreateInfiniteQueryOptions<
TQueryFnData,
TError,
@@ -29,7 +29,7 @@ export function createInfiniteQuery<
TPageParam
>
>,
- queryClient?: QueryClient,
+ queryClient?: Accessor,
): CreateInfiniteQueryResult {
return createBaseQuery(
options,
diff --git a/packages/svelte-query/src/createMutation.svelte.ts b/packages/svelte-query/src/createMutation.svelte.ts
new file mode 100644
index 0000000000..51ff74827a
--- /dev/null
+++ b/packages/svelte-query/src/createMutation.svelte.ts
@@ -0,0 +1,91 @@
+import { MutationObserver, noop, notifyManager } from '@tanstack/query-core'
+import { useQueryClient } from './useQueryClient.js'
+import { watchChanges } from './utils.svelte.js'
+import type {
+ Accessor,
+ CreateMutateFunction,
+ CreateMutationOptions,
+ CreateMutationResult,
+} from './types.js'
+
+import type { DefaultError, QueryClient } from '@tanstack/query-core'
+
+/**
+ * @param options - A function that returns mutation options
+ * @param queryClient - Custom query client which overrides provider
+ */
+export function createMutation<
+ TData = unknown,
+ TError = DefaultError,
+ TVariables = void,
+ TContext = unknown,
+>(
+ options: Accessor>,
+ queryClient?: Accessor,
+): CreateMutationResult {
+ const client = $derived(useQueryClient(queryClient?.()))
+
+ // svelte-ignore state_referenced_locally - intentional, initial value
+ let observer = $state(
+ // svelte-ignore state_referenced_locally - intentional, initial value
+ new MutationObserver(
+ client,
+ options(),
+ ),
+ )
+
+ watchChanges(
+ () => client,
+ 'pre',
+ () => {
+ observer = new MutationObserver(client, options())
+ },
+ )
+
+ $effect.pre(() => {
+ observer.setOptions(options())
+ })
+
+ const mutate = >((
+ variables,
+ mutateOptions,
+ ) => {
+ observer.mutate(variables, mutateOptions).catch(noop)
+ })
+
+ let result = $state(observer.getCurrentResult())
+ watchChanges(
+ () => observer,
+ 'pre',
+ () => {
+ result = observer.getCurrentResult()
+ },
+ )
+
+ $effect.pre(() => {
+ const unsubscribe = observer.subscribe((val) => {
+ notifyManager.batchCalls(() => {
+ Object.assign(result, val)
+ })()
+ })
+ return unsubscribe
+ })
+
+ const resultProxy = $derived(
+ new Proxy(result, {
+ get: (_, prop) => {
+ const r = {
+ ...result,
+ mutate,
+ mutateAsync: result.mutate,
+ }
+ if (prop == 'value') return r
+ // @ts-expect-error
+ return r[prop]
+ },
+ }),
+ )
+
+ // @ts-expect-error
+ return resultProxy
+}
diff --git a/packages/svelte-query/src/createMutation.ts b/packages/svelte-query/src/createMutation.ts
deleted file mode 100644
index 698ea75ac7..0000000000
--- a/packages/svelte-query/src/createMutation.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { derived, get, readable } from 'svelte/store'
-import { MutationObserver, noop, notifyManager } from '@tanstack/query-core'
-import { useQueryClient } from './useQueryClient.js'
-import { isSvelteStore } from './utils.js'
-import type {
- CreateMutateFunction,
- CreateMutationOptions,
- CreateMutationResult,
- StoreOrVal,
-} from './types.js'
-import type { DefaultError, QueryClient } from '@tanstack/query-core'
-
-export function createMutation<
- TData = unknown,
- TError = DefaultError,
- TVariables = void,
- TOnMutateResult = unknown,
->(
- options: StoreOrVal<
- CreateMutationOptions
- >,
- queryClient?: QueryClient,
-): CreateMutationResult {
- const client = useQueryClient(queryClient)
-
- const optionsStore = isSvelteStore(options) ? options : readable(options)
-
- const observer = new MutationObserver<
- TData,
- TError,
- TVariables,
- TOnMutateResult
- >(client, get(optionsStore))
- let mutate: CreateMutateFunction
-
- optionsStore.subscribe(($options) => {
- mutate = (variables, mutateOptions) => {
- observer.mutate(variables, mutateOptions).catch(noop)
- }
- observer.setOptions($options)
- })
-
- const result = readable(observer.getCurrentResult(), (set) => {
- return observer.subscribe(notifyManager.batchCalls((val) => set(val)))
- })
-
- const { subscribe } = derived(result, ($result) => ({
- ...$result,
- mutate,
- mutateAsync: $result.mutate,
- }))
-
- return { subscribe }
-}
diff --git a/packages/svelte-query/src/createQueries.ts b/packages/svelte-query/src/createQueries.svelte.ts
similarity index 59%
rename from packages/svelte-query/src/createQueries.ts
rename to packages/svelte-query/src/createQueries.svelte.ts
index 25e9731c10..dec5756129 100644
--- a/packages/svelte-query/src/createQueries.ts
+++ b/packages/svelte-query/src/createQueries.svelte.ts
@@ -1,33 +1,33 @@
-import { QueriesObserver, noop, notifyManager } from '@tanstack/query-core'
-import { derived, get, readable } from 'svelte/store'
+import { QueriesObserver } from '@tanstack/query-core'
import { useIsRestoring } from './useIsRestoring.js'
+import { createRawRef } from './containers.svelte.js'
import { useQueryClient } from './useQueryClient.js'
-import { isSvelteStore } from './utils.js'
-import type { Readable } from 'svelte/store'
-import type { StoreOrVal } from './types.js'
+import type {
+ Accessor,
+ CreateQueryOptions,
+ CreateQueryResult,
+ DefinedCreateQueryResult,
+} from './types.js'
import type {
DefaultError,
- DefinedQueryObserverResult,
OmitKeyof,
QueriesObserverOptions,
QueriesPlaceholderDataFunction,
QueryClient,
QueryFunction,
QueryKey,
- QueryObserverOptions,
- QueryObserverResult,
ThrowOnError,
} from '@tanstack/query-core'
// This defines the `CreateQueryOptions` that are accepted in `QueriesOptions` & `GetOptions`.
// `placeholderData` function always gets undefined passed
-type QueryObserverOptionsForCreateQueries<
+type CreateQueryOptionsForCreateQueries<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> = OmitKeyof<
- QueryObserverOptions,
+ CreateQueryOptions,
'placeholderData'
> & {
placeholderData?: TQueryFnData | QueriesPlaceholderDataFunction
@@ -37,60 +37,60 @@ type QueryObserverOptionsForCreateQueries<
type MAXIMUM_DEPTH = 20
// Widen the type of the symbol to enable type inference even if skipToken is not immutable.
-type SkipTokenForUseQueries = symbol
+type SkipTokenForCreateQueries = symbol
-type GetQueryObserverOptionsForCreateQueries =
+type GetCreateQueryOptionsForCreateQueries =
// Part 1: responsible for applying explicit type parameter to function arguments, if object { queryFnData: TQueryFnData, error: TError, data: TData }
T extends {
queryFnData: infer TQueryFnData
error?: infer TError
data: infer TData
}
- ? QueryObserverOptionsForCreateQueries
+ ? CreateQueryOptionsForCreateQueries
: T extends { queryFnData: infer TQueryFnData; error?: infer TError }
- ? QueryObserverOptionsForCreateQueries
+ ? CreateQueryOptionsForCreateQueries
: T extends { data: infer TData; error?: infer TError }
- ? QueryObserverOptionsForCreateQueries
+ ? CreateQueryOptionsForCreateQueries
: // Part 2: responsible for applying explicit type parameter to function arguments, if tuple [TQueryFnData, TError, TData]
T extends [infer TQueryFnData, infer TError, infer TData]
- ? QueryObserverOptionsForCreateQueries
+ ? CreateQueryOptionsForCreateQueries
: T extends [infer TQueryFnData, infer TError]
- ? QueryObserverOptionsForCreateQueries
+ ? CreateQueryOptionsForCreateQueries
: T extends [infer TQueryFnData]
- ? QueryObserverOptionsForCreateQueries
+ ? CreateQueryOptionsForCreateQueries
: // Part 3: responsible for inferring and enforcing type if no explicit parameter was provided
T extends {
queryFn?:
| QueryFunction
- | SkipTokenForUseQueries
+ | SkipTokenForCreateQueries
select?: (data: any) => infer TData
throwOnError?: ThrowOnError
}
- ? QueryObserverOptionsForCreateQueries<
+ ? CreateQueryOptionsForCreateQueries<
TQueryFnData,
unknown extends TError ? DefaultError : TError,
unknown extends TData ? TQueryFnData : TData,
TQueryKey
>
: // Fallback
- QueryObserverOptionsForCreateQueries
+ CreateQueryOptionsForCreateQueries
-// A defined initialData setting should return a DefinedQueryObserverResult rather than CreateQueryResult
+// A defined initialData setting should return a DefinedCreateQueryResult rather than CreateQueryResult
type GetDefinedOrUndefinedQueryResult = T extends {
initialData?: infer TInitialData
}
? unknown extends TInitialData
- ? QueryObserverResult
+ ? CreateQueryResult
: TInitialData extends TData
- ? DefinedQueryObserverResult
+ ? DefinedCreateQueryResult
: TInitialData extends () => infer TInitialDataResult
? unknown extends TInitialDataResult
- ? QueryObserverResult
+ ? CreateQueryResult
: TInitialDataResult extends TData
- ? DefinedQueryObserverResult
- : QueryObserverResult
- : QueryObserverResult
- : QueryObserverResult
+ ? DefinedCreateQueryResult
+ : CreateQueryResult
+ : CreateQueryResult
+ : CreateQueryResult
type GetCreateQueryResult =
// Part 1: responsible for mapping explicit type parameter to function result, if object
@@ -111,7 +111,7 @@ type GetCreateQueryResult =
T extends {
queryFn?:
| QueryFunction
- | SkipTokenForUseQueries
+ | SkipTokenForCreateQueries
select?: (data: any) => infer TData
throwOnError?: ThrowOnError
}
@@ -121,7 +121,7 @@ type GetCreateQueryResult =
unknown extends TError ? DefaultError : TError
>
: // Fallback
- QueryObserverResult
+ CreateQueryResult
/**
* QueriesOptions reducer recursively unwraps function arguments to infer/enforce type param
@@ -131,15 +131,15 @@ export type QueriesOptions<
TResults extends Array = [],
TDepth extends ReadonlyArray = [],
> = TDepth['length'] extends MAXIMUM_DEPTH
- ? Array
+ ? Array
: T extends []
? []
: T extends [infer Head]
- ? [...TResults, GetQueryObserverOptionsForCreateQueries]
+ ? [...TResults, GetCreateQueryOptionsForCreateQueries]
: T extends [infer Head, ...infer Tails]
? QueriesOptions<
[...Tails],
- [...TResults, GetQueryObserverOptionsForCreateQueries],
+ [...TResults, GetCreateQueryOptionsForCreateQueries],
[...TDepth, 1]
>
: ReadonlyArray extends T
@@ -147,7 +147,7 @@ export type QueriesOptions<
: // If T is *some* array but we couldn't assign unknown[] to it, then it must hold some known/homogenous type!
// use this to infer the param types in the case of Array.map() argument
T extends Array<
- QueryObserverOptionsForCreateQueries<
+ CreateQueryOptionsForCreateQueries<
infer TQueryFnData,
infer TError,
infer TData,
@@ -155,7 +155,7 @@ export type QueriesOptions<
>
>
? Array<
- QueryObserverOptionsForCreateQueries<
+ CreateQueryOptionsForCreateQueries<
TQueryFnData,
TError,
TData,
@@ -163,7 +163,7 @@ export type QueriesOptions<
>
>
: // Fallback
- Array
+ Array
/**
* QueriesResults reducer recursively maps type param to results
@@ -173,7 +173,7 @@ export type QueriesResults<
TResults extends Array = [],
TDepth extends ReadonlyArray = [],
> = TDepth['length'] extends MAXIMUM_DEPTH
- ? Array
+ ? Array
: T extends []
? []
: T extends [infer Head]
@@ -190,75 +190,65 @@ export function createQueries<
T extends Array,
TCombinedResult = QueriesResults,
>(
- {
- queries,
- ...options
- }: {
+ createQueriesOptions: Accessor<{
queries:
- | StoreOrVal<[...QueriesOptions]>
- | StoreOrVal<
- [...{ [K in keyof T]: GetQueryObserverOptionsForCreateQueries }]
- >
+ | readonly [...QueriesOptions]
+ | readonly [
+ ...{ [K in keyof T]: GetCreateQueryOptionsForCreateQueries },
+ ]
combine?: (result: QueriesResults) => TCombinedResult
- },
- queryClient?: QueryClient,
-): Readable {
- const client = useQueryClient(queryClient)
+ }>,
+ queryClient?: Accessor,
+): TCombinedResult {
+ const client = $derived(useQueryClient(queryClient?.()))
const isRestoring = useIsRestoring()
- const queriesStore = isSvelteStore(queries) ? queries : readable(queries)
-
- const defaultedQueriesStore = derived(
- [queriesStore, isRestoring],
- ([$queries, $isRestoring]) => {
- return $queries.map((opts) => {
- const defaultedOptions = client.defaultQueryOptions(
- opts as QueryObserverOptions,
- )
- // Make sure the results are already in fetching state before subscribing or updating options
- defaultedOptions._optimisticResults = $isRestoring
- ? 'isRestoring'
- : 'optimistic'
- return defaultedOptions
- })
- },
+ const { queries, combine } = $derived.by(createQueriesOptions)
+ const resolvedQueryOptions = $derived(
+ queries.map((opts) => {
+ const resolvedOptions = client.defaultQueryOptions(opts)
+ // Make sure the results are already in fetching state before subscribing or updating options
+ resolvedOptions._optimisticResults = isRestoring.current
+ ? 'isRestoring'
+ : 'optimistic'
+ return resolvedOptions
+ }),
)
- const observer = new QueriesObserver(
- client,
- get(defaultedQueriesStore),
- options as QueriesObserverOptions,
+
+ // can't do same as createMutation, as QueriesObserver has no `setOptions` method
+ const observer = $derived(
+ new QueriesObserver(
+ client,
+ resolvedQueryOptions,
+ combine as QueriesObserverOptions,
+ ),
)
- defaultedQueriesStore.subscribe(($defaultedQueries) => {
- // Do not notify on updates because of changes in the options because
- // these changes should already be reflected in the optimistic result.
- observer.setQueries(
- $defaultedQueries,
- options as QueriesObserverOptions,
+ function createResult() {
+ const [_, getCombinedResult, trackResult] = observer.getOptimisticResult(
+ resolvedQueryOptions,
+ combine as QueriesObserverOptions['combine'],
)
- })
+ return getCombinedResult(trackResult())
+ }
- const result = derived([isRestoring], ([$isRestoring], set) => {
- const unsubscribe = $isRestoring
- ? noop
- : observer.subscribe(notifyManager.batchCalls(set))
+ // @ts-expect-error - the crazy-complex TCombinedResult type doesn't like being called an array
+ // svelte-ignore state_referenced_locally
+ const [results, update] = createRawRef(createResult())
- return () => unsubscribe()
+ $effect(() => {
+ const unsubscribe = isRestoring.current
+ ? () => undefined
+ : observer.subscribe(() => update(createResult()))
+ return unsubscribe
})
- const { subscribe } = derived(
- [result, defaultedQueriesStore],
- // @ts-expect-error svelte-check thinks this is unused
- ([$result, $defaultedQueriesStore]) => {
- const [rawResult, combineResult, trackResult] =
- observer.getOptimisticResult(
- $defaultedQueriesStore,
- (options as QueriesObserverOptions).combine,
- )
- $result = rawResult
- return combineResult(trackResult())
- },
- )
+ $effect.pre(() => {
+ observer.setQueries(resolvedQueryOptions, {
+ combine,
+ } as QueriesObserverOptions)
+ update(createResult())
+ })
- return { subscribe }
+ return results
}
diff --git a/packages/svelte-query/src/createQuery.ts b/packages/svelte-query/src/createQuery.ts
index 51a43c274c..bf7efe81a7 100644
--- a/packages/svelte-query/src/createQuery.ts
+++ b/packages/svelte-query/src/createQuery.ts
@@ -1,11 +1,11 @@
import { QueryObserver } from '@tanstack/query-core'
-import { createBaseQuery } from './createBaseQuery.js'
+import { createBaseQuery } from './createBaseQuery.svelte.js'
import type { DefaultError, QueryClient, QueryKey } from '@tanstack/query-core'
import type {
+ Accessor,
CreateQueryOptions,
CreateQueryResult,
DefinedCreateQueryResult,
- StoreOrVal,
} from './types.js'
import type {
DefinedInitialDataOptions,
@@ -18,11 +18,11 @@ export function createQuery<
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
- options: StoreOrVal<
- DefinedInitialDataOptions
+ options: Accessor<
+ UndefinedInitialDataOptions
>,
- queryClient?: QueryClient,
-): DefinedCreateQueryResult
+ queryClient?: Accessor,
+): CreateQueryResult
export function createQuery<
TQueryFnData = unknown,
@@ -30,27 +30,25 @@ export function createQuery<
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
- options: StoreOrVal<
- UndefinedInitialDataOptions
+ options: Accessor<
+ DefinedInitialDataOptions
>,
- queryClient?: QueryClient,
-): CreateQueryResult
+ queryClient?: Accessor,
+): DefinedCreateQueryResult
export function createQuery<
- TQueryFnData = unknown,
+ TQueryFnData,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
- options: StoreOrVal<
- CreateQueryOptions
- >,
- queryClient?: QueryClient,
+ options: Accessor>,
+ queryClient?: Accessor,
): CreateQueryResult
export function createQuery(
- options: StoreOrVal,
- queryClient?: QueryClient,
+ options: Accessor,
+ queryClient?: Accessor,
) {
return createBaseQuery(options, QueryObserver, queryClient)
}
diff --git a/packages/svelte-query/src/index.ts b/packages/svelte-query/src/index.ts
index 735cd89f59..1b74a9be91 100644
--- a/packages/svelte-query/src/index.ts
+++ b/packages/svelte-query/src/index.ts
@@ -8,20 +8,20 @@ export * from './types.js'
export * from './context.js'
export { createQuery } from './createQuery.js'
-export type { QueriesResults, QueriesOptions } from './createQueries.js'
+export type { QueriesResults, QueriesOptions } from './createQueries.svelte.js'
export type {
DefinedInitialDataOptions,
UndefinedInitialDataOptions,
} from './queryOptions.js'
export { queryOptions } from './queryOptions.js'
-export { createQueries } from './createQueries.js'
+export { createQueries } from './createQueries.svelte.js'
export { createInfiniteQuery } from './createInfiniteQuery.js'
export { infiniteQueryOptions } from './infiniteQueryOptions.js'
-export { createMutation } from './createMutation.js'
-export { useMutationState } from './useMutationState.js'
+export { createMutation } from './createMutation.svelte.js'
+export { useMutationState } from './useMutationState.svelte.js'
export { useQueryClient } from './useQueryClient.js'
-export { useIsFetching } from './useIsFetching.js'
-export { useIsMutating } from './useIsMutating.js'
+export { useIsFetching } from './useIsFetching.svelte.js'
+export { useIsMutating } from './useIsMutating.svelte.js'
export { useIsRestoring } from './useIsRestoring.js'
export { useHydrate } from './useHydrate.js'
export { default as HydrationBoundary } from './HydrationBoundary.svelte'
diff --git a/packages/svelte-query/src/types.ts b/packages/svelte-query/src/types.ts
index 7a25a15bce..9a64d10dc3 100644
--- a/packages/svelte-query/src/types.ts
+++ b/packages/svelte-query/src/types.ts
@@ -1,3 +1,4 @@
+import type { Snippet } from 'svelte'
import type {
DefaultError,
DefinedQueryObserverResult,
@@ -11,14 +12,13 @@ import type {
MutationState,
OmitKeyof,
Override,
+ QueryClient,
QueryKey,
QueryObserverOptions,
QueryObserverResult,
} from '@tanstack/query-core'
-import type { Readable } from 'svelte/store'
-/** Allows a type to be either the base object or a store of that object */
-export type StoreOrVal = T | Readable
+export type Accessor = () => T
/** Options for createBaseQuery */
export type CreateBaseQueryOptions<
@@ -33,7 +33,7 @@ export type CreateBaseQueryOptions<
export type CreateBaseQueryResult<
TData = unknown,
TError = DefaultError,
-> = Readable>
+> = QueryObserverResult
/** Options for createQuery */
export type CreateQueryOptions<
@@ -68,13 +68,13 @@ export type CreateInfiniteQueryOptions<
export type CreateInfiniteQueryResult<
TData = unknown,
TError = DefaultError,
-> = Readable>
+> = InfiniteQueryObserverResult
/** Options for createBaseQuery with initialData */
export type DefinedCreateBaseQueryResult<
TData = unknown,
TError = DefaultError,
-> = Readable>
+> = DefinedQueryObserverResult
/** Options for createQuery with initialData */
export type DefinedCreateQueryResult<
@@ -134,9 +134,7 @@ export type CreateMutationResult<
TError = DefaultError,
TVariables = unknown,
TOnMutateResult = unknown,
-> = Readable<
- CreateBaseMutationResult
->
+> = CreateBaseMutationResult
/** Options for useMutationState */
export type MutationStateOptions = {
@@ -145,3 +143,8 @@ export type MutationStateOptions = {
mutation: Mutation,
) => TResult
}
+
+export type QueryClientProviderProps = {
+ client: QueryClient
+ children: Snippet
+}
diff --git a/packages/svelte-query/src/useIsFetching.svelte.ts b/packages/svelte-query/src/useIsFetching.svelte.ts
new file mode 100644
index 0000000000..0b8c47e3fd
--- /dev/null
+++ b/packages/svelte-query/src/useIsFetching.svelte.ts
@@ -0,0 +1,16 @@
+import { ReactiveValue } from './containers.svelte.js'
+import { useQueryClient } from './useQueryClient.js'
+import type { QueryClient, QueryFilters } from '@tanstack/query-core'
+
+export function useIsFetching(
+ filters?: QueryFilters,
+ queryClient?: QueryClient,
+): ReactiveValue {
+ const client = useQueryClient(queryClient)
+ const queryCache = client.getQueryCache()
+
+ return new ReactiveValue(
+ () => client.isFetching(filters),
+ (update) => queryCache.subscribe(update),
+ )
+}
diff --git a/packages/svelte-query/src/useIsFetching.ts b/packages/svelte-query/src/useIsFetching.ts
deleted file mode 100644
index e784896192..0000000000
--- a/packages/svelte-query/src/useIsFetching.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { notifyManager } from '@tanstack/query-core'
-import { readable } from 'svelte/store'
-import { useQueryClient } from './useQueryClient.js'
-import type { Readable } from 'svelte/store'
-import type { QueryClient, QueryFilters } from '@tanstack/query-core'
-
-export function useIsFetching(
- filters?: QueryFilters,
- queryClient?: QueryClient,
-): Readable {
- const client = useQueryClient(queryClient)
- const cache = client.getQueryCache()
- // isFetching is the prev value initialized on mount *
- let isFetching = client.isFetching(filters)
-
- const { subscribe } = readable(isFetching, (set) => {
- return cache.subscribe(
- notifyManager.batchCalls(() => {
- const newIsFetching = client.isFetching(filters)
- if (isFetching !== newIsFetching) {
- // * and update with each change
- isFetching = newIsFetching
- set(isFetching)
- }
- }),
- )
- })
-
- return { subscribe }
-}
diff --git a/packages/svelte-query/src/useIsMutating.svelte.ts b/packages/svelte-query/src/useIsMutating.svelte.ts
new file mode 100644
index 0000000000..21ac56e7a8
--- /dev/null
+++ b/packages/svelte-query/src/useIsMutating.svelte.ts
@@ -0,0 +1,16 @@
+import { useQueryClient } from './useQueryClient.js'
+import { ReactiveValue } from './containers.svelte.js'
+import type { MutationFilters, QueryClient } from '@tanstack/query-core'
+
+export function useIsMutating(
+ filters?: MutationFilters,
+ queryClient?: QueryClient,
+): ReactiveValue {
+ const client = useQueryClient(queryClient)
+ const cache = client.getMutationCache()
+
+ return new ReactiveValue(
+ () => client.isMutating(filters),
+ (update) => cache.subscribe(update),
+ )
+}
diff --git a/packages/svelte-query/src/useIsMutating.ts b/packages/svelte-query/src/useIsMutating.ts
deleted file mode 100644
index 258e8fe98c..0000000000
--- a/packages/svelte-query/src/useIsMutating.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { notifyManager } from '@tanstack/query-core'
-import { readable } from 'svelte/store'
-import { useQueryClient } from './useQueryClient.js'
-import type { Readable } from 'svelte/store'
-import type { MutationFilters, QueryClient } from '@tanstack/query-core'
-
-export function useIsMutating(
- filters?: MutationFilters,
- queryClient?: QueryClient,
-): Readable {
- const client = useQueryClient(queryClient)
- const cache = client.getMutationCache()
- // isMutating is the prev value initialized on mount *
- let isMutating = client.isMutating(filters)
-
- const { subscribe } = readable(isMutating, (set) => {
- return cache.subscribe(
- notifyManager.batchCalls(() => {
- const newIisMutating = client.isMutating(filters)
- if (isMutating !== newIisMutating) {
- // * and update with each change
- isMutating = newIisMutating
- set(isMutating)
- }
- }),
- )
- })
-
- return { subscribe }
-}
diff --git a/packages/svelte-query/src/useIsRestoring.ts b/packages/svelte-query/src/useIsRestoring.ts
index c22d8af402..99dd4ddacb 100644
--- a/packages/svelte-query/src/useIsRestoring.ts
+++ b/packages/svelte-query/src/useIsRestoring.ts
@@ -1,6 +1,6 @@
import { getIsRestoringContext } from './context.js'
-import type { Readable } from 'svelte/store'
+import type { Box } from './containers.svelte.js'
-export function useIsRestoring(): Readable {
+export function useIsRestoring(): Box {
return getIsRestoringContext()
}
diff --git a/packages/svelte-query/src/useMutationState.svelte.ts b/packages/svelte-query/src/useMutationState.svelte.ts
new file mode 100644
index 0000000000..c517e64b48
--- /dev/null
+++ b/packages/svelte-query/src/useMutationState.svelte.ts
@@ -0,0 +1,56 @@
+import { replaceEqualDeep } from '@tanstack/query-core'
+import { useQueryClient } from './useQueryClient.js'
+import type {
+ MutationCache,
+ MutationState,
+ QueryClient,
+} from '@tanstack/query-core'
+import type { MutationStateOptions } from './types.js'
+
+function getResult(
+ mutationCache: MutationCache,
+ options: MutationStateOptions,
+): Array {
+ return mutationCache
+ .findAll(options.filters)
+ .map(
+ (mutation): TResult =>
+ (options.select ? options.select(mutation) : mutation.state) as TResult,
+ )
+}
+
+export function useMutationState(
+ options: MutationStateOptions = {},
+ queryClient?: QueryClient,
+): Array {
+ const mutationCache = useQueryClient(queryClient).getMutationCache()
+ const result = $state(getResult(mutationCache, options))
+
+ $effect(() => {
+ const unsubscribe = mutationCache.subscribe(() => {
+ const nextResult = replaceEqualDeep(
+ result,
+ getResult(mutationCache, options),
+ )
+ if (result !== nextResult) {
+ Object.assign(result, nextResult)
+ }
+ })
+
+ return unsubscribe
+ })
+
+ /* $effect(() => {
+ mutationCache.subscribe(() => {
+ const nextResult = replaceEqualDeep(
+ result.current,
+ getResult(mutationCache, optionsRef),
+ )
+ if (result.current !== nextResult) {
+ result = nextResult
+ //notifyManager.schedule(onStoreChange)
+ }
+ })
+ }) */
+ return result
+}
diff --git a/packages/svelte-query/src/useMutationState.ts b/packages/svelte-query/src/useMutationState.ts
deleted file mode 100644
index 0367eee5db..0000000000
--- a/packages/svelte-query/src/useMutationState.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { readable } from 'svelte/store'
-import { notifyManager, replaceEqualDeep } from '@tanstack/query-core'
-import { useQueryClient } from './useQueryClient.js'
-import type {
- MutationCache,
- MutationState,
- QueryClient,
-} from '@tanstack/query-core'
-import type { Readable } from 'svelte/store'
-import type { MutationStateOptions } from './types.js'
-
-function getResult(
- mutationCache: MutationCache,
- options: MutationStateOptions,
-): Array {
- return mutationCache
- .findAll(options.filters)
- .map(
- (mutation): TResult =>
- (options.select ? options.select(mutation) : mutation.state) as TResult,
- )
-}
-
-export function useMutationState(
- options: MutationStateOptions = {},
- queryClient?: QueryClient,
-): Readable> {
- const client = useQueryClient(queryClient)
- const mutationCache = client.getMutationCache()
-
- let result = getResult(mutationCache, options)
-
- const { subscribe } = readable(result, (set) => {
- return mutationCache.subscribe(
- notifyManager.batchCalls(() => {
- const nextResult = replaceEqualDeep(
- result,
- getResult(mutationCache, options),
- )
- if (result !== nextResult) {
- result = nextResult
- set(result)
- }
- }),
- )
- })
-
- return { subscribe }
-}
diff --git a/packages/svelte-query/src/utils.svelte.ts b/packages/svelte-query/src/utils.svelte.ts
new file mode 100644
index 0000000000..9e8073aab7
--- /dev/null
+++ b/packages/svelte-query/src/utils.svelte.ts
@@ -0,0 +1,44 @@
+import { untrack } from 'svelte'
+// modified from the great https://github.com/svecosystem/runed
+function runEffect(
+ flush: 'post' | 'pre',
+ effect: () => void | VoidFunction,
+): void {
+ switch (flush) {
+ case 'post':
+ $effect(effect)
+ break
+ case 'pre':
+ $effect.pre(effect)
+ break
+ }
+}
+type Getter = () => T
+export const watchChanges = (
+ sources: Getter | Array>,
+ flush: 'post' | 'pre',
+ effect: (
+ values: T | Array,
+ previousValues: T | undefined | Array,
+ ) => void,
+) => {
+ let active = false
+ let previousValues: T | undefined | Array = Array.isArray(
+ sources,
+ )
+ ? []
+ : undefined
+ runEffect(flush, () => {
+ const values = Array.isArray(sources)
+ ? sources.map((source) => source())
+ : sources()
+ if (!active) {
+ active = true
+ previousValues = values
+ return
+ }
+ const cleanup = untrack(() => effect(values, previousValues))
+ previousValues = values
+ return cleanup
+ })
+}
diff --git a/packages/svelte-query/src/utils.ts b/packages/svelte-query/src/utils.ts
deleted file mode 100644
index 35d60730aa..0000000000
--- a/packages/svelte-query/src/utils.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import type { Readable } from 'svelte/store'
-import type { StoreOrVal } from './types.js'
-
-export function isSvelteStore(
- obj: StoreOrVal,
-): obj is Readable {
- return 'subscribe' in obj && typeof obj.subscribe === 'function'
-}
diff --git a/packages/svelte-query/svelte.config.js b/packages/svelte-query/svelte.config.js
index 94ca454ac7..076d2dcd50 100644
--- a/packages/svelte-query/svelte.config.js
+++ b/packages/svelte-query/svelte.config.js
@@ -2,6 +2,9 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
const config = {
preprocess: vitePreprocess(),
+ compilerOptions: {
+ runes: true,
+ },
}
export default config
diff --git a/packages/svelte-query/tests/ProviderWrapper.svelte b/packages/svelte-query/tests/ProviderWrapper.svelte
new file mode 100644
index 0000000000..b61d2d99da
--- /dev/null
+++ b/packages/svelte-query/tests/ProviderWrapper.svelte
@@ -0,0 +1,14 @@
+
+
+
+ {@render children()}
+
diff --git a/packages/svelte-query/tests/QueryClientProvider/ChildComponent.svelte b/packages/svelte-query/tests/QueryClientProvider/ChildComponent.svelte
index 7fef03d0d9..f025d2e365 100644
--- a/packages/svelte-query/tests/QueryClientProvider/ChildComponent.svelte
+++ b/packages/svelte-query/tests/QueryClientProvider/ChildComponent.svelte
@@ -2,10 +2,10 @@
import { createQuery } from '../../src/index.js'
import { sleep } from '@tanstack/query-test-utils'
- const query = createQuery({
+ const query = createQuery(() => ({
queryKey: ['hello'],
queryFn: () => sleep(10).then(() => 'test'),
- })
+ }))
-Data: {$query.data ?? 'undefined'}
+Data: {query.data ?? 'undefined'}
diff --git a/packages/svelte-query/tests/QueryClientProvider/ParentComponent.svelte b/packages/svelte-query/tests/QueryClientProvider/ParentComponent.svelte
index dc2440ef94..c7b6fa0c5b 100644
--- a/packages/svelte-query/tests/QueryClientProvider/ParentComponent.svelte
+++ b/packages/svelte-query/tests/QueryClientProvider/ParentComponent.svelte
@@ -1,12 +1,9 @@
diff --git a/packages/svelte-query/tests/QueryClientProvider/QueryClientProvider.test.ts b/packages/svelte-query/tests/QueryClientProvider/QueryClientProvider.svelte.test.ts
similarity index 78%
rename from packages/svelte-query/tests/QueryClientProvider/QueryClientProvider.test.ts
rename to packages/svelte-query/tests/QueryClientProvider/QueryClientProvider.svelte.test.ts
index 66a40915f5..754e492fcc 100644
--- a/packages/svelte-query/tests/QueryClientProvider/QueryClientProvider.test.ts
+++ b/packages/svelte-query/tests/QueryClientProvider/QueryClientProvider.svelte.test.ts
@@ -1,6 +1,6 @@
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { render } from '@testing-library/svelte'
-import { QueryCache } from '@tanstack/query-core'
+import { QueryClient } from '@tanstack/query-core'
import ParentComponent from './ParentComponent.svelte'
describe('QueryClientProvider', () => {
@@ -13,11 +13,12 @@ describe('QueryClientProvider', () => {
})
test('Sets a specific cache for all queries to use', async () => {
- const queryCache = new QueryCache()
+ const queryClient = new QueryClient()
+ const queryCache = queryClient.getQueryCache()
const rendered = render(ParentComponent, {
props: {
- queryCache: queryCache,
+ queryClient: queryClient,
},
})
diff --git a/packages/svelte-query/tests/containers.svelte.test.ts b/packages/svelte-query/tests/containers.svelte.test.ts
new file mode 100644
index 0000000000..3511dbb5b5
--- /dev/null
+++ b/packages/svelte-query/tests/containers.svelte.test.ts
@@ -0,0 +1,219 @@
+import { flushSync } from 'svelte'
+import { describe, expect, it } from 'vitest'
+import { createRawRef } from '../src/containers.svelte.js'
+import { withEffectRoot } from './utils.svelte.js'
+
+describe('createRawRef', () => {
+ it('should create a reactive reference', () => {
+ const [ref, update] = createRawRef({ a: 1, b: 2 })
+
+ expect(ref).toEqual({ a: 1, b: 2 })
+
+ update({ a: 3, b: 4 })
+ expect(ref).toEqual({ a: 3, b: 4 })
+
+ ref.a = 5
+ expect(ref).toEqual({ a: 5, b: 4 })
+ })
+
+ it('should handle nested objects', () => {
+ const [ref, update] = createRawRef<{ a: any }>({ a: { b: { c: 1 } } })
+
+ expect(ref).toEqual({ a: { b: { c: 1 } } })
+
+ // update with same structure
+ update({ a: { b: { c: 2 } } })
+ expect(ref).toEqual({ a: { b: { c: 2 } } })
+
+ ref.a.b.c = 3
+ expect(ref).toEqual({ a: { b: { c: 3 } } })
+
+ // update with different structure should wipe out everything below the first level
+ update({ a: { b: 3 } })
+ expect(ref).toEqual({ a: { b: 3 } })
+ })
+
+ it('should remove properties when a new object is assigned', () => {
+ const [ref, update] = createRawRef>({
+ a: 1,
+ b: 2,
+ })
+
+ expect(ref).toEqual({ a: 1, b: 2 })
+
+ update({ a: 3 })
+ expect(ref).toEqual({ a: 3 })
+ })
+
+ it(
+ 'should not break reactivity when removing keys',
+ withEffectRoot(() => {
+ const [ref, update] = createRawRef>({ a: 1, b: 2 })
+ const states: Array = []
+ $effect(() => {
+ states.push(ref.b)
+ })
+
+ // these flushSync calls force the effect to run and push the value to the states array
+ flushSync()
+ update({ a: 3 }) // should remove b, and should rerun the effect
+ flushSync()
+ update({ a: 3, b: 4 }) // should add b back, and should rerun the effect
+ flushSync()
+ delete ref.b // should remove b, and should rerun the effect
+ flushSync()
+ delete ref.a // should remove a, and should _not_ rerun the effect
+ expect(states).toEqual([2, undefined, 4, undefined])
+ }),
+ )
+
+ it(
+ 'should correctly trap calls to `in`',
+ withEffectRoot(() => {
+ const [ref, update] = createRawRef>({
+ a: 1,
+ b: 2,
+ })
+
+ expect('b' in ref).toBe(true)
+ delete ref.b
+ expect('b' in ref).toBe(false)
+ update({})
+ expect('a' in ref).toBe(false)
+ update({ a: 1, b: 2 })
+ expect('b' in ref).toBe(true)
+ expect('a' in ref).toBe(true)
+ }),
+ )
+
+ it('should correctly trap calls to `ownKeys`', () => {
+ const [ref, update] = createRawRef>({
+ a: 1,
+ b: 2,
+ })
+
+ expect(Object.keys(ref)).toEqual(['a', 'b'])
+
+ delete ref.b
+ expect(Reflect.ownKeys(ref)).toEqual(['a'])
+
+ update({})
+ expect(Object.keys(ref)).toEqual([])
+
+ update({ a: 1, b: 2 })
+ expect(Object.keys(ref)).toEqual(['a', 'b'])
+ })
+
+ it('should correctly trap calls to `getOwnPropertyDescriptor`', () => {
+ const [ref, update] = createRawRef>({
+ a: 1,
+ b: 2,
+ })
+
+ expect(Reflect.getOwnPropertyDescriptor(ref, 'b')).toEqual({
+ configurable: true,
+ enumerable: true,
+ get: expect.any(Function),
+ set: expect.any(Function),
+ })
+
+ delete ref.b
+ expect(Reflect.getOwnPropertyDescriptor(ref, 'b')).toEqual(undefined)
+
+ update({})
+ expect(Reflect.getOwnPropertyDescriptor(ref, 'a')).toEqual(undefined)
+
+ update({ a: 1, b: 2 })
+ expect(Reflect.getOwnPropertyDescriptor(ref, 'a')).toEqual({
+ configurable: true,
+ enumerable: true,
+ get: expect.any(Function),
+ set: expect.any(Function),
+ })
+ expect(Reflect.getOwnPropertyDescriptor(ref, 'b')).toEqual({
+ configurable: true,
+ enumerable: true,
+ get: expect.any(Function),
+ set: expect.any(Function),
+ })
+ })
+
+ it('should lazily access values when using `update`', () => {
+ let aAccessed = false
+ let bAccessed = false
+ const [ref, update] = createRawRef({
+ get a() {
+ aAccessed = true
+ return 1
+ },
+ get b() {
+ bAccessed = true
+ return 2
+ },
+ })
+
+ expect(aAccessed).toBe(false)
+ expect(bAccessed).toBe(false)
+
+ expect(ref.a).toBe(1)
+
+ expect(aAccessed).toBe(true)
+ expect(bAccessed).toBe(false)
+
+ aAccessed = false
+ bAccessed = false
+
+ update({
+ get a() {
+ aAccessed = true
+ return 2
+ },
+ get b() {
+ bAccessed = true
+ return 3
+ },
+ })
+
+ expect(aAccessed).toBe(false)
+ expect(bAccessed).toBe(false)
+
+ expect(ref.a).toBe(2)
+
+ expect(aAccessed).toBe(true)
+ expect(bAccessed).toBe(false)
+ })
+
+ it('should handle arrays', () => {
+ const [ref, update] = createRawRef([1, 2, 3])
+
+ expect(ref).toEqual([1, 2, 3])
+
+ ref[0] = 4
+ expect(ref).toEqual([4, 2, 3])
+
+ update([5, 6])
+ expect(ref).toEqual([5, 6])
+
+ update([7, 8, 9])
+ expect(ref).toEqual([7, 8, 9])
+ })
+
+ it('should behave like a regular object when not using `update`', () => {
+ const [ref] = createRawRef>({ a: 1, b: 2 })
+
+ expect(ref).toEqual({ a: 1, b: 2 })
+
+ ref.a = 3
+ expect(ref).toEqual({ a: 3, b: 2 })
+
+ ref.b = 4
+ expect(ref).toEqual({ a: 3, b: 4 })
+
+ ref.c = 5
+ expect(ref).toEqual({ a: 3, b: 4, c: 5 })
+
+ ref.fn = () => 6
+ expect(ref).toEqual({ a: 3, b: 4, c: 5, fn: expect.any(Function) })
+ expect((ref.fn as () => number)()).toBe(6)
+ })
+})
diff --git a/packages/svelte-query/tests/context/context.test.ts b/packages/svelte-query/tests/context/context.svelte.test.ts
similarity index 100%
rename from packages/svelte-query/tests/context/context.test.ts
rename to packages/svelte-query/tests/context/context.svelte.test.ts
diff --git a/packages/svelte-query/tests/createInfiniteQuery/BaseExample.svelte b/packages/svelte-query/tests/createInfiniteQuery/BaseExample.svelte
index 3ad7a0d51c..a16cdc9214 100644
--- a/packages/svelte-query/tests/createInfiniteQuery/BaseExample.svelte
+++ b/packages/svelte-query/tests/createInfiniteQuery/BaseExample.svelte
@@ -1,25 +1,29 @@
-Status: {$query.status}
+Status: {query.status}
diff --git a/packages/svelte-query/tests/createInfiniteQuery/ChangeClient.svelte b/packages/svelte-query/tests/createInfiniteQuery/ChangeClient.svelte
index a5d547c35c..1993dca149 100644
--- a/packages/svelte-query/tests/createInfiniteQuery/ChangeClient.svelte
+++ b/packages/svelte-query/tests/createInfiniteQuery/ChangeClient.svelte
@@ -3,25 +3,25 @@
import { createInfiniteQuery } from '../../src/index.js'
import { sleep } from '@tanstack/query-test-utils'
- export let queryClient: QueryClient
+ let { queryClient }: { queryClient: QueryClient } = $props()
const queryKey = ['test']
- let firstPage = 0
+ let firstPage = $state(0)
const query = createInfiniteQuery(
- {
+ () => ({
queryKey: queryKey,
queryFn: ({ pageParam }) => sleep(10).then(() => pageParam),
getNextPageParam: (lastPage) => lastPage + 1,
initialPageParam: firstPage,
- },
- queryClient,
+ }),
+ () => queryClient,
)
{
+ onclick={() => {
queryClient.setQueryData(queryKey, {
pages: [7, 8],
pageParams: [7, 8],
@@ -32,4 +32,4 @@
setPages
-Data: {JSON.stringify($query.data)}
+Data: {JSON.stringify(query.data)}
diff --git a/packages/svelte-query/tests/createInfiniteQuery/SelectExample.svelte b/packages/svelte-query/tests/createInfiniteQuery/SelectExample.svelte
index d860bfbd7b..84f7247136 100644
--- a/packages/svelte-query/tests/createInfiniteQuery/SelectExample.svelte
+++ b/packages/svelte-query/tests/createInfiniteQuery/SelectExample.svelte
@@ -1,16 +1,16 @@
-{$query.data?.pages.join(',')}
+{query.data?.pages.join(',')}
diff --git a/packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.test.ts b/packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts
similarity index 86%
rename from packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.test.ts
rename to packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts
index 14c263e2a2..344cd902b8 100644
--- a/packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.test.ts
+++ b/packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts
@@ -1,11 +1,10 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
-import { QueryClient } from '@tanstack/query-core'
import { fireEvent, render } from '@testing-library/svelte'
-import { get, writable } from 'svelte/store'
+import { QueryClient } from '@tanstack/query-core'
+import { ref } from '../utils.svelte.js'
import BaseExample from './BaseExample.svelte'
import SelectExample from './SelectExample.svelte'
import ChangeClient from './ChangeClient.svelte'
-import type { Writable } from 'svelte/store'
import type { QueryObserverResult } from '@tanstack/query-core'
describe('createInfiniteQuery', () => {
@@ -18,21 +17,20 @@ describe('createInfiniteQuery', () => {
})
it('should return the correct states for a successful query', async () => {
- const statesStore: Writable> = writable([])
+ let states = ref>([])
const rendered = render(BaseExample, {
props: {
- states: statesStore,
+ states,
},
})
await vi.advanceTimersByTimeAsync(11)
expect(rendered.getByText('Status: success')).toBeInTheDocument()
- const states = get(statesStore)
+ expect(states.value).toHaveLength(2)
- expect(states).toHaveLength(2)
- expect(states[0]).toEqual({
+ expect(states.value[0]).toEqual({
data: undefined,
dataUpdatedAt: 0,
error: null,
@@ -68,7 +66,8 @@ describe('createInfiniteQuery', () => {
fetchStatus: 'fetching',
promise: expect.any(Promise),
})
- expect(states[1]).toEqual({
+
+ expect(states.value[1]).toEqual({
data: { pages: [0], pageParams: [0] },
dataUpdatedAt: expect.any(Number),
error: null,
@@ -107,25 +106,25 @@ describe('createInfiniteQuery', () => {
})
it('should be able to select a part of the data', async () => {
- const statesStore: Writable> = writable([])
+ let states = ref>([])
const rendered = render(SelectExample, {
props: {
- states: statesStore,
+ states,
},
})
await vi.advanceTimersByTimeAsync(11)
expect(rendered.getByText('count: 1')).toBeInTheDocument()
- const states = get(statesStore)
+ expect(states.value).toHaveLength(2)
- expect(states).toHaveLength(2)
- expect(states[0]).toMatchObject({
+ expect(states.value[0]).toMatchObject({
data: undefined,
isSuccess: false,
})
- expect(states[1]).toMatchObject({
+
+ expect(states.value[1]).toMatchObject({
data: { pages: ['count: 1'] },
isSuccess: true,
})
diff --git a/packages/svelte-query/tests/createMutation/FailureExample.svelte b/packages/svelte-query/tests/createMutation/FailureExample.svelte
index 1ed739a622..ac3cd58633 100644
--- a/packages/svelte-query/tests/createMutation/FailureExample.svelte
+++ b/packages/svelte-query/tests/createMutation/FailureExample.svelte
@@ -1,23 +1,23 @@
- $mutation.mutate({ count: ++$count })}>Mutate
+ mutation.mutate({ count: ++count })}>Mutate
-Data: {$mutation.data?.count ?? 'undefined'}
-Status: {$mutation.status}
-Failure Count: {$mutation.failureCount}
-Failure Reason: {$mutation.failureReason ?? 'undefined'}
+Data: {mutation.data?.count ?? 'undefined'}
+Status: {mutation.status}
+Failure Count: {mutation.failureCount}
+Failure Reason: {mutation.failureReason ?? 'undefined'}
diff --git a/packages/svelte-query/tests/createMutation/OnSuccessExample.svelte b/packages/svelte-query/tests/createMutation/OnSuccessExample.svelte
index ae9ba3da02..92cfc4a752 100644
--- a/packages/svelte-query/tests/createMutation/OnSuccessExample.svelte
+++ b/packages/svelte-query/tests/createMutation/OnSuccessExample.svelte
@@ -1,18 +1,21 @@
- $mutation.mutate({ count: ++$count })}>Mutate
+ mutation.mutate({ count: ++count })}>Mutate
-Count: {$count}
+Count: {count}
diff --git a/packages/svelte-query/tests/createMutation/ResetExample.svelte b/packages/svelte-query/tests/createMutation/ResetExample.svelte
index 04e0987362..060e936a35 100644
--- a/packages/svelte-query/tests/createMutation/ResetExample.svelte
+++ b/packages/svelte-query/tests/createMutation/ResetExample.svelte
@@ -6,13 +6,13 @@
const queryClient = new QueryClient()
setQueryClientContext(queryClient)
- const mutation = createMutation({
+ const mutation = createMutation(() => ({
mutationFn: () =>
sleep(10).then(() => Promise.reject(new Error('Expected mock error'))),
- })
+ }))
- $mutation.reset()}>Reset
- $mutation.mutate()}>Mutate
+ mutation.reset()}>Reset
+ mutation.mutate()}>Mutate
-Error: {$mutation.error?.message ?? 'undefined'}
+Error: {mutation.error?.message ?? 'undefined'}
diff --git a/packages/svelte-query/tests/createMutation/createMutation.test.ts b/packages/svelte-query/tests/createMutation/createMutation.svelte.test.ts
similarity index 100%
rename from packages/svelte-query/tests/createMutation/createMutation.test.ts
rename to packages/svelte-query/tests/createMutation/createMutation.svelte.test.ts
diff --git a/packages/svelte-query/tests/createQueries.svelte.test.ts b/packages/svelte-query/tests/createQueries.svelte.test.ts
new file mode 100644
index 0000000000..c648942483
--- /dev/null
+++ b/packages/svelte-query/tests/createQueries.svelte.test.ts
@@ -0,0 +1,934 @@
+import { afterEach, describe, expect, expectTypeOf, it, vi } from 'vitest'
+import { QueryClient, createQueries } from '../src/index.js'
+import { promiseWithResolvers, withEffectRoot } from './utils.svelte.js'
+import type {
+ CreateQueryOptions,
+ CreateQueryResult,
+ QueryFunction,
+ QueryFunctionContext,
+ QueryKey,
+ skipToken,
+} from '../src/index.js'
+
+describe('createQueries', () => {
+ const queryClient = new QueryClient()
+
+ afterEach(() => {
+ queryClient.clear()
+ })
+
+ it(
+ 'should return the correct states',
+ withEffectRoot(async () => {
+ const key1 = ['test-1']
+ const key2 = ['test-2']
+ const results: Array> = []
+ const { promise: promise1, resolve: resolve1 } = promiseWithResolvers()
+ const { promise: promise2, resolve: resolve2 } = promiseWithResolvers()
+
+ const result = createQueries(
+ () => ({
+ queries: [
+ {
+ queryKey: key1,
+ queryFn: () => promise1,
+ },
+ {
+ queryKey: key2,
+ queryFn: () => promise2,
+ },
+ ],
+ }),
+ () => queryClient,
+ )
+
+ $effect(() => {
+ results.push([{ ...result[0] }, { ...result[1] }])
+ })
+
+ resolve1(1)
+
+ await vi.waitFor(() => expect(result[0].data).toBe(1))
+
+ resolve2(2)
+ await vi.waitFor(() => expect(result[1].data).toBe(2))
+
+ expect(results.length).toBe(3)
+ expect(results[0]).toMatchObject([
+ { data: undefined },
+ { data: undefined },
+ ])
+ expect(results[1]).toMatchObject([{ data: 1 }, { data: undefined }])
+ expect(results[2]).toMatchObject([{ data: 1 }, { data: 2 }])
+ }),
+ )
+
+ it(
+ 'handles type parameter - tuple of tuples',
+ withEffectRoot(() => {
+ const key1 = ['test-key-1']
+ const key2 = ['test-key-2']
+ const key3 = ['test-key-3']
+
+ const result1 = createQueries<
+ [[number], [string], [Array, boolean]]
+ >(
+ () => ({
+ queries: [
+ {
+ queryKey: key1,
+ queryFn: () => 1,
+ },
+ {
+ queryKey: key2,
+ queryFn: () => 'string',
+ },
+ {
+ queryKey: key3,
+ queryFn: () => ['string[]'],
+ },
+ ],
+ }),
+ () => queryClient,
+ )
+
+ expectTypeOf(result1[0]).toEqualTypeOf<
+ CreateQueryResult
+ >()
+ expectTypeOf(result1[1]).toEqualTypeOf<
+ CreateQueryResult
+ >()
+ expectTypeOf(result1[2]).toEqualTypeOf<
+ CreateQueryResult, boolean>
+ >()
+ expectTypeOf(result1[0].data).toEqualTypeOf()
+ expectTypeOf(result1[1].data).toEqualTypeOf()
+ expectTypeOf(result1[2].data).toEqualTypeOf | undefined>()
+ expectTypeOf(result1[2].error).toEqualTypeOf()
+
+ // TData (3rd element) takes precedence over TQueryFnData (1st element)
+ const result2 = createQueries<
+ [[string, unknown, string], [string, unknown, number]]
+ >(
+ () => ({
+ queries: [
+ {
+ queryKey: key1,
+ queryFn: () => 'string',
+ select: (a) => {
+ expectTypeOf(a).toEqualTypeOf()
+ return a.toLowerCase()
+ },
+ },
+ {
+ queryKey: key2,
+ queryFn: () => 'string',
+ select: (a) => {
+ expectTypeOf(a).toEqualTypeOf()
+ return parseInt(a)
+ },
+ },
+ ],
+ }),
+ () => queryClient,
+ )
+
+ expectTypeOf(result2[0]).toEqualTypeOf<
+ CreateQueryResult
+ >()
+ expectTypeOf(result2[1]).toEqualTypeOf<
+ CreateQueryResult