Skip to content

Commit 3e60f15

Browse files
feat: Draft proposal
1 parent 647cbf9 commit 3e60f15

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1094
-2197
lines changed

packages/svelte-query/package.json

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
"type": "github",
1515
"url": "https://github.com/sponsors/tannerlinsley"
1616
},
17+
"keywords": [
18+
"tanstack",
19+
"query",
20+
"svelte",
21+
"swr"
22+
],
1723
"scripts": {
1824
"clean": "premove ./dist ./coverage ./.svelte-kit ./dist-ts",
1925
"compile": "tsc --build",
@@ -42,18 +48,18 @@
4248
"src",
4349
"!src/__tests__"
4450
],
45-
"dependencies": {
46-
"@tanstack/query-core": "workspace:*"
47-
},
4851
"devDependencies": {
4952
"@sveltejs/package": "^2.3.7",
50-
"@sveltejs/vite-plugin-svelte": "^4.0.0",
53+
"@sveltejs/vite-plugin-svelte": "^5.0.3",
54+
"@tanstack/query-core": "workspace:*",
5155
"@testing-library/svelte": "^5.2.6",
5256
"eslint-plugin-svelte": "^2.46.0",
5357
"svelte": "^5.20.1",
54-
"svelte-check": "^4.0.0"
58+
"svelte-check": "^4.0.0",
59+
"vite": "^6.0.0",
60+
"vitest": "^3.0.0"
5561
},
5662
"peerDependencies": {
57-
"svelte": "^5.0.0"
63+
"svelte": "^5.7.0"
5864
}
5965
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { createSubscriber } from 'svelte/reactivity'
2+
3+
type VoidFn = () => void
4+
type Subscriber = (update: VoidFn) => void | VoidFn
5+
6+
export class ReactiveValue<T> {
7+
#fn
8+
#subscribe
9+
10+
constructor(fn: () => T, onSubscribe: Subscriber) {
11+
this.#fn = fn
12+
this.#subscribe = createSubscriber(onSubscribe)
13+
}
14+
15+
get current() {
16+
this.#subscribe()
17+
return this.#fn()
18+
}
19+
}
20+
21+
export function createReactiveThunk<T>(fn: () => T, onSubscribe: Subscriber) {
22+
const reactiveValue = new ReactiveValue(fn, onSubscribe)
23+
return () => reactiveValue.current
24+
}

packages/svelte-query/src/context.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
import { getContext, setContext } from 'svelte'
22
import type { QueryClient } from '@tanstack/query-core'
33

4-
const _contextKey = '$$_queryClient'
4+
const _contextKey = Symbol('QueryClient')
55

66
/** Retrieves a Client from Svelte's context */
77
export const getQueryClientContext = (): QueryClient => {
8-
const client = getContext(_contextKey)
8+
const client = getContext<QueryClient | undefined>(_contextKey)
99
if (!client) {
1010
throw new Error(
1111
'No QueryClient was found in Svelte context. Did you forget to wrap your component with QueryClientProvider?',
1212
)
1313
}
1414

15-
return client as QueryClient
15+
return client
1616
}
1717

1818
/** Sets a QueryClient on Svelte's context */
1919
export const setQueryClientContext = (client: QueryClient): void => {
2020
setContext(_contextKey, client)
2121
}
2222

23-
const _isRestoringContextKey = '$$_isRestoring'
23+
const _isRestoringContextKey = Symbol('isRestoring')
2424

