Skip to content

Commit 86b6a49

Browse files
fix: Update API, add a bunch of tests
1 parent 7dfc8fd commit 86b6a49

12 files changed

+915
-109
lines changed

packages/svelte-query/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"test:types": "svelte-check --tsconfig ./tsconfig.json",
2121
"test:eslint": "eslint ./src",
2222
"test:lib": "vitest run",
23-
"test:lib:dev": "pnpm run test:lib --watch",
23+
"test:lib:dev": "vitest",
2424
"test:build": "publint --strict && attw --pack",
2525
"build": "svelte-package --input ./src --output ./dist"
2626
},
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/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,

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { createReactiveThunk } from './containers.svelte.js'
1+
import { ReactiveValue } from './containers.svelte.js'
22
import { useQueryClient } from './useQueryClient.js'
33
import type { QueryClient, QueryFilters } from '@tanstack/query-core'
44

55
export function useIsFetching(
66
filters?: QueryFilters,
77
queryClient?: QueryClient,
8-
): () => number {
8+
): ReactiveValue<number> {
99
const client = useQueryClient(queryClient)
1010
const queryCache = client.getQueryCache()
1111

12-
return createReactiveThunk(
12+
return new ReactiveValue(
1313
() => client.isFetching(filters),
1414
(update) => queryCache.subscribe(update),
1515
)

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { useQueryClient } from './useQueryClient.js'
2-
import { createReactiveThunk } from './containers.svelte.js'
2+
import { ReactiveValue } from './containers.svelte.js'
33
import type { MutationFilters, QueryClient } from '@tanstack/query-core'
44

55
export function useIsMutating(
66
filters?: MutationFilters,
77
queryClient?: QueryClient,
8-
): () => number {
8+
): ReactiveValue<number> {
99
const client = useQueryClient(queryClient)
1010
const cache = client.getMutationCache()
1111

12-
return createReactiveThunk(
12+
return new ReactiveValue(
1313
() => client.isMutating(filters),
1414
(update) => cache.subscribe(update),
1515
)
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getIsRestoringContext } from './context.js'
2+
import type { Box } from './containers.svelte.js'
23

3-
export function useIsRestoring(): () => boolean {
4+
export function useIsRestoring(): Box<boolean> {
45
return getIsRestoringContext()
56
}

0 commit comments

Comments
 (0)