@ttt-productions/query-core 0.14.21 → 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 (153) hide show
  1. package/README.md +6 -6
  2. package/dist/defaults.d.ts +0 -22
  3. package/dist/defaults.d.ts.map +1 -1
  4. package/dist/defaults.js +0 -35
  5. package/dist/defaults.js.map +1 -1
  6. package/dist/firestore/types.d.ts +1 -191
  7. package/dist/firestore/types.d.ts.map +1 -1
  8. package/dist/firestore/types.js +1 -6
  9. package/dist/firestore/types.js.map +1 -1
  10. package/dist/index.d.ts +2 -9
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1 -8
  13. package/dist/index.js.map +1 -1
  14. package/dist/keys.d.ts +0 -22
  15. package/dist/keys.d.ts.map +1 -1
  16. package/dist/keys.js +0 -4
  17. package/dist/keys.js.map +1 -1
  18. package/dist/react/cache-helpers.d.ts +16 -0
  19. package/dist/react/cache-helpers.d.ts.map +1 -0
  20. package/dist/{cache-helpers.js → react/cache-helpers.js} +0 -28
  21. package/dist/react/cache-helpers.js.map +1 -0
  22. package/dist/react/defaults.d.ts +4 -0
  23. package/dist/react/defaults.d.ts.map +1 -0
  24. package/dist/react/defaults.js +17 -0
  25. package/dist/react/defaults.js.map +1 -0
  26. package/dist/react/domain-events/cache-invalidation.d.ts +33 -0
  27. package/dist/react/domain-events/cache-invalidation.d.ts.map +1 -0
  28. package/dist/react/domain-events/cache-invalidation.js +74 -0
  29. package/dist/react/domain-events/cache-invalidation.js.map +1 -0
  30. package/dist/react/domain-events/create-domain-event-invalidator.d.ts +28 -0
  31. package/dist/react/domain-events/create-domain-event-invalidator.d.ts.map +1 -0
  32. package/dist/{domain-events → react/domain-events}/create-domain-event-invalidator.js +1 -6
  33. package/dist/react/domain-events/create-domain-event-invalidator.js.map +1 -0
  34. package/dist/react/firestore/absence-scheduler.d.ts +0 -28
  35. package/dist/react/firestore/absence-scheduler.d.ts.map +1 -1
  36. package/dist/react/firestore/absence-scheduler.js +1 -39
  37. package/dist/react/firestore/absence-scheduler.js.map +1 -1
  38. package/dist/react/firestore/batch-doc-loader.d.ts +0 -30
  39. package/dist/react/firestore/batch-doc-loader.d.ts.map +1 -1
  40. package/dist/react/firestore/batch-doc-loader.js +0 -34
  41. package/dist/react/firestore/batch-doc-loader.js.map +1 -1
  42. package/dist/react/firestore/context.d.ts +0 -19
  43. package/dist/react/firestore/context.d.ts.map +1 -1
  44. package/dist/react/firestore/context.js +1 -20
  45. package/dist/react/firestore/context.js.map +1 -1
  46. package/dist/react/firestore/doc-subscription-registry.d.ts +0 -23
  47. package/dist/react/firestore/doc-subscription-registry.d.ts.map +1 -1
  48. package/dist/react/firestore/doc-subscription-registry.js +5 -26
  49. package/dist/react/firestore/doc-subscription-registry.js.map +1 -1
  50. package/dist/react/firestore/infinite-helpers.d.ts +9 -0
  51. package/dist/react/firestore/infinite-helpers.d.ts.map +1 -0
  52. package/dist/react/firestore/infinite-helpers.js +11 -0
  53. package/dist/react/firestore/infinite-helpers.js.map +1 -0
  54. package/dist/react/firestore/resubscribe.d.ts +0 -20
  55. package/dist/react/firestore/resubscribe.d.ts.map +1 -1
  56. package/dist/react/firestore/resubscribe.js +0 -20
  57. package/dist/react/firestore/resubscribe.js.map +1 -1
  58. package/dist/react/firestore/subscription-status.d.ts +0 -29
  59. package/dist/react/firestore/subscription-status.d.ts.map +1 -1
  60. package/dist/react/firestore/subscription-status.js +1 -34
  61. package/dist/react/firestore/subscription-status.js.map +1 -1
  62. package/dist/react/firestore/types.d.ts +80 -0
  63. package/dist/react/firestore/types.d.ts.map +1 -0
  64. package/dist/react/firestore/types.js +4 -0
  65. package/dist/react/firestore/types.js.map +1 -0
  66. package/dist/react/firestore/use-firestore-collection.d.ts +2 -21
  67. package/dist/react/firestore/use-firestore-collection.d.ts.map +1 -1
  68. package/dist/react/firestore/use-firestore-collection.js +23 -44
  69. package/dist/react/firestore/use-firestore-collection.js.map +1 -1
  70. package/dist/react/firestore/use-firestore-count.d.ts +1 -16
  71. package/dist/react/firestore/use-firestore-count.d.ts.map +1 -1
  72. package/dist/react/firestore/use-firestore-count.js +0 -15
  73. package/dist/react/firestore/use-firestore-count.js.map +1 -1
  74. package/dist/react/firestore/use-firestore-doc.d.ts +2 -24
  75. package/dist/react/firestore/use-firestore-doc.d.ts.map +1 -1
  76. package/dist/react/firestore/use-firestore-doc.js +21 -42
  77. package/dist/react/firestore/use-firestore-doc.js.map +1 -1
  78. package/dist/react/firestore/use-firestore-infinite.d.ts +1 -30
  79. package/dist/react/firestore/use-firestore-infinite.d.ts.map +1 -1
  80. package/dist/react/firestore/use-firestore-infinite.js +0 -32
  81. package/dist/react/firestore/use-firestore-infinite.js.map +1 -1
  82. package/dist/react/firestore/use-firestore-live-infinite.d.ts +2 -35
  83. package/dist/react/firestore/use-firestore-live-infinite.d.ts.map +1 -1
  84. package/dist/react/firestore/use-firestore-live-infinite.js +10 -36
  85. package/dist/react/firestore/use-firestore-live-infinite.js.map +1 -1
  86. package/dist/react/firestore/use-firestore-mutations.d.ts +2 -1
  87. package/dist/react/firestore/use-firestore-mutations.d.ts.map +1 -1
  88. package/dist/react/firestore/use-firestore-mutations.js +3 -3
  89. package/dist/react/firestore/use-firestore-mutations.js.map +1 -1
  90. package/dist/react/firestore/use-firestore-paginated.d.ts +2 -31
  91. package/dist/react/firestore/use-firestore-paginated.d.ts.map +1 -1
  92. package/dist/react/firestore/use-firestore-paginated.js +8 -36
  93. package/dist/react/firestore/use-firestore-paginated.js.map +1 -1
  94. package/dist/react/firestore/useBatchFirestoreDocs.d.ts +1 -123
  95. package/dist/react/firestore/useBatchFirestoreDocs.d.ts.map +1 -1
  96. package/dist/react/firestore/useBatchFirestoreDocs.js +12 -61
  97. package/dist/react/firestore/useBatchFirestoreDocs.js.map +1 -1
  98. package/dist/react/index.d.ts +13 -4
  99. package/dist/react/index.d.ts.map +1 -1
  100. package/dist/react/index.js +8 -13
  101. package/dist/react/index.js.map +1 -1
  102. package/dist/react/pending-mutations.d.ts +2 -22
  103. package/dist/react/pending-mutations.d.ts.map +1 -1
  104. package/dist/react/pending-mutations.js +7 -38
  105. package/dist/react/pending-mutations.js.map +1 -1
  106. package/dist/react/provider.d.ts +1 -6
  107. package/dist/react/provider.d.ts.map +1 -1
  108. package/dist/react/provider.js +1 -5
  109. package/dist/react/provider.js.map +1 -1
  110. package/dist/{query-client.d.ts → react/query-client.d.ts} +0 -4
  111. package/dist/react/query-client.d.ts.map +1 -0
  112. package/dist/{query-client.js → react/query-client.js} +0 -4
  113. package/dist/react/query-client.js.map +1 -0
  114. package/dist/react/search/types.d.ts +16 -0
  115. package/dist/react/search/types.d.ts.map +1 -0
  116. package/dist/react/search/types.js +2 -0
  117. package/dist/react/search/types.js.map +1 -0
  118. package/dist/react/search/use-firestore-search.d.ts +1 -30
  119. package/dist/react/search/use-firestore-search.d.ts.map +1 -1
  120. package/dist/react/search/use-firestore-search.js +3 -41
  121. package/dist/react/search/use-firestore-search.js.map +1 -1
  122. package/dist/react/use-read-retry-state.d.ts +1 -21
  123. package/dist/react/use-read-retry-state.d.ts.map +1 -1
  124. package/dist/react/use-read-retry-state.js +3 -22
  125. package/dist/react/use-read-retry-state.js.map +1 -1
  126. package/dist/search/rule.d.ts +0 -6
  127. package/dist/search/rule.d.ts.map +1 -1
  128. package/dist/search/rule.js +0 -6
  129. package/dist/search/rule.js.map +1 -1
  130. package/dist/search/types.d.ts +1 -39
  131. package/dist/search/types.d.ts.map +1 -1
  132. package/dist/search/types.js.map +1 -1
  133. package/package.json +2 -2
  134. package/dist/cache-helpers.d.ts +0 -48
  135. package/dist/cache-helpers.d.ts.map +0 -1
  136. package/dist/cache-helpers.js.map +0 -1
  137. package/dist/domain-events/cache-invalidation.d.ts +0 -70
  138. package/dist/domain-events/cache-invalidation.d.ts.map +0 -1
  139. package/dist/domain-events/cache-invalidation.js +0 -123
  140. package/dist/domain-events/cache-invalidation.js.map +0 -1
  141. package/dist/domain-events/create-domain-event-invalidator.d.ts +0 -46
  142. package/dist/domain-events/create-domain-event-invalidator.d.ts.map +0 -1
  143. package/dist/domain-events/create-domain-event-invalidator.js.map +0 -1
  144. package/dist/domain-events/index.d.ts +0 -5
  145. package/dist/domain-events/index.d.ts.map +0 -1
  146. package/dist/domain-events/index.js +0 -3
  147. package/dist/domain-events/index.js.map +0 -1
  148. package/dist/firestore/infinite-helpers.d.ts +0 -20
  149. package/dist/firestore/infinite-helpers.d.ts.map +0 -1
  150. package/dist/firestore/infinite-helpers.js +0 -23
  151. package/dist/firestore/infinite-helpers.js.map +0 -1
  152. package/dist/query-client.d.ts.map +0 -1
  153. package/dist/query-client.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export function docWithId(snap) {
2
+ return { id: snap.id, ...snap.data() };
3
+ }
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/react/firestore/types.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,SAAS,CAAyB,IAA8B;IAC9E,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;AACzC,CAAC","sourcesContent":["import type {\n DocumentData,\n DocumentSnapshot,\n QueryDocumentSnapshot,\n QueryConstraint,\n} from 'firebase/firestore';\nimport type { WithId, FirestoreBaseOptions } from '../../firestore/types.js';\n\nexport function docWithId<T extends DocumentData>(snap: QueryDocumentSnapshot<T>): WithId<T> {\n return { id: snap.id, ...snap.data() };\n}\n\nexport interface FirestoreDocOptions<T> extends FirestoreBaseOptions {\n docPath: string;\n subscribe?: boolean;\n select?: (data: DocumentData & { id: string }) => T;\n}\n\nexport interface FirestoreCollectionOptions<T> extends FirestoreBaseOptions {\n collectionPath: string;\n constraints?: QueryConstraint[];\n subscribe?: boolean;\n select?: (data: DocumentData & { id: string }) => T;\n}\n\nexport interface FirestoreInfiniteOptions<T> extends FirestoreBaseOptions {\n collectionPath: string;\n constraints?: QueryConstraint[];\n pageSize?: number;\n select?: (data: DocumentData & { id: string }) => T;\n}\n\nexport interface FirestorePaginatedOptions<T> extends FirestoreBaseOptions {\n collectionPath: string;\n constraints?: QueryConstraint[];\n pageSize?: number;\n initialPage?: number;\n refetchInterval?: number;\n select?: (data: DocumentData & { id: string }) => T;\n}\n\nexport interface FirestoreCountOptions extends FirestoreBaseOptions {\n collectionPath: string;\n constraints?: QueryConstraint[];\n refetchInterval?: number;\n}\n\nexport interface FirestoreLiveInfiniteOptions<T = DocumentData & { id: string }> {\n collectionPath: string;\n queryKey: readonly unknown[];\n orderByField: string;\n constraints?: QueryConstraint[];\n pageSize?: number;\n enabled?: boolean;\n select?: (data: DocumentData & { id: string }) => T;\n sort?: 'asc' | 'desc';\n}\n\nexport interface InfinitePage<T> {\n items: WithId<T>[];\n lastDoc: DocumentSnapshot | null;\n size: number;\n}\n\nexport interface PaginatedPage<T> {\n items: T[];\n cursor: DocumentSnapshot | null;\n hasMore: boolean;\n after: string | null;\n}\n\nexport type MutationOperation =\n | { type: 'set'; docPath: string; data: DocumentData; merge?: boolean }\n | { type: 'update'; docPath: string; data: DocumentData }\n | { type: 'delete'; docPath: string };\n"]}
@@ -1,25 +1,6 @@
1
1
  import { type UseQueryResult } from '@tanstack/react-query';