2525
/** Retrieves a `isRestoring` from Svelte's context */
2626
export const getIsRestoringContext = (): (() => boolean) => {
Lines changed: 35 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
1-
import { notifyManager } from '@tanstack/query-core'
21
import { useIsRestoring } from './useIsRestoring.js'
32
import { useQueryClient } from './useQueryClient.js'
4-
import type {
5-
CreateBaseQueryOptions,
6-
CreateBaseQueryResult,
7-
FunctionedParams,
8-
} from './types.js'
9-
import type {
10-
QueryClient,
11-
QueryKey,
12-
QueryObserver,
13-
QueryObserverResult,
14-
} from '@tanstack/query-core'
3+
import { createReactiveThunk } from './containers.svelte.js'
4+
import type { CreateBaseQueryOptions, CreateBaseQueryResult } from './types.js'
5+
import type { QueryClient, QueryKey, QueryObserver } from '@tanstack/query-core'
156

167
export function createBaseQuery<
178
TQueryFnData,
@@ -20,64 +11,56 @@ export function createBaseQuery<
2011
TQueryData,
2112
TQueryKey extends QueryKey,
2213
>(
23-
options: FunctionedParams<
24-
CreateBaseQueryOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>
14+
options: CreateBaseQueryOptions<
15+
TQueryFnData,
16+
TError,
17+
TData,
18+
TQueryData,
19+
TQueryKey
2520
>,
2621
Observer: typeof QueryObserver,
2722
queryClient?: QueryClient,
28-
): CreateBaseQueryResult<TData, TError> {
23+
): () => CreateBaseQueryResult<TData, TError> {
2924
/** Load query client */
3025
const client = useQueryClient(queryClient)
31-
const isRestoring = useIsRestoring()
26+
const isRestoring = $derived.by(useIsRestoring())
3227

3328
/** Creates a store that has the default options applied */
34-
const defaultedOptions = $derived(() => {
35-
const defaultOptions = client.defaultQueryOptions(options())
36-
defaultOptions._optimisticResults = isRestoring()
37-
? 'isRestoring'
38-
: 'optimistic'
39-
defaultOptions.structuralSharing = false
40-
return defaultOptions
29+
const resolvedOptions = $derived.by(() => {
30+
const opts = client.defaultQueryOptions(options)
31+
opts._optimisticResults = isRestoring ? 'isRestoring' : 'optimistic'
32+
opts.structuralSharing = false
33+
return opts
4134
})
4235

36+
let updateEffects = () => {}
37+
4338
/** Creates the observer */
4439
const observer = new Observer<
4540
TQueryFnData,
4641
TError,
4742
TData,
4843
TQueryData,
4944
TQueryKey
50-
>(client, defaultedOptions())
45+
>(client, resolvedOptions)
5146

52-
const result = $state<QueryObserverResult<TData, TError>>(
53-
observer.getOptimisticResult(defaultedOptions()),
54-
)
55-
56-
function updateResult(r: QueryObserverResult<TData, TError>) {
57-
Object.assign(result, r)
58-
}
59-
60-
$effect(() => {
61-
const unsubscribe = isRestoring()
62-
? () => undefined
63-
: observer.subscribe(() => {
64-
notifyManager.batchCalls(() => {
65-
updateResult(observer.getOptimisticResult(defaultedOptions()))
66-
})()
67-
})
68-
69-
observer.updateResult()
70-
return () => unsubscribe()
71-
})
72-
73-
/** Subscribe to changes in result and defaultedOptionsStore */
47+
/** Subscribe to changes in result and defaultedOptions */
7448
$effect.pre(() => {
75-
observer.setOptions(defaultedOptions(), { listeners: false })
76-
updateResult(observer.getOptimisticResult(defaultedOptions()))
49+
observer.setOptions(resolvedOptions, { listeners: false })
50+
updateEffects()
7751
})
7852

79-
// Handle result property usage tracking
80-
return !defaultedOptions().notifyOnChangeProps
81-
? observer.trackResult(result)
82-
: result
53+
return createReactiveThunk(
54+
() => {
55+
const result = observer.getOptimisticResult(resolvedOptions)
56+
if (!resolvedOptions.notifyOnChangeProps) {
57+
return observer.trackResult(result)
58+
}
59+
return result
60+
},
61+
(update) => {
62+
updateEffects = update
63+
return observer.subscribe(update)
64+
},
65+
)
8366
}

packages/svelte-query/src/createInfiniteQuery.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import type {
1010
import type {
1111
CreateInfiniteQueryOptions,
1212
CreateInfiniteQueryResult,
13-
FunctionedParams,
1413
} from './types.js'
1514

1615
export function createInfiniteQuery<
@@ -20,21 +19,19 @@ export function createInfiniteQuery<
2019
TQueryKey extends QueryKey = QueryKey,
2120
TPageParam = unknown,
2221
>(
23-
options: FunctionedParams<
24-
CreateInfiniteQueryOptions<
25-
TQueryFnData,
26-
TError,
27-
TData,
28-
TQueryFnData,
29-
TQueryKey,
30-
TPageParam
31-
>
22+
options: CreateInfiniteQueryOptions<
23+
TQueryFnData,
24+
TError,
25+
TData,
26+
TQueryFnData,
27+
TQueryKey,
28+
TPageParam
3229
>,
3330
queryClient?: QueryClient,
34-
): CreateInfiniteQueryResult<TData, TError> {
31+
): () => CreateInfiniteQueryResult<TData, TError> {
3532
return createBaseQuery(
3633
options,
3734
InfiniteQueryObserver as typeof QueryObserver,
3835
queryClient,
39-
) as CreateInfiniteQueryResult<TData, TError>
36+
) as () => CreateInfiniteQueryResult<TData, TError>
4037
}
Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import { onDestroy } from 'svelte'
2-
3-
import { MutationObserver, notifyManager } from '@tanstack/query-core'
1+
import { MutationObserver } from '@tanstack/query-core'
42
import { useQueryClient } from './useQueryClient.js'
3+
import { createReactiveThunk } from './containers.svelte.js'
54
import type {
65
CreateMutateFunction,
76
CreateMutationOptions,
87
CreateMutationResult,
9-
FunctionedParams,
108
} from './types.js'
119

1210
import type { DefaultError, QueryClient } from '@tanstack/query-core'
@@ -17,18 +15,14 @@ export function createMutation<
1715
TVariables = void,
1816
TContext = unknown,
1917
>(
20-
options: FunctionedParams<
21-
CreateMutationOptions<TData, TError, TVariables, TContext>
22-
>,
18+
options: CreateMutationOptions<TData, TError, TVariables, TContext>,
2319
queryClient?: QueryClient,
24-
): CreateMutationResult<TData, TError, TVariables, TContext> {
20+
): () => CreateMutationResult<TData, TError, TVariables, TContext> {
2521
const client = useQueryClient(queryClient)
2622

27-
const observer = $derived(
28-
new MutationObserver<TData, TError, TVariables, TContext>(
29-
client,
30-
options(),
31-
),
23+
const observer = new MutationObserver<TData, TError, TVariables, TContext>(
24+
client,
25+
options,
3226
)
3327

3428
const mutate = $state<
@@ -37,35 +31,20 @@ export function createMutation<
3731
observer.mutate(variables, mutateOptions).catch(noop)
3832
})
3933

40-
$effect.pre(() => {
41-
observer.setOptions(options())
42-
})
43-
44-
const result = $state(observer.getCurrentResult())
45-
46-
const unsubscribe = observer.subscribe((val) => {
47-
notifyManager.batchCalls(() => {
48-
Object.assign(result, val)
49-
})()
50-
})
51-
52-
onDestroy(() => {
53-
unsubscribe()
54-
})
55-
5634
// @ts-expect-error
57-
return new Proxy(result, {
58-
get: (_, prop) => {
59-
const r = {
60-
...result,
61-
mutate,
62-
mutateAsync: result.mutate,
63-
}
64-
if (prop == 'value') return r
65-
// @ts-expect-error
66-
return r[prop]
35+
return createReactiveThunk(
36+
() => {
37+
const result = observer.getCurrentResult()
38+
Object.defineProperty(result, 'mutateAsync', {
39+
value: result.mutate,
40+
})
41+
Object.defineProperty(result, 'mutate', {
42+
value: mutate,
43+
})
44+
return result
6745
},
68-
})
46+
(update) => observer.subscribe(update),
47+
)
6948
}
7049

7150
function noop() {}

0 commit comments

Comments
 (0)