@ttt-productions/query-core 0.14.22 → 0.14.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +6 -6
  2. package/dist/defaults.d.ts +0 -15
  3. package/dist/defaults.d.ts.map +1 -1
  4. package/dist/defaults.js +0 -15
  5. package/dist/defaults.js.map +1 -1
  6. package/dist/firestore/types.d.ts +1 -49
  7. package/dist/firestore/types.d.ts.map +1 -1
  8. package/dist/firestore/types.js.map +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +0 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/keys.d.ts +0 -22
  13. package/dist/keys.d.ts.map +1 -1
  14. package/dist/keys.js +0 -4
  15. package/dist/keys.js.map +1 -1
  16. package/dist/react/cache-helpers.d.ts +0 -32
  17. package/dist/react/cache-helpers.d.ts.map +1 -1
  18. package/dist/react/cache-helpers.js +0 -28
  19. package/dist/react/cache-helpers.js.map +1 -1
  20. package/dist/react/defaults.d.ts +0 -4
  21. package/dist/react/defaults.d.ts.map +1 -1
  22. package/dist/react/defaults.js +2 -6
  23. package/dist/react/defaults.js.map +1 -1
  24. package/dist/react/domain-events/cache-invalidation.d.ts +0 -37
  25. package/dist/react/domain-events/cache-invalidation.d.ts.map +1 -1
  26. package/dist/react/domain-events/cache-invalidation.js +4 -53
  27. package/dist/react/domain-events/cache-invalidation.js.map +1 -1
  28. package/dist/react/domain-events/create-domain-event-invalidator.d.ts +0 -18
  29. package/dist/react/domain-events/create-domain-event-invalidator.d.ts.map +1 -1
  30. package/dist/react/domain-events/create-domain-event-invalidator.js +0 -5
  31. package/dist/react/domain-events/create-domain-event-invalidator.js.map +1 -1
  32. package/dist/react/firestore/absence-scheduler.d.ts +0 -28
  33. package/dist/react/firestore/absence-scheduler.d.ts.map +1 -1
  34. package/dist/react/firestore/absence-scheduler.js +1 -39
  35. package/dist/react/firestore/absence-scheduler.js.map +1 -1
  36. package/dist/react/firestore/batch-doc-loader.d.ts +0 -30
  37. package/dist/react/firestore/batch-doc-loader.d.ts.map +1 -1
  38. package/dist/react/firestore/batch-doc-loader.js +0 -34
  39. package/dist/react/firestore/batch-doc-loader.js.map +1 -1
  40. package/dist/react/firestore/context.d.ts +0 -19
  41. package/dist/react/firestore/context.d.ts.map +1 -1
  42. package/dist/react/firestore/context.js +0 -19
  43. package/dist/react/firestore/context.js.map +1 -1
  44. package/dist/react/firestore/doc-subscription-registry.d.ts +0 -23
  45. package/dist/react/firestore/doc-subscription-registry.d.ts.map +1 -1
  46. package/dist/react/firestore/doc-subscription-registry.js +0 -25
  47. package/dist/react/firestore/doc-subscription-registry.js.map +1 -1
  48. package/dist/react/firestore/infinite-helpers.d.ts +0 -12
  49. package/dist/react/firestore/infinite-helpers.d.ts.map +1 -1
  50. package/dist/react/firestore/infinite-helpers.js +0 -12
  51. package/dist/react/firestore/infinite-helpers.js.map +1 -1
  52. package/dist/react/firestore/resubscribe.d.ts +0 -20
  53. package/dist/react/firestore/resubscribe.d.ts.map +1 -1
  54. package/dist/react/firestore/resubscribe.js +0 -20
  55. package/dist/react/firestore/resubscribe.js.map +1 -1
  56. package/dist/react/firestore/subscription-status.d.ts +0 -29
  57. package/dist/react/firestore/subscription-status.d.ts.map +1 -1
  58. package/dist/react/firestore/subscription-status.js +1 -34
  59. package/dist/react/firestore/subscription-status.js.map +1 -1
  60. package/dist/react/firestore/types.d.ts +0 -64
  61. package/dist/react/firestore/types.d.ts.map +1 -1
  62. package/dist/react/firestore/types.js +0 -3
  63. package/dist/react/firestore/types.js.map +1 -1
  64. package/dist/react/firestore/use-firestore-collection.d.ts +0 -20
  65. package/dist/react/firestore/use-firestore-collection.d.ts.map +1 -1
  66. package/dist/react/firestore/use-firestore-collection.js +1 -35
  67. package/dist/react/firestore/use-firestore-collection.js.map +1 -1
  68. package/dist/react/firestore/use-firestore-count.d.ts +0 -15
  69. package/dist/react/firestore/use-firestore-count.d.ts.map +1 -1
  70. package/dist/react/firestore/use-firestore-count.js +0 -15
  71. package/dist/react/firestore/use-firestore-count.js.map +1 -1
  72. package/dist/react/firestore/use-firestore-doc.d.ts +0 -23
  73. package/dist/react/firestore/use-firestore-doc.d.ts.map +1 -1
  74. package/dist/react/firestore/use-firestore-doc.js +3 -40
  75. package/dist/react/firestore/use-firestore-doc.js.map +1 -1
  76. package/dist/react/firestore/use-firestore-infinite.d.ts +0 -29
  77. package/dist/react/firestore/use-firestore-infinite.d.ts.map +1 -1
  78. package/dist/react/firestore/use-firestore-infinite.js +0 -32
  79. package/dist/react/firestore/use-firestore-infinite.js.map +1 -1
  80. package/dist/react/firestore/use-firestore-live-infinite.d.ts +0 -34
  81. package/dist/react/firestore/use-firestore-live-infinite.d.ts.map +1 -1
  82. package/dist/react/firestore/use-firestore-live-infinite.js +1 -35
  83. package/dist/react/firestore/use-firestore-live-infinite.js.map +1 -1
  84. package/dist/react/firestore/use-firestore-paginated.d.ts +0 -30
  85. package/dist/react/firestore/use-firestore-paginated.d.ts.map +1 -1
  86. package/dist/react/firestore/use-firestore-paginated.js +2 -33
  87. package/dist/react/firestore/use-firestore-paginated.js.map +1 -1
  88. package/dist/react/firestore/useBatchFirestoreDocs.d.ts +1 -123
  89. package/dist/react/firestore/useBatchFirestoreDocs.d.ts.map +1 -1
  90. package/dist/react/firestore/useBatchFirestoreDocs.js +5 -58
  91. package/dist/react/firestore/useBatchFirestoreDocs.js.map +1 -1
  92. package/dist/react/index.d.ts.map +1 -1
  93. package/dist/react/index.js +0 -13
  94. package/dist/react/index.js.map +1 -1
  95. package/dist/react/pending-mutations.d.ts +1 -21
  96. package/dist/react/pending-mutations.d.ts.map +1 -1
  97. package/dist/react/pending-mutations.js +1 -32
  98. package/dist/react/pending-mutations.js.map +1 -1
  99. package/dist/react/provider.d.ts +0 -5
  100. package/dist/react/provider.d.ts.map +1 -1
  101. package/dist/react/provider.js +0 -4
  102. package/dist/react/provider.js.map +1 -1
  103. package/dist/react/query-client.d.ts +0 -4
  104. package/dist/react/query-client.d.ts.map +1 -1
  105. package/dist/react/query-client.js +0 -4
  106. package/dist/react/query-client.js.map +1 -1
  107. package/dist/react/search/types.d.ts +0 -12
  108. package/dist/react/search/types.d.ts.map +1 -1
  109. package/dist/react/search/types.js.map +1 -1
  110. package/dist/react/search/use-firestore-search.d.ts +0 -29
  111. package/dist/react/search/use-firestore-search.d.ts.map +1 -1
  112. package/dist/react/search/use-firestore-search.js +3 -41
  113. package/dist/react/search/use-firestore-search.js.map +1 -1
  114. package/dist/react/use-read-retry-state.d.ts +1 -21
  115. package/dist/react/use-read-retry-state.d.ts.map +1 -1
  116. package/dist/react/use-read-retry-state.js +2 -21
  117. package/dist/react/use-read-retry-state.js.map +1 -1
  118. package/dist/search/rule.d.ts +0 -6
  119. package/dist/search/rule.d.ts.map +1 -1
  120. package/dist/search/rule.js +0 -6
  121. package/dist/search/rule.js.map +1 -1
  122. package/dist/search/types.d.ts +1 -13
  123. package/dist/search/types.d.ts.map +1 -1
  124. package/dist/search/types.js.map +1 -1
  125. package/package.json +2 -2
@@ -3,42 +3,12 @@ import { useInfiniteQuery } from '@tanstack/react-query';
3
3
  import { collection, query, getDocs, limit, startAfter, } from 'firebase/firestore';
4
4
  import { useFirestoreDb } from './context.js';
5
5
  const DEFAULT_PAGE_SIZE = 20;
