Skip to content

Commit 4c93461

Browse files
fix: Update API, add a bunch of tests
1 parent a6917da commit 4c93461

13 files changed

+947
-145
lines changed

packages/svelte-query/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"test:types": "svelte-check --tsconfig ./tsconfig.json",
2727
"test:eslint": "eslint ./src",
2828
"test:lib": "vitest run",
29-
"test:lib:dev": "pnpm run test:lib --watch",
29+
"test:lib:dev": "vitest",
3030
"test:build": "publint --strict && attw --pack",
3131
"build": "svelte-package --input ./src --output ./dist"
3232
},
Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,16 @@
1+
import { tick, untrack } from 'svelte'
12
import { createSubscriber } from 'svelte/reactivity'
23

34
type VoidFn = () => void
45
type Subscriber = (update: VoidFn) => void | VoidFn
5-
type Effect =
6-
| { type: 'pre'; fn: Subscriber }
7-
| { type: 'regular'; fn: Subscriber }
86

9-
export class ReactiveValue<T> {
7+
export class ReactiveValue<T> implements Box<T> {
108
#fn
119
#subscribe
1210

13-
constructor(
14-
fn: () => T,
15-
onSubscribe: Subscriber,
16-
effects: Array<Effect> = [],
17-
) {
11+
constructor(fn: () => T, onSubscribe: Subscriber) {
1812
this.#fn = fn
19-
this.#subscribe = createSubscriber((update) => {
20-
const cleanup = $effect.root(() => {
21-
for (const effect of effects) {
22-
if (effect.type === 'pre') {
23-
$effect.pre(() => effect.fn(update))
24-
} else {
25-
$effect(() => effect.fn(update))
26-
}
27-
}
28-
})
29-
const off = onSubscribe(update)
30-
return () => {
31-
cleanup()
32-
off?.()
33-
}
34-
})
13+
this.#subscribe = createSubscriber((update) => onSubscribe(update))
3514
}
3615

3716
get current() {
@@ -40,11 +19,44 @@ export class ReactiveValue<T> {
4019
}
4120
}
4221

43-
export function createReactiveThunk<T>(
44-
fn: () => T,
45-
onSubscribe: Subscriber,
46-
effects?: Array<Effect>,
47-
) {
48-
const reactiveValue = new ReactiveValue(fn, onSubscribe, effects)
49-
return () => reactiveValue.current
22+
export type Box<T> = { current: T }
23+
24+
export function box<T>(value: T): Box<T> {
25+
let current = $state(value)
26+
return {
27+
get current() {
28+
return current
29+
},
30+
set current(newValue) {
31+
current = newValue
32+
},
33+
}
34+
}
35+
36+
/**
37+
* Makes all of the top-level keys of an object into $state.raw fields whose initial values
38+
* are the same as in the original object. Does not mutate the original object. Provides an `update`
39+
* function that _can_ (but does not have to be) be used to replace all of the object's top-level keys
40+
* with the values of the new object, while maintaining the original root object's reference.
41+
*/
42+
export function createRawRef<T extends {} | Array<unknown>>(
43+
init: T,
44+
): [T, (newValue: T) => void] {
45+
const out = (Array.isArray(init) ? [] : {}) as T
46+
47+
function update(newValue: T) {
48+
Object.assign(out, newValue)
49+
}
50+
51+
for (const [key, value] of Object.entries(init)) {
52+
let state = $state.raw(value)
53+
Object.defineProperty(out, key, {
54+
enumerable: true,
55+
get: () => state,
56+
set: (v) => {
57+
state = v
58+
},
59+
})
60+
}
61+
return [out, update]
5062
}

packages/svelte-query/src/context.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getContext, setContext } from 'svelte'
22
import type { QueryClient } from '@tanstack/query-core'
3+
import type { Box, ReactiveValue } from './containers.svelte'
34

45
const _contextKey = Symbol('QueryClient')
56

@@ -23,18 +24,18 @@ export const setQueryClientContext = (client: QueryClient): void => {
2324
const _isRestoringContextKey = Symbol('isRestoring')
2425

2526
/** Retrieves a `isRestoring` from Svelte's context */
26-
export const getIsRestoringContext = (): (() => boolean) => {
27+
export const getIsRestoringContext = (): Box<boolean> => {
2728
try {
28-
const isRestoring = getContext<(() => boolean) | undefined>(
29+
const isRestoring = getContext<Box<boolean> | undefined>(
2930
_isRestoringContextKey,
3031
)
31-
return isRestoring ?? (() => false)
32+
return isRestoring ?? { current: false }
3233
} catch (error) {
33-
return () => false
34+
return { current: false }
3435
}
3536
}
3637

3738
/** Sets a `isRestoring` on Svelte's context */
38-
export const setIsRestoringContext = (isRestoring: () => boolean): void => {
39+
export const setIsRestoringContext = (isRestoring: Box<boolean>): void => {
3940
setContext(_isRestoringContextKey, isRestoring)
4041
}
Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { useIsRestoring } from './useIsRestoring.js'
22
import { useQueryClient } from './useQueryClient.js'
3-
import { createReactiveThunk } from './containers.svelte.js'
4-
import type { CreateBaseQueryOptions, CreateBaseQueryResult } from './types.js'
3+
import { createRawRef } from './containers.svelte.js'
54
import type { QueryClient, QueryKey, QueryObserver } from '@tanstack/query-core'
5+
import type { CreateBaseQueryOptions, CreateBaseQueryResult } from './types.js'
66

77
export function createBaseQuery<
88
TQueryFnData,
@@ -20,15 +20,16 @@ export function createBaseQuery<
2020
>,
2121
Observer: typeof QueryObserver,
2222
queryClient?: QueryClient,
23-
): () => CreateBaseQueryResult<TData, TError> {
23+
): CreateBaseQueryResult<TData, TError> {
2424
/** Load query client */
25+
console.log('createBaseQuery', options)
2526
const client = useQueryClient(queryClient)
26-
const isRestoring = $derived.by(useIsRestoring())
27+
const isRestoring = useIsRestoring()
2728

2829
/** Creates a store that has the default options applied */
2930
const resolvedOptions = $derived.by(() => {
3031
const opts = client.defaultQueryOptions(options)
31-
opts._optimisticResults = isRestoring ? 'isRestoring' : 'optimistic'
32+
opts._optimisticResults = isRestoring.current ? 'isRestoring' : 'optimistic'
3233
opts.structuralSharing = false
3334
return opts
3435
})
@@ -42,23 +43,25 @@ export function createBaseQuery<
4243
TQueryKey
4344
>(client, resolvedOptions)
4445

45-
return createReactiveThunk(
46-
() => {
46+
let [query, update] = createRawRef(
47+
observer.getOptimisticResult(resolvedOptions),
48+
)
49+
50+
// if you update this effect in the future, _make sure_ the unsubscribe function is still being returned
51+
$effect(() =>
52+
observer.subscribe(() => {
4753
const result = observer.getOptimisticResult(resolvedOptions)
48-
if (!resolvedOptions.notifyOnChangeProps) {
49-
return observer.trackResult(result)
50-
}
51-
return result
52-
},
53-
(update) => observer.subscribe(update),
54-
[
55-
{
56-
type: 'pre',
57-
fn: (update) => {
58-
observer.setOptions(resolvedOptions, { listeners: false })
59-
update()
60-
},
61-
},
62-
],
54+
update(result)
55+
}),
6356
)
57+
58+
$effect.pre(() => {
59+
observer.setOptions(resolvedOptions, { listeners: false })
60+
const result = observer.getOptimisticResult(resolvedOptions)
61+
update(result)
62+
})
63+
64+
return resolvedOptions.notifyOnChangeProps
65+
? observer.trackResult(query)
66+
: query
6467
}

packages/svelte-query/src/createInfiniteQuery.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ export function createInfiniteQuery<
2828
TPageParam
2929
>,
3030
queryClient?: QueryClient,
31-
): () => CreateInfiniteQueryResult<TData, TError> {
31+
): CreateInfiniteQueryResult<TData, TError> {
3232
return createBaseQuery(
3333
options,
3434
InfiniteQueryObserver as typeof QueryObserver,
3535
queryClient,
36-
) as () => CreateInfiniteQueryResult<TData, TError>
36+
) as CreateInfiniteQueryResult<TData, TError>
3737
}

packages/svelte-query/src/createMutation.svelte.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { MutationObserver } from '@tanstack/query-core'
22
import { useQueryClient } from './useQueryClient.js'
3-
import { createReactiveThunk } from './containers.svelte.js'
3+
import { createRawRef } from './containers.svelte.js'
44
import type {
55
CreateMutateFunction,
66
CreateMutationOptions,
@@ -31,20 +31,23 @@ export function createMutation<
3131
observer.mutate(variables, mutateOptions).catch(noop)
3232
})
3333

34+
function createResult() {
35+
const result = observer.getCurrentResult()
36+
Object.defineProperty(result, 'mutateAsync', {
37+
value: result.mutate,
38+
})
39+
Object.defineProperty(result, 'mutate', {
40+
value: mutate,
41+
})
42+
return result
43+
}
44+
45+
let [mutation, update] = createRawRef(createResult())
46+
47+
$effect(() => update(createResult()))
48+
3449
// @ts-expect-error
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
45-
},
46-
(update) => observer.subscribe(update),
47-
)
50+
return mutation
4851
}
4952