2
2
  import { type DocumentData } from 'firebase/firestore';
3
- import type { FirestoreCollectionOptions, WithId, WithSourceState } from '../../firestore/types.js';
4
- /**
5
- * Fetch all documents from a Firestore collection with optional realtime updates.
6
- * Use this for small collections where you need all documents at once.
7
- * For large collections, use useFirestoreInfinite or useFirestorePaginated.
8
- * * @example
9
- * ```tsx
10
- * // Fetch all channels in an entity
11
- * const { data: channels } = useFirestoreCollection<Channel>({
12
- * collectionPath: `entities/${entityId}/channels`,
13
- * queryKey: ['channels', entityId],
14
- * constraints: [orderBy('createdAt', 'asc')],
15
- * });
16
- * * // With realtime updates
17
- * const { data: members } = useFirestoreCollection<Member>({
18
- * collectionPath: `entities/${entityId}/members`,
19
- * queryKey: ['members', entityId],
20
- * subscribe: true,
21
- * });
22
- * ```
23
- */
3
+ import type { WithId, WithSourceState } from '../../firestore/types.js';
4
+ import type { FirestoreCollectionOptions } from './types.js';
24
5
  export declare function useFirestoreCollection<T extends DocumentData = DocumentData>({ collectionPath, queryKey, constraints, enabled, subscribe, staleTime, gcTime, select, }: FirestoreCollectionOptions<T>): WithSourceState<UseQueryResult<WithId<T>[], Error>>;
