import { QueriesObserver } from '@tanstack/query-core' import { useIsRestoring } from './useIsRestoring.js' import { createRawRef } from './containers.svelte.js' import { useQueryClient } from './useQueryClient.js' import type { Accessor, CreateQueryOptions, CreateQueryResult, DefinedCreateQueryResult, } from './types.js' import type { DefaultError, OmitKeyof, QueriesObserverOptions, QueriesPlaceholderDataFunction, QueryClient, QueryFunction, QueryKey, ThrowOnError, } from '@tanstack/query-core' // This defines the `CreateQueryOptions` that are accepted in `QueriesOptions` & `GetOptions`. // `placeholderData` function always gets undefined passed type CreateQueryOptionsForCreateQueries< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, > = OmitKeyof< CreateQueryOptions, 'placeholderData' > & { placeholderData?: TQueryFnData | QueriesPlaceholderDataFunction } // Avoid TS depth-limit error in case of large array literal type MAXIMUM_DEPTH = 20 // Widen the type of the symbol to enable type inference even if skipToken is not immutable. type SkipTokenForCreateQueries = symbol type GetCreateQueryOptionsForCreateQueries = // Part 1: responsible for applying explicit type parameter to function arguments, if object { queryFnData: TQueryFnData, error: TError, data: TData } T extends { queryFnData: infer TQueryFnData error?: infer TError data: infer TData } ? CreateQueryOptionsForCreateQueries : T extends { queryFnData: infer TQueryFnData; error?: infer TError } ? CreateQueryOptionsForCreateQueries : T extends { data: infer TData; error?: infer TError } ? CreateQueryOptionsForCreateQueries : // Part 2: responsible for applying explicit type parameter to function arguments, if tuple [TQueryFnData, TError, TData] T extends [infer TQueryFnData, infer TError, infer TData] ? CreateQueryOptionsForCreateQueries : T extends [infer TQueryFnData, infer TError] ? CreateQueryOptionsForCreateQueries : T extends [infer TQueryFnData] ? CreateQueryOptionsForCreateQueries : // Part 3: responsible for inferring and enforcing type if no explicit parameter was provided T extends { queryFn?: | QueryFunction | SkipTokenForCreateQueries select?: (data: any) => infer TData throwOnError?: ThrowOnError } ? CreateQueryOptionsForCreateQueries< TQueryFnData, unknown extends TError ? DefaultError : TError, unknown extends TData ? TQueryFnData : TData, TQueryKey > : // Fallback CreateQueryOptionsForCreateQueries // A defined initialData setting should return a DefinedCreateQueryResult rather than CreateQueryResult type GetDefinedOrUndefinedQueryResult = T extends { initialData?: infer TInitialData } ? unknown extends TInitialData ? CreateQueryResult : TInitialData extends TData ? DefinedCreateQueryResult : TInitialData extends () => infer TInitialDataResult ? unknown extends TInitialDataResult ? CreateQueryResult : TInitialDataResult extends TData ? DefinedCreateQueryResult : CreateQueryResult : CreateQueryResult : CreateQueryResult type GetCreateQueryResult = // Part 1: responsible for mapping explicit type parameter to function result, if object T extends { queryFnData: any; error?: infer TError; data: infer TData } ? GetDefinedOrUndefinedQueryResult : T extends { queryFnData: infer TQueryFnData; error?: infer TError } ? GetDefinedOrUndefinedQueryResult : T extends { data: infer TData; error?: infer TError } ? GetDefinedOrUndefinedQueryResult : // Part 2: responsible for mapping explicit type parameter to function result, if tuple T extends [any, infer TError, infer TData] ? GetDefinedOrUndefinedQueryResult : T extends [infer TQueryFnData, infer TError] ? GetDefinedOrUndefinedQueryResult : T extends [infer TQueryFnData] ? GetDefinedOrUndefinedQueryResult : // Part 3: responsible for mapping inferred type to results, if no explicit parameter was provided T extends { queryFn?: | QueryFunction | SkipTokenForCreateQueries select?: (data: any) => infer TData throwOnError?: ThrowOnError } ? GetDefinedOrUndefinedQueryResult< T, unknown extends TData ? TQueryFnData : TData, unknown extends TError ? DefaultError : TError > : // Fallback CreateQueryResult /** * QueriesOptions reducer recursively unwraps function arguments to infer/enforce type param */ export type QueriesOptions< T extends Array, TResults extends Array = [], TDepth extends ReadonlyArray = [], > = TDepth['length'] extends MAXIMUM_DEPTH ? Array : T extends [] ? [] : T extends [infer Head] ? [...TResults, GetCreateQueryOptionsForCreateQueries] : T extends [infer Head, ...infer Tails] ? QueriesOptions< [...Tails], [...TResults, GetCreateQueryOptionsForCreateQueries], [...TDepth, 1] > : ReadonlyArray extends T ? T : // If T is *some* array but we couldn't assign unknown[] to it, then it must hold some known/homogenous type! // use this to infer the param types in the case of Array.map() argument T extends Array< CreateQueryOptionsForCreateQueries< infer TQueryFnData, infer TError, infer TData, infer TQueryKey > > ? Array< CreateQueryOptionsForCreateQueries< TQueryFnData, TError, TData, TQueryKey > > : // Fallback Array /** * QueriesResults reducer recursively maps type param to results */ export type QueriesResults< T extends Array, TResults extends Array = [], TDepth extends ReadonlyArray = [], > = TDepth['length'] extends MAXIMUM_DEPTH ? Array : T extends [] ? [] : T extends [infer Head] ? [...TResults, GetCreateQueryResult] : T extends [infer Head, ...infer Tails] ? QueriesResults< [...Tails], [...TResults, GetCreateQueryResult], [...TDepth, 1] > : { [K in keyof T]: GetCreateQueryResult } export function createQueries< T extends Array, TCombinedResult = QueriesResults, >( createQueriesOptions: Accessor<{ queries: | readonly [...QueriesOptions] | readonly [ ...{ [K in keyof T]: GetCreateQueryOptionsForCreateQueries }, ] combine?: (result: QueriesResults) => TCombinedResult }>, queryClient?: Accessor, ): TCombinedResult { const client = $derived(useQueryClient(queryClient?.())) const isRestoring = useIsRestoring() const { queries, combine } = $derived.by(createQueriesOptions) const resolvedQueryOptions = $derived( queries.map((opts) => { const resolvedOptions = client.defaultQueryOptions(opts) // Make sure the results are already in fetching state before subscribing or updating options resolvedOptions._optimisticResults = isRestoring.current ? 'isRestoring' : 'optimistic' return resolvedOptions }), ) // can't do same as createMutation, as QueriesObserver has no `setOptions` method const observer = $derived( new QueriesObserver( client, resolvedQueryOptions, combine as QueriesObserverOptions, ), ) function createResult() { const [_, getCombinedResult, trackResult] = observer.getOptimisticResult( resolvedQueryOptions, combine as QueriesObserverOptions['combine'], ) return getCombinedResult(trackResult()) } // @ts-expect-error - the crazy-complex TCombinedResult type doesn't like being called an array // svelte-ignore state_referenced_locally const [results, update] = createRawRef(createResult()) $effect(() => { const unsubscribe = isRestoring.current ? () => undefined : observer.subscribe(() => update(createResult())) return unsubscribe }) $effect.pre(() => { observer.setQueries(resolvedQueryOptions, { combine, } as QueriesObserverOptions) update(createResult()) }) return results }