5053
function noop() {}

packages/svelte-query/src/createQueries.svelte.ts

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { QueriesObserver } from '@tanstack/query-core'
22
import { useIsRestoring } from './useIsRestoring.js'
33
import { useQueryClient } from './useQueryClient.js'
4-
import { createReactiveThunk } from './containers.svelte.js'
4+
import { createRawRef } from './containers.svelte.js'
55
import type {
66
DefaultError,
77
DefinedQueryObserverResult,
@@ -197,15 +197,15 @@ export function createQueries<
197197
subscribed?: boolean
198198
},
199199
queryClient?: QueryClient,
200-
): () => TCombinedResult {
200+
): TCombinedResult {
201201
const client = useQueryClient(queryClient)
202-
const isRestoring = $derived.by(useIsRestoring())
202+
const isRestoring = useIsRestoring()
203203

204204
const resolvedQueries = $derived(
205205
queries.map((opts) => {
206206
const resolvedOptions = client.defaultQueryOptions(opts)
207207
// Make sure the results are already in fetching state before subscribing or updating options
208-
resolvedOptions._optimisticResults = isRestoring
208+
resolvedOptions._optimisticResults = isRestoring.current
209209
? 'isRestoring'
210210
: 'optimistic'
211211
return resolvedOptions
@@ -218,36 +218,32 @@ export function createQueries<
218218
combine as QueriesObserverOptions<TCombinedResult>,
219219
)
220220

221-
return createReactiveThunk(
222-
() => {
223-
const [_, getCombinedResult, trackResult] = observer.getOptimisticResult(
224-
resolvedQueries,
225-
combine as QueriesObserverOptions<TCombinedResult>['combine'],
226-
)
227-
return getCombinedResult(trackResult())
228-
},
229-
() => {},
230-
[
231-
{
232-
type: 'pre',
233-
fn: (update) => {
234-
observer.setQueries(
235-
resolvedQueries,
236-
{ combine } as QueriesObserverOptions<TCombinedResult>,
237-
{ listeners: false },
238-
)
239-
update()
240-
},
241-
},
242-
{
243-
type: 'regular',
244-
fn: (update) => {
245-
if (isRestoring || subscribed === false) {
246-
return
247-
}
248-
observer.subscribe(update)
249-
},
250-
},
251-
],
252-
)
221+
function createResult() {
222+
const [_, getCombinedResult, trackResult] = observer.getOptimisticResult(
223+
resolvedQueries,
224+
combine as QueriesObserverOptions<TCombinedResult>['combine'],
225+
)
226+
return getCombinedResult(trackResult())
227+
}
228+
229+
// @ts-expect-error - the crazy-complex TCombinedResult type doesn't like being called an array
230+
let [results, update] = createRawRef<TCombinedResult>(createResult())
231+
232+
$effect(() => {
233+
if (isRestoring.current || subscribed === false) {
234+
return
235+
}
236+
return observer.subscribe(() => update(createResult()))
237+
})
238+
239+
$effect.pre(() => {
240+
observer.setQueries(
241+
resolvedQueries,
242+
{ combine } as QueriesObserverOptions<TCombinedResult>,
243+
{ listeners: false },
244+
)
245+
update(createResult())
246+
})
247+
248+
return results
253249
}

packages/svelte-query/src/createQuery.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export function createQuery<
1919
>(
2020
options: DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
2121
queryClient?: QueryClient,
22-
): () => DefinedCreateQueryResult<TData, TError>
22+
): DefinedCreateQueryResult<TData, TError>
2323

2424
export function createQuery<
2525
TQueryFnData = unknown,
@@ -29,7 +29,7 @@ export function createQuery<
2929
>(
3030
options: UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
3131
queryClient?: QueryClient,
32-
): () => CreateQueryResult<TData, TError>
32+
): CreateQueryResult<TData, TError>
3333

3434
export function createQuery<
3535
TQueryFnData = unknown,
@@ -39,7 +39,7 @@ export function createQuery<
3939
>(
4040
options: CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
4141
queryClient?: QueryClient,
42-
): () => CreateQueryResult<TData, TError>
42+
): CreateQueryResult<TData, TError>
4343

4444
export function createQuery(
4545
options: CreateQueryOptions,

0 commit comments

Comments
 (0)