25
6
  //# sourceMappingURL=use-firestore-collection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-collection.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-collection.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,KAAK,EACV,0BAA0B,EAC1B,MAAM,EACN,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAElC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC5E,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,OAAc,EACd,SAAiB,EACjB,SAAS,EACT,MAAM,EACN,MAAM,GACP,EAAE,0BAA0B,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CA4GrF"}
1
+ {"version":3,"file":"use-firestore-collection.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-collection.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAA0C,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAI/F,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAE7D,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC5E,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,OAAc,EACd,SAAiB,EACjB,SAAS,EACT,MAAM,EACN,MAAM,GACP,EAAE,0BAA0B,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CA2GrF"}
@@ -1,49 +1,21 @@
1
1
  'use client';
2
2
  import { useEffect } from 'react';
3
3
  import { useQuery, useQueryClient } from '@tanstack/react-query';
4
- import { collection, query, getDocs, onSnapshot, } from 'firebase/firestore';
4
+ import { collection, query, getDocs, onSnapshot } from 'firebase/firestore';
5
5
  import { useFirestoreDb } from './context.js';
6
6
  import { RESUBSCRIBE_DELAYS_MS, isPermissionDeniedError } from './resubscribe.js';
7
7
  import { useSubscribedResult, useSubscriptionStatus } from './subscription-status.js';
