{"version":3,"file":"inject-queries.mjs","sources":["../src/inject-queries.ts"],"sourcesContent":["import {\n  QueriesObserver,\n  QueryClient,\n  notifyManager,\n} from '@tanstack/query-core'\nimport {\n  DestroyRef,\n  Injector,\n  NgZone,\n  assertInInjectionContext,\n  computed,\n  effect,\n  inject,\n  runInInjectionContext,\n  signal,\n  untracked,\n} from '@angular/core'\nimport { signalProxy } from './signal-proxy'\nimport { injectIsRestoring } from './inject-is-restoring'\nimport type {\n  DefaultError,\n  OmitKeyof,\n  QueriesObserverOptions,\n  QueriesPlaceholderDataFunction,\n  QueryFunction,\n  QueryKey,\n  QueryObserverOptions,\n  ThrowOnError,\n} from '@tanstack/query-core'\nimport type {\n  CreateQueryOptions,\n  CreateQueryResult,\n  DefinedCreateQueryResult,\n} from './types'\nimport type { Signal } from '@angular/core'\n\n// This defines the `CreateQueryOptions` that are accepted in `QueriesOptions` & `GetOptions`.\n// `placeholderData` function always gets undefined passed\ntype QueryObserverOptionsForCreateQueries<\n  TQueryFnData = unknown,\n  TError = DefaultError,\n  TData = TQueryFnData,\n  TQueryKey extends QueryKey = QueryKey,\n> = OmitKeyof<\n  CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey>,\n  'placeholderData'\n> & {\n  placeholderData?: TQueryFnData | QueriesPlaceholderDataFunction<TQueryFnData>\n}\n\n// Avoid TS depth-limit error in case of large array literal\ntype MAXIMUM_DEPTH = 20\n\n// Widen the type of the symbol to enable type inference even if skipToken is not immutable.\ntype SkipTokenForCreateQueries = symbol\n\ntype GetCreateQueryOptionsForCreateQueries<T> =\n  // Part 1: responsible for applying explicit type parameter to function arguments, if object { queryFnData: TQueryFnData, error: TError, data: TData }\n  T extends {\n    queryFnData: infer TQueryFnData\n    error?: infer TError\n    data: infer TData\n  }\n    ? QueryObserverOptionsForCreateQueries<TQueryFnData, TError, TData>\n    : T extends { queryFnData: infer TQueryFnData; error?: infer TError }\n      ? QueryObserverOptionsForCreateQueries<TQueryFnData, TError>\n      : T extends { data: infer TData; error?: infer TError }\n        ? QueryObserverOptionsForCreateQueries<unknown, TError, TData>\n        : // Part 2: responsible for applying explicit type parameter to function arguments, if tuple [TQueryFnData, TError, TData]\n          T extends [infer TQueryFnData, infer TError, infer TData]\n          ? QueryObserverOptionsForCreateQueries<TQueryFnData, TError, TData>\n          : T extends [infer TQueryFnData, infer TError]\n            ? QueryObserverOptionsForCreateQueries<TQueryFnData, TError>\n            : T extends [infer TQueryFnData]\n              ? QueryObserverOptionsForCreateQueries<TQueryFnData>\n              : // Part 3: responsible for inferring and enforcing type if no explicit parameter was provided\n                T extends {\n                    queryFn?:\n                      | QueryFunction<infer TQueryFnData, infer TQueryKey>\n                      | SkipTokenForCreateQueries\n                    select?: (data: any) => infer TData\n                    throwOnError?: ThrowOnError<any, infer TError, any, any>\n                  }\n                ? QueryObserverOptionsForCreateQueries<\n                    TQueryFnData,\n                    unknown extends TError ? DefaultError : TError,\n                    unknown extends TData ? TQueryFnData : TData,\n                    TQueryKey\n                  >\n                : // Fallback\n                  QueryObserverOptionsForCreateQueries\n\n// A defined initialData setting should return a DefinedCreateQueryResult rather than CreateQueryResult\ntype GetDefinedOrUndefinedQueryResult<T, TData, TError = unknown> = T extends {\n  initialData?: infer TInitialData\n}\n  ? unknown extends TInitialData\n    ? CreateQueryResult<TData, TError>\n    : TInitialData extends TData\n      ? DefinedCreateQueryResult<TData, TError>\n      : TInitialData extends () => infer TInitialDataResult\n        ? unknown extends TInitialDataResult\n          ? CreateQueryResult<TData, TError>\n          : TInitialDataResult extends TData\n            ? DefinedCreateQueryResult<TData, TError>\n            : CreateQueryResult<TData, TError>\n        : CreateQueryResult<TData, TError>\n  : CreateQueryResult<TData, TError>\n\ntype GetCreateQueryResult<T> =\n  // Part 1: responsible for mapping explicit type parameter to function result, if object\n  T extends { queryFnData: any; error?: infer TError; data: infer TData }\n    ? GetDefinedOrUndefinedQueryResult<T, TData, TError>\n    : T extends { queryFnData: infer TQueryFnData; error?: infer TError }\n      ? GetDefinedOrUndefinedQueryResult<T, TQueryFnData, TError>\n      : T extends { data: infer TData; error?: infer TError }\n        ? GetDefinedOrUndefinedQueryResult<T, TData, TError>\n        : // Part 2: responsible for mapping explicit type parameter to function result, if tuple\n          T extends [any, infer TError, infer TData]\n          ? GetDefinedOrUndefinedQueryResult<T, TData, TError>\n          : T extends [infer TQueryFnData, infer TError]\n            ? GetDefinedOrUndefinedQueryResult<T, TQueryFnData, TError>\n            : T extends [infer TQueryFnData]\n              ? GetDefinedOrUndefinedQueryResult<T, TQueryFnData>\n              : // Part 3: responsible for mapping inferred type to results, if no explicit parameter was provided\n                T extends {\n                    queryFn?:\n                      | QueryFunction<infer TQueryFnData, any>\n                      | SkipTokenForCreateQueries\n                    select?: (data: any) => infer TData\n                    throwOnError?: ThrowOnError<any, infer TError, any, any>\n                  }\n                ? GetDefinedOrUndefinedQueryResult<\n                    T,\n                    unknown extends TData ? TQueryFnData : TData,\n                    unknown extends TError ? DefaultError : TError\n                  >\n                : // Fallback\n                  CreateQueryResult\n\n/**\n * The `queries` array accepted by `injectQueries`. Recursively unwraps each tuple element so every entry's\n * `queryFn`/`select`/`throwOnError` are inferred individually, up to 20 elements — past that, tuple\n * recursion falls back to a single homogeneous options type. An opaque array (e.g. `unknown[]`) is returned\n * as-is; a non-tuple array of a known element type is mapped to that element type instead, with no such\n * limit.\n *\n * @template T - The type of the `queries` array as written at the call site.\n * @template TResults - The internal accumulator that this type builds during recursion. It is not meant to\n * be set explicitly.\n * @template TDepth - The internal recursion-depth counter, checked against the 20-element limit. It is not\n * meant to be set explicitly.\n */\nexport type QueriesOptions<\n  T extends Array<any>,\n  TResults extends Array<any> = [],\n  TDepth extends ReadonlyArray<number> = [],\n> = TDepth['length'] extends MAXIMUM_DEPTH\n  ? Array<QueryObserverOptionsForCreateQueries>\n  : T extends []\n    ? []\n    : T extends [infer Head]\n      ? [...TResults, GetCreateQueryOptionsForCreateQueries<Head>]\n      : T extends [infer Head, ...infer Tails]\n        ? QueriesOptions<\n            [...Tails],\n            [...TResults, GetCreateQueryOptionsForCreateQueries<Head>],\n            [...TDepth, 1]\n          >\n        : ReadonlyArray<unknown> extends T\n          ? T\n          : // If T is *some* array but we couldn't assign unknown[] to it, then it must hold some known/homogeneous type!\n            // use this to infer the param types in the case of Array.map() argument\n            T extends Array<\n                QueryObserverOptionsForCreateQueries<\n                  infer TQueryFnData,\n                  infer TError,\n                  infer TData,\n                  infer TQueryKey\n                >\n              >\n            ? Array<\n                QueryObserverOptionsForCreateQueries<\n                  TQueryFnData,\n                  TError,\n                  TData,\n                  TQueryKey\n                >\n              >\n            : // Fallback\n              Array<QueryObserverOptionsForCreateQueries>\n\n/**\n * The result type returned by `injectQueries`, when no `combine` is provided. Mirrors {@link QueriesOptions}:\n * each tuple element's result type is inferred individually, up to 20 elements — past that, tuple recursion\n * falls back to a single homogeneous {@link CreateQueryResult} type. A non-tuple array is mapped per-element\n * instead, with no such limit — every entry keeps its individually inferred type regardless of array length.\n *\n * @template T - The type of the `queries` array, as inferred by {@link QueriesOptions}.\n * @template TResults - The internal accumulator that this type builds during recursion. It is not meant to\n * be set explicitly.\n * @template TDepth - The internal recursion-depth counter, checked against the 20-element limit. It is not\n * meant to be set explicitly.\n */\nexport type QueriesResults<\n  T extends Array<any>,\n  TResults extends Array<any> = [],\n  TDepth extends ReadonlyArray<number> = [],\n> = TDepth['length'] extends MAXIMUM_DEPTH\n  ? Array<CreateQueryResult>\n  : T extends []\n    ? []\n    : T extends [infer Head]\n      ? [...TResults, GetCreateQueryResult<Head>]\n      : T extends [infer Head, ...infer Tails]\n        ? QueriesResults<\n            [...Tails],\n            [...TResults, GetCreateQueryResult<Head>],\n            [...TDepth, 1]\n          >\n        : { [K in keyof T]: GetCreateQueryResult<T[K]> }\n\nexport interface InjectQueriesOptions<\n  T extends Array<any>,\n  TCombinedResult = QueriesResults<T>,\n> {\n  queries:\n    | readonly [...QueriesOptions<T>]\n    | readonly [\n        ...{ [K in keyof T]: GetCreateQueryOptionsForCreateQueries<T[K]> },\n      ]\n  combine?: (result: QueriesResults<T>) => TCombinedResult\n}\n\n/**\n * Injects a signal to fetch a variable number of queries.\n *\n * The `queries` key accepts an array with query option objects mostly identical to `injectQuery`'s. Having\n * the same query key more than once in the array of query objects may cause some data to be shared between\n * queries. To avoid this, consider de-duplicating the queries and map the results back to the desired\n * structure.\n *\n * The `combine` option can be used to combine the results of the queries into a single value. The result\n * will be structurally shared to be as referentially stable as possible.\n *\n * @remarks Unlike `injectQuery`, `injectQueries` cannot infer the `data` argument of an _inline_ `select`\n * from its sibling `queryFn`. Because `injectQueries` infers the type of the whole `queries` array at once,\n * the `select` parameter of a query object written inline cannot be contextually typed from that same\n * object's `queryFn`, so it falls back to `unknown` — a\n * [known TypeScript limitation](https://github.com/TanStack/query/issues/6556). Annotate the `select`\n * parameter explicitly, or define the query with {@link queryOptions}, which resolves its types in a single\n * object _before_ it reaches `injectQueries`, to work around this — see the example below.\n * @param optionsFn - A function returning the queries' options — an array of query option objects under\n * `queries`, and an optional `combine`. Similar to `computed` from Angular, this function runs in the\n * reactive context, so signals read inside it (e.g. to build the `queries` array) drive the queries.\n * @param injector - The `Injector` in which to create the queries. If this is not provided, the current\n * injection context will be used instead (via `inject`).\n * @returns A `Signal` with the combined result. Without `combine`, this is an array with all the query\n * results, in the same order as the input. When `combine` is provided, this is the value returned by\n * `combine` instead.\n *\n * @example\n * ```angular-ts\n * @Component({\n *   selector: 'posts',\n *   template: `\n *     <ul>\n *       @for (query of postQueries(); track $index) {\n *         @if (query.isPending()) {\n *           <li>Loading...</li>\n *         } @else if (query.isError()) {\n *           <li>Error: {{ query.error()?.message }}</li>\n *         } @else {\n *           <li>{{ query.data().title }}</li>\n *         }\n *       }\n *     </ul>\n *   `,\n * })\n * export class Posts {\n *   readonly ids = signal([1, 2, 3])\n *\n *   readonly postQueries = injectQueries(() => ({\n *     queries: this.ids().map((id) => ({\n *       queryKey: ['post', id],\n *       queryFn: () => fetchPost(id),\n *       staleTime: Infinity,\n *     })),\n *   }))\n * }\n * ```\n *\n * @example\n * Combining results into a single value:\n * ```angular-ts\n * @Component({\n *   selector: 'posts',\n *   template: `\n *     @if (combined().isPending) {\n *       Loading...\n *     } @else if (combined().isError) {\n *       Error loading posts\n *     } @else {\n *       <ul>\n *         @for (post of combined().data; track post?.id) {\n *           <li>{{ post?.title }}</li>\n *         }\n *       </ul>\n *     }\n *   `,\n * })\n * export class Posts {\n *   readonly ids = signal([1, 2, 3])\n *\n *   readonly combined = injectQueries(() => ({\n *     queries: this.ids().map((id) => ({\n *       queryKey: ['post', id],\n *       queryFn: () => fetchPost(id),\n *     })),\n *     combine: (postQueries) => ({\n *       data: postQueries.map((query) => query.data),\n *       isPending: postQueries.some((query) => query.isPending),\n *       isError: postQueries.some((query) => query.isError),\n *     }),\n *   }))\n * }\n * ```\n *\n * @example\n * Typing `select` via {@link queryOptions}. Note that spreading a `queryOptions` result and overriding\n * `select` inline still falls back to `unknown` — wrap the spread in `queryOptions` again so the override is\n * resolved before it reaches `injectQueries`:\n * ```angular-ts\n * const postOptions = (id: number) =>\n *   queryOptions({\n *     queryKey: ['post', id],\n *     queryFn: () => fetchPost(id),\n *   })\n *\n * @Component({\n *   selector: 'post-title',\n *   template: `<h1>{{ fixed()[0].data() }}</h1>`,\n * })\n * export class PostTitle {\n *   readonly id = signal(1)\n *\n *   readonly broken = injectQueries(() => ({\n *     queries: [\n *       {\n *         ...postOptions(this.id()),\n *         // ❌ `data` is `unknown` here\n *         select: (data) => data.title,\n *       },\n *     ],\n *   }))\n *\n *   readonly fixed = injectQueries(() => ({\n *     queries: [\n *       queryOptions({\n *         ...postOptions(this.id()),\n *         // ✅ `data` is `Post`\n *         select: (data) => data.title,\n *       }),\n *     ],\n *   }))\n * }\n * ```\n */\nexport function injectQueries<\n  T extends Array<any>,\n  TCombinedResult = QueriesResults<T>,\n>(\n  optionsFn: () => InjectQueriesOptions<T, TCombinedResult>,\n  injector?: Injector,\n): Signal<TCombinedResult> {\n  !injector && assertInInjectionContext(injectQueries)\n  return runInInjectionContext(injector ?? inject(Injector), () => {\n    const destroyRef = inject(DestroyRef)\n    const ngZone = inject(NgZone)\n    const queryClient = inject(QueryClient)\n    const isRestoring = injectIsRestoring()\n\n    /**\n     * Signal that has the default options from query client applied\n     * computed() is used so signals can be inserted into the options\n     * making it reactive. Wrapping options in a function ensures embedded expressions\n     * are preserved and can keep being applied after signal changes\n     */\n    const optionsSignal = computed(() => {\n      return optionsFn()\n    })\n\n    const defaultedQueries = computed(() => {\n      return optionsSignal().queries.map((opts) => {\n        const defaultedOptions = queryClient.defaultQueryOptions(\n          opts as QueryObserverOptions,\n        )\n        // Make sure the results are already in fetching state before subscribing or updating options\n        defaultedOptions._optimisticResults = isRestoring()\n          ? 'isRestoring'\n          : 'optimistic'\n\n        return defaultedOptions as QueryObserverOptions\n      })\n    })\n\n    const observerSignal = (() => {\n      let instance: QueriesObserver<TCombinedResult> | null = null\n\n      return computed(() => {\n        return (instance ||= new QueriesObserver<TCombinedResult>(\n          queryClient,\n          defaultedQueries(),\n          optionsSignal() as QueriesObserverOptions<TCombinedResult>,\n        ))\n      })\n    })()\n\n    const optimisticResultSignal = computed(() =>\n      observerSignal().getOptimisticResult(\n        defaultedQueries(),\n        (optionsSignal() as QueriesObserverOptions<TCombinedResult>).combine,\n      ),\n    )\n\n    // Do not notify on updates because of changes in the options because\n    // these changes should already be reflected in the optimistic result.\n    effect(() => {\n      observerSignal().setQueries(\n        defaultedQueries(),\n        optionsSignal() as QueriesObserverOptions<TCombinedResult>,\n      )\n    })\n\n    const optimisticCombinedResultSignal = computed(() => {\n      const [_optimisticResult, getCombinedResult, trackResult] =\n        optimisticResultSignal()\n      return getCombinedResult(trackResult())\n    })\n\n    const resultFromSubscriberSignal = signal<TCombinedResult | null>(null)\n\n    effect(() => {\n      const observer = observerSignal()\n      const [_optimisticResult, getCombinedResult] = optimisticResultSignal()\n\n      untracked(() => {\n        const unsubscribe = isRestoring()\n          ? () => undefined\n          : ngZone.runOutsideAngular(() =>\n              observer.subscribe(\n                notifyManager.batchCalls((state) => {\n                  resultFromSubscriberSignal.set(getCombinedResult(state))\n                }),\n              ),\n            )\n\n        destroyRef.onDestroy(unsubscribe)\n      })\n    })\n\n    const resultSignal = computed(() => {\n      const subscriberResult = resultFromSubscriberSignal()\n      const optimisticResult = optimisticCombinedResultSignal()\n      return subscriberResult ?? optimisticResult\n    })\n\n    return computed(() => {\n      const result = resultSignal()\n      const { combine } = optionsSignal()\n\n      return combine\n        ? result\n        : (result as QueriesResults<T>).map((query) =>\n            signalProxy(signal(query)),\n          )\n    })\n  }) as unknown as Signal<TCombinedResult>\n}\n"],"names":[],"mappings":";;;;AAgXO,SAAS,cAId,WACA,UACyB;AACzB,GAAC,YAAY,yBAAyB,aAAa;AACnD,SAAO,sBAAsB,YAAY,OAAO,QAAQ,GAAG,MAAM;AAC/D,UAAM,aAAa,OAAO,UAAU;AACpC,UAAM,SAAS,OAAO,MAAM;AAC5B,UAAM,cAAc,OAAO,WAAW;AACtC,UAAM,cAAc,kBAAA;AAQpB,UAAM,gBAAgB,SAAS,MAAM;AACnC,aAAO,UAAA;AAAA,IACT,CAAC;AAED,UAAM,mBAAmB,SAAS,MAAM;AACtC,aAAO,cAAA,EAAgB,QAAQ,IAAI,CAAC,SAAS;AAC3C,cAAM,mBAAmB,YAAY;AAAA,UACnC;AAAA,QAAA;AAGF,yBAAiB,qBAAqB,YAAA,IAClC,gBACA;AAEJ,eAAO;AAAA,MACT,CAAC;AAAA,IACH,CAAC;AAED,UAAM,kBAAkB,MAAM;AAC5B,UAAI,WAAoD;AAExD,aAAO,SAAS,MAAM;AACpB,eAAQ,wBAAa,IAAI;AAAA,UACvB;AAAA,UACA,iBAAA;AAAA,UACA,cAAA;AAAA,QAAc;AAAA,MAElB,CAAC;AAAA,IACH,GAAA;AAEA,UAAM,yBAAyB;AAAA,MAAS,MACtC,iBAAiB;AAAA,QACf,iBAAA;AAAA,QACC,gBAA4D;AAAA,MAAA;AAAA,IAC/D;AAKF,WAAO,MAAM;AACX,qBAAA,EAAiB;AAAA,QACf,iBAAA;AAAA,QACA,cAAA;AAAA,MAAc;AAAA,IAElB,CAAC;AAED,UAAM,iCAAiC,SAAS,MAAM;AACpD,YAAM,CAAC,mBAAmB,mBAAmB,WAAW,IACtD,uBAAA;AACF,aAAO,kBAAkB,aAAa;AAAA,IACxC,CAAC;AAED,UAAM,6BAA6B,OAA+B,IAAI;AAEtE,WAAO,MAAM;AACX,YAAM,WAAW,eAAA;AACjB,YAAM,CAAC,mBAAmB,iBAAiB,IAAI,uBAAA;AAE/C,gBAAU,MAAM;AACd,cAAM,cAAc,YAAA,IAChB,MAAM,SACN,OAAO;AAAA,UAAkB,MACvB,SAAS;AAAA,YACP,cAAc,WAAW,CAAC,UAAU;AAClC,yCAA2B,IAAI,kBAAkB,KAAK,CAAC;AAAA,YACzD,CAAC;AAAA,UAAA;AAAA,QACH;AAGN,mBAAW,UAAU,WAAW;AAAA,MAClC,CAAC;AAAA,IACH,CAAC;AAED,UAAM,eAAe,SAAS,MAAM;AAClC,YAAM,mBAAmB,2BAAA;AACzB,YAAM,mBAAmB,+BAAA;AACzB,aAAO,oBAAoB;AAAA,IAC7B,CAAC;AAED,WAAO,SAAS,MAAM;AACpB,YAAM,SAAS,aAAA;AACf,YAAM,EAAE,QAAA,IAAY,cAAA;AAEpB,aAAO,UACH,SACC,OAA6B;AAAA,QAAI,CAAC,UACjC,YAAY,OAAO,KAAK,CAAC;AAAA,MAAA;AAAA,IAEjC,CAAC;AAAA,EACH,CAAC;AACH;"}