Skip to content

Commit e87d25c

Browse files
authored
feat(queries): infer types correctly when using useQueries (#56)
1 parent 2d5de76 commit e87d25c

File tree

7 files changed

+66
-27
lines changed

7 files changed

+66
-27
lines changed

src/queries/Queries.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
import type { UseQueryOptions, UseQueryResult } from '../types'
55
import useQueries from './useQueries'
66
7-
export let queries: UseQueryOptions[]
7+
export let queries: readonly UseQueryOptions[]
88
// useful for binding
9-
export let currentResult: UseQueryResult[]
9+
export let currentResult: readonly UseQueryResult[] = []
1010
1111
let firstRender = true
1212

src/queries/useQueries.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,35 @@
11
import { readable } from 'svelte/store';
22

3-
import { notifyManager, QueriesObserver, QueryClient } from "../queryCore/core";
3+
import { notifyManager, QueriesObserver, QueryClient, QueryKey } from "../queryCore/core";
44
import { useQueryClient } from "../queryClientProvider";
5-
import type { UseQueryOptions } from "../types";
5+
import type { UseQueryOptions, UseQueriesStoreResult } from "../types";
66

7-
export default function useQueries(
8-
queries: UseQueryOptions[]
9-
) {
7+
export default function useQueries<
8+
TQueryFnData = unknown,
9+
TError = unknown,
10+
TData = TQueryFnData,
11+
TQueryKey extends QueryKey = QueryKey
12+
>(queries: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>[]): UseQueriesStoreResult<UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>[]>;
13+
export default function useQueries<
14+
TQueryFnData = unknown,
15+
TError = unknown,
16+
TData = TQueryFnData,
17+
TQueryKey extends QueryKey = QueryKey
18+
>(queries: []): UseQueriesStoreResult<UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>[]>;
19+
export default function useQueries<
20+
T extends readonly [...UseQueryOptions[]]
21+
>(queries: T): UseQueriesStoreResult<T>;
22+
export default function useQueries<
23+
T extends readonly [...UseQueryOptions[]]
24+
>(queries: T): UseQueriesStoreResult<T> {
1025
const client: QueryClient = useQueryClient();
1126
const observer = new QueriesObserver(client, queries);
1227

1328
const { subscribe } = readable(observer.getCurrentResult(), (set) => {
1429
return observer.subscribe(notifyManager.batchCalls(set));
1530
});
1631

17-
const setQueries = (newQueries: UseQueryOptions[]) => {
32+
const setQueries = (newQueries: T) => {
1833
if (observer.hasListeners()) {
1934
observer.setQueries(newQueries)
2035
}

src/queryCore/core/queriesObserver.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
import { difference, replaceAt } from './utils'
22
import { notifyManager } from './notifyManager'
3-
import type { QueryObserverOptions, QueryObserverResult } from './types'
3+
import type { QueryObserverOptions, QueryObserverResult, QueriesObserverResult } from './types'
44
import type { QueryClient } from './queryClient'
55
import { NotifyOptions, QueryObserver } from './queryObserver'
66
import { Subscribable } from './subscribable'
77

8-
type QueriesObserverListener = (result: QueryObserverResult[]) => void
8+
type QueriesObserverListener<T extends readonly [...QueryObserverOptions[]]> = (result: QueriesObserverResult<T>) => void
99

10-
export class QueriesObserver extends Subscribable<QueriesObserverListener> {
10+
export class QueriesObserver<T extends readonly [...QueryObserverOptions[]]> extends Subscribable<QueriesObserverListener<T>> {
1111
private client: QueryClient
12-
private result: QueryObserverResult[]
13-
private queries: QueryObserverOptions[]
12+
private result: QueriesObserverResult<T>
13+
private queries: T
1414
private observers: QueryObserver[]
1515
private observersMap: Record<string, QueryObserver>
1616

17-
constructor(client: QueryClient, queries?: QueryObserverOptions[]) {
17+
constructor(client: QueryClient, queries?: T) {
1818
super()
1919

2020
this.client = client
21-
this.queries = []
22-
this.result = []
21+
this.queries = [] as any
22+
this.result = [] as any
2323
this.observers = []
2424
this.observersMap = {}
2525

@@ -52,24 +52,24 @@ export class QueriesObserver extends Subscribable<QueriesObserverListener> {
5252
}
5353

5454
setQueries(
55-
queries: QueryObserverOptions[],
55+
queries: T,
5656
notifyOptions?: NotifyOptions
5757
): void {
5858
this.queries = queries
5959
this.updateObservers(notifyOptions)
6060
}
6161

62-
getCurrentResult(): QueryObserverResult[] {
62+
getCurrentResult(): QueriesObserverResult<T> {
6363
return this.result
6464
}
6565

66-
getOptimisticResult(queries: QueryObserverOptions[]): QueryObserverResult[] {
66+
getOptimisticResult(queries: T): QueriesObserverResult<T> {
6767
return queries.map(options => {
6868
const defaultedOptions = this.client.defaultQueryObserverOptions(options)
6969
return this.getObserver(defaultedOptions).getOptimisticResult(
7070
defaultedOptions
7171
)
72-
})
72+
}) as any
7373
}
7474

7575
private getObserver(options: QueryObserverOptions): QueryObserver {
@@ -117,7 +117,7 @@ export class QueriesObserver extends Subscribable<QueriesObserverListener> {
117117

118118
this.observers = newObservers
119119
this.observersMap = newObserversMap
120-
this.result = newResult
120+
this.result = newResult as any
121121

122122
if (!this.hasListeners()) {
123123
return

src/queryCore/core/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,18 @@ export type InfiniteQueryObserverResult<TData = unknown, TError = unknown> =
457457
| InfiniteQueryObserverRefetchErrorResult<TData, TError>
458458
| InfiniteQueryObserverSuccessResult<TData, TError>
459459

460+
export type QueryObserverOptionsToQueryObserverResult<T extends QueryObserverOptions> =
461+
T extends QueryObserverOptions<any, infer TError> & { queryFn: (...args: any[]) => infer TData | Promise<infer TData> }
462+
? QueryObserverResult<TData, TError>
463+
: T extends QueryObserverOptions<any, infer TError, infer TData, any, any>
464+
? QueryObserverResult<TData, TError>
465+
: never
466+
467+
468+
export type QueriesObserverResult<T extends readonly [...QueryObserverOptions[]]> = {
469+
[K in keyof T]: QueryObserverOptionsToQueryObserverResult<T[K]>
470+
}
471+
460472
export type MutationKey = string | readonly unknown[]
461473

462474
export type MutationStatus = 'idle' | 'loading' | 'success' | 'error'

src/queryCore/core/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,10 @@ export function difference<T>(array1: T[], array2: T[]): T[] {
9696
return array1.filter(x => array2.indexOf(x) === -1)
9797
}
9898

99-
export function replaceAt<T>(array: T[], index: number, value: T): T[] {
100-
const copy = array.slice(0)
99+
export function replaceAt<T extends readonly V[], V>(array: T, index: number, value: V): T {
100+
const copy = [...array] as const
101101
copy[index] = value
102-
return copy
102+
return copy as T
103103
}
104104

105105
export function timeUntilStale(updatedAt: number, staleTime?: number): number {

src/types.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import type {
55
QueryObserverResult,
66
QueryFunction, QueryKey, MutationStatus, MutationKey, MutationFunction,
77
InfiniteQueryObserverOptions,
8-
InfiniteQueryObserverResult
8+
InfiniteQueryObserverResult,
9+
QueriesObserverResult
910
} from "./queryCore";
1011
import { RetryDelayValue, RetryValue } from "./queryCore/core/retryer";
1112

@@ -75,6 +76,12 @@ export interface UseInfiniteQueryOptions<
7576

7677
export type UseInfiniteQueryResult<TData = unknown, TError = unknown> = InfiniteQueryObserverResult<TData, TError>
7778

79+
export interface UseQueriesStoreResult<T extends readonly [...UseQueryOptions[]]> extends Readable<UseQueriesResult<T>> {
80+
setQueries(newQueries: T): void
81+
}
82+
83+
export type UseQueriesResult<T extends readonly [...UseQueryOptions[]]> = QueriesObserverResult<T>
84+
7885

7986
export interface MutationStoreResult<
8087
TData = unknown,

storybook/stories/queries/Queries.svelte

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,23 @@
22
import { Queries } from '../../../src'
33
import { useQueries } from '../../../src/queries'
44
5-
const later = (delay, value) =>
5+
type Later = <T>(delay: number, value: T) => Promise<T>
6+
7+
const later: Later = (delay, value) =>
68
new Promise(resolve => setTimeout(resolve, delay, value))
79
810
// the query fn
911
const queryFn = () => later(500, 'My Data')
1012
// the query fn 2
1113
const queryFn2 = () => later(500, 'My Data 2')
14+
// the query fn 3
15+
const queryFn3 = () => later(500, true)
1216
1317
const queries = [
1418
{ queryKey: 'myQuery', queryFn },
1519
{ queryKey: 'myQuery2', queryFn: queryFn2 },
16-
]
20+
{ queryKey: 'myQuery3', queryFn: queryFn3 },
21+
] as const
1722
1823
const queriesStore = useQueries(queries)
1924
</script>

0 commit comments

Comments
 (0)