@ttt-productions/query-core 0.14.22 → 0.14.24
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.
- package/README.md +6 -6
- package/dist/defaults.d.ts +0 -15
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +0 -15
- package/dist/defaults.js.map +1 -1
- package/dist/firestore/types.d.ts +5 -49
- package/dist/firestore/types.d.ts.map +1 -1
- package/dist/firestore/types.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/keys.d.ts +0 -22
- package/dist/keys.d.ts.map +1 -1
- package/dist/keys.js +0 -4
- package/dist/keys.js.map +1 -1
- package/dist/react/cache-helpers.d.ts +0 -32
- package/dist/react/cache-helpers.d.ts.map +1 -1
- package/dist/react/cache-helpers.js +0 -28
- package/dist/react/cache-helpers.js.map +1 -1
- package/dist/react/callables/create-callables.d.ts +40 -0
- package/dist/react/callables/create-callables.d.ts.map +1 -0
- package/dist/react/callables/create-callables.js +101 -0
- package/dist/react/callables/create-callables.js.map +1 -0
- package/dist/react/defaults.d.ts +0 -4
- package/dist/react/defaults.d.ts.map +1 -1
- package/dist/react/defaults.js +2 -6
- package/dist/react/defaults.js.map +1 -1
- package/dist/react/domain-events/cache-invalidation.d.ts +0 -37
- package/dist/react/domain-events/cache-invalidation.d.ts.map +1 -1
- package/dist/react/domain-events/cache-invalidation.js +4 -53
- package/dist/react/domain-events/cache-invalidation.js.map +1 -1
- package/dist/react/domain-events/create-domain-event-invalidator.d.ts +0 -18
- package/dist/react/domain-events/create-domain-event-invalidator.d.ts.map +1 -1
- package/dist/react/domain-events/create-domain-event-invalidator.js +0 -5
- package/dist/react/domain-events/create-domain-event-invalidator.js.map +1 -1
- package/dist/react/firestore/absence-scheduler.d.ts +0 -28
- package/dist/react/firestore/absence-scheduler.d.ts.map +1 -1
- package/dist/react/firestore/absence-scheduler.js +1 -39
- package/dist/react/firestore/absence-scheduler.js.map +1 -1
- package/dist/react/firestore/batch-doc-loader.d.ts +0 -30
- package/dist/react/firestore/batch-doc-loader.d.ts.map +1 -1
- package/dist/react/firestore/batch-doc-loader.js +0 -34
- package/dist/react/firestore/batch-doc-loader.js.map +1 -1
- package/dist/react/firestore/context.d.ts +0 -19
- package/dist/react/firestore/context.d.ts.map +1 -1
- package/dist/react/firestore/context.js +0 -19
- package/dist/react/firestore/context.js.map +1 -1
- package/dist/react/firestore/doc-subscription-registry.d.ts +0 -23
- package/dist/react/firestore/doc-subscription-registry.d.ts.map +1 -1
- package/dist/react/firestore/doc-subscription-registry.js +5 -26
- package/dist/react/firestore/doc-subscription-registry.js.map +1 -1
- package/dist/react/firestore/infinite-helpers.d.ts +0 -12
- package/dist/react/firestore/infinite-helpers.d.ts.map +1 -1
- package/dist/react/firestore/infinite-helpers.js +0 -12
- package/dist/react/firestore/infinite-helpers.js.map +1 -1
- package/dist/react/firestore/resubscribe.d.ts +0 -20
- package/dist/react/firestore/resubscribe.d.ts.map +1 -1
- package/dist/react/firestore/resubscribe.js +0 -20
- package/dist/react/firestore/resubscribe.js.map +1 -1
- package/dist/react/firestore/subscription-status.d.ts +0 -29
- package/dist/react/firestore/subscription-status.d.ts.map +1 -1
- package/dist/react/firestore/subscription-status.js +1 -34
- package/dist/react/firestore/subscription-status.js.map +1 -1
- package/dist/react/firestore/types.d.ts +0 -64
- package/dist/react/firestore/types.d.ts.map +1 -1
- package/dist/react/firestore/types.js +0 -3
- package/dist/react/firestore/types.js.map +1 -1
- package/dist/react/firestore/use-firestore-collection.d.ts +0 -20
- package/dist/react/firestore/use-firestore-collection.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-collection.js +1 -35
- package/dist/react/firestore/use-firestore-collection.js.map +1 -1
- package/dist/react/firestore/use-firestore-count.d.ts +0 -15
- package/dist/react/firestore/use-firestore-count.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-count.js +0 -15
- package/dist/react/firestore/use-firestore-count.js.map +1 -1
- package/dist/react/firestore/use-firestore-doc.d.ts +0 -23
- package/dist/react/firestore/use-firestore-doc.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-doc.js +3 -40
- package/dist/react/firestore/use-firestore-doc.js.map +1 -1
- package/dist/react/firestore/use-firestore-infinite.d.ts +0 -29
- package/dist/react/firestore/use-firestore-infinite.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-infinite.js +0 -32
- package/dist/react/firestore/use-firestore-infinite.js.map +1 -1
- package/dist/react/firestore/use-firestore-live-infinite.d.ts +0 -34
- package/dist/react/firestore/use-firestore-live-infinite.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-live-infinite.js +1 -35
- package/dist/react/firestore/use-firestore-live-infinite.js.map +1 -1
- package/dist/react/firestore/use-firestore-mutations.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-mutations.js +6 -5
- package/dist/react/firestore/use-firestore-mutations.js.map +1 -1
- package/dist/react/firestore/use-firestore-paginated.d.ts +0 -30
- package/dist/react/firestore/use-firestore-paginated.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-paginated.js +2 -33
- package/dist/react/firestore/use-firestore-paginated.js.map +1 -1
- package/dist/react/firestore/useBatchFirestoreDocs.d.ts +1 -123
- package/dist/react/firestore/useBatchFirestoreDocs.d.ts.map +1 -1
- package/dist/react/firestore/useBatchFirestoreDocs.js +23 -64
- package/dist/react/firestore/useBatchFirestoreDocs.js.map +1 -1
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -13
- package/dist/react/index.js.map +1 -1
- package/dist/react/pending-mutations.d.ts +1 -21
- package/dist/react/pending-mutations.d.ts.map +1 -1
- package/dist/react/pending-mutations.js +1 -32
- package/dist/react/pending-mutations.js.map +1 -1
- package/dist/react/provider.d.ts +0 -5
- package/dist/react/provider.d.ts.map +1 -1
- package/dist/react/provider.js +0 -4
- package/dist/react/provider.js.map +1 -1
- package/dist/react/query-client.d.ts +0 -4
- package/dist/react/query-client.d.ts.map +1 -1
- package/dist/react/query-client.js +0 -4
- package/dist/react/query-client.js.map +1 -1
- package/dist/react/search/types.d.ts +0 -12
- package/dist/react/search/types.d.ts.map +1 -1
- package/dist/react/search/types.js.map +1 -1
- package/dist/react/search/use-firestore-search.d.ts +0 -29
- package/dist/react/search/use-firestore-search.d.ts.map +1 -1
- package/dist/react/search/use-firestore-search.js +3 -41
- package/dist/react/search/use-firestore-search.js.map +1 -1
- package/dist/react/session-mutation.d.ts +27 -0
- package/dist/react/session-mutation.d.ts.map +1 -0
- package/dist/react/session-mutation.js +150 -0
- package/dist/react/session-mutation.js.map +1 -0
- package/dist/react/use-read-retry-state.d.ts +1 -21
- package/dist/react/use-read-retry-state.d.ts.map +1 -1
- package/dist/react/use-read-retry-state.js +2 -21
- package/dist/react/use-read-retry-state.js.map +1 -1
- package/dist/search/rule.d.ts +0 -6
- package/dist/search/rule.d.ts.map +1 -1
- package/dist/search/rule.js +0 -6
- package/dist/search/rule.js.map +1 -1
- package/dist/search/types.d.ts +1 -13
- package/dist/search/types.d.ts.map +1 -1
- package/dist/search/types.js.map +1 -1
- package/package.json +4 -4
|
@@ -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);
|
|
@@ -75,9 +44,6 @@ export function useFirestoreDoc({ docPath, queryKey, enabled = true, subscribe =
|
|
|
75
44
|
: 'live',
|
|
76
45
|
}));
|
|
77
46
|
}, (error) => {
|
|
78
|
-
// Heal a transient permission-denied by resubscribing on the backoff ladder,
|
|
79
|
-
// keeping the last-known data visible. Only surface the error once the ladder
|
|
80
|
-
// is exhausted; any other error surfaces immediately, as before.
|
|
81
47
|
if (isPermissionDeniedError(error) && attempt < RESUBSCRIBE_DELAYS_MS.length) {
|
|
82
48
|
currentUnsubscribe?.();
|
|
83
49
|
currentUnsubscribe = null;
|
|
@@ -99,9 +65,7 @@ export function useFirestoreDoc({ docPath, queryKey, enabled = true, subscribe =
|
|
|
99
65
|
clearTimeout(retryTimer);
|
|
100
66
|
currentUnsubscribe?.();
|
|
101
67
|
};
|
|
102
|
-
//
|
|
103
|
-
// passes an inline function. queryKey is tracked via queryKeyMemo above.
|
|
104
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
68
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- queryKey is tracked by queryKeyMemo; an inline select must not re-subscribe every render
|
|
105
69
|
}, [
|
|
106
70
|
db,
|
|
107
71
|
docPath,
|
|
@@ -122,13 +86,12 @@ export function useFirestoreDoc({ docPath, queryKey, enabled = true, subscribe =
|
|
|
122
86
|
return null;
|
|
123
87
|
}
|
|
124
88
|
const rawData = snapshot.data();
|
|
125
|
-
// Include id in data passed to select, so it can be renamed/transformed
|
|
126
89
|
const dataWithId = { id: snapshot.id, ...rawData };
|
|
127
90
|
const data = select ? select(dataWithId) : dataWithId;
|
|
128
91
|
return data;
|
|
129
92
|
},
|
|
130
|
-
enabled: enabled && !subscribe,
|
|
131
|
-
staleTime: subscribe ? Infinity : staleTime,
|
|
93
|
+
enabled: enabled && !subscribe,
|
|
94
|
+
staleTime: subscribe ? Infinity : staleTime,
|
|
132
95
|
gcTime,
|
|
133
96
|
});
|
|
134
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":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAuB,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAqB,MAAM,oBAAoB,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAClF,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAItF
|
|
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
3
|
import type { FirestoreInfiniteOptions, InfinitePage } from './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
|
-
*/
|
|
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;AAG5B,OAAO,KAAK,EAAE,wBAAwB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAIzE
|
|
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;AAI9C,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B
|
|
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,
|
|
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
|
-
//
|
|
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":"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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 /** 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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-firestore-mutations.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-mutations.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"use-firestore-mutations.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-mutations.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkB,KAAK,QAAQ,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC9F,OAAO,EAML,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAChG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAIpD,UAAU,SAAS,CAAC,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,CAAC,CAAC;CACT;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,YAAY,EAAE,EACtD,cAAmB,GACpB,GAAE,IAAI,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAAE,gBAAgB,CAAM,GAAG,iBAAiB,CAC7E,SAAS,CAAC,CAAC,CAAC,EACZ,KAAK,EACL;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAC9C,CAwBA;AAED,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,YAAY,EAAE,EACzD,cAAmB,EACnB,UAAU,GACX,GAAE,wBAAwB,CAAC,CAAC,CAAM,GAAG,iBAAiB,CACrD,OAAO,CAAC,CAAC,CAAC,EACV,KAAK,EACL;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;CAAE,EACrC;IAAE,YAAY,EAAE,CAAC,GAAG,SAAS,CAAA;CAAE,GAAG,SAAS,CAC5C,CAoCA;AAED,wBAAgB,kBAAkB,CAAC,CAAC,GAAG,OAAO,EAAE,EAC9C,cAAmB,EACnB,UAAU,GACX,GAAE;IACD,cAAc,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;IACrC,UAAU,CAAC,EAAE;QACX,QAAQ,EAAE,QAAQ,CAAC;QACnB,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC,CAAC;KACzD,CAAC;CACE,GAAG,iBAAiB,CACxB,MAAM,EACN,KAAK,EACL;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,EACnB;IAAE,YAAY,EAAE,CAAC,GAAG,SAAS,CAAA;CAAE,GAAG,SAAS,CAC5C,CAkCA;AAED,UAAU,WAAW;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,cAAmB,EACnB,SAA8B,GAC/B,GAAE,qBAA0B,GAAG,iBAAiB,CAC/C,WAAW,EACX,KAAK,EACL;IAAE,UAAU,EAAE,iBAAiB,EAAE,CAAA;CAAE,CACpC,CAmDA"}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
2
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
3
3
|
import { doc, setDoc, updateDoc, deleteDoc, writeBatch, } from 'firebase/firestore';
|
|
4
4
|
import { useFirestoreDb } from './context.js';
|
|
5
|
+
import { useSessionMutation } from '../session-mutation.js';
|
|
5
6
|
const DEFAULT_BATCH_SIZE = 450;
|
|
6
7
|
export function useFirestoreSet({ invalidateKeys = [], } = {}) {
|
|
7
8
|
const db = useFirestoreDb();
|
|
8
9
|
const queryClient = useQueryClient();
|
|
9
|
-
return
|
|
10
|
+
return useSessionMutation({
|
|
10
11
|
mutationFn: async ({ docPath, data, merge = false, }) => {
|
|
11
12
|
const docRef = doc(db, docPath);
|
|
12
13
|
await setDoc(docRef, data, { merge });
|
|
@@ -22,7 +23,7 @@ export function useFirestoreSet({ invalidateKeys = [], } = {}) {
|
|
|
22
23
|
export function useFirestoreUpdate({ invalidateKeys = [], optimistic, } = {}) {
|
|
23
24
|
const db = useFirestoreDb();
|
|
24
25
|
const queryClient = useQueryClient();
|
|
25
|
-
return
|
|
26
|
+
return useSessionMutation({
|
|
26
27
|
mutationFn: async ({ docPath, data }) => {
|
|
27
28
|
const docRef = doc(db, docPath);
|
|
28
29
|
await updateDoc(docRef, data);
|
|
@@ -54,7 +55,7 @@ export function useFirestoreUpdate({ invalidateKeys = [], optimistic, } = {}) {
|
|
|
54
55
|
export function useFirestoreDelete({ invalidateKeys = [], optimistic, } = {}) {
|
|
55
56
|
const db = useFirestoreDb();
|
|
56
57
|
const queryClient = useQueryClient();
|
|
57
|
-
return
|
|
58
|
+
return useSessionMutation({
|
|
58
59
|
mutationFn: async ({ docPath }) => {
|
|
59
60
|
const docRef = doc(db, docPath);
|
|
60
61
|
await deleteDoc(docRef);
|
|
@@ -86,7 +87,7 @@ export function useFirestoreDelete({ invalidateKeys = [], optimistic, } = {}) {
|
|
|
86
87
|
export function useFirestoreBatch({ invalidateKeys = [], batchSize = DEFAULT_BATCH_SIZE, } = {}) {
|
|
87
88
|
const db = useFirestoreDb();
|
|
88
89
|
const queryClient = useQueryClient();
|
|
89
|
-
return
|
|
90
|
+
return useSessionMutation({
|
|
90
91
|
mutationFn: async ({ operations }) => {
|
|
91
92
|
const chunks = [];
|
|
92
93
|
for (let i = 0; i < operations.length; i += batchSize) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-firestore-mutations.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-mutations.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport {\n useMutation,\n useQueryClient,\n type QueryKey,\n type UseMutationResult,\n} from '@tanstack/react-query';\nimport {\n doc,\n setDoc,\n updateDoc,\n deleteDoc,\n writeBatch,\n type DocumentData,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport type { FirestoreMutationOptions, FirestoreBatchOptions } from '../../firestore/types.js';\nimport type { MutationOperation } from './types.js';\n\nconst DEFAULT_BATCH_SIZE = 450;\n\ninterface SetResult<T> {\n id: string;\n data: T;\n}\n\nexport function useFirestoreSet<T extends DocumentData>({\n invalidateKeys = [],\n}: Pick<FirestoreMutationOptions<T>, 'invalidateKeys'> = {}): UseMutationResult<\n SetResult<T>,\n Error,\n { docPath: string; data: T; merge?: boolean }\n> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({\n docPath,\n data,\n merge = false,\n }: {\n docPath: string;\n data: T;\n merge?: boolean;\n }) => {\n const docRef = doc(db, docPath);\n await setDoc(docRef, data, { merge });\n return { id: docRef.id, data };\n },\n onSuccess: () => {\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: key });\n });\n },\n });\n}\n\nexport function useFirestoreUpdate<T extends DocumentData>({\n invalidateKeys = [],\n optimistic,\n}: FirestoreMutationOptions<T> = {}): UseMutationResult<\n Partial<T>,\n Error,\n { docPath: string; data: Partial<T> },\n { previousData: T | undefined } | undefined\n> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({ docPath, data }: { docPath: string; data: Partial<T> }) => {\n const docRef = doc(db, docPath);\n await updateDoc(docRef, data as DocumentData);\n return data;\n },\n onMutate: async ({ data }) => {\n if (!optimistic) return undefined;\n\n await queryClient.cancelQueries({ queryKey: optimistic.queryKey });\n const previousData = queryClient.getQueryData<T>(optimistic.queryKey);\n\n queryClient.setQueryData<T>(optimistic.queryKey, (old) =>\n optimistic.updater(old, data as Partial<T>),\n );\n\n return { previousData };\n },\n onError: (_error, _variables, context) => {\n if (optimistic && context?.previousData !== undefined) {\n queryClient.setQueryData(optimistic.queryKey, context.previousData);\n }\n },\n onSettled: () => {\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: key });\n });\n if (optimistic) {\n queryClient.invalidateQueries({ queryKey: optimistic.queryKey });\n }\n },\n });\n}\n\nexport function useFirestoreDelete<T = unknown>({\n invalidateKeys = [],\n optimistic,\n}: {\n invalidateKeys?: readonly QueryKey[];\n optimistic?: {\n queryKey: QueryKey;\n updater: (oldData: T | undefined, docPath: string) => T;\n };\n} = {}): UseMutationResult<\n string,\n Error,\n { docPath: string },\n { previousData: T | undefined } | undefined\n> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({ docPath }: { docPath: string }) => {\n const docRef = doc(db, docPath);\n await deleteDoc(docRef);\n return docPath;\n },\n onMutate: async ({ docPath }) => {\n if (!optimistic) return undefined;\n\n await queryClient.cancelQueries({ queryKey: optimistic.queryKey });\n const previousData = queryClient.getQueryData<T>(optimistic.queryKey);\n\n queryClient.setQueryData<T>(optimistic.queryKey, (old) => optimistic.updater(old, docPath));\n\n return { previousData };\n },\n onError: (_error, _variables, context) => {\n if (optimistic && context?.previousData !== undefined) {\n queryClient.setQueryData(optimistic.queryKey, context.previousData);\n }\n },\n onSettled: () => {\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: key });\n });\n if (optimistic) {\n queryClient.invalidateQueries({ queryKey: optimistic.queryKey });\n }\n },\n });\n}\n\ninterface BatchResult {\n committed: number;\n batches: number;\n}\n\nexport function useFirestoreBatch({\n invalidateKeys = [],\n batchSize = DEFAULT_BATCH_SIZE,\n}: FirestoreBatchOptions = {}): UseMutationResult<\n BatchResult,\n Error,\n { operations: MutationOperation[] }\n> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({ operations }: { operations: MutationOperation[] }) => {\n const chunks: MutationOperation[][] = [];\n for (let i = 0; i < operations.length; i += batchSize) {\n chunks.push(operations.slice(i, i + batchSize));\n }\n\n let totalCommitted = 0;\n\n for (const chunk of chunks) {\n const batch = writeBatch(db);\n\n for (const op of chunk) {\n const docRef = doc(db, op.docPath);\n\n switch (op.type) {\n case 'set':\n if (op.merge) {\n batch.set(docRef, op.data, { merge: true });\n } else {\n batch.set(docRef, op.data);\n }\n break;\n case 'update':\n batch.update(docRef, op.data);\n break;\n case 'delete':\n batch.delete(docRef);\n break;\n }\n }\n\n await batch.commit();\n totalCommitted += chunk.length;\n }\n\n return {\n committed: totalCommitted,\n batches: chunks.length,\n };\n },\n onSuccess: () => {\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: key });\n });\n },\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"use-firestore-mutations.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-mutations.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useQueryClient, type QueryKey, type UseMutationResult } from '@tanstack/react-query';\nimport {\n doc,\n setDoc,\n updateDoc,\n deleteDoc,\n writeBatch,\n type DocumentData,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport { useSessionMutation } from '../session-mutation.js';\nimport type { FirestoreMutationOptions, FirestoreBatchOptions } from '../../firestore/types.js';\nimport type { MutationOperation } from './types.js';\n\nconst DEFAULT_BATCH_SIZE = 450;\n\ninterface SetResult<T> {\n id: string;\n data: T;\n}\n\nexport function useFirestoreSet<T extends DocumentData>({\n invalidateKeys = [],\n}: Pick<FirestoreMutationOptions<T>, 'invalidateKeys'> = {}): UseMutationResult<\n SetResult<T>,\n Error,\n { docPath: string; data: T; merge?: boolean }\n> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n return useSessionMutation({\n mutationFn: async ({\n docPath,\n data,\n merge = false,\n }: {\n docPath: string;\n data: T;\n merge?: boolean;\n }) => {\n const docRef = doc(db, docPath);\n await setDoc(docRef, data, { merge });\n return { id: docRef.id, data };\n },\n onSuccess: () => {\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: key });\n });\n },\n });\n}\n\nexport function useFirestoreUpdate<T extends DocumentData>({\n invalidateKeys = [],\n optimistic,\n}: FirestoreMutationOptions<T> = {}): UseMutationResult<\n Partial<T>,\n Error,\n { docPath: string; data: Partial<T> },\n { previousData: T | undefined } | undefined\n> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n return useSessionMutation({\n mutationFn: async ({ docPath, data }: { docPath: string; data: Partial<T> }) => {\n const docRef = doc(db, docPath);\n await updateDoc(docRef, data as DocumentData);\n return data;\n },\n onMutate: async ({ data }) => {\n if (!optimistic) return undefined;\n\n await queryClient.cancelQueries({ queryKey: optimistic.queryKey });\n const previousData = queryClient.getQueryData<T>(optimistic.queryKey);\n\n queryClient.setQueryData<T>(optimistic.queryKey, (old) =>\n optimistic.updater(old, data as Partial<T>),\n );\n\n return { previousData };\n },\n onError: (_error, _variables, context) => {\n if (optimistic && context?.previousData !== undefined) {\n queryClient.setQueryData(optimistic.queryKey, context.previousData);\n }\n },\n onSettled: () => {\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: key });\n });\n if (optimistic) {\n queryClient.invalidateQueries({ queryKey: optimistic.queryKey });\n }\n },\n });\n}\n\nexport function useFirestoreDelete<T = unknown>({\n invalidateKeys = [],\n optimistic,\n}: {\n invalidateKeys?: readonly QueryKey[];\n optimistic?: {\n queryKey: QueryKey;\n updater: (oldData: T | undefined, docPath: string) => T;\n };\n} = {}): UseMutationResult<\n string,\n Error,\n { docPath: string },\n { previousData: T | undefined } | undefined\n> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n return useSessionMutation({\n mutationFn: async ({ docPath }: { docPath: string }) => {\n const docRef = doc(db, docPath);\n await deleteDoc(docRef);\n return docPath;\n },\n onMutate: async ({ docPath }) => {\n if (!optimistic) return undefined;\n\n await queryClient.cancelQueries({ queryKey: optimistic.queryKey });\n const previousData = queryClient.getQueryData<T>(optimistic.queryKey);\n\n queryClient.setQueryData<T>(optimistic.queryKey, (old) => optimistic.updater(old, docPath));\n\n return { previousData };\n },\n onError: (_error, _variables, context) => {\n if (optimistic && context?.previousData !== undefined) {\n queryClient.setQueryData(optimistic.queryKey, context.previousData);\n }\n },\n onSettled: () => {\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: key });\n });\n if (optimistic) {\n queryClient.invalidateQueries({ queryKey: optimistic.queryKey });\n }\n },\n });\n}\n\ninterface BatchResult {\n committed: number;\n batches: number;\n}\n\nexport function useFirestoreBatch({\n invalidateKeys = [],\n batchSize = DEFAULT_BATCH_SIZE,\n}: FirestoreBatchOptions = {}): UseMutationResult<\n BatchResult,\n Error,\n { operations: MutationOperation[] }\n> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n return useSessionMutation({\n mutationFn: async ({ operations }: { operations: MutationOperation[] }) => {\n const chunks: MutationOperation[][] = [];\n for (let i = 0; i < operations.length; i += batchSize) {\n chunks.push(operations.slice(i, i + batchSize));\n }\n\n let totalCommitted = 0;\n\n for (const chunk of chunks) {\n const batch = writeBatch(db);\n\n for (const op of chunk) {\n const docRef = doc(db, op.docPath);\n\n switch (op.type) {\n case 'set':\n if (op.merge) {\n batch.set(docRef, op.data, { merge: true });\n } else {\n batch.set(docRef, op.data);\n }\n break;\n case 'update':\n batch.update(docRef, op.data);\n break;\n case 'delete':\n batch.delete(docRef);\n break;\n }\n }\n\n await batch.commit();\n totalCommitted += chunk.length;\n }\n\n return {\n committed: totalCommitted,\n batches: chunks.length,\n };\n },\n onSuccess: () => {\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: key });\n });\n },\n });\n}\n"]}
|