8
- /**
9
- * Fetch all documents from a Firestore collection with optional realtime updates.
10
- * Use this for small collections where you need all documents at once.
11
- * For large collections, use useFirestoreInfinite or useFirestorePaginated.
12
- * * @example
13
- * ```tsx
14
- * // Fetch all channels in an entity
15
- * const { data: channels } = useFirestoreCollection<Channel>({
16
- * collectionPath: `entities/${entityId}/channels`,
17
- * queryKey: ['channels', entityId],
18
- * constraints: [orderBy('createdAt', 'asc')],
19
- * });
20
- * * // With realtime updates
21
- * const { data: members } = useFirestoreCollection<Member>({
22
- * collectionPath: `entities/${entityId}/members`,
23
- * queryKey: ['members', entityId],
24
- * subscribe: true,
25
- * });
26
- * ```
27
- */
28
8
  export function useFirestoreCollection({ collectionPath, queryKey, constraints = [], enabled = true, subscribe = false, staleTime, gcTime, select, }) {
29
9
  const db = useFirestoreDb();
30
10
  const queryClient = useQueryClient();
31
- // STABILIZE DEPENDENCIES:
32
- // We stringify these because [where('a', '==', 'b')] !== [where('a', '==', 'b')] in JS.
33
11
  const constraintsMemo = JSON.stringify(constraints);
34
12
  const queryKeyMemo = JSON.stringify(queryKey);
35
- // Listener errors can't surface through useQuery (the queryFn is disabled while
36
- // subscribed), so they're tracked per subscription identity and merged into the result.
37
13
  const subscription = useSubscriptionStatus(JSON.stringify([collectionPath, queryKeyMemo, constraintsMemo, enabled, subscribe]));
38
14
  const { reset: resetStatus, update: updateStatus, restarts } = subscription;
39
15
  useEffect(() => {
40
16
  if (!subscribe || !enabled)
41
17
  return;
42
18
  resetStatus();
43
- // Bounded resubscribe: a transient `permission-denied` (e.g. a failed mid-session
44
- // App Check token refresh) is retried on the RESUBSCRIBE_DELAYS_MS ladder before the
45
- // error is surfaced. `attempt` resets on any healthy snapshot; `disposed`/timers are
46
- // cleaned up on unmount so a pending retry never fires after teardown.
47
19
  let currentUnsubscribe = null;
48
20
  let retryTimer = null;
49
21
  let attempt = 0;
@@ -52,9 +24,7 @@ export function useFirestoreCollection({ collectionPath, queryKey, constraints =
52
24
  if (disposed)
53
25
  return;
54
26
  const collectionRef = collection(db, collectionPath);
55
- const q = constraints.length > 0
56
- ? query(collectionRef, ...constraints)
57
- : collectionRef;
27
+ const q = constraints.length > 0 ? query(collectionRef, ...constraints) : collectionRef;
58
28
  currentUnsubscribe = onSnapshot(q, { includeMetadataChanges: true }, (snapshot) => {
59
29
  attempt = 0;
60
30
  updateStatus({ error: null, restart: null });
@@ -66,11 +36,14 @@ export function useFirestoreCollection({ collectionPath, queryKey, constraints =
66
36
  });
67
37
  queryClient.setQueryData(queryKey, items);
68
38
  const fromCache = snapshot.metadata?.fromCache ?? false;
69
- updateStatus((prev) => ({ sourceState: fromCache ? (prev.sourceState === 'connecting' ? 'connecting' : 'offline') : 'live' }));
39
+ updateStatus((prev) => ({
40
+ sourceState: fromCache
41
+ ? prev.sourceState === 'connecting'
42
+ ? 'connecting'
43
+ : 'offline'
44
+ : 'live',
45
+ }));
70
46
  }, (error) => {
71
- // Heal a transient permission-denied by resubscribing on the backoff ladder,
72
- // keeping the last-known data visible. Only surface the error once the ladder
73
- // is exhausted; any other error surfaces immediately, as before.
74
47
  if (isPermissionDeniedError(error) && attempt < RESUBSCRIBE_DELAYS_MS.length) {
75
48
  currentUnsubscribe?.();
76
49
  currentUnsubscribe = null;
@@ -92,21 +65,27 @@ export function useFirestoreCollection({ collectionPath, queryKey, constraints =
92
65
  clearTimeout(retryTimer);
93
66
  currentUnsubscribe?.();
94
67
  };
95
- // queryKey & constraints are tracked via their stringified forms above; select is intentionally
96
- // excluded so an inline caller function doesn't force a re-subscribe on every render.
97
- // eslint-disable-next-line react-hooks/exhaustive-deps
98
- }, [db, collectionPath, queryKeyMemo, constraintsMemo, enabled, subscribe, restarts, queryClient, resetStatus, updateStatus]);
68
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- queryKey and constraints are tracked by their serialized forms; an inline select must not re-subscribe every render
69
+ }, [
70
+ db,
71
+ collectionPath,
72
+ queryKeyMemo,
73
+ constraintsMemo,
74
+ enabled,
75
+ subscribe,
76
+ restarts,
77
+ queryClient,
78
+ resetStatus,
79
+ updateStatus,
80
+ ]);
99
81
  const queryResult = useQuery({
100
82
  queryKey,
101
83
  queryFn: async () => {
102
84
  const collectionRef = collection(db, collectionPath);
103
- const q = constraints.length > 0
104
- ? query(collectionRef, ...constraints)
105
- : collectionRef;
85
+ const q = constraints.length > 0 ? query(collectionRef, ...constraints) : collectionRef;
106
86
  const snapshot = await getDocs(q);
107
87
  return snapshot.docs.map((docSnap) => {
108
88
  const rawData = docSnap.data();
109
- // Include id in data passed to select, so it can be renamed/transformed
110
89
  const dataWithId = { id: docSnap.id, ...rawData };
111
90
  const data = select ? select(dataWithId) : dataWithId;
112
91
  return data;
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-collection.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-collection.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\nimport { useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';\nimport {\n collection,\n query,\n getDocs,\n onSnapshot,\n type DocumentData,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport { RESUBSCRIBE_DELAYS_MS, isPermissionDeniedError } from './resubscribe.js';\nimport { useSubscribedResult, useSubscriptionStatus } from './subscription-status.js';\nimport type {\n FirestoreCollectionOptions,\n WithId,\n WithSourceState,\n} from '../../firestore/types.js';\n\n/**\n * Fetch all documents from a Firestore collection with optional realtime updates.\n * Use this for small collections where you need all documents at once.\n * For large collections, use useFirestoreInfinite or useFirestorePaginated.\n * * @example\n * ```tsx\n * // Fetch all channels in an entity\n * const { data: channels } = useFirestoreCollection<Channel>({\n * collectionPath: `entities/${entityId}/channels`,\n * queryKey: ['channels', entityId],\n * constraints: [orderBy('createdAt', 'asc')],\n * });\n * * // With realtime updates\n * const { data: members } = useFirestoreCollection<Member>({\n * collectionPath: `entities/${entityId}/members`,\n * queryKey: ['members', entityId],\n * subscribe: true,\n * });\n * ```\n */\nexport function useFirestoreCollection<T extends DocumentData = DocumentData>({\n collectionPath,\n queryKey,\n constraints = [],\n enabled = true,\n subscribe = false,\n staleTime,\n gcTime,\n select,\n}: FirestoreCollectionOptions<T>): WithSourceState<UseQueryResult<WithId<T>[], Error>> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n // STABILIZE DEPENDENCIES:\n // We stringify these because [where('a', '==', 'b')] !== [where('a', '==', 'b')] in JS.\n const constraintsMemo = JSON.stringify(constraints);\n const queryKeyMemo = JSON.stringify(queryKey);\n\n // Listener errors can't surface through useQuery (the queryFn is disabled while\n // subscribed), so they're tracked per subscription identity and merged into the result.\n const subscription = useSubscriptionStatus(\n JSON.stringify([collectionPath, queryKeyMemo, constraintsMemo, enabled, subscribe]),\n );\n const { reset: resetStatus, update: updateStatus, restarts } = subscription;\n\n useEffect(() => {\n if (!subscribe || !enabled) return;\n\n resetStatus();\n\n // Bounded resubscribe: a transient `permission-denied` (e.g. a failed mid-session\n // App Check token refresh) is retried on the RESUBSCRIBE_DELAYS_MS ladder before the\n // error is surfaced. `attempt` resets on any healthy snapshot; `disposed`/timers are\n // cleaned up on unmount so a pending retry never fires after teardown.\n let currentUnsubscribe: (() => void) | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let attempt = 0;\n let disposed = false;\n\n const start = () => {\n if (disposed) return;\n const collectionRef = collection(db, collectionPath);\n const q = constraints.length > 0\n ? query(collectionRef, ...constraints)\n : collectionRef;\n\n currentUnsubscribe = onSnapshot(\n q,\n { includeMetadataChanges: true },\n (snapshot) => {\n attempt = 0;\n updateStatus({ error: null, restart: null });\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 queryClient.setQueryData(queryKey, items);\n const fromCache = snapshot.metadata?.fromCache ?? false;\n updateStatus((prev) => ({ sourceState: fromCache ? (prev.sourceState === 'connecting' ? 'connecting' : 'offline') : 'live' }));\n },\n (error) => {\n // Heal a transient permission-denied by resubscribing on the backoff ladder,\n // keeping the last-known data visible. Only surface the error once the ladder\n // is exhausted; any other error surfaces immediately, as before.\n if (isPermissionDeniedError(error) && attempt < RESUBSCRIBE_DELAYS_MS.length) {\n currentUnsubscribe?.();\n currentUnsubscribe = null;\n const delay = RESUBSCRIBE_DELAYS_MS[attempt];\n attempt += 1;\n updateStatus({ sourceState: 'connecting' });\n retryTimer = setTimeout(start, delay);\n return;\n }\n console.error('[useFirestoreCollection] Subscription error:', error);\n updateStatus({ error, sourceState: 'error', restart: null });\n queryClient.setQueryData(queryKey, undefined);\n }\n );\n };\n\n start();\n\n return () => {\n disposed = true;\n if (retryTimer) clearTimeout(retryTimer);\n currentUnsubscribe?.();\n };\n // queryKey & constraints are tracked via their stringified forms above; select is intentionally\n // excluded so an inline caller function doesn't force a re-subscribe on every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [db, collectionPath, queryKeyMemo, constraintsMemo, enabled, subscribe, restarts, queryClient, resetStatus, updateStatus]);\n\n const queryResult = useQuery({\n queryKey,\n queryFn: async (): Promise<WithId<T>[]> => {\n const collectionRef = collection(db, collectionPath);\n const q = constraints.length > 0\n ? query(collectionRef, ...constraints)\n : collectionRef;\n\n const snapshot = await getDocs(q);\n return 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 enabled: enabled && !subscribe,\n staleTime: subscribe ? Infinity : staleTime,\n gcTime,\n });\n\n return useSubscribedResult(queryResult, subscribe, enabled, subscription);\n}\n"]}
1
+ {"version":3,"file":"use-firestore-collection.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-collection.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\nimport { useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';\nimport { collection, query, getDocs, onSnapshot, type DocumentData } from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport { RESUBSCRIBE_DELAYS_MS, isPermissionDeniedError } from './resubscribe.js';\nimport { useSubscribedResult, useSubscriptionStatus } from './subscription-status.js';\nimport type { WithId, WithSourceState } from '../../firestore/types.js';\nimport type { FirestoreCollectionOptions } from './types.js';\n\nexport function useFirestoreCollection<T extends DocumentData = DocumentData>({\n collectionPath,\n queryKey,\n constraints = [],\n enabled = true,\n subscribe = false,\n staleTime,\n gcTime,\n select,\n}: FirestoreCollectionOptions<T>): WithSourceState<UseQueryResult<WithId<T>[], Error>> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n const constraintsMemo = JSON.stringify(constraints);\n const queryKeyMemo = JSON.stringify(queryKey);\n\n const subscription = useSubscriptionStatus(\n JSON.stringify([collectionPath, queryKeyMemo, constraintsMemo, enabled, subscribe]),\n );\n const { reset: resetStatus, update: updateStatus, restarts } = subscription;\n\n useEffect(() => {\n if (!subscribe || !enabled) return;\n\n resetStatus();\n\n let currentUnsubscribe: (() => void) | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let attempt = 0;\n let disposed = false;\n\n const start = () => {\n if (disposed) return;\n const collectionRef = collection(db, collectionPath);\n const q = constraints.length > 0 ? query(collectionRef, ...constraints) : collectionRef;\n\n currentUnsubscribe = onSnapshot(\n q,\n { includeMetadataChanges: true },\n (snapshot) => {\n attempt = 0;\n updateStatus({ error: null, restart: null });\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 queryClient.setQueryData(queryKey, items);\n const fromCache = snapshot.metadata?.fromCache ?? false;\n updateStatus((prev) => ({\n sourceState: fromCache\n ? prev.sourceState === 'connecting'\n ? 'connecting'\n : 'offline'\n : 'live',\n }));\n },\n (error) => {\n if (isPermissionDeniedError(error) && attempt < RESUBSCRIBE_DELAYS_MS.length) {\n currentUnsubscribe?.();\n currentUnsubscribe = null;\n const delay = RESUBSCRIBE_DELAYS_MS[attempt];\n attempt += 1;\n updateStatus({ sourceState: 'connecting' });\n retryTimer = setTimeout(start, delay);\n return;\n }\n console.error('[useFirestoreCollection] Subscription error:', error);\n updateStatus({ error, sourceState: 'error', restart: null });\n queryClient.setQueryData(queryKey, undefined);\n },\n );\n };\n\n start();\n\n return () => {\n disposed = true;\n if (retryTimer) clearTimeout(retryTimer);\n currentUnsubscribe?.();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- queryKey and constraints are tracked by their serialized forms; an inline select must not re-subscribe every render\n }, [\n db,\n collectionPath,\n queryKeyMemo,\n constraintsMemo,\n enabled,\n subscribe,\n restarts,\n queryClient,\n resetStatus,\n updateStatus,\n ]);\n\n const queryResult = useQuery({\n queryKey,\n queryFn: async (): Promise<WithId<T>[]> => {\n const collectionRef = collection(db, collectionPath);\n const q = constraints.length > 0 ? query(collectionRef, ...constraints) : collectionRef;\n\n const snapshot = await getDocs(q);\n return 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 enabled: enabled && !subscribe,\n staleTime: subscribe ? Infinity : staleTime,\n gcTime,\n });\n\n return useSubscribedResult(queryResult, subscribe, enabled, subscription);\n}\n"]}
@@ -1,19 +1,4 @@
1
1
  import { type UseQueryResult } from '@tanstack/react-query';
2
- import type { FirestoreCountOptions } from '../../firestore/types.js';
3
- /**
4
- * Count documents in a Firestore collection via a server-side `count()`
5
- * aggregation (cheap — it does not read the matching documents). Optional
6
- * polling via `refetchInterval`.
7
- *
8
- * @example
9
- * ```tsx
10
- * const { data: count } = useFirestoreCount({
11
- * collectionPath: 'activeUserNotifications',
12
- * queryKey: ['notifications', 'unread-count', userId],
13
- * constraints: [where('targetUserId', '==', userId), where('seenAt', '==', 0)],
14
- * refetchInterval: 30_000,
15
- * });
16
- * ```
17
- */
2
+ import type { FirestoreCountOptions } from './types.js';
18
3
  export declare function useFirestoreCount({ collectionPath, queryKey, constraints, enabled, staleTime, gcTime, refetchInterval, }: FirestoreCountOptions): UseQueryResult<number, Error>;
19
4
  //# sourceMappingURL=use-firestore-count.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-count.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-count.ts"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEtE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,OAAc,EACd,SAAS,EACT,MAAM,EACN,eAAe,GAChB,EAAE,qBAAqB,GAAG,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,CAgBvD"}
1
+ {"version":3,"file":"use-firestore-count.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-count.ts"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,wBAAgB,iBAAiB,CAAC,EAChC,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,OAAc,EACd,SAAS,EACT,MAAM,EACN,eAAe,GAChB,EAAE,qBAAqB,GAAG,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,CAgBvD"}
@@ -2,21 +2,6 @@
2
2
  import { useQuery } from '@tanstack/react-query';
3
3
  import { collection, query, getCountFromServer } from 'firebase/firestore';
4
4
  import { useFirestoreDb } from './context.js';
5
- /**
6
- * Count documents in a Firestore collection via a server-side `count()`
7
- * aggregation (cheap — it does not read the matching documents). Optional
8
- * polling via `refetchInterval`.
9
- *
10
- * @example
11
- * ```tsx
12
- * const { data: count } = useFirestoreCount({
13
- * collectionPath: 'activeUserNotifications',
14
- * queryKey: ['notifications', 'unread-count', userId],
15
- * constraints: [where('targetUserId', '==', userId), where('seenAt', '==', 0)],
16
- * refetchInterval: 30_000,
17
- * });
18
- * ```
19
- */
20
5
  export function useFirestoreCount({ collectionPath, queryKey, constraints = [], enabled = true, staleTime, gcTime, refetchInterval, }) {
21
6
  const db = useFirestoreDb();
22
7
  return useQuery({
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-count.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-count.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,QAAQ,EAAuB,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAChC,cAAc,EACd,QAAQ,EACR,WAAW,GAAG,EAAE,EAChB,OAAO,GAAG,IAAI,EACd,SAAS,EACT,MAAM,EACN,eAAe,GACO;IACtB,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IAE5B,OAAO,QAAQ,CAAC;QACd,QAAQ;QACR,OAAO,EAAE,KAAK,IAAqB,EAAE;YACnC,MAAM,aAAa,GAAG,UAAU,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;YACrD,MAAM,CAAC,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;YACxF,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,CAAC,CAAC,CAAC;YAC7C,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;QAC/B,CAAC;QACD,OAAO;QACP,SAAS;QACT,MAAM;QACN,eAAe;KAChB,CAAC,CAAC;AACL,CAAC","sourcesContent":["'use client';\n\nimport { useQuery, type UseQueryResult } from '@tanstack/react-query';\nimport { collection, query, getCountFromServer } from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport type { FirestoreCountOptions } from '../../firestore/types.js';\n\n/**\n * Count documents in a Firestore collection via a server-side `count()`\n * aggregation (cheap — it does not read the matching documents). Optional\n * polling via `refetchInterval`.\n *\n * @example\n * ```tsx\n * const { data: count } = useFirestoreCount({\n * collectionPath: 'activeUserNotifications',\n * queryKey: ['notifications', 'unread-count', userId],\n * constraints: [where('targetUserId', '==', userId), where('seenAt', '==', 0)],\n * refetchInterval: 30_000,\n * });\n * ```\n */\nexport function useFirestoreCount({\n collectionPath,\n queryKey,\n constraints = [],\n enabled = true,\n staleTime,\n gcTime,\n refetchInterval,\n}: FirestoreCountOptions): UseQueryResult<number, Error> {\n const db = useFirestoreDb();\n\n return useQuery({\n queryKey,\n queryFn: async (): Promise<number> => {\n const collectionRef = collection(db, collectionPath);\n const q = constraints.length > 0 ? query(collectionRef, ...constraints) : collectionRef;\n const snapshot = await getCountFromServer(q);\n return snapshot.data().count;\n },\n enabled,\n staleTime,\n gcTime,\n refetchInterval,\n });\n}\n"]}
1
+ {"version":3,"file":"use-firestore-count.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-count.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,QAAQ,EAAuB,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,MAAM,UAAU,iBAAiB,CAAC,EAChC,cAAc,EACd,QAAQ,EACR,WAAW,GAAG,EAAE,EAChB,OAAO,GAAG,IAAI,EACd,SAAS,EACT,MAAM,EACN,eAAe,GACO;IACtB,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IAE5B,OAAO,QAAQ,CAAC;QACd,QAAQ;QACR,OAAO,EAAE,KAAK,IAAqB,EAAE;YACnC,MAAM,aAAa,GAAG,UAAU,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;YACrD,MAAM,CAAC,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;YACxF,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,CAAC,CAAC,CAAC;YAC7C,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;QAC/B,CAAC;QACD,OAAO;QACP,SAAS;QACT,MAAM;QACN,eAAe;KAChB,CAAC,CAAC;AACL,CAAC","sourcesContent":["'use client';\n\nimport { useQuery, type UseQueryResult } from '@tanstack/react-query';\nimport { collection, query, getCountFromServer } from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport type { FirestoreCountOptions } from './types.js';\n\nexport function useFirestoreCount({\n collectionPath,\n queryKey,\n constraints = [],\n enabled = true,\n staleTime,\n gcTime,\n refetchInterval,\n}: FirestoreCountOptions): UseQueryResult<number, Error> {\n const db = useFirestoreDb();\n\n return useQuery({\n queryKey,\n queryFn: async (): Promise<number> => {\n const collectionRef = collection(db, collectionPath);\n const q = constraints.length > 0 ? query(collectionRef, ...constraints) : collectionRef;\n const snapshot = await getCountFromServer(q);\n return snapshot.data().count;\n },\n enabled,\n staleTime,\n gcTime,\n refetchInterval,\n });\n}\n"]}
@@ -1,28 +1,6 @@
1
1
  import { type UseQueryResult } from '@tanstack/react-query';
2
2
  import { type DocumentData } from 'firebase/firestore';
3
- import type { FirestoreDocOptions, WithId, WithSourceState } from '../../firestore/types.js';
4
- /**
5
- * Fetch a single Firestore document with optional realtime updates.
6
- * * @example
7
- * ```tsx
8
- * // Simple fetch
9
- * const { data: user } = useFirestoreDoc<User>({
10
- * docPath: `users/${userId}`,
11
- * queryKey: ['user', userId],
12
- * });
13
- * * // With realtime updates
14
- * const { data: entity } = useFirestoreDoc<Entity>({
15
- * docPath: `entities/${entityId}`,
16
- * queryKey: ['entity', entityId],
17
- * subscribe: true,
18
- * });
19
- * * // Conditional fetch
20
- * const { data: profile } = useFirestoreDoc<Profile>({
21
- * docPath: `profiles/${userId}`,
22
- * queryKey: ['profile', userId],
23
- * enabled: !!userId,
24
- * });
25
- * ```
26
- */
3
+ import type { WithId, WithSourceState } from '../../firestore/types.js';
4
+ import type { FirestoreDocOptions } from './types.js';
27
5
  export declare function useFirestoreDoc<T extends DocumentData = DocumentData>({ docPath, queryKey, enabled, subscribe, staleTime, gcTime, select, }: FirestoreDocOptions<T>): WithSourceState<UseQueryResult<WithId<T> | null, Error>>;
28
6
  //# sourceMappingURL=use-firestore-doc.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-doc.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-doc.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAA2B,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIhF,OAAO,KAAK,EACV,mBAAmB,EACnB,MAAM,EACN,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAElC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EACrE,OAAO,EACP,QAAQ,EACR,OAAc,EACd,SAAiB,EACjB,SAAS,EACT,MAAM,EACN,MAAM,GACP,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,CAAC,CAoGnF"}
1
+ {"version":3,"file":"use-firestore-doc.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-doc.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAA2B,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIhF,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,wBAAgB,eAAe,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EACrE,OAAO,EACP,QAAQ,EACR,OAAc,EACd,SAAiB,EACjB,SAAS,EACT,MAAM,EACN,MAAM,GACP,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,CAAC,CAwGnF"}
@@ -5,46 +5,16 @@ import { doc, getDoc, onSnapshot } from 'firebase/firestore';
5
5
  import { useFirestoreDb } from './context.js';
6
6
  import { RESUBSCRIBE_DELAYS_MS, isPermissionDeniedError } from './resubscribe.js';
7
7
  import { useSubscribedResult, useSubscriptionStatus } from './subscription-status.js';
8
- /**
9
- * Fetch a single Firestore document with optional realtime updates.
10
- * * @example
11
- * ```tsx
12
- * // Simple fetch
13
- * const { data: user } = useFirestoreDoc<User>({
14
- * docPath: `users/${userId}`,
15
- * queryKey: ['user', userId],
16
- * });
17
- * * // With realtime updates
18
- * const { data: entity } = useFirestoreDoc<Entity>({
19
- * docPath: `entities/${entityId}`,
20
- * queryKey: ['entity', entityId],
21
- * subscribe: true,
22
- * });
23
- * * // Conditional fetch
24
- * const { data: profile } = useFirestoreDoc<Profile>({
25
- * docPath: `profiles/${userId}`,
26
- * queryKey: ['profile', userId],
27
- * enabled: !!userId,
28
- * });
29
- * ```
30
- */
31
8
  export function useFirestoreDoc({ docPath, queryKey, enabled = true, subscribe = false, staleTime, gcTime, select, }) {
32
9
  const db = useFirestoreDb();
33
10
  const queryClient = useQueryClient();
34
11
  const queryKeyMemo = JSON.stringify(queryKey);
35
- // Listener errors can't surface through useQuery (the queryFn is disabled while
36
- // subscribed), so they're tracked per subscription identity and merged into the result.
37
12
  const subscription = useSubscriptionStatus(JSON.stringify([docPath, queryKeyMemo, enabled, subscribe]));
38
13
  const { reset: resetStatus, update: updateStatus, restarts } = subscription;
39
- // Set up realtime subscription
40
14
  useEffect(() => {
41
15
  if (!subscribe || !enabled)
42
16
  return;
43
17
  resetStatus();
44
- // Bounded resubscribe: a transient `permission-denied` (e.g. a failed mid-session
45
- // App Check token refresh) is retried on the RESUBSCRIBE_DELAYS_MS ladder before the
46
- // error is surfaced. `attempt` resets on any healthy snapshot; `disposed`/timers are
47
- // cleaned up on unmount so a pending retry never fires after teardown.
48
18
  let currentUnsubscribe = null;
49
19
  let retryTimer = null;
50
20
  let attempt = 0;
@@ -58,7 +28,6 @@ export function useFirestoreDoc({ docPath, queryKey, enabled = true, subscribe =
58
28
  updateStatus({ error: null, restart: null });
59
29
  if (snapshot.exists()) {
60
30
  const rawData = snapshot.data();
61
- // Include id in data passed to select, so it can be renamed/transformed
62
31
  const dataWithId = { id: snapshot.id, ...rawData };
63
32
  const data = select ? select(dataWithId) : dataWithId;
64
33
  queryClient.setQueryData(queryKey, data);
@@ -67,11 +36,14 @@ export function useFirestoreDoc({ docPath, queryKey, enabled = true, subscribe =
67
36
  queryClient.setQueryData(queryKey, null);
68
37
  }
69
38
  const fromCache = snapshot.metadata?.fromCache ?? false;
70
- updateStatus((prev) => ({ sourceState: fromCache ? (prev.sourceState === 'connecting' ? 'connecting' : 'offline') : 'live' }));
39
+ updateStatus((prev) => ({
40
+ sourceState: fromCache
41
+ ? prev.sourceState === 'connecting'
42
+ ? 'connecting'
43
+ : 'offline'
44
+ : 'live',
45
+ }));
71
46
  }, (error) => {
72
- // Heal a transient permission-denied by resubscribing on the backoff ladder,
73
- // keeping the last-known data visible. Only surface the error once the ladder
74
- // is exhausted; any other error surfaces immediately, as before.
75
47
  if (isPermissionDeniedError(error) && attempt < RESUBSCRIBE_DELAYS_MS.length) {
76
48
  currentUnsubscribe?.();
77
49
  currentUnsubscribe = null;
@@ -93,10 +65,18 @@ export function useFirestoreDoc({ docPath, queryKey, enabled = true, subscribe =
93
65
  clearTimeout(retryTimer);
94
66
  currentUnsubscribe?.();
95
67
  };
96
- // Note: select intentionally excluded to prevent re-subscribing on every render if the caller
97
- // passes an inline function. queryKey is tracked via queryKeyMemo above.
98
- // eslint-disable-next-line react-hooks/exhaustive-deps
99
- }, [db, docPath, queryKeyMemo, enabled, subscribe, restarts, queryClient, resetStatus, updateStatus]);
68
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- queryKey is tracked by queryKeyMemo; an inline select must not re-subscribe every render
69
+ }, [
70
+ db,
71
+ docPath,
72
+ queryKeyMemo,
73
+ enabled,
74
+ subscribe,
75
+ restarts,
76
+ queryClient,
77
+ resetStatus,
78
+ updateStatus,
79
+ ]);
100
80
  const query = useQuery({
101
81
  queryKey,
102
82
  queryFn: async () => {
@@ -106,13 +86,12 @@ export function useFirestoreDoc({ docPath, queryKey, enabled = true, subscribe =
106
86
  return null;
107
87
  }
108
88
  const rawData = snapshot.data();
109
- // Include id in data passed to select, so it can be renamed/transformed
110
89
  const dataWithId = { id: snapshot.id, ...rawData };
111
90
  const data = select ? select(dataWithId) : dataWithId;
112
91
  return data;
113
92
  },
114
- enabled: enabled && !subscribe, // Don't fetch if subscribing (snapshot handles it)
115
- staleTime: subscribe ? Infinity : staleTime, // Realtime data is always fresh
93
+ enabled: enabled && !subscribe,
94
+ staleTime: subscribe ? Infinity : staleTime,
116
95
  gcTime,
117
96
  });
118
97
  return useSubscribedResult(query, subscribe, enabled, subscription);
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-doc.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-doc.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\nimport { useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';\nimport { doc, getDoc, onSnapshot, type DocumentData } from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport { RESUBSCRIBE_DELAYS_MS, isPermissionDeniedError } from './resubscribe.js';\nimport { useSubscribedResult, useSubscriptionStatus } from './subscription-status.js';\nimport type {\n FirestoreDocOptions,\n WithId,\n WithSourceState,\n} from '../../firestore/types.js';\n\n/**\n * Fetch a single Firestore document with optional realtime updates.\n * * @example\n * ```tsx\n * // Simple fetch\n * const { data: user } = useFirestoreDoc<User>({\n * docPath: `users/${userId}`,\n * queryKey: ['user', userId],\n * });\n * * // With realtime updates\n * const { data: entity } = useFirestoreDoc<Entity>({\n * docPath: `entities/${entityId}`,\n * queryKey: ['entity', entityId],\n * subscribe: true,\n * });\n * * // Conditional fetch\n * const { data: profile } = useFirestoreDoc<Profile>({\n * docPath: `profiles/${userId}`,\n * queryKey: ['profile', userId],\n * enabled: !!userId,\n * });\n * ```\n */\nexport function useFirestoreDoc<T extends DocumentData = DocumentData>({\n docPath,\n queryKey,\n enabled = true,\n subscribe = false,\n staleTime,\n gcTime,\n select,\n}: FirestoreDocOptions<T>): WithSourceState<UseQueryResult<WithId<T> | null, Error>> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n const queryKeyMemo = JSON.stringify(queryKey);\n\n // Listener errors can't surface through useQuery (the queryFn is disabled while\n // subscribed), so they're tracked per subscription identity and merged into the result.\n const subscription = useSubscriptionStatus(JSON.stringify([docPath, queryKeyMemo, enabled, subscribe]));\n const { reset: resetStatus, update: updateStatus, restarts } = subscription;\n\n // Set up realtime subscription\n useEffect(() => {\n if (!subscribe || !enabled) return;\n\n resetStatus();\n\n // Bounded resubscribe: a transient `permission-denied` (e.g. a failed mid-session\n // App Check token refresh) is retried on the RESUBSCRIBE_DELAYS_MS ladder before the\n // error is surfaced. `attempt` resets on any healthy snapshot; `disposed`/timers are\n // cleaned up on unmount so a pending retry never fires after teardown.\n let currentUnsubscribe: (() => void) | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let attempt = 0;\n let disposed = false;\n\n const start = () => {\n if (disposed) return;\n const docRef = doc(db, docPath);\n currentUnsubscribe = onSnapshot(\n docRef,\n { includeMetadataChanges: true },\n (snapshot) => {\n attempt = 0;\n updateStatus({ error: null, restart: null });\n if (snapshot.exists()) {\n const rawData = snapshot.data();\n // Include id in data passed to select, so it can be renamed/transformed\n const dataWithId = { id: snapshot.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n queryClient.setQueryData(queryKey, data as WithId<T>);\n } else {\n queryClient.setQueryData(queryKey, null);\n }\n const fromCache = snapshot.metadata?.fromCache ?? false;\n updateStatus((prev) => ({ sourceState: fromCache ? (prev.sourceState === 'connecting' ? 'connecting' : 'offline') : 'live' }));\n },\n (error) => {\n // Heal a transient permission-denied by resubscribing on the backoff ladder,\n // keeping the last-known data visible. Only surface the error once the ladder\n // is exhausted; any other error surfaces immediately, as before.\n if (isPermissionDeniedError(error) && attempt < RESUBSCRIBE_DELAYS_MS.length) {\n currentUnsubscribe?.();\n currentUnsubscribe = null;\n const delay = RESUBSCRIBE_DELAYS_MS[attempt];\n attempt += 1;\n updateStatus({ sourceState: 'connecting' });\n retryTimer = setTimeout(start, delay);\n return;\n }\n console.error('[useFirestoreDoc] Subscription error:', error);\n updateStatus({ error, sourceState: 'error', restart: null });\n queryClient.setQueryData(queryKey, undefined);\n }\n );\n };\n\n start();\n\n return () => {\n disposed = true;\n if (retryTimer) clearTimeout(retryTimer);\n currentUnsubscribe?.();\n };\n // Note: select intentionally excluded to prevent re-subscribing on every render if the caller\n // passes an inline function. queryKey is tracked via queryKeyMemo above.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [db, docPath, queryKeyMemo, enabled, subscribe, restarts, queryClient, resetStatus, updateStatus]);\n\n const query = useQuery({\n queryKey,\n queryFn: async (): Promise<WithId<T> | null> => {\n const docRef = doc(db, docPath);\n const snapshot = await getDoc(docRef);\n\n if (!snapshot.exists()) {\n return null;\n }\n\n const rawData = snapshot.data();\n // Include id in data passed to select, so it can be renamed/transformed\n const dataWithId = { id: snapshot.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n return data as WithId<T>;\n },\n enabled: enabled && !subscribe, // Don't fetch if subscribing (snapshot handles it)\n staleTime: subscribe ? Infinity : staleTime, // Realtime data is always fresh\n gcTime,\n });\n\n return useSubscribedResult(query, subscribe, enabled, subscription);\n}\n"]}
1
+ {"version":3,"file":"use-firestore-doc.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-doc.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\nimport { useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';\nimport { doc, getDoc, onSnapshot, type DocumentData } from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport { RESUBSCRIBE_DELAYS_MS, isPermissionDeniedError } from './resubscribe.js';\nimport { useSubscribedResult, useSubscriptionStatus } from './subscription-status.js';\nimport type { WithId, WithSourceState } from '../../firestore/types.js';\nimport type { FirestoreDocOptions } from './types.js';\n\nexport function useFirestoreDoc<T extends DocumentData = DocumentData>({\n docPath,\n queryKey,\n enabled = true,\n subscribe = false,\n staleTime,\n gcTime,\n select,\n}: FirestoreDocOptions<T>): WithSourceState<UseQueryResult<WithId<T> | null, Error>> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n const queryKeyMemo = JSON.stringify(queryKey);\n\n const subscription = useSubscriptionStatus(\n JSON.stringify([docPath, queryKeyMemo, enabled, subscribe]),\n );\n const { reset: resetStatus, update: updateStatus, restarts } = subscription;\n\n useEffect(() => {\n if (!subscribe || !enabled) return;\n\n resetStatus();\n\n let currentUnsubscribe: (() => void) | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let attempt = 0;\n let disposed = false;\n\n const start = () => {\n if (disposed) return;\n const docRef = doc(db, docPath);\n currentUnsubscribe = onSnapshot(\n docRef,\n { includeMetadataChanges: true },\n (snapshot) => {\n attempt = 0;\n updateStatus({ error: null, restart: null });\n if (snapshot.exists()) {\n const rawData = snapshot.data();\n const dataWithId = { id: snapshot.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n queryClient.setQueryData(queryKey, data as WithId<T>);\n } else {\n queryClient.setQueryData(queryKey, null);\n }\n const fromCache = snapshot.metadata?.fromCache ?? false;\n updateStatus((prev) => ({\n sourceState: fromCache\n ? prev.sourceState === 'connecting'\n ? 'connecting'\n : 'offline'\n : 'live',\n }));\n },\n (error) => {\n if (isPermissionDeniedError(error) && attempt < RESUBSCRIBE_DELAYS_MS.length) {\n currentUnsubscribe?.();\n currentUnsubscribe = null;\n const delay = RESUBSCRIBE_DELAYS_MS[attempt];\n attempt += 1;\n updateStatus({ sourceState: 'connecting' });\n retryTimer = setTimeout(start, delay);\n return;\n }\n console.error('[useFirestoreDoc] Subscription error:', error);\n updateStatus({ error, sourceState: 'error', restart: null });\n queryClient.setQueryData(queryKey, undefined);\n },\n );\n };\n\n start();\n\n return () => {\n disposed = true;\n if (retryTimer) clearTimeout(retryTimer);\n currentUnsubscribe?.();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- queryKey is tracked by queryKeyMemo; an inline select must not re-subscribe every render\n }, [\n db,\n docPath,\n queryKeyMemo,\n enabled,\n subscribe,\n restarts,\n queryClient,\n resetStatus,\n updateStatus,\n ]);\n\n const query = useQuery({\n queryKey,\n queryFn: async (): Promise<WithId<T> | null> => {\n const docRef = doc(db, docPath);\n const snapshot = await getDoc(docRef);\n\n if (!snapshot.exists()) {\n return null;\n }\n\n const rawData = snapshot.data();\n const dataWithId = { id: snapshot.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n return data as WithId<T>;\n },\n enabled: enabled && !subscribe,\n staleTime: subscribe ? Infinity : staleTime,\n gcTime,\n });\n\n return useSubscribedResult(query, subscribe, enabled, subscription);\n}\n"]}
@@ -1,35 +1,6 @@
1
1
  import { type UseInfiniteQueryResult } from '@tanstack/react-query';
2
2
  import { type DocumentData, type DocumentSnapshot } from 'firebase/firestore';
3
- import type { FirestoreInfiniteOptions, InfinitePage } from '../../firestore/types.js';
4
- /**
5
- * Infinite scroll pagination for Firestore collections.
6
- * Automatically handles cursor-based pagination with React Query.
7
- *
8
- * @example
9
- * ```tsx
10
- * const {
11
- * data,
12
- * fetchNextPage,
13
- * hasNextPage,
14
- * isFetchingNextPage,
15
- * isLoading,
16
- * } = useFirestoreInfinite<Post>({
17
- * collectionPath: 'posts',
18
- * queryKey: ['posts', 'feed'],
19
- * constraints: [
20
- * where('status', '==', 'published'),
21
- * orderBy('createdAt', 'desc'),
22
- * ],
23
- * pageSize: 20,
24
- * });
25
- *
26
- * // Flatten pages for rendering
27
- * const posts = data?.pages.flatMap(page => page.items) ?? [];
28
- *
29
- * // Load more on scroll
30
- * <InfiniteScroll onLoadMore={fetchNextPage} hasMore={hasNextPage} />
31
- * ```
32
- */
3
+ import type { FirestoreInfiniteOptions, InfinitePage } from './types.js';
33
4
  export declare function useFirestoreInfinite<T extends DocumentData = DocumentData>({ collectionPath, queryKey, constraints, pageSize, enabled, staleTime, gcTime, select, }: FirestoreInfiniteOptions<T>): UseInfiniteQueryResult<{
34
5
  pages: InfinitePage<T>[];
35
6
  pageParams: (DocumentSnapshot | null)[];
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-infinite.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-infinite.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAML,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,wBAAwB,EAAE,YAAY,EAAU,MAAM,0BAA0B,CAAC;AAI/F;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC1E,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,QAA4B,EAC5B,OAAc,EACd,SAAS,EACT,MAAM,EACN,MAAM,GACP,EAAE,wBAAwB,CAAC,CAAC,CAAC,GAAG,sBAAsB,CAAC;IAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAAC,UAAU,EAAE,CAAC,gBAAgB,GAAG,IAAI,CAAC,EAAE,CAAA;CAAE,EAAE,KAAK,CAAC,CA0CpI"}
1
+ {"version":3,"file":"use-firestore-infinite.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-infinite.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAML,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,wBAAwB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAIzE,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC1E,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,QAA4B,EAC5B,OAAc,EACd,SAAS,EACT,MAAM,EACN,MAAM,GACP,EAAE,wBAAwB,CAAC,CAAC,CAAC,GAAG,sBAAsB,CACrD;IAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAAC,UAAU,EAAE,CAAC,gBAAgB,GAAG,IAAI,CAAC,EAAE,CAAA;CAAE,EACrE,KAAK,CACN,CAuCA"}
@@ -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;AAG9C,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;IAC5B,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 { FirestoreInfiniteOptions, InfinitePage, WithId } from '../../firestore/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<{ pages: InfinitePage<T>[]; pageParams: (DocumentSnapshot | null)[] }, Error> {\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"]}