From e839538f294bd58de5d5ab3c7b82448267eaa35c Mon Sep 17 00:00:00 2001 From: Maxime LUCE Date: Sat, 4 Sep 2021 21:47:22 +0200 Subject: [PATCH] feat(queries): infer types correctly when using useQueries --- src/queries/Queries.svelte | 4 ++-- src/queries/useQueries.ts | 27 ++++++++++++++++++------ src/queryCore/core/queriesObserver.ts | 26 +++++++++++------------ src/queryCore/core/types.ts | 12 +++++++++++ src/queryCore/core/utils.ts | 6 +++--- src/types.ts | 9 +++++++- storybook/stories/queries/Queries.svelte | 9 ++++++-- 7 files changed, 66 insertions(+), 27 deletions(-) diff --git a/src/queries/Queries.svelte b/src/queries/Queries.svelte index 7cc60f0..252cf3d 100644 --- a/src/queries/Queries.svelte +++ b/src/queries/Queries.svelte @@ -4,9 +4,9 @@ import type { UseQueryOptions, UseQueryResult } from '../types' import useQueries from './useQueries' - export let queries: UseQueryOptions[] + export let queries: readonly UseQueryOptions[] // useful for binding - export let currentResult: UseQueryResult[] + export let currentResult: readonly UseQueryResult[] = [] let firstRender = true diff --git a/src/queries/useQueries.ts b/src/queries/useQueries.ts index b97d76a..b1286af 100644 --- a/src/queries/useQueries.ts +++ b/src/queries/useQueries.ts @@ -1,12 +1,27 @@ import { readable } from 'svelte/store'; -import { notifyManager, QueriesObserver, QueryClient } from "../queryCore/core"; +import { notifyManager, QueriesObserver, QueryClient, QueryKey } from "../queryCore/core"; import { useQueryClient } from "../queryClientProvider"; -import type { UseQueryOptions } from "../types"; +import type { UseQueryOptions, UseQueriesStoreResult } from "../types"; -export default function useQueries( - queries: UseQueryOptions[] -) { +export default function useQueries< + TQueryFnData = unknown, + TError = unknown, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey +>(queries: UseQueryOptions[]): UseQueriesStoreResult[]>; +export default function useQueries< + TQueryFnData = unknown, + TError = unknown, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey +>(queries: []): UseQueriesStoreResult[]>; +export default function useQueries< + T extends readonly [...UseQueryOptions[]] +>(queries: T): UseQueriesStoreResult; +export default function useQueries< + T extends readonly [...UseQueryOptions[]] +>(queries: T): UseQueriesStoreResult { const client: QueryClient = useQueryClient(); const observer = new QueriesObserver(client, queries); @@ -14,7 +29,7 @@ export default function useQueries( return observer.subscribe(notifyManager.batchCalls(set)); }); - const setQueries = (newQueries: UseQueryOptions[]) => { + const setQueries = (newQueries: T) => { if (observer.hasListeners()) { observer.setQueries(newQueries) } diff --git a/src/queryCore/core/queriesObserver.ts b/src/queryCore/core/queriesObserver.ts index e133d7c..70c0880 100644 --- a/src/queryCore/core/queriesObserver.ts +++ b/src/queryCore/core/queriesObserver.ts @@ -1,25 +1,25 @@ import { difference, replaceAt } from './utils' import { notifyManager } from './notifyManager' -import type { QueryObserverOptions, QueryObserverResult } from './types' +import type { QueryObserverOptions, QueryObserverResult, QueriesObserverResult } from './types' import type { QueryClient } from './queryClient' import { NotifyOptions, QueryObserver } from './queryObserver' import { Subscribable } from './subscribable' -type QueriesObserverListener = (result: QueryObserverResult[]) => void +type QueriesObserverListener = (result: QueriesObserverResult) => void -export class QueriesObserver extends Subscribable { +export class QueriesObserver extends Subscribable> { private client: QueryClient - private result: QueryObserverResult[] - private queries: QueryObserverOptions[] + private result: QueriesObserverResult + private queries: T private observers: QueryObserver[] private observersMap: Record - constructor(client: QueryClient, queries?: QueryObserverOptions[]) { + constructor(client: QueryClient, queries?: T) { super() this.client = client - this.queries = [] - this.result = [] + this.queries = [] as any + this.result = [] as any this.observers = [] this.observersMap = {} @@ -52,24 +52,24 @@ export class QueriesObserver extends Subscribable { } setQueries( - queries: QueryObserverOptions[], + queries: T, notifyOptions?: NotifyOptions ): void { this.queries = queries this.updateObservers(notifyOptions) } - getCurrentResult(): QueryObserverResult[] { + getCurrentResult(): QueriesObserverResult { return this.result } - getOptimisticResult(queries: QueryObserverOptions[]): QueryObserverResult[] { + getOptimisticResult(queries: T): QueriesObserverResult { return queries.map(options => { const defaultedOptions = this.client.defaultQueryObserverOptions(options) return this.getObserver(defaultedOptions).getOptimisticResult( defaultedOptions ) - }) + }) as any } private getObserver(options: QueryObserverOptions): QueryObserver { @@ -117,7 +117,7 @@ export class QueriesObserver extends Subscribable { this.observers = newObservers this.observersMap = newObserversMap - this.result = newResult + this.result = newResult as any if (!this.hasListeners()) { return diff --git a/src/queryCore/core/types.ts b/src/queryCore/core/types.ts index e6df097..0df843e 100644 --- a/src/queryCore/core/types.ts +++ b/src/queryCore/core/types.ts @@ -457,6 +457,18 @@ export type InfiniteQueryObserverResult = | InfiniteQueryObserverRefetchErrorResult | InfiniteQueryObserverSuccessResult +export type QueryObserverOptionsToQueryObserverResult = + T extends QueryObserverOptions & { queryFn: (...args: any[]) => infer TData | Promise } + ? QueryObserverResult + : T extends QueryObserverOptions + ? QueryObserverResult + : never + + +export type QueriesObserverResult = { + [K in keyof T]: QueryObserverOptionsToQueryObserverResult +} + export type MutationKey = string | readonly unknown[] export type MutationStatus = 'idle' | 'loading' | 'success' | 'error' diff --git a/src/queryCore/core/utils.ts b/src/queryCore/core/utils.ts index eef6dea..4e54185 100644 --- a/src/queryCore/core/utils.ts +++ b/src/queryCore/core/utils.ts @@ -96,10 +96,10 @@ export function difference(array1: T[], array2: T[]): T[] { return array1.filter(x => array2.indexOf(x) === -1) } -export function replaceAt(array: T[], index: number, value: T): T[] { - const copy = array.slice(0) +export function replaceAt(array: T, index: number, value: V): T { + const copy = [...array] as const copy[index] = value - return copy + return copy as T } export function timeUntilStale(updatedAt: number, staleTime?: number): number { diff --git a/src/types.ts b/src/types.ts index 751fd31..72cdf38 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,7 +5,8 @@ import type { QueryObserverResult, QueryFunction, QueryKey, MutationStatus, MutationKey, MutationFunction, InfiniteQueryObserverOptions, - InfiniteQueryObserverResult + InfiniteQueryObserverResult, + QueriesObserverResult } from "./queryCore"; import { RetryDelayValue, RetryValue } from "./queryCore/core/retryer"; @@ -75,6 +76,12 @@ export interface UseInfiniteQueryOptions< export type UseInfiniteQueryResult = InfiniteQueryObserverResult +export interface UseQueriesStoreResult extends Readable> { + setQueries(newQueries: T): void +} + +export type UseQueriesResult = QueriesObserverResult + export interface MutationStoreResult< TData = unknown, diff --git a/storybook/stories/queries/Queries.svelte b/storybook/stories/queries/Queries.svelte index ca479d1..d9e6014 100644 --- a/storybook/stories/queries/Queries.svelte +++ b/storybook/stories/queries/Queries.svelte @@ -2,18 +2,23 @@ import { Queries } from '../../../src' import { useQueries } from '../../../src/queries' - const later = (delay, value) => + type Later = (delay: number, value: T) => Promise + + const later: Later = (delay, value) => new Promise(resolve => setTimeout(resolve, delay, value)) // the query fn const queryFn = () => later(500, 'My Data') // the query fn 2 const queryFn2 = () => later(500, 'My Data 2') + // the query fn 3 + const queryFn3 = () => later(500, true) const queries = [ { queryKey: 'myQuery', queryFn }, { queryKey: 'myQuery2', queryFn: queryFn2 }, - ] + { queryKey: 'myQuery3', queryFn: queryFn3 }, + ] as const const queriesStore = useQueries(queries)