import { Injector, Signal } from '@angular/core'; import { DefaultError, OmitKeyof, QueriesPlaceholderDataFunction, QueryFunction, QueryKey, ThrowOnError } from '@tanstack/query-core'; import { CreateQueryOptions, CreateQueryResult, DefinedCreateQueryResult } from './types.js'; type QueryObserverOptionsForCreateQueries = OmitKeyof, 'placeholderData'> & { placeholderData?: TQueryFnData | QueriesPlaceholderDataFunction; }; type MAXIMUM_DEPTH = 20; type SkipTokenForCreateQueries = symbol; type GetCreateQueryOptionsForCreateQueries = T extends { queryFnData: infer TQueryFnData; error?: infer TError; data: infer TData; } ? QueryObserverOptionsForCreateQueries : T extends { queryFnData: infer TQueryFnData; error?: infer TError; } ? QueryObserverOptionsForCreateQueries : T extends { data: infer TData; error?: infer TError; } ? QueryObserverOptionsForCreateQueries : T extends [infer TQueryFnData, infer TError, infer TData] ? QueryObserverOptionsForCreateQueries : T extends [infer TQueryFnData, infer TError] ? QueryObserverOptionsForCreateQueries : T extends [infer TQueryFnData] ? QueryObserverOptionsForCreateQueries : T extends { queryFn?: QueryFunction | SkipTokenForCreateQueries; select?: (data: any) => infer TData; throwOnError?: ThrowOnError; } ? QueryObserverOptionsForCreateQueries : QueryObserverOptionsForCreateQueries; 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 = 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 : T extends [any, infer TError, infer TData] ? GetDefinedOrUndefinedQueryResult : T extends [infer TQueryFnData, infer TError] ? GetDefinedOrUndefinedQueryResult : T extends [infer TQueryFnData] ? GetDefinedOrUndefinedQueryResult : T extends { queryFn?: QueryFunction | SkipTokenForCreateQueries; select?: (data: any) => infer TData; throwOnError?: ThrowOnError; } ? GetDefinedOrUndefinedQueryResult : CreateQueryResult; /** * The `queries` array accepted by `injectQueries`. Recursively unwraps each tuple element so every entry's * `queryFn`/`select`/`throwOnError` are inferred individually, up to 20 elements — past that, tuple * recursion falls back to a single homogeneous options type. An opaque array (e.g. `unknown[]`) is returned * as-is; a non-tuple array of a known element type is mapped to that element type instead, with no such * limit. * * @template T - The type of the `queries` array as written at the call site. * @template TResults - The internal accumulator that this type builds during recursion. It is not meant to * be set explicitly. * @template TDepth - The internal recursion-depth counter, checked against the 20-element limit. It is not * meant to be set explicitly. */ export type QueriesOptions, 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 : T extends Array> ? Array> : Array; /** * The result type returned by `injectQueries`, when no `combine` is provided. Mirrors {@link QueriesOptions}: * each tuple element's result type is inferred individually, up to 20 elements — past that, tuple recursion * falls back to a single homogeneous {@link CreateQueryResult} type. A non-tuple array is mapped per-element * instead, with no such limit — every entry keeps its individually inferred type regardless of array length. * * @template T - The type of the `queries` array, as inferred by {@link QueriesOptions}. * @template TResults - The internal accumulator that this type builds during recursion. It is not meant to * be set explicitly. * @template TDepth - The internal recursion-depth counter, checked against the 20-element limit. It is not * meant to be set explicitly. */ export type QueriesResults, 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 interface InjectQueriesOptions, TCombinedResult = QueriesResults> { queries: readonly [...QueriesOptions] | readonly [ ...{ [K in keyof T]: GetCreateQueryOptionsForCreateQueries; } ]; combine?: (result: QueriesResults) => TCombinedResult; } /** * Injects a signal to fetch a variable number of queries. * * The `queries` key accepts an array with query option objects mostly identical to `injectQuery`'s. Having * the same query key more than once in the array of query objects may cause some data to be shared between * queries. To avoid this, consider de-duplicating the queries and map the results back to the desired * structure. * * The `combine` option can be used to combine the results of the queries into a single value. The result * will be structurally shared to be as referentially stable as possible. * * @remarks Unlike `injectQuery`, `injectQueries` cannot infer the `data` argument of an _inline_ `select` * from its sibling `queryFn`. Because `injectQueries` infers the type of the whole `queries` array at once, * the `select` parameter of a query object written inline cannot be contextually typed from that same * object's `queryFn`, so it falls back to `unknown` — a * [known TypeScript limitation](https://github.com/TanStack/query/issues/6556). Annotate the `select` * parameter explicitly, or define the query with {@link queryOptions}, which resolves its types in a single * object _before_ it reaches `injectQueries`, to work around this — see the example below. * @param optionsFn - A function returning the queries' options — an array of query option objects under * `queries`, and an optional `combine`. Similar to `computed` from Angular, this function runs in the * reactive context, so signals read inside it (e.g. to build the `queries` array) drive the queries. * @param injector - The `Injector` in which to create the queries. If this is not provided, the current * injection context will be used instead (via `inject`). * @returns A `Signal` with the combined result. Without `combine`, this is an array with all the query * results, in the same order as the input. When `combine` is provided, this is the value returned by * `combine` instead. * * @example * ```angular-ts * @Component({ * selector: 'posts', * template: ` *
    * @for (query of postQueries(); track $index) { * @if (query.isPending()) { *
  • Loading...
  • * } @else if (query.isError()) { *
  • Error: {{ query.error()?.message }}
  • * } @else { *
  • {{ query.data().title }}
  • * } * } *
* `, * }) * export class Posts { * readonly ids = signal([1, 2, 3]) * * readonly postQueries = injectQueries(() => ({ * queries: this.ids().map((id) => ({ * queryKey: ['post', id], * queryFn: () => fetchPost(id), * staleTime: Infinity, * })), * })) * } * ``` * * @example * Combining results into a single value: * ```angular-ts * @Component({ * selector: 'posts', * template: ` * @if (combined().isPending) { * Loading... * } @else if (combined().isError) { * Error loading posts * } @else { *
    * @for (post of combined().data; track post?.id) { *
  • {{ post?.title }}
  • * } *
* } * `, * }) * export class Posts { * readonly ids = signal([1, 2, 3]) * * readonly combined = injectQueries(() => ({ * queries: this.ids().map((id) => ({ * queryKey: ['post', id], * queryFn: () => fetchPost(id), * })), * combine: (postQueries) => ({ * data: postQueries.map((query) => query.data), * isPending: postQueries.some((query) => query.isPending), * isError: postQueries.some((query) => query.isError), * }), * })) * } * ``` * * @example * Typing `select` via {@link queryOptions}. Note that spreading a `queryOptions` result and overriding * `select` inline still falls back to `unknown` — wrap the spread in `queryOptions` again so the override is * resolved before it reaches `injectQueries`: * ```angular-ts * const postOptions = (id: number) => * queryOptions({ * queryKey: ['post', id], * queryFn: () => fetchPost(id), * }) * * @Component({ * selector: 'post-title', * template: `

{{ fixed()[0].data() }}

`, * }) * export class PostTitle { * readonly id = signal(1) * * readonly broken = injectQueries(() => ({ * queries: [ * { * ...postOptions(this.id()), * // ❌ `data` is `unknown` here * select: (data) => data.title, * }, * ], * })) * * readonly fixed = injectQueries(() => ({ * queries: [ * queryOptions({ * ...postOptions(this.id()), * // ✅ `data` is `Post` * select: (data) => data.title, * }), * ], * })) * } * ``` */ export declare function injectQueries, TCombinedResult = QueriesResults>(optionsFn: () => InjectQueriesOptions, injector?: Injector): Signal; export {};