{"version":3,"file":"inject-infinite-query.mjs","sources":["../src/inject-infinite-query.ts"],"sourcesContent":["import { InfiniteQueryObserver } from '@tanstack/query-core'\nimport {\n  Injector,\n  assertInInjectionContext,\n  inject,\n  runInInjectionContext,\n} from '@angular/core'\nimport { createBaseQuery } from './create-base-query'\nimport type {\n  DefaultError,\n  InfiniteData,\n  QueryKey,\n  QueryObserver,\n} from '@tanstack/query-core'\nimport type {\n  CreateInfiniteQueryOptions,\n  CreateInfiniteQueryResult,\n  DefinedCreateInfiniteQueryResult,\n} from './types'\nimport type {\n  DefinedInitialDataInfiniteOptions,\n  UndefinedInitialDataInfiniteOptions,\n} from './infinite-query-options'\n\nexport interface InjectInfiniteQueryOptions {\n  /**\n   * The `Injector` in which to create the infinite query.\n   *\n   * If this is not provided, the current injection context will be used instead (via `inject`).\n   */\n  injector?: Injector\n}\n\n/**\n * The options for `injectInfiniteQuery` are identical to `injectQuery`, with the addition of\n * `initialPageParam`, `getNextPageParam`, `getPreviousPageParam`, and `maxPages`. Infinite queries can\n * additively \"load more\" data onto an existing set of data, or \"infinite scroll\".\n *\n * This overload is selected when `initialData` is set on the options returned by `injectInfiniteQueryFn`,\n * so the resulting `data` signal is never `undefined` (unless a `select` changes `TData` to include `undefined`).\n *\n * @remarks Keep in mind that imperative fetch calls, such as `fetchNextPage`, may interfere with the default\n * refetch behavior, resulting in outdated data. Make sure to call these functions only in response to user\n * actions, or add conditions like `hasNextPage() && !isFetching()`.\n * @see {@link infiniteQueryOptions} to share these options between `injectInfiniteQuery` and imperative APIs\n * like `queryClient.fetchInfiniteQuery`.\n * @param injectInfiniteQueryFn - A function returning the {@link DefinedInitialDataInfiniteOptions} to use —\n * everything you can pass to `injectInfiniteQuery`, with `initialData` set. Similar to `computed` from\n * Angular, this function runs in the reactive context, so signals read inside it drive the query.\n * @param options - Additional configuration.\n * @returns The same signals as `injectQuery`, with the addition of `fetchNextPage`, `fetchPreviousPage`,\n * `hasNextPage`, `hasPreviousPage`, `isFetchingNextPage`, and `isFetchingPreviousPage`. `data().pages` and\n * `data().pageParams` are also added, as long as a `select` doesn't change `TData` away from its default\n * `InfiniteData<TQueryFnData>` shape.\n *\n * @example\n * ```angular-ts\n * @Component({\n *   selector: 'projects',\n *   template: `\n *     <!-- `projectsQuery.data()` is never `undefined`, thanks to `initialData` — even if a\n *     refetch fails, so the list stays visible alongside the error. -->\n *     <ul>\n *       @for (page of projectsQuery.data().pages; track $index) {\n *         @for (project of page.projects; track project.id) {\n *           <li>{{ project.name }}</li>\n *         }\n *       }\n *     </ul>\n *   `,\n * })\n * export class Projects {\n *   readonly projectsQuery = injectInfiniteQuery(() => ({\n *     queryKey: ['projects'],\n *     queryFn: ({ pageParam }) => fetchProjects(pageParam),\n *     initialPageParam: 0,\n *     getNextPageParam: (lastPage) => lastPage.nextId,\n *     initialData: { pages: [], pageParams: [] },\n *   }))\n * }\n * ```\n */\nexport function injectInfiniteQuery<\n  TQueryFnData,\n  TError = DefaultError,\n  TData = InfiniteData<TQueryFnData>,\n  TQueryKey extends QueryKey = QueryKey,\n  TPageParam = unknown,\n>(\n  injectInfiniteQueryFn: () => DefinedInitialDataInfiniteOptions<\n    TQueryFnData,\n    TError,\n    TData,\n    TQueryKey,\n    TPageParam\n  >,\n  options?: InjectInfiniteQueryOptions,\n): DefinedCreateInfiniteQueryResult<TData, TError>\n\n/**\n * Injects an infinite query: a declarative dependency on an asynchronous source of data that is tied to a\n * unique key. Infinite queries can additively \"load more\" data onto an existing set of data, or\n * \"infinite scroll\".\n *\n * @remarks Keep in mind that imperative fetch calls, such as `fetchNextPage`, may interfere with the default\n * refetch behavior, resulting in outdated data. Make sure to call these functions only in response to user\n * actions, or add conditions like `hasNextPage() && !isFetching()`. This is the only overload that accepts\n * `queryFn: skipToken`, shown below.\n * @see {@link infiniteQueryOptions} to share these options between `injectInfiniteQuery` and imperative APIs\n * like `queryClient.fetchInfiniteQuery`.\n * @param injectInfiniteQueryFn - A function returning the {@link UndefinedInitialDataInfiniteOptions} to use\n * — everything you can pass to `injectInfiniteQuery`. Similar to `computed` from Angular, this function runs\n * in the reactive context, so signals read inside it drive the query.\n * @param options - Additional configuration.\n * @returns The same signals as `injectQuery`, with the addition of `fetchNextPage`, `fetchPreviousPage`,\n * `hasNextPage`, `hasPreviousPage`, `isFetchingNextPage`, and `isFetchingPreviousPage`. `data().pages` and\n * `data().pageParams` are also added, as long as a `select` doesn't change `TData` away from its default\n * `InfiniteData<TQueryFnData>` shape.\n *\n * @example\n * Fetching the next page from a button click:\n * ```angular-ts\n * @Component({\n *   selector: 'projects-list',\n *   template: `\n *     <ul>\n *       @for (page of projectsQuery.data()?.pages; track $index) {\n *         @for (project of page.projects; track project.id) {\n *           <li>{{ project.name }}</li>\n *         }\n *       }\n *     </ul>\n *     <button\n *       [disabled]=\"!projectsQuery.hasNextPage() || projectsQuery.isFetching()\"\n *       (click)=\"projectsQuery.fetchNextPage()\"\n *     >\n *       Load More\n *     </button>\n *   `,\n * })\n * export class ProjectsList {\n *   readonly projectsQuery = injectInfiniteQuery(() => ({\n *     queryKey: ['projects'],\n *     queryFn: ({ pageParam }) => fetchProjects(pageParam),\n *     initialPageParam: 0,\n *     getNextPageParam: (lastPage) => lastPage.nextId,\n *   }))\n * }\n * ```\n *\n * @example\n * Fetching the next page automatically as the user scrolls, using an `IntersectionObserver` on a sentinel\n * element after the list:\n * ```angular-ts\n * @Component({\n *   selector: 'projects-list',\n *   template: `\n *     <ul>\n *       @for (page of projectsQuery.data()?.pages; track $index) {\n *         @for (project of page.projects; track project.id) {\n *           <li>{{ project.name }}</li>\n *         }\n *       }\n *     </ul>\n *     <div #sentinel>{{ projectsQuery.isFetchingNextPage() ? 'Loading more...' : '' }}</div>\n *   `,\n * })\n * export class ProjectsList {\n *   readonly sentinel = viewChild<ElementRef<HTMLElement>>('sentinel')\n *\n *   readonly projectsQuery = injectInfiniteQuery(() => ({\n *     queryKey: ['projects'],\n *     queryFn: ({ pageParam }) => fetchProjects(pageParam),\n *     initialPageParam: 0,\n *     getNextPageParam: (lastPage) => lastPage.nextId,\n *   }))\n *\n *   constructor() {\n *     effect((onCleanup) => {\n *       const sentinel = this.sentinel()?.nativeElement\n *       if (\n *         sentinel == null ||\n *         !this.projectsQuery.hasNextPage() ||\n *         this.projectsQuery.isFetching()\n *       ) {\n *         return\n *       }\n *\n *       const observer = new IntersectionObserver(([entry]) => {\n *         if (entry?.isIntersecting) this.projectsQuery.fetchNextPage()\n *       })\n *       observer.observe(sentinel)\n *\n *       onCleanup(() => observer.disconnect())\n *     })\n *   }\n * }\n * ```\n *\n * @example\n * A query that's disabled, type safe, until `postId` is set — pass `skipToken` as `queryFn` instead of\n * setting `enabled: false`:\n * ```angular-ts\n * @Component({\n *   selector: 'comments',\n *   template: `\n *     @if (postId() == null) {\n *       Select a post\n *     } @else if (commentsQuery.isPending()) {\n *       Loading...\n *     } @else if (commentsQuery.isError()) {\n *       <span>Error: {{ commentsQuery.error()?.message }}</span>\n *     } @else {\n *       <ul>\n *         @for (page of commentsQuery.data().pages; track $index) {\n *           @for (comment of page.comments; track comment.id) {\n *             <li>{{ comment.text }}</li>\n *           }\n *         }\n *       </ul>\n *     }\n *   `,\n * })\n * export class Comments {\n *   readonly postId = signal<string | undefined>(undefined)\n *\n *   readonly commentsQuery = injectInfiniteQuery(() => ({\n *     queryKey: ['post', this.postId(), 'comments'],\n *     queryFn:\n *       this.postId() != null\n *         ? ({ pageParam }) => fetchComments(this.postId()!, pageParam)\n *         : skipToken,\n *     initialPageParam: 0,\n *     getNextPageParam: (lastPage) => lastPage.nextId,\n *   }))\n * }\n * ```\n */\nexport function injectInfiniteQuery<\n  TQueryFnData,\n  TError = DefaultError,\n  TData = InfiniteData<TQueryFnData>,\n  TQueryKey extends QueryKey = QueryKey,\n  TPageParam = unknown,\n>(\n  injectInfiniteQueryFn: () => UndefinedInitialDataInfiniteOptions<\n    TQueryFnData,\n    TError,\n    TData,\n    TQueryKey,\n    TPageParam\n  >,\n  options?: InjectInfiniteQueryOptions,\n): CreateInfiniteQueryResult<TData, TError>\n\n/**\n * This overload accepts the general {@link CreateInfiniteQueryOptions} shape rather than the\n * `initialData`-aware overloads above, so whether `data` is defined can't be inferred from the call site —\n * useful when wrapping `injectInfiniteQuery` in your own helper function that forwards caller-provided\n * options.\n *\n * @param injectInfiniteQueryFn - A function that returns infinite query options. Similar to `computed` from\n * Angular, this function runs in the reactive context, so signals read inside it drive the query.\n * @param options - Additional configuration.\n * @returns The infinite query result.\n */\nexport function injectInfiniteQuery<\n  TQueryFnData,\n  TError = DefaultError,\n  TData = InfiniteData<TQueryFnData>,\n  TQueryKey extends QueryKey = QueryKey,\n  TPageParam = unknown,\n>(\n  injectInfiniteQueryFn: () => CreateInfiniteQueryOptions<\n    TQueryFnData,\n    TError,\n    TData,\n    TQueryKey,\n    TPageParam\n  >,\n  options?: InjectInfiniteQueryOptions,\n): CreateInfiniteQueryResult<TData, TError>\n\nexport function injectInfiniteQuery(\n  injectInfiniteQueryFn: () => CreateInfiniteQueryOptions,\n  options?: InjectInfiniteQueryOptions,\n) {\n  !options?.injector && assertInInjectionContext(injectInfiniteQuery)\n  const injector = options?.injector ?? inject(Injector)\n  return runInInjectionContext(injector, () =>\n    createBaseQuery(\n      injectInfiniteQueryFn,\n      InfiniteQueryObserver as typeof QueryObserver,\n    ),\n  )\n}\n"],"names":[],"mappings":";;;AA2RO,SAAS,oBACd,uBACA,SACA;AACA,IAAC,mCAAS,aAAY,yBAAyB,mBAAmB;AAClE,QAAM,YAAW,mCAAS,aAAY,OAAO,QAAQ;AACrD,SAAO;AAAA,IAAsB;AAAA,IAAU,MACrC;AAAA,MACE;AAAA,MACA;AAAA,IAAA;AAAA,EACF;AAEJ;"}