6
- /**
7
- * Infinite scroll pagination for Firestore collections.
8
- * Automatically handles cursor-based pagination with React Query.
9
- *
10
- * @example
11
- * ```tsx
12
- * const {
13
- * data,
14
- * fetchNextPage,
15
- * hasNextPage,
16
- * isFetchingNextPage,
17
- * isLoading,
18
- * } = useFirestoreInfinite<Post>({
19
- * collectionPath: 'posts',
20
- * queryKey: ['posts', 'feed'],
21
- * constraints: [
22
- * where('status', '==', 'published'),
23
- * orderBy('createdAt', 'desc'),
24
- * ],
25
- * pageSize: 20,
26
- * });
27
- *
28
- * // Flatten pages for rendering
29
- * const posts = data?.pages.flatMap(page => page.items) ?? [];
30
- *
31
- * // Load more on scroll
32
- * <InfiniteScroll onLoadMore={fetchNextPage} hasMore={hasNextPage} />
33
- * ```
34
- */
35
6
  export function useFirestoreInfinite({ collectionPath, queryKey, constraints = [], pageSize = DEFAULT_PAGE_SIZE, enabled = true, staleTime, gcTime, select, }) {
36
7
  const db = useFirestoreDb();
37
8
  return useInfiniteQuery({
38
9
  queryKey,
39
10
  queryFn: async ({ pageParam }) => {
40
11
  const collectionRef = collection(db, collectionPath);
41
- // Build query with constraints
42
12
  const queryConstraints = [
43
13
  ...constraints,
44
14
  ...(pageParam ? [startAfter(pageParam)] : []),
@@ -48,7 +18,6 @@ export function useFirestoreInfinite({ collectionPath, queryKey, constraints = [
48
18
  const snapshot = await getDocs(q);
49
19
  const items = snapshot.docs.map((docSnap) => {
50
20
  const rawData = docSnap.data();
51
- // Include id in data passed to select, so it can be renamed/transformed
52
21
  const dataWithId = { id: docSnap.id, ...rawData };
53
22
  const data = select ? select(dataWithId) : dataWithId;
54
23
  return data;
@@ -61,7 +30,6 @@ export function useFirestoreInfinite({ collectionPath, queryKey, constraints = [
61
30
  },
62
31
  initialPageParam: null,
63
32
  getNextPageParam: (lastPage) => {
64
- // If we got fewer items than requested, there are no more pages
65
33
  if (lastPage.size < pageSize)
66
34
  return undefined;
67
35
  return lastPage.lastDoc;
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-infinite.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-infinite.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,gBAAgB,EAA+B,MAAM,uBAAuB,CAAC;AACtF,OAAO,EACL,UAAU,EACV,KAAK,EACL,OAAO,EACP,KAAK,EACL,UAAU,GAGX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAI9C,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,oBAAoB,CAAwC,EAC1E,cAAc,EACd,QAAQ,EACR,WAAW,GAAG,EAAE,EAChB,QAAQ,GAAG,iBAAiB,EAC5B,OAAO,GAAG,IAAI,EACd,SAAS,EACT,MAAM,EACN,MAAM,GACsB;IAI5B,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IAE5B,OAAO,gBAAgB,CAAC;QACtB,QAAQ;QACR,OAAO,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,EAA4B,EAAE;YACzD,MAAM,aAAa,GAAG,UAAU,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;YAErD,+BAA+B;YAC/B,MAAM,gBAAgB,GAAG;gBACvB,GAAG,WAAW;gBACd,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,SAA6B,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjE,KAAK,CAAC,QAAQ,CAAC;aAChB,CAAC;YAEF,MAAM,CAAC,GAAG,KAAK,CAAC,aAAa,EAAE,GAAG,gBAAgB,CAAC,CAAC;YACpD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;YAElC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;gBAC1C,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;gBAC/B,wEAAwE;gBACxE,MAAM,UAAU,GAAG,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC;gBAClD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;gBACtD,OAAO,IAAiB,CAAC;YAC3B,CAAC,CAAC,CAAC;YAEH,OAAO;gBACL,KAAK;gBACL,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI;gBACxD,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM;aAC3B,CAAC;QACJ,CAAC;QACD,gBAAgB,EAAE,IAA+B;QACjD,gBAAgB,EAAE,CAAC,QAAQ,EAAE,EAAE;YAC7B,gEAAgE;YAChE,IAAI,QAAQ,CAAC,IAAI,GAAG,QAAQ;gBAAE,OAAO,SAAS,CAAC;YAC/C,OAAO,QAAQ,CAAC,OAAO,CAAC;QAC1B,CAAC;QACD,OAAO;QACP,SAAS;QACT,MAAM;KACP,CAAC,CAAC;AACL,CAAC","sourcesContent":["'use client';\n\nimport { useInfiniteQuery, type UseInfiniteQueryResult } from '@tanstack/react-query';\nimport {\n collection,\n query,\n getDocs,\n limit,\n startAfter,\n type DocumentData,\n type DocumentSnapshot,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport type { WithId } from '../../firestore/types.js';\nimport type { FirestoreInfiniteOptions, InfinitePage } from './types.js';\n\nconst DEFAULT_PAGE_SIZE = 20;\n\n/**\n * Infinite scroll pagination for Firestore collections.\n * Automatically handles cursor-based pagination with React Query.\n *\n * @example\n * ```tsx\n * const {\n * data,\n * fetchNextPage,\n * hasNextPage,\n * isFetchingNextPage,\n * isLoading,\n * } = useFirestoreInfinite<Post>({\n * collectionPath: 'posts',\n * queryKey: ['posts', 'feed'],\n * constraints: [\n * where('status', '==', 'published'),\n * orderBy('createdAt', 'desc'),\n * ],\n * pageSize: 20,\n * });\n *\n * // Flatten pages for rendering\n * const posts = data?.pages.flatMap(page => page.items) ?? [];\n *\n * // Load more on scroll\n * <InfiniteScroll onLoadMore={fetchNextPage} hasMore={hasNextPage} />\n * ```\n */\nexport function useFirestoreInfinite<T extends DocumentData = DocumentData>({\n collectionPath,\n queryKey,\n constraints = [],\n pageSize = DEFAULT_PAGE_SIZE,\n enabled = true,\n staleTime,\n gcTime,\n select,\n}: FirestoreInfiniteOptions<T>): UseInfiniteQueryResult<\n { pages: InfinitePage<T>[]; pageParams: (DocumentSnapshot | null)[] },\n Error\n> {\n const db = useFirestoreDb();\n\n return useInfiniteQuery({\n queryKey,\n queryFn: async ({ pageParam }): Promise<InfinitePage<T>> => {\n const collectionRef = collection(db, collectionPath);\n\n // Build query with constraints\n const queryConstraints = [\n ...constraints,\n ...(pageParam ? [startAfter(pageParam as DocumentSnapshot)] : []),\n limit(pageSize),\n ];\n\n const q = query(collectionRef, ...queryConstraints);\n const snapshot = await getDocs(q);\n\n const items = snapshot.docs.map((docSnap) => {\n const rawData = docSnap.data();\n // Include id in data passed to select, so it can be renamed/transformed\n const dataWithId = { id: docSnap.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n return data as WithId<T>;\n });\n\n return {\n items,\n lastDoc: snapshot.docs[snapshot.docs.length - 1] ?? null,\n size: snapshot.docs.length,\n };\n },\n initialPageParam: null as DocumentSnapshot | null,\n getNextPageParam: (lastPage) => {\n // If we got fewer items than requested, there are no more pages\n if (lastPage.size < pageSize) return undefined;\n return lastPage.lastDoc;\n },\n enabled,\n staleTime,\n gcTime,\n });\n}\n"]}
1
+ {"version":3,"file":"use-firestore-infinite.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-infinite.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,gBAAgB,EAA+B,MAAM,uBAAuB,CAAC;AACtF,OAAO,EACL,UAAU,EACV,KAAK,EACL,OAAO,EACP,KAAK,EACL,UAAU,GAGX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAI9C,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B,MAAM,UAAU,oBAAoB,CAAwC,EAC1E,cAAc,EACd,QAAQ,EACR,WAAW,GAAG,EAAE,EAChB,QAAQ,GAAG,iBAAiB,EAC5B,OAAO,GAAG,IAAI,EACd,SAAS,EACT,MAAM,EACN,MAAM,GACsB;IAI5B,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IAE5B,OAAO,gBAAgB,CAAC;QACtB,QAAQ;QACR,OAAO,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,EAA4B,EAAE;YACzD,MAAM,aAAa,GAAG,UAAU,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;YAErD,MAAM,gBAAgB,GAAG;gBACvB,GAAG,WAAW;gBACd,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,SAA6B,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjE,KAAK,CAAC,QAAQ,CAAC;aAChB,CAAC;YAEF,MAAM,CAAC,GAAG,KAAK,CAAC,aAAa,EAAE,GAAG,gBAAgB,CAAC,CAAC;YACpD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;YAElC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;gBAC1C,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;gBAC/B,MAAM,UAAU,GAAG,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC;gBAClD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;gBACtD,OAAO,IAAiB,CAAC;YAC3B,CAAC,CAAC,CAAC;YAEH,OAAO;gBACL,KAAK;gBACL,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI;gBACxD,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM;aAC3B,CAAC;QACJ,CAAC;QACD,gBAAgB,EAAE,IAA+B;QACjD,gBAAgB,EAAE,CAAC,QAAQ,EAAE,EAAE;YAC7B,IAAI,QAAQ,CAAC,IAAI,GAAG,QAAQ;gBAAE,OAAO,SAAS,CAAC;YAC/C,OAAO,QAAQ,CAAC,OAAO,CAAC;QAC1B,CAAC;QACD,OAAO;QACP,SAAS;QACT,MAAM;KACP,CAAC,CAAC;AACL,CAAC","sourcesContent":["'use client';\n\nimport { useInfiniteQuery, type UseInfiniteQueryResult } from '@tanstack/react-query';\nimport {\n collection,\n query,\n getDocs,\n limit,\n startAfter,\n type DocumentData,\n type DocumentSnapshot,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport type { WithId } from '../../firestore/types.js';\nimport type { FirestoreInfiniteOptions, InfinitePage } from './types.js';\n\nconst DEFAULT_PAGE_SIZE = 20;\n\nexport function useFirestoreInfinite<T extends DocumentData = DocumentData>({\n collectionPath,\n queryKey,\n constraints = [],\n pageSize = DEFAULT_PAGE_SIZE,\n enabled = true,\n staleTime,\n gcTime,\n select,\n}: FirestoreInfiniteOptions<T>): UseInfiniteQueryResult<\n { pages: InfinitePage<T>[]; pageParams: (DocumentSnapshot | null)[] },\n Error\n> {\n const db = useFirestoreDb();\n\n return useInfiniteQuery({\n queryKey,\n queryFn: async ({ pageParam }): Promise<InfinitePage<T>> => {\n const collectionRef = collection(db, collectionPath);\n\n const queryConstraints = [\n ...constraints,\n ...(pageParam ? [startAfter(pageParam as DocumentSnapshot)] : []),\n limit(pageSize),\n ];\n\n const q = query(collectionRef, ...queryConstraints);\n const snapshot = await getDocs(q);\n\n const items = snapshot.docs.map((docSnap) => {\n const rawData = docSnap.data();\n const dataWithId = { id: docSnap.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n return data as WithId<T>;\n });\n\n return {\n items,\n lastDoc: snapshot.docs[snapshot.docs.length - 1] ?? null,\n size: snapshot.docs.length,\n };\n },\n initialPageParam: null as DocumentSnapshot | null,\n getNextPageParam: (lastPage) => {\n if (lastPage.size < pageSize) return undefined;\n return lastPage.lastDoc;\n },\n enabled,\n staleTime,\n gcTime,\n });\n}\n"]}
@@ -2,50 +2,16 @@ import { type DocumentData } from 'firebase/firestore';
2
2
  import type { FirestoreSourceState } from '../../firestore/types.js';
3
3
  import type { FirestoreLiveInfiniteOptions } from './types.js';
4
4
  export interface FirestoreLiveInfiniteResult<T> {
5
- /** The current subscription identity's rows — live set plus loaded older pages, ordered per `sort`. */
6
5
  items: T[];
7
- /** True until the current identity's first snapshot arrives, unless its listener has failed. */
8
6
  isInitialLoading: boolean;
9
- /** Liveness of the current identity's listener; `connecting` from that identity's first render. */
10
7
  sourceState: FirestoreSourceState;
11
- /** The listener's failure for the current identity; `null` while connecting, live, or offline. */
12
8
  error: Error | null;
13
- /** Load the next older page; nothing older is read until this is called. Never rejects — a failed read lands in `olderError`. */
14
9
  fetchOlder: () => Promise<void>;
15
- /** Whether older rows exist that are not loaded yet (a one-doc look-ahead, never a full-page guess). */
16
10
  hasOlder: boolean;
17
- /** Whether an older page is being read (including a `retry` of one). */
18
11
  isFetchingOlder: boolean;
19
- /** The last older-page read failed; `null` once an older read succeeds. */
20
12
  olderError: Error | null;
21
- /** Re-run what failed: resubscribe a failed listener, re-read a failed older page. */
22
13
  retry: () => void;
23
14
  }
24
- /**
25
- * A live list (newest rows realtime) with older rows paged in on request, merged into
26
- * one ordered list.
27
- *
28
- * Until the first server-confirmed snapshot that has rows, the listener reads the newest
29
- * `pageSize` rows plus one look-ahead row. That snapshot fixes an anchor — the oldest row
30
- * of the window — and from then on the listener covers anchor → newest with no limit, so
31
- * it grows only with rows that arrive while the list is open, and every older page starts
32
- * after the anchor. Nothing can fall between the live set and the older pages, however
33
- * many rows arrive. Older pages carry the same one-row look-ahead, so `hasOlder` is false
34
- * exactly when nothing older exists. No older page is read until `fetchOlder` asks for one.
35
- *
36
- * Both reads arrive newest first and meet at the anchor, so the merged list is their
37
- * concatenation: a snapshot never re-sorts the rows already loaded.
38
- *
39
- * Everything read is tagged to its subscription identity (path, key, ordering,
40
- * constraints, page size, enabled): a new identity renders empty and connecting in its
41
- * first render, and a retired identity's late callback or older page never lands.
42
- *
43
- * A `permission-denied` listener error resubscribes on the `RESUBSCRIBE_DELAYS_MS` ladder
44
- * before surfacing; any other listener error surfaces at once. Rows already shown are
45
- * kept beside the error — except beside a surfaced `permission-denied`: the reader lost
46
- * access, so the live rows, the anchor, and the loaded older pages are all dropped, and a
47
- * retry reads the list afresh. `db` comes from `<FirestoreProvider>`.
48
- */
49
15
  export declare function useFirestoreLiveInfinite<T = DocumentData & {
50
16
  id: string;
51
17
  }>({ collectionPath, queryKey, orderByField, constraints, pageSize: requestedPageSize, enabled, select, sort, }: FirestoreLiveInfiniteOptions<T>): FirestoreLiveInfiniteResult<T>;
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-live-infinite.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-live-infinite.ts"],"names":[],"mappings":"AAIA,OAAO,EASL,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAI/D,MAAM,WAAW,2BAA2B,CAAC,CAAC;IAC5C,uGAAuG;IACvG,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,gGAAgG;IAChG,gBAAgB,EAAE,OAAO,CAAC;IAC1B,mGAAmG;IACnG,WAAW,EAAE,oBAAoB,CAAC;IAClC,kGAAkG;IAClG,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,iIAAiI;IACjI,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,wGAAwG;IACxG,QAAQ,EAAE,OAAO,CAAC;IAClB,wEAAwE;IACxE,eAAe,EAAE,OAAO,CAAC;IACzB,2EAA2E;IAC3E,UAAU,EAAE,KAAK,GAAG,IAAI,CAAC;IACzB,sFAAsF;IACtF,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AA2BD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,GAAG,YAAY,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,EAC1E,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,WAAgB,EAChB,QAAQ,EAAE,iBAAsB,EAChC,OAAc,EACd,MAAM,EACN,IAAY,GACb,EAAE,4BAA4B,CAAC,CAAC,CAAC,GAAG,2BAA2B,CAAC,CAAC,CAAC,CAuQlE"}
1
+ {"version":3,"file":"use-firestore-live-infinite.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-live-infinite.ts"],"names":[],"mappings":"AAIA,OAAO,EASL,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAI/D,MAAM,WAAW,2BAA2B,CAAC,CAAC;IAC5C,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,oBAAoB,CAAC;IAClC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,UAAU,EAAE,KAAK,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AA2BD,wBAAgB,wBAAwB,CAAC,CAAC,GAAG,YAAY,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,EAC1E,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,WAAgB,EAChB,QAAQ,EAAE,iBAAsB,EAChC,OAAc,EACd,MAAM,EACN,IAAY,GACb,EAAE,4BAA4B,CAAC,CAAC,CAAC,GAAG,2BAA2B,CAAC,CAAC,CAAC,CA8PlE"}
@@ -16,36 +16,10 @@ function freshWindow(identity) {
16
16
  error: null,
17
17
  };
18
18
  }
19
- /**
20
- * A live list (newest rows realtime) with older rows paged in on request, merged into
21
- * one ordered list.
22
- *
23
- * Until the first server-confirmed snapshot that has rows, the listener reads the newest
24
- * `pageSize` rows plus one look-ahead row. That snapshot fixes an anchor — the oldest row
25
- * of the window — and from then on the listener covers anchor → newest with no limit, so
26
- * it grows only with rows that arrive while the list is open, and every older page starts
27
- * after the anchor. Nothing can fall between the live set and the older pages, however
28
- * many rows arrive. Older pages carry the same one-row look-ahead, so `hasOlder` is false
29
- * exactly when nothing older exists. No older page is read until `fetchOlder` asks for one.
30
- *
31
- * Both reads arrive newest first and meet at the anchor, so the merged list is their
32
- * concatenation: a snapshot never re-sorts the rows already loaded.
33
- *
34
- * Everything read is tagged to its subscription identity (path, key, ordering,
35
- * constraints, page size, enabled): a new identity renders empty and connecting in its
36
- * first render, and a retired identity's late callback or older page never lands.
37
- *
38
- * A `permission-denied` listener error resubscribes on the `RESUBSCRIBE_DELAYS_MS` ladder
39
- * before surfacing; any other listener error surfaces at once. Rows already shown are
40
- * kept beside the error — except beside a surfaced `permission-denied`: the reader lost
41
- * access, so the live rows, the anchor, and the loaded older pages are all dropped, and a
42
- * retry reads the list afresh. `db` comes from `<FirestoreProvider>`.
43
- */
44
19
  export function useFirestoreLiveInfinite({ collectionPath, queryKey, orderByField, constraints = [], pageSize: requestedPageSize = 20, enabled = true, select, sort = 'asc', }) {
45
20
  const db = useFirestoreDb();
46
21
  const queryClient = useQueryClient();
47
22
  const pageSize = Math.min(requestedPageSize, MAX_PAGE_SIZE);
48
- // Latest-callback ref: an inline select must not re-subscribe the listener on every render.
49
23
  const selectRef = React.useRef(select);
50
24
  selectRef.current = select;
51
25
  const toEntry = React.useCallback((snap) => {
@@ -53,7 +27,6 @@ export function useFirestoreLiveInfinite({ collectionPath, queryKey, orderByFiel
53
27
  const item = selectRef.current ? selectRef.current(dataWithId) : dataWithId;
54
28
  return { docId: snap.id, item };
55
29
  }, []);
56
- // Constraint objects are fresh every render; their serialized form is the identity.
57
30
  const queryKeyMemo = JSON.stringify(queryKey);
58
31
  const constraintsMemo = JSON.stringify(constraints);
59
32
  const identity = JSON.stringify([
@@ -71,11 +44,9 @@ export function useFirestoreLiveInfinite({ collectionPath, queryKey, orderByFiel
71
44
  const current = windowState.identity === identity ? windowState : freshWindow(identity);
72
45
  React.useEffect(() => {
73
46
  if (!enabled) {
74
- // Retire the enabled identity's rows, so re-enabling starts afresh rather than resuming.
75
47
  setWindowState(freshWindow(identity));
76
48
  return;
77
49
  }
78
- // A retry of the same identity resumes from its anchor and keeps its rows.
79
50
  const prior = windowRef.current;
80
51
  const resumed = prior.identity === identity;
81
52
  setWindowState((prev) => prev.identity === identity
@@ -178,13 +149,10 @@ export function useFirestoreLiveInfinite({ collectionPath, queryKey, orderByFiel
178
149
  clearTimeout(retryTimer);
179
150
  unsubscribe?.();
180
151
  };
181
- // `identity` carries collectionPath, queryKey, orderByField, constraints, pageSize, and
182
- // enabled; constraints are read from the render that produced it.
183
- // eslint-disable-next-line react-hooks/exhaustive-deps
152
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- identity carries every read input; constraints are read from the render that produced it
184
153
  }, [db, identity, retryNonce, toEntry, queryClient]);
185
154
  const anchor = enabled ? current.anchor : null;
186
155
  const olderEnabled = anchor !== null && current.olderExists;
187
- // Never fetched on its own: `fetchOlder` reads the first page (`refetch`) and each one after it.
188
156
  const older = useInfiniteQuery({
189
157
  queryKey: [...queryKey, 'older', identity, anchor?.id ?? null],
190
158
  enabled: false,
@@ -245,8 +213,6 @@ export function useFirestoreLiveInfinite({ collectionPath, queryKey, orderByFiel
245
213
  const items = React.useMemo(() => {
246
214
  if (!enabled)
247
215
  return [];
248
- // A row edited so its ordering value moved into the live range is in both reads; its live
249
- // copy is the fresh one.
250
216
  const liveIds = new Set(current.entries.map((entry) => entry.docId));
251
217
  const newestFirst = [
252
218
  ...current.entries.map((entry) => entry.item),
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-live-infinite.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-live-infinite.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EACL,UAAU,EACV,KAAK,EACL,OAAO,EACP,KAAK,EACL,UAAU,EACV,KAAK,EACL,OAAO,EACP,UAAU,GAGX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAIlF,MAAM,aAAa,GAAG,GAAG,CAAC;AAoC1B,SAAS,WAAW,CAAI,QAAgB;IACtC,OAAO;QACL,QAAQ;QACR,KAAK,EAAE,KAAK;QACZ,OAAO,EAAE,EAAE;QACX,MAAM,EAAE,IAAI;QACZ,WAAW,EAAE,KAAK;QAClB,WAAW,EAAE,YAAY;QACzB,KAAK,EAAE,IAAI;KACZ,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,wBAAwB,CAAoC,EAC1E,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,WAAW,GAAG,EAAE,EAChB,QAAQ,EAAE,iBAAiB,GAAG,EAAE,EAChC,OAAO,GAAG,IAAI,EACd,MAAM,EACN,IAAI,GAAG,KAAK,GACoB;IAChC,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IAC5B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;IAE5D,4FAA4F;IAC5F,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACvC,SAAS,CAAC,OAAO,GAAG,MAAM,CAAC;IAE3B,MAAM,OAAO,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,IAAU,EAAY,EAAE;QACzD,MAAM,UAAU,GAAkC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QAClF,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAE,UAA2B,CAAC;QAC9F,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC;IAClC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,oFAAoF;IACpF,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC9C,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAC9B,cAAc;QACd,YAAY;QACZ,YAAY;QACZ,eAAe;QACf,QAAQ;QACR,OAAO;KACR,CAAC,CAAC;IAEH,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAiB,GAAG,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClG,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC5C,SAAS,CAAC,OAAO,GAAG,WAAW,CAAC;IAChC,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAEtD,MAAM,OAAO,GACX,WAAW,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAI,QAAQ,CAAC,CAAC;IAE7E,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,yFAAyF;YACzF,cAAc,CAAC,WAAW,CAAI,QAAQ,CAAC,CAAC,CAAC;YACzC,OAAO;QACT,CAAC;QAED,2EAA2E;QAC3E,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC;QAChC,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,KAAK,QAAQ,CAAC;QAC5C,cAAc,CAAC,CAAC,IAAI,EAAE,EAAE,CACtB,IAAI,CAAC,QAAQ,KAAK,QAAQ;YACxB,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE;YACrD,CAAC,CAAC,WAAW,CAAI,QAAQ,CAAC,CAC7B,CAAC;QAEF,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,IAAI,WAAW,GAAwB,IAAI,CAAC;QAC5C,IAAI,UAAU,GAAyC,IAAI,CAAC;QAC5D,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,IAAI,aAAa,GAAgB,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;QAE/D,MAAM,MAAM,GAAG,CAAC,EAA4C,EAAE,EAAE;YAC9D,IAAI,QAAQ;gBAAE,OAAO;YACrB,cAAc,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3E,CAAC,CAAC;QAEF,MAAM,MAAM,GAAG,CAAC,MAAmB,EAAE,EAAE;YACrC,IAAI,QAAQ;gBAAE,OAAO;YACrB,aAAa,GAAG,MAAM,CAAC;YACvB,MAAM,MAAM,GAAG,UAAU,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;YAC9C,MAAM,CAAC,GAAG,MAAM;gBACd,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,WAAW,EAAE,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;gBAC7E,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,WAAW,EAAE,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC;YACtF,IAAI,eAAe,GAAG,KAAK,CAAC;YAE5B,WAAW,GAAG,UAAU,CACtB,CAAC,EACD,EAAE,sBAAsB,EAAE,IAAI,EAAE,EAChC,CAAC,IAAI,EAAE,EAAE;gBACP,IAAI,QAAQ;oBAAE,OAAO;gBACrB,OAAO,GAAG,CAAC,CAAC;gBACZ,MAAM,IAAI,GAAG,IAAI,CAAC,IAAc,CAAC;gBACjC,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,SAAS,IAAI,KAAK,CAAC;gBACpD,MAAM,UAAU,GAAG,CAAC,IAA0B,EAAwB,EAAE;oBACtE,IAAI,CAAC,SAAS;wBAAE,OAAO,MAAM,CAAC;oBAC9B,IAAI,CAAC,eAAe;wBAAE,OAAO,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC;oBACpE,OAAO,SAAS,CAAC;gBACnB,CAAC,CAAC;gBACF,IAAI,CAAC,SAAS;oBAAE,eAAe,GAAG,IAAI,CAAC;gBAEvC,IAAI,MAAM,EAAE,CAAC;oBACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;wBAChB,GAAG,IAAI;wBACP,KAAK,EAAE,IAAI;wBACX,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;wBAC1B,WAAW,EAAE,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC;wBACzC,KAAK,EAAE,IAAI;qBACZ,CAAC,CAAC,CAAC;oBACJ,OAAO;gBACT,CAAC;gBAED,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;gBAC3C,IAAI,SAAS,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACzC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;wBAChB,GAAG,IAAI;wBACP,KAAK,EAAE,IAAI;wBACX,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC;wBAChC,WAAW,EAAE,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC;wBACzC,KAAK,EAAE,IAAI;qBACZ,CAAC,CAAC,CAAC;oBACJ,OAAO;gBACT,CAAC;gBAED,MAAM,SAAS,GAAG,UAAU,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;gBACpD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;oBAChB,GAAG,IAAI;oBACP,KAAK,EAAE,IAAI;oBACX,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC;oBAChC,MAAM,EAAE,SAAS;oBACjB,WAAW,EAAE,IAAI,CAAC,MAAM,GAAG,QAAQ;oBACnC,WAAW,EAAE,MAAM;oBACnB,KAAK,EAAE,IAAI;iBACZ,CAAC,CAAC,CAAC;gBACJ,WAAW,EAAE,EAAE,CAAC;gBAChB,WAAW,GAAG,IAAI,CAAC;gBACnB,MAAM,CAAC,SAAS,CAAC,CAAC;YACpB,CAAC,EACD,CAAC,KAAK,EAAE,EAAE;gBACR,IAAI,QAAQ;oBAAE,OAAO;gBACrB,WAAW,EAAE,EAAE,CAAC;gBAChB,WAAW,GAAG,IAAI,CAAC;gBACnB,IAAI,uBAAuB,CAAC,KAAK,CAAC,IAAI,OAAO,GAAG,qBAAqB,CAAC,MAAM,EAAE,CAAC;oBAC7E,MAAM,KAAK,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;oBAC7C,OAAO,IAAI,CAAC,CAAC;oBACb,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;oBAC3D,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC;oBAC5D,OAAO;gBACT,CAAC;gBACD,OAAO,CAAC,KAAK,CAAC,gDAAgD,EAAE,KAAK,CAAC,CAAC;gBACvE,IAAI,uBAAuB,CAAC,KAAK,CAAC,EAAE,CAAC;oBACnC,aAAa,GAAG,IAAI,CAAC;oBACrB,WAAW,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;oBAC1E,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,WAAW,CAAI,QAAQ,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;oBAC7E,OAAO;gBACT,CAAC;gBACD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;YAC/D,CAAC,CACF,CAAC;QACJ,CAAC,CAAC;QAEF,MAAM,CAAC,aAAa,CAAC,CAAC;QAEtB,OAAO,GAAG,EAAE;YACV,QAAQ,GAAG,IAAI,CAAC;YAChB,IAAI,UAAU;gBAAE,YAAY,CAAC,UAAU,CAAC,CAAC;YACzC,WAAW,EAAE,EAAE,CAAC;QAClB,CAAC,CAAC;QACF,wFAAwF;QACxF,kEAAkE;QAClE,uDAAuD;IACzD,CAAC,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC;IAErD,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/C,MAAM,YAAY,GAAG,MAAM,KAAK,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC;IAE5D,iGAAiG;IACjG,MAAM,KAAK,GAAG,gBAAgB,CAAC;QAC7B,QAAQ,EAAE,CAAC,GAAG,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,IAAI,IAAI,CAAC;QAC9D,OAAO,EAAE,KAAK;QACd,gBAAgB,EAAE,SAA6B;QAC/C,OAAO,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE;YAC/B,MAAM,MAAM,GAAG,SAAS,IAAI,MAAM,CAAC;YACnC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO,EAAE,OAAO,EAAE,EAAgB,EAAE,UAAU,EAAE,SAA6B,EAAE,CAAC;YAClF,CAAC;YACD,MAAM,MAAM,GAAG,UAAU,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;YAC9C,MAAM,CAAC,GAAG,KAAK,CACb,MAAM,EACN,GAAG,WAAW,EACd,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,EAC7B,UAAU,CAAC,MAAM,CAAC,EAClB,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CACpB,CAAC;YACF,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;YAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAc,CAAC;YACjC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;YACrC,OAAO;gBACL,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;gBAC1B,UAAU,EAAE,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;aACvE,CAAC;QACJ,CAAC;QACD,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU;KAC5C,CAAC,CAAC;IAEH,MAAM,EACJ,IAAI,EAAE,SAAS,EACf,UAAU,EAAE,aAAa,EACzB,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE,QAAQ,EACf,WAAW,EAAE,YAAY,EACzB,aAAa,EACb,OAAO,EAAE,YAAY,GACtB,GAAG,KAAK,CAAC;IAEV,MAAM,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/D,MAAM,QAAQ,GAAG,YAAY,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC7E,MAAM,UAAU,GAAG,YAAY,IAAI,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;IAElE,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,CAAC,KAAK,IAAI,EAAE;QAC9C,IAAI,CAAC,YAAY,IAAI,aAAa;YAAE,OAAO;QAC3C,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,YAAY,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QACD,IAAI,YAAY;YAAE,MAAM,aAAa,EAAE,CAAC;IAC1C,CAAC,EAAE,CAAC,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC,CAAC;IAEzF,MAAM,cAAc,GAAG,OAAO,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,CAAC;IACzD,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;QACnC,IAAI,cAAc,EAAE,CAAC;YACnB,cAAc,CAAC,CAAC,IAAI,EAAE,EAAE,CACtB,IAAI,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,CACxF,CAAC;YACF,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9B,CAAC;QACD,IAAI,UAAU,IAAI,CAAC,aAAa,EAAE,CAAC;YACjC,IAAI,UAAU;gBAAE,KAAK,aAAa,EAAE,CAAC;;gBAChC,KAAK,YAAY,EAAE,CAAC;QAC3B,CAAC;IACH,CAAC,EAAE;QACD,cAAc;QACd,QAAQ;QACR,UAAU;QACV,aAAa;QACb,UAAU;QACV,aAAa;QACb,YAAY;KACb,CAAC,CAAC;IAEH,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAChC,GAAG,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAClD,CAAC,UAAU,CAAC,CACb,CAAC;IAEF,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QAC/B,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QACxB,0FAA0F;QAC1F,yBAAyB;QACzB,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACrE,MAAM,WAAW,GAAG;YAClB,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;YAC7C,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;SACxF,CAAC;QACF,OAAO,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;IAC/D,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;IAEnD,OAAO;QACL,KAAK;QACL,gBAAgB,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK;QAC5E,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;QACrC,UAAU;QACV,QAAQ;QACR,eAAe,EAAE,YAAY,IAAI,aAAa;QAC9C,UAAU;QACV,KAAK;KACN,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useInfiniteQuery, useQueryClient } from '@tanstack/react-query';\nimport {\n collection,\n query,\n orderBy,\n limit,\n startAfter,\n endAt,\n getDocs,\n onSnapshot,\n type DocumentData,\n type QueryDocumentSnapshot,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport { RESUBSCRIBE_DELAYS_MS, isPermissionDeniedError } from './resubscribe.js';\nimport type { FirestoreSourceState } from '../../firestore/types.js';\nimport type { FirestoreLiveInfiniteOptions } from './types.js';\n\nconst MAX_PAGE_SIZE = 100;\n\nexport interface FirestoreLiveInfiniteResult<T> {\n /** The current subscription identity's rows — live set plus loaded older pages, ordered per `sort`. */\n items: T[];\n /** True until the current identity's first snapshot arrives, unless its listener has failed. */\n isInitialLoading: boolean;\n /** Liveness of the current identity's listener; `connecting` from that identity's first render. */\n sourceState: FirestoreSourceState;\n /** The listener's failure for the current identity; `null` while connecting, live, or offline. */\n error: Error | null;\n /** Load the next older page; nothing older is read until this is called. Never rejects — a failed read lands in `olderError`. */\n fetchOlder: () => Promise<void>;\n /** Whether older rows exist that are not loaded yet (a one-doc look-ahead, never a full-page guess). */\n hasOlder: boolean;\n /** Whether an older page is being read (including a `retry` of one). */\n isFetchingOlder: boolean;\n /** The last older-page read failed; `null` once an older read succeeds. */\n olderError: Error | null;\n /** Re-run what failed: resubscribe a failed listener, re-read a failed older page. */\n retry: () => void;\n}\n\ntype Entry<T> = { docId: string; item: T };\ntype Snap = QueryDocumentSnapshot<DocumentData>;\n\ninterface WindowState<T> {\n identity: string;\n ready: boolean;\n entries: Entry<T>[];\n anchor: Snap | null;\n olderExists: boolean;\n sourceState: FirestoreSourceState;\n error: Error | null;\n}\n\nfunction freshWindow<T>(identity: string): WindowState<T> {\n return {\n identity,\n ready: false,\n entries: [],\n anchor: null,\n olderExists: false,\n sourceState: 'connecting',\n error: null,\n };\n}\n\n/**\n * A live list (newest rows realtime) with older rows paged in on request, merged into\n * one ordered list.\n *\n * Until the first server-confirmed snapshot that has rows, the listener reads the newest\n * `pageSize` rows plus one look-ahead row. That snapshot fixes an anchor — the oldest row\n * of the window — and from then on the listener covers anchor → newest with no limit, so\n * it grows only with rows that arrive while the list is open, and every older page starts\n * after the anchor. Nothing can fall between the live set and the older pages, however\n * many rows arrive. Older pages carry the same one-row look-ahead, so `hasOlder` is false\n * exactly when nothing older exists. No older page is read until `fetchOlder` asks for one.\n *\n * Both reads arrive newest first and meet at the anchor, so the merged list is their\n * concatenation: a snapshot never re-sorts the rows already loaded.\n *\n * Everything read is tagged to its subscription identity (path, key, ordering,\n * constraints, page size, enabled): a new identity renders empty and connecting in its\n * first render, and a retired identity's late callback or older page never lands.\n *\n * A `permission-denied` listener error resubscribes on the `RESUBSCRIBE_DELAYS_MS` ladder\n * before surfacing; any other listener error surfaces at once. Rows already shown are\n * kept beside the error — except beside a surfaced `permission-denied`: the reader lost\n * access, so the live rows, the anchor, and the loaded older pages are all dropped, and a\n * retry reads the list afresh. `db` comes from `<FirestoreProvider>`.\n */\nexport function useFirestoreLiveInfinite<T = DocumentData & { id: string }>({\n collectionPath,\n queryKey,\n orderByField,\n constraints = [],\n pageSize: requestedPageSize = 20,\n enabled = true,\n select,\n sort = 'asc',\n}: FirestoreLiveInfiniteOptions<T>): FirestoreLiveInfiniteResult<T> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n const pageSize = Math.min(requestedPageSize, MAX_PAGE_SIZE);\n\n // Latest-callback ref: an inline select must not re-subscribe the listener on every render.\n const selectRef = React.useRef(select);\n selectRef.current = select;\n\n const toEntry = React.useCallback((snap: Snap): Entry<T> => {\n const dataWithId: DocumentData & { id: string } = { id: snap.id, ...snap.data() };\n const item = selectRef.current ? selectRef.current(dataWithId) : (dataWithId as unknown as T);\n return { docId: snap.id, item };\n }, []);\n\n // Constraint objects are fresh every render; their serialized form is the identity.\n const queryKeyMemo = JSON.stringify(queryKey);\n const constraintsMemo = JSON.stringify(constraints);\n const identity = JSON.stringify([\n collectionPath,\n queryKeyMemo,\n orderByField,\n constraintsMemo,\n pageSize,\n enabled,\n ]);\n\n const [windowState, setWindowState] = React.useState<WindowState<T>>(() => freshWindow(identity));\n const windowRef = React.useRef(windowState);\n windowRef.current = windowState;\n const [retryNonce, setRetryNonce] = React.useState(0);\n\n const current: WindowState<T> =\n windowState.identity === identity ? windowState : freshWindow<T>(identity);\n\n React.useEffect(() => {\n if (!enabled) {\n // Retire the enabled identity's rows, so re-enabling starts afresh rather than resuming.\n setWindowState(freshWindow<T>(identity));\n return;\n }\n\n // A retry of the same identity resumes from its anchor and keeps its rows.\n const prior = windowRef.current;\n const resumed = prior.identity === identity;\n setWindowState((prev) =>\n prev.identity === identity\n ? { ...prev, error: null, sourceState: 'connecting' }\n : freshWindow<T>(identity),\n );\n\n let disposed = false;\n let unsubscribe: (() => void) | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let attempt = 0;\n let currentAnchor: Snap | null = resumed ? prior.anchor : null;\n\n const update = (fn: (prev: WindowState<T>) => WindowState<T>) => {\n if (disposed) return;\n setWindowState((prev) => (prev.identity === identity ? fn(prev) : prev));\n };\n\n const listen = (anchor: Snap | null) => {\n if (disposed) return;\n currentAnchor = anchor;\n const colRef = collection(db, collectionPath);\n const q = anchor\n ? query(colRef, ...constraints, orderBy(orderByField, 'desc'), endAt(anchor))\n : query(colRef, ...constraints, orderBy(orderByField, 'desc'), limit(pageSize + 1));\n let serverConfirmed = false;\n\n unsubscribe = onSnapshot(\n q,\n { includeMetadataChanges: true },\n (snap) => {\n if (disposed) return;\n attempt = 0;\n const docs = snap.docs as Snap[];\n const fromCache = snap.metadata?.fromCache ?? false;\n const nextSource = (prev: FirestoreSourceState): FirestoreSourceState => {\n if (!fromCache) return 'live';\n if (!serverConfirmed) return prev === 'error' ? 'connecting' : prev;\n return 'offline';\n };\n if (!fromCache) serverConfirmed = true;\n\n if (anchor) {\n update((prev) => ({\n ...prev,\n ready: true,\n entries: docs.map(toEntry),\n sourceState: nextSource(prev.sourceState),\n error: null,\n }));\n return;\n }\n\n const windowDocs = docs.slice(0, pageSize);\n if (fromCache || windowDocs.length === 0) {\n update((prev) => ({\n ...prev,\n ready: true,\n entries: windowDocs.map(toEntry),\n sourceState: nextSource(prev.sourceState),\n error: null,\n }));\n return;\n }\n\n const newAnchor = windowDocs[windowDocs.length - 1];\n update((prev) => ({\n ...prev,\n ready: true,\n entries: windowDocs.map(toEntry),\n anchor: newAnchor,\n olderExists: docs.length > pageSize,\n sourceState: 'live',\n error: null,\n }));\n unsubscribe?.();\n unsubscribe = null;\n listen(newAnchor);\n },\n (error) => {\n if (disposed) return;\n unsubscribe?.();\n unsubscribe = null;\n if (isPermissionDeniedError(error) && attempt < RESUBSCRIBE_DELAYS_MS.length) {\n const delay = RESUBSCRIBE_DELAYS_MS[attempt];\n attempt += 1;\n update((prev) => ({ ...prev, sourceState: 'connecting' }));\n retryTimer = setTimeout(() => listen(currentAnchor), delay);\n return;\n }\n console.error('[useFirestoreLiveInfinite] Subscription error:', error);\n if (isPermissionDeniedError(error)) {\n currentAnchor = null;\n queryClient.removeQueries({ queryKey: [...queryKey, 'older', identity] });\n update(() => ({ ...freshWindow<T>(identity), sourceState: 'error', error }));\n return;\n }\n update((prev) => ({ ...prev, sourceState: 'error', error }));\n },\n );\n };\n\n listen(currentAnchor);\n\n return () => {\n disposed = true;\n if (retryTimer) clearTimeout(retryTimer);\n unsubscribe?.();\n };\n // `identity` carries collectionPath, queryKey, orderByField, constraints, pageSize, and\n // enabled; constraints are read from the render that produced it.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [db, identity, retryNonce, toEntry, queryClient]);\n\n const anchor = enabled ? current.anchor : null;\n const olderEnabled = anchor !== null && current.olderExists;\n\n // Never fetched on its own: `fetchOlder` reads the first page (`refetch`) and each one after it.\n const older = useInfiniteQuery({\n queryKey: [...queryKey, 'older', identity, anchor?.id ?? null],\n enabled: false,\n initialPageParam: undefined as Snap | undefined,\n queryFn: async ({ pageParam }) => {\n const cursor = pageParam ?? anchor;\n if (!cursor) {\n return { entries: [] as Entry<T>[], nextCursor: undefined as Snap | undefined };\n }\n const colRef = collection(db, collectionPath);\n const q = query(\n colRef,\n ...constraints,\n orderBy(orderByField, 'desc'),\n startAfter(cursor),\n limit(pageSize + 1),\n );\n const snap = await getDocs(q);\n const docs = snap.docs as Snap[];\n const page = docs.slice(0, pageSize);\n return {\n entries: page.map(toEntry),\n nextCursor: docs.length > pageSize ? page[page.length - 1] : undefined,\n };\n },\n getNextPageParam: (last) => last.nextCursor,\n });\n\n const {\n data: olderData,\n isFetching: olderFetching,\n isError: olderIsError,\n error: olderErr,\n hasNextPage: olderHasNext,\n fetchNextPage,\n refetch: refetchOlder,\n } = older;\n\n const olderPages = olderEnabled ? olderData?.pages : undefined;\n const hasOlder = olderEnabled && (olderPages ? Boolean(olderHasNext) : true);\n const olderError = olderEnabled && olderIsError ? olderErr : null;\n\n const fetchOlder = React.useCallback(async () => {\n if (!olderEnabled || olderFetching) return;\n if (!olderPages) {\n await refetchOlder();\n return;\n }\n if (olderHasNext) await fetchNextPage();\n }, [olderEnabled, olderFetching, olderPages, olderHasNext, refetchOlder, fetchNextPage]);\n\n const listenerFailed = enabled && current.error !== null;\n const retry = React.useCallback(() => {\n if (listenerFailed) {\n setWindowState((prev) =>\n prev.identity === identity ? { ...prev, error: null, sourceState: 'connecting' } : prev,\n );\n setRetryNonce((n) => n + 1);\n }\n if (olderError && !olderFetching) {\n if (olderPages) void fetchNextPage();\n else void refetchOlder();\n }\n }, [\n listenerFailed,\n identity,\n olderError,\n olderFetching,\n olderPages,\n fetchNextPage,\n refetchOlder,\n ]);\n\n const olderEntries = React.useMemo(\n () => (olderPages ?? []).flatMap((p) => p.entries),\n [olderPages],\n );\n\n const items = React.useMemo(() => {\n if (!enabled) return [];\n // A row edited so its ordering value moved into the live range is in both reads; its live\n // copy is the fresh one.\n const liveIds = new Set(current.entries.map((entry) => entry.docId));\n const newestFirst = [\n ...current.entries.map((entry) => entry.item),\n ...olderEntries.filter((entry) => !liveIds.has(entry.docId)).map((entry) => entry.item),\n ];\n return sort === 'desc' ? newestFirst : newestFirst.reverse();\n }, [enabled, olderEntries, current.entries, sort]);\n\n return {\n items,\n isInitialLoading: enabled ? !current.ready && current.error === null : false,\n sourceState: current.sourceState,\n error: enabled ? current.error : null,\n fetchOlder,\n hasOlder,\n isFetchingOlder: olderEnabled && olderFetching,\n olderError,\n retry,\n };\n}\n"]}
1
+ {"version":3,"file":"use-firestore-live-infinite.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-live-infinite.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useInfiniteQuery, useQueryClient } from '@tanstack/react-query';\nimport {\n collection,\n query,\n orderBy,\n limit,\n startAfter,\n endAt,\n getDocs,\n onSnapshot,\n type DocumentData,\n type QueryDocumentSnapshot,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport { RESUBSCRIBE_DELAYS_MS, isPermissionDeniedError } from './resubscribe.js';\nimport type { FirestoreSourceState } from '../../firestore/types.js';\nimport type { FirestoreLiveInfiniteOptions } from './types.js';\n\nconst MAX_PAGE_SIZE = 100;\n\nexport interface FirestoreLiveInfiniteResult<T> {\n items: T[];\n isInitialLoading: boolean;\n sourceState: FirestoreSourceState;\n error: Error | null;\n fetchOlder: () => Promise<void>;\n hasOlder: boolean;\n isFetchingOlder: boolean;\n olderError: Error | null;\n retry: () => void;\n}\n\ntype Entry<T> = { docId: string; item: T };\ntype Snap = QueryDocumentSnapshot<DocumentData>;\n\ninterface WindowState<T> {\n identity: string;\n ready: boolean;\n entries: Entry<T>[];\n anchor: Snap | null;\n olderExists: boolean;\n sourceState: FirestoreSourceState;\n error: Error | null;\n}\n\nfunction freshWindow<T>(identity: string): WindowState<T> {\n return {\n identity,\n ready: false,\n entries: [],\n anchor: null,\n olderExists: false,\n sourceState: 'connecting',\n error: null,\n };\n}\n\nexport function useFirestoreLiveInfinite<T = DocumentData & { id: string }>({\n collectionPath,\n queryKey,\n orderByField,\n constraints = [],\n pageSize: requestedPageSize = 20,\n enabled = true,\n select,\n sort = 'asc',\n}: FirestoreLiveInfiniteOptions<T>): FirestoreLiveInfiniteResult<T> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n const pageSize = Math.min(requestedPageSize, MAX_PAGE_SIZE);\n\n const selectRef = React.useRef(select);\n selectRef.current = select;\n\n const toEntry = React.useCallback((snap: Snap): Entry<T> => {\n const dataWithId: DocumentData & { id: string } = { id: snap.id, ...snap.data() };\n const item = selectRef.current ? selectRef.current(dataWithId) : (dataWithId as unknown as T);\n return { docId: snap.id, item };\n }, []);\n\n const queryKeyMemo = JSON.stringify(queryKey);\n const constraintsMemo = JSON.stringify(constraints);\n const identity = JSON.stringify([\n collectionPath,\n queryKeyMemo,\n orderByField,\n constraintsMemo,\n pageSize,\n enabled,\n ]);\n\n const [windowState, setWindowState] = React.useState<WindowState<T>>(() => freshWindow(identity));\n const windowRef = React.useRef(windowState);\n windowRef.current = windowState;\n const [retryNonce, setRetryNonce] = React.useState(0);\n\n const current: WindowState<T> =\n windowState.identity === identity ? windowState : freshWindow<T>(identity);\n\n React.useEffect(() => {\n if (!enabled) {\n setWindowState(freshWindow<T>(identity));\n return;\n }\n\n const prior = windowRef.current;\n const resumed = prior.identity === identity;\n setWindowState((prev) =>\n prev.identity === identity\n ? { ...prev, error: null, sourceState: 'connecting' }\n : freshWindow<T>(identity),\n );\n\n let disposed = false;\n let unsubscribe: (() => void) | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let attempt = 0;\n let currentAnchor: Snap | null = resumed ? prior.anchor : null;\n\n const update = (fn: (prev: WindowState<T>) => WindowState<T>) => {\n if (disposed) return;\n setWindowState((prev) => (prev.identity === identity ? fn(prev) : prev));\n };\n\n const listen = (anchor: Snap | null) => {\n if (disposed) return;\n currentAnchor = anchor;\n const colRef = collection(db, collectionPath);\n const q = anchor\n ? query(colRef, ...constraints, orderBy(orderByField, 'desc'), endAt(anchor))\n : query(colRef, ...constraints, orderBy(orderByField, 'desc'), limit(pageSize + 1));\n let serverConfirmed = false;\n\n unsubscribe = onSnapshot(\n q,\n { includeMetadataChanges: true },\n (snap) => {\n if (disposed) return;\n attempt = 0;\n const docs = snap.docs as Snap[];\n const fromCache = snap.metadata?.fromCache ?? false;\n const nextSource = (prev: FirestoreSourceState): FirestoreSourceState => {\n if (!fromCache) return 'live';\n if (!serverConfirmed) return prev === 'error' ? 'connecting' : prev;\n return 'offline';\n };\n if (!fromCache) serverConfirmed = true;\n\n if (anchor) {\n update((prev) => ({\n ...prev,\n ready: true,\n entries: docs.map(toEntry),\n sourceState: nextSource(prev.sourceState),\n error: null,\n }));\n return;\n }\n\n const windowDocs = docs.slice(0, pageSize);\n if (fromCache || windowDocs.length === 0) {\n update((prev) => ({\n ...prev,\n ready: true,\n entries: windowDocs.map(toEntry),\n sourceState: nextSource(prev.sourceState),\n error: null,\n }));\n return;\n }\n\n const newAnchor = windowDocs[windowDocs.length - 1];\n update((prev) => ({\n ...prev,\n ready: true,\n entries: windowDocs.map(toEntry),\n anchor: newAnchor,\n olderExists: docs.length > pageSize,\n sourceState: 'live',\n error: null,\n }));\n unsubscribe?.();\n unsubscribe = null;\n listen(newAnchor);\n },\n (error) => {\n if (disposed) return;\n unsubscribe?.();\n unsubscribe = null;\n if (isPermissionDeniedError(error) && attempt < RESUBSCRIBE_DELAYS_MS.length) {\n const delay = RESUBSCRIBE_DELAYS_MS[attempt];\n attempt += 1;\n update((prev) => ({ ...prev, sourceState: 'connecting' }));\n retryTimer = setTimeout(() => listen(currentAnchor), delay);\n return;\n }\n console.error('[useFirestoreLiveInfinite] Subscription error:', error);\n if (isPermissionDeniedError(error)) {\n currentAnchor = null;\n queryClient.removeQueries({ queryKey: [...queryKey, 'older', identity] });\n update(() => ({ ...freshWindow<T>(identity), sourceState: 'error', error }));\n return;\n }\n update((prev) => ({ ...prev, sourceState: 'error', error }));\n },\n );\n };\n\n listen(currentAnchor);\n\n return () => {\n disposed = true;\n if (retryTimer) clearTimeout(retryTimer);\n unsubscribe?.();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- identity carries every read input; constraints are read from the render that produced it\n }, [db, identity, retryNonce, toEntry, queryClient]);\n\n const anchor = enabled ? current.anchor : null;\n const olderEnabled = anchor !== null && current.olderExists;\n\n const older = useInfiniteQuery({\n queryKey: [...queryKey, 'older', identity, anchor?.id ?? null],\n enabled: false,\n initialPageParam: undefined as Snap | undefined,\n queryFn: async ({ pageParam }) => {\n const cursor = pageParam ?? anchor;\n if (!cursor) {\n return { entries: [] as Entry<T>[], nextCursor: undefined as Snap | undefined };\n }\n const colRef = collection(db, collectionPath);\n const q = query(\n colRef,\n ...constraints,\n orderBy(orderByField, 'desc'),\n startAfter(cursor),\n limit(pageSize + 1),\n );\n const snap = await getDocs(q);\n const docs = snap.docs as Snap[];\n const page = docs.slice(0, pageSize);\n return {\n entries: page.map(toEntry),\n nextCursor: docs.length > pageSize ? page[page.length - 1] : undefined,\n };\n },\n getNextPageParam: (last) => last.nextCursor,\n });\n\n const {\n data: olderData,\n isFetching: olderFetching,\n isError: olderIsError,\n error: olderErr,\n hasNextPage: olderHasNext,\n fetchNextPage,\n refetch: refetchOlder,\n } = older;\n\n const olderPages = olderEnabled ? olderData?.pages : undefined;\n const hasOlder = olderEnabled && (olderPages ? Boolean(olderHasNext) : true);\n const olderError = olderEnabled && olderIsError ? olderErr : null;\n\n const fetchOlder = React.useCallback(async () => {\n if (!olderEnabled || olderFetching) return;\n if (!olderPages) {\n await refetchOlder();\n return;\n }\n if (olderHasNext) await fetchNextPage();\n }, [olderEnabled, olderFetching, olderPages, olderHasNext, refetchOlder, fetchNextPage]);\n\n const listenerFailed = enabled && current.error !== null;\n const retry = React.useCallback(() => {\n if (listenerFailed) {\n setWindowState((prev) =>\n prev.identity === identity ? { ...prev, error: null, sourceState: 'connecting' } : prev,\n );\n setRetryNonce((n) => n + 1);\n }\n if (olderError && !olderFetching) {\n if (olderPages) void fetchNextPage();\n else void refetchOlder();\n }\n }, [\n listenerFailed,\n identity,\n olderError,\n olderFetching,\n olderPages,\n fetchNextPage,\n refetchOlder,\n ]);\n\n const olderEntries = React.useMemo(\n () => (olderPages ?? []).flatMap((p) => p.entries),\n [olderPages],\n );\n\n const items = React.useMemo(() => {\n if (!enabled) return [];\n const liveIds = new Set(current.entries.map((entry) => entry.docId));\n const newestFirst = [\n ...current.entries.map((entry) => entry.item),\n ...olderEntries.filter((entry) => !liveIds.has(entry.docId)).map((entry) => entry.item),\n ];\n return sort === 'desc' ? newestFirst : newestFirst.reverse();\n }, [enabled, olderEntries, current.entries, sort]);\n\n return {\n items,\n isInitialLoading: enabled ? !current.ready && current.error === null : false,\n sourceState: current.sourceState,\n error: enabled ? current.error : null,\n fetchOlder,\n hasOlder,\n isFetchingOlder: olderEnabled && olderFetching,\n olderError,\n retry,\n };\n}\n"]}
@@ -2,10 +2,6 @@ import { type UseQueryResult } from '@tanstack/react-query';
2
2
  import { type DocumentData } from 'firebase/firestore';
3
3
  import type { WithId } from '../../firestore/types.js';
4
4
  import type { FirestorePaginatedOptions, PaginatedPage } from './types.js';
5
- /**
6
- * Return type for useFirestorePaginated hook. `data` is the displayed page's rows; the
7
- * page's cache entry (rows, cursor, has-more) lives at `paginatedPageKey(queryKey, page)`.
8
- */
9
5
  export type UseFirestorePaginatedResult<T> = Omit<UseQueryResult<PaginatedPage<WithId<T>>, Error>, 'data'> & {
10
6
  data: WithId<T>[] | undefined;
11
7
  page: number;
@@ -16,31 +12,5 @@ export type UseFirestorePaginatedResult<T> = Omit<UseQueryResult<PaginatedPage<W
16
12
  nextPage: () => void;
17
13
  prevPage: () => void;
18
14
  };
19
- /**
20
- * Page-based pagination for Firestore collections (Previous / Next, one page at a time).
21
- *
22
- * Each page is ONE cache entry at `[...queryKey, 'page', n]` holding its rows, its last
23
- * row as the next page's cursor, and whether a further page exists — read with a one-row
24
- * look-ahead, so `hasNextPage` is false exactly when nothing follows the displayed page.
25
- * Page n reads page n-1's cursor from the cache (loading the chain first if it is
26
- * missing) and records it as `after`. A displayed page whose `after` no longer matches
27
- * page n-1's cached cursor (page n-1 was re-read and its last row moved) is re-read before
28
- * it is shown, so every instance, remount, and refetch of a key agrees on where a page
29
- * starts. The page number belongs to the query identity (path, key, constraints, page
30
- * size): a new identity renders `initialPage` in its first render.
31
- *
32
- * `queryKey` must encode everything that changes the read — path, filters, ordering, and
33
- * page size — because the cache entries are shared by key.
34
- *
35
- * @example
36
- * ```tsx
37
- * const { data, page, nextPage, prevPage, hasNextPage, hasPrevPage } = useFirestorePaginated<Task>({
38
- * collectionPath: 'tasks',
39
- * queryKey: ['tasks', 'active', { pageSize: 10 }],
40
- * constraints: [where('status', '==', 'active'), orderBy('createdAt', 'desc')],
41
- * pageSize: 10,
42
- * });
43
- * ```
44
- */
45
15
  export declare function useFirestorePaginated<T extends DocumentData = DocumentData>({ collectionPath, queryKey, constraints, pageSize, initialPage, enabled, staleTime, gcTime, refetchInterval, select, }: FirestorePaginatedOptions<T>): UseFirestorePaginatedResult<T>;
46
16
  //# sourceMappingURL=use-firestore-paginated.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-paginated.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-paginated.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAML,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,KAAK,EAAE,yBAAyB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAI3E;;;GAGG;AACH,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI,IAAI,CAC/C,cAAc,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAC/C,MAAM,CACP,GAAG;IACF,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC3E,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,QAA4B,EAC5B,WAAe,EACf,OAAc,EACd,SAAS,EACT,MAAM,EACN,eAAe,EACf,MAAM,GACP,EAAE,yBAAyB,CAAC,CAAC,CAAC,GAAG,2BAA2B,CAAC,CAAC,CAAC,CAsH/D"}
1
+ {"version":3,"file":"use-firestore-paginated.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-paginated.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAML,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,KAAK,EAAE,yBAAyB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAI3E,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI,IAAI,CAC/C,cAAc,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAC/C,MAAM,CACP,GAAG;IACF,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC3E,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,QAA4B,EAC5B,WAAe,EACf,OAAc,EACd,SAAS,EACT,MAAM,EACN,eAAe,EACf,MAAM,GACP,EAAE,yBAAyB,CAAC,CAAC,CAAC,GAAG,2BAA2B,CAAC,CAAC,CAAC,CAiH/D"}
@@ -5,32 +5,6 @@ import { collection, query, getDocs, limit, startAfter, } from 'firebase/firesto
5
5
  import { useFirestoreDb } from './context.js';
6
6
  import { paginatedPageKey } from '../cache-helpers.js';
7
7
  const DEFAULT_PAGE_SIZE = 10;
8
- /**
9
- * Page-based pagination for Firestore collections (Previous / Next, one page at a time).
10
- *
11
- * Each page is ONE cache entry at `[...queryKey, 'page', n]` holding its rows, its last
12
- * row as the next page's cursor, and whether a further page exists — read with a one-row
13
- * look-ahead, so `hasNextPage` is false exactly when nothing follows the displayed page.
14
- * Page n reads page n-1's cursor from the cache (loading the chain first if it is
15
- * missing) and records it as `after`. A displayed page whose `after` no longer matches
16
- * page n-1's cached cursor (page n-1 was re-read and its last row moved) is re-read before
17
- * it is shown, so every instance, remount, and refetch of a key agrees on where a page
18
- * starts. The page number belongs to the query identity (path, key, constraints, page
19
- * size): a new identity renders `initialPage` in its first render.
20
- *
21
- * `queryKey` must encode everything that changes the read — path, filters, ordering, and
22
- * page size — because the cache entries are shared by key.
23
- *
24
- * @example
25
- * ```tsx
26
- * const { data, page, nextPage, prevPage, hasNextPage, hasPrevPage } = useFirestorePaginated<Task>({
27
- * collectionPath: 'tasks',
28
- * queryKey: ['tasks', 'active', { pageSize: 10 }],
29
- * constraints: [where('status', '==', 'active'), orderBy('createdAt', 'desc')],
30
- * pageSize: 10,
31
- * });
32
- * ```
33
- */
34
8
  export function useFirestorePaginated({ collectionPath, queryKey, constraints = [], pageSize = DEFAULT_PAGE_SIZE, initialPage = 1, enabled = true, staleTime, gcTime, refetchInterval, select, }) {
35
9
  const db = useFirestoreDb();
36
10
  const queryClient = useQueryClient();
@@ -68,8 +42,7 @@ export function useFirestorePaginated({ collectionPath, queryKey, constraints =
68
42
  after: cursor?.id ?? null,
69
43
  };
70
44
  },
71
- // queryKey and constraints are captured through their serialized identity.
72
- // eslint-disable-next-line react-hooks/exhaustive-deps
45
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- queryKey and constraints are captured through their serialized identity
73
46
  [db, queryClient, identity, selectRef]);
74
47
  const queryResult = useQuery({
75
48
  queryKey: paginatedPageKey(queryKey, page),
@@ -79,9 +52,6 @@ export function useFirestorePaginated({ collectionPath, queryKey, constraints =
79
52
  gcTime,
80
53
  refetchInterval,
81
54
  });
82
- // The page before the displayed one, observed from the cache only (never fetched here).
83
- // When it is re-read and its last row moves, the displayed entry no longer starts where the
84
- // chain says it must: it is re-read before it is shown as current.
85
55
  const previousPage = useQuery({
86
56
  queryKey: paginatedPageKey(queryKey, Math.max(1, page - 1)),
87
57
  queryFn: () => fetchPage(Math.max(1, page - 1)),
@@ -112,8 +82,7 @@ export function useFirestorePaginated({ collectionPath, queryKey, constraints =
112
82
  return;
113
83
  setPageState({ identity, page: next });
114
84
  },
115
- // queryKey is captured through its serialized identity.
116
- // eslint-disable-next-line react-hooks/exhaustive-deps
85
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- queryKey is captured through its serialized identity
117
86
  [page, hasNextPage, identity, queryClient]);
118
87
  const nextPage = useCallback(() => setPage(page + 1), [page, setPage]);
119
88
  const prevPage = useCallback(() => setPage(page - 1), [page, setPage]);
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-paginated.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-paginated.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useState, useCallback, useEffect, useRef } from 'react';\nimport { useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';\nimport {\n collection,\n query,\n getDocs,\n limit,\n startAfter,\n type DocumentData,\n type DocumentSnapshot,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport { paginatedPageKey } from '../cache-helpers.js';\nimport type { WithId } from '../../firestore/types.js';\nimport type { FirestorePaginatedOptions, PaginatedPage } from './types.js';\n\nconst DEFAULT_PAGE_SIZE = 10;\n\n/**\n * Return type for useFirestorePaginated hook. `data` is the displayed page's rows; the\n * page's cache entry (rows, cursor, has-more) lives at `paginatedPageKey(queryKey, page)`.\n */\nexport type UseFirestorePaginatedResult<T> = Omit<\n UseQueryResult<PaginatedPage<WithId<T>>, Error>,\n 'data'\n> & {\n data: WithId<T>[] | undefined;\n page: number;\n pageSize: number;\n hasNextPage: boolean;\n hasPrevPage: boolean;\n setPage: (page: number) => void;\n nextPage: () => void;\n prevPage: () => void;\n};\n\n/**\n * Page-based pagination for Firestore collections (Previous / Next, one page at a time).\n *\n * Each page is ONE cache entry at `[...queryKey, 'page', n]` holding its rows, its last\n * row as the next page's cursor, and whether a further page exists — read with a one-row\n * look-ahead, so `hasNextPage` is false exactly when nothing follows the displayed page.\n * Page n reads page n-1's cursor from the cache (loading the chain first if it is\n * missing) and records it as `after`. A displayed page whose `after` no longer matches\n * page n-1's cached cursor (page n-1 was re-read and its last row moved) is re-read before\n * it is shown, so every instance, remount, and refetch of a key agrees on where a page\n * starts. The page number belongs to the query identity (path, key, constraints, page\n * size): a new identity renders `initialPage` in its first render.\n *\n * `queryKey` must encode everything that changes the read — path, filters, ordering, and\n * page size — because the cache entries are shared by key.\n *\n * @example\n * ```tsx\n * const { data, page, nextPage, prevPage, hasNextPage, hasPrevPage } = useFirestorePaginated<Task>({\n * collectionPath: 'tasks',\n * queryKey: ['tasks', 'active', { pageSize: 10 }],\n * constraints: [where('status', '==', 'active'), orderBy('createdAt', 'desc')],\n * pageSize: 10,\n * });\n * ```\n */\nexport function useFirestorePaginated<T extends DocumentData = DocumentData>({\n collectionPath,\n queryKey,\n constraints = [],\n pageSize = DEFAULT_PAGE_SIZE,\n initialPage = 1,\n enabled = true,\n staleTime,\n gcTime,\n refetchInterval,\n select,\n}: FirestorePaginatedOptions<T>): UseFirestorePaginatedResult<T> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n const queryKeyMemo = JSON.stringify(queryKey);\n const constraintsMemo = JSON.stringify(constraints);\n const identity = JSON.stringify([collectionPath, queryKeyMemo, constraintsMemo, pageSize]);\n const firstPage = Math.max(1, Math.floor(initialPage));\n\n const [pageState, setPageState] = useState(() => ({ identity, page: firstPage }));\n const page = pageState.identity === identity ? pageState.page : firstPage;\n\n const selectRef = useRef(select);\n selectRef.current = select;\n\n const fetchPage = useCallback(\n async (n: number): Promise<PaginatedPage<WithId<T>>> => {\n let cursor: DocumentSnapshot | null = null;\n if (n > 1) {\n const previous = await queryClient.ensureQueryData<PaginatedPage<WithId<T>>>({\n queryKey: paginatedPageKey(queryKey, n - 1),\n queryFn: () => fetchPage(n - 1),\n });\n if (!previous.hasMore || !previous.cursor) {\n return { items: [], cursor: null, hasMore: false, after: previous.cursor?.id ?? null };\n }\n cursor = previous.cursor;\n }\n const q = query(\n collection(db, collectionPath),\n ...constraints,\n ...(cursor ? [startAfter(cursor)] : []),\n limit(pageSize + 1),\n );\n const snapshot = await getDocs(q);\n const pageDocs = snapshot.docs.slice(0, pageSize);\n return {\n items: pageDocs.map((docSnap) => {\n const dataWithId = { id: docSnap.id, ...docSnap.data() };\n const mapped = selectRef.current ? selectRef.current(dataWithId) : dataWithId;\n return mapped as WithId<T>;\n }),\n cursor: pageDocs[pageDocs.length - 1] ?? null,\n hasMore: snapshot.docs.length > pageSize,\n after: cursor?.id ?? null,\n };\n },\n // queryKey and constraints are captured through their serialized identity.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [db, queryClient, identity, selectRef],\n );\n\n const queryResult = useQuery({\n queryKey: paginatedPageKey(queryKey, page),\n queryFn: () => fetchPage(page),\n enabled,\n staleTime,\n gcTime,\n refetchInterval,\n });\n\n // The page before the displayed one, observed from the cache only (never fetched here).\n // When it is re-read and its last row moves, the displayed entry no longer starts where the\n // chain says it must: it is re-read before it is shown as current.\n const previousPage = useQuery({\n queryKey: paginatedPageKey(queryKey, Math.max(1, page - 1)),\n queryFn: () => fetchPage(Math.max(1, page - 1)),\n enabled: false,\n });\n const cachedEntry = queryResult.data;\n const outOfChain =\n page > 1 &&\n cachedEntry !== undefined &&\n (previousPage.data === undefined ||\n cachedEntry.after !== (previousPage.data.cursor?.id ?? null));\n const { refetch, isFetching } = queryResult;\n useEffect(() => {\n if (enabled && outOfChain && !isFetching) void refetch();\n }, [enabled, outOfChain, isFetching, refetch]);\n\n const entry = outOfChain ? undefined : cachedEntry;\n const hasNextPage = Boolean(entry?.hasMore && entry.cursor);\n\n const setPage = useCallback(\n (target: number) => {\n const next = Math.floor(target);\n if (next < 1 || next === page) return;\n if (next > page + 1) {\n const before = queryClient.getQueryData<PaginatedPage<unknown>>(\n paginatedPageKey(queryKey, next - 1),\n );\n if (!before?.hasMore) return;\n }\n if (next === page + 1 && !hasNextPage) return;\n setPageState({ identity, page: next });\n },\n // queryKey is captured through its serialized identity.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [page, hasNextPage, identity, queryClient],\n );\n\n const nextPage = useCallback(() => setPage(page + 1), [page, setPage]);\n const prevPage = useCallback(() => setPage(page - 1), [page, setPage]);\n\n return {\n ...queryResult,\n ...(outOfChain\n ? { isLoading: true, isPending: true, isSuccess: false, status: 'pending' as const }\n : {}),\n data: entry?.items,\n page,\n pageSize,\n hasNextPage,\n hasPrevPage: page > 1,\n setPage,\n nextPage,\n prevPage,\n } as UseFirestorePaginatedResult<T>;\n}\n"]}
1
+ {"version":3,"file":"use-firestore-paginated.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-paginated.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useState, useCallback, useEffect, useRef } from 'react';\nimport { useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';\nimport {\n collection,\n query,\n getDocs,\n limit,\n startAfter,\n type DocumentData,\n type DocumentSnapshot,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport { paginatedPageKey } from '../cache-helpers.js';\nimport type { WithId } from '../../firestore/types.js';\nimport type { FirestorePaginatedOptions, PaginatedPage } from './types.js';\n\nconst DEFAULT_PAGE_SIZE = 10;\n\nexport type UseFirestorePaginatedResult<T> = Omit<\n UseQueryResult<PaginatedPage<WithId<T>>, Error>,\n 'data'\n> & {\n data: WithId<T>[] | undefined;\n page: number;\n pageSize: number;\n hasNextPage: boolean;\n hasPrevPage: boolean;\n setPage: (page: number) => void;\n nextPage: () => void;\n prevPage: () => void;\n};\n\nexport function useFirestorePaginated<T extends DocumentData = DocumentData>({\n collectionPath,\n queryKey,\n constraints = [],\n pageSize = DEFAULT_PAGE_SIZE,\n initialPage = 1,\n enabled = true,\n staleTime,\n gcTime,\n refetchInterval,\n select,\n}: FirestorePaginatedOptions<T>): UseFirestorePaginatedResult<T> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n const queryKeyMemo = JSON.stringify(queryKey);\n const constraintsMemo = JSON.stringify(constraints);\n const identity = JSON.stringify([collectionPath, queryKeyMemo, constraintsMemo, pageSize]);\n const firstPage = Math.max(1, Math.floor(initialPage));\n\n const [pageState, setPageState] = useState(() => ({ identity, page: firstPage }));\n const page = pageState.identity === identity ? pageState.page : firstPage;\n\n const selectRef = useRef(select);\n selectRef.current = select;\n\n const fetchPage = useCallback(\n async (n: number): Promise<PaginatedPage<WithId<T>>> => {\n let cursor: DocumentSnapshot | null = null;\n if (n > 1) {\n const previous = await queryClient.ensureQueryData<PaginatedPage<WithId<T>>>({\n queryKey: paginatedPageKey(queryKey, n - 1),\n queryFn: () => fetchPage(n - 1),\n });\n if (!previous.hasMore || !previous.cursor) {\n return { items: [], cursor: null, hasMore: false, after: previous.cursor?.id ?? null };\n }\n cursor = previous.cursor;\n }\n const q = query(\n collection(db, collectionPath),\n ...constraints,\n ...(cursor ? [startAfter(cursor)] : []),\n limit(pageSize + 1),\n );\n const snapshot = await getDocs(q);\n const pageDocs = snapshot.docs.slice(0, pageSize);\n return {\n items: pageDocs.map((docSnap) => {\n const dataWithId = { id: docSnap.id, ...docSnap.data() };\n const mapped = selectRef.current ? selectRef.current(dataWithId) : dataWithId;\n return mapped as WithId<T>;\n }),\n cursor: pageDocs[pageDocs.length - 1] ?? null,\n hasMore: snapshot.docs.length > pageSize,\n after: cursor?.id ?? null,\n };\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps -- queryKey and constraints are captured through their serialized identity\n [db, queryClient, identity, selectRef],\n );\n\n const queryResult = useQuery({\n queryKey: paginatedPageKey(queryKey, page),\n queryFn: () => fetchPage(page),\n enabled,\n staleTime,\n gcTime,\n refetchInterval,\n });\n\n const previousPage = useQuery({\n queryKey: paginatedPageKey(queryKey, Math.max(1, page - 1)),\n queryFn: () => fetchPage(Math.max(1, page - 1)),\n enabled: false,\n });\n const cachedEntry = queryResult.data;\n const outOfChain =\n page > 1 &&\n cachedEntry !== undefined &&\n (previousPage.data === undefined ||\n cachedEntry.after !== (previousPage.data.cursor?.id ?? null));\n const { refetch, isFetching } = queryResult;\n useEffect(() => {\n if (enabled && outOfChain && !isFetching) void refetch();\n }, [enabled, outOfChain, isFetching, refetch]);\n\n const entry = outOfChain ? undefined : cachedEntry;\n const hasNextPage = Boolean(entry?.hasMore && entry.cursor);\n\n const setPage = useCallback(\n (target: number) => {\n const next = Math.floor(target);\n if (next < 1 || next === page) return;\n if (next > page + 1) {\n const before = queryClient.getQueryData<PaginatedPage<unknown>>(\n paginatedPageKey(queryKey, next - 1),\n );\n if (!before?.hasMore) return;\n }\n if (next === page + 1 && !hasNextPage) return;\n setPageState({ identity, page: next });\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps -- queryKey is captured through its serialized identity\n [page, hasNextPage, identity, queryClient],\n );\n\n const nextPage = useCallback(() => setPage(page + 1), [page, setPage]);\n const prevPage = useCallback(() => setPage(page - 1), [page, setPage]);\n\n return {\n ...queryResult,\n ...(outOfChain\n ? { isLoading: true, isPending: true, isSuccess: false, status: 'pending' as const }\n : {}),\n data: entry?.items,\n page,\n pageSize,\n hasNextPage,\n hasPrevPage: page > 1,\n setPage,\n nextPage,\n prevPage,\n } as UseFirestorePaginatedResult<T>;\n}\n"]}