@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.
- package/README.md +6 -6
- package/dist/defaults.d.ts +0 -22
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +0 -35
- package/dist/defaults.js.map +1 -1
- package/dist/firestore/types.d.ts +1 -191
- package/dist/firestore/types.d.ts.map +1 -1
- package/dist/firestore/types.js +1 -6
- package/dist/firestore/types.js.map +1 -1
- package/dist/index.d.ts +2 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -8
- 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 +16 -0
- package/dist/react/cache-helpers.d.ts.map +1 -0
- package/dist/{cache-helpers.js → react/cache-helpers.js} +0 -28
- package/dist/react/cache-helpers.js.map +1 -0
- package/dist/react/defaults.d.ts +4 -0
- package/dist/react/defaults.d.ts.map +1 -0
- package/dist/react/defaults.js +17 -0
- package/dist/react/defaults.js.map +1 -0
- package/dist/react/domain-events/cache-invalidation.d.ts +33 -0
- package/dist/react/domain-events/cache-invalidation.d.ts.map +1 -0
- package/dist/react/domain-events/cache-invalidation.js +74 -0
- package/dist/react/domain-events/cache-invalidation.js.map +1 -0
- package/dist/react/domain-events/create-domain-event-invalidator.d.ts +28 -0
- package/dist/react/domain-events/create-domain-event-invalidator.d.ts.map +1 -0
- package/dist/{domain-events → react/domain-events}/create-domain-event-invalidator.js +1 -6
- package/dist/react/domain-events/create-domain-event-invalidator.js.map +1 -0
- 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 +1 -20
- 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 +9 -0
- package/dist/react/firestore/infinite-helpers.d.ts.map +1 -0
- package/dist/react/firestore/infinite-helpers.js +11 -0
- package/dist/react/firestore/infinite-helpers.js.map +1 -0
- 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 +80 -0
- package/dist/react/firestore/types.d.ts.map +1 -0
- package/dist/react/firestore/types.js +4 -0
- package/dist/react/firestore/types.js.map +1 -0
- package/dist/react/firestore/use-firestore-collection.d.ts +2 -21
- package/dist/react/firestore/use-firestore-collection.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-collection.js +23 -44
- package/dist/react/firestore/use-firestore-collection.js.map +1 -1
- package/dist/react/firestore/use-firestore-count.d.ts +1 -16
- 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 +2 -24
- package/dist/react/firestore/use-firestore-doc.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-doc.js +21 -42
- package/dist/react/firestore/use-firestore-doc.js.map +1 -1
- package/dist/react/firestore/use-firestore-infinite.d.ts +1 -30
- 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 +2 -35
- package/dist/react/firestore/use-firestore-live-infinite.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-live-infinite.js +10 -36
- package/dist/react/firestore/use-firestore-live-infinite.js.map +1 -1
- package/dist/react/firestore/use-firestore-mutations.d.ts +2 -1
- package/dist/react/firestore/use-firestore-mutations.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-mutations.js +3 -3
- package/dist/react/firestore/use-firestore-mutations.js.map +1 -1
- package/dist/react/firestore/use-firestore-paginated.d.ts +2 -31
- package/dist/react/firestore/use-firestore-paginated.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-paginated.js +8 -36
- 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 +12 -61
- package/dist/react/firestore/useBatchFirestoreDocs.js.map +1 -1
- package/dist/react/index.d.ts +13 -4
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +8 -13
- package/dist/react/index.js.map +1 -1
- package/dist/react/pending-mutations.d.ts +2 -22
- package/dist/react/pending-mutations.d.ts.map +1 -1
- package/dist/react/pending-mutations.js +7 -38
- package/dist/react/pending-mutations.js.map +1 -1
- package/dist/react/provider.d.ts +1 -6
- package/dist/react/provider.d.ts.map +1 -1
- package/dist/react/provider.js +1 -5
- package/dist/react/provider.js.map +1 -1
- package/dist/{query-client.d.ts → react/query-client.d.ts} +0 -4
- package/dist/react/query-client.d.ts.map +1 -0
- package/dist/{query-client.js → react/query-client.js} +0 -4
- package/dist/react/query-client.js.map +1 -0
- package/dist/react/search/types.d.ts +16 -0
- package/dist/react/search/types.d.ts.map +1 -0
- package/dist/react/search/types.js +2 -0
- package/dist/react/search/types.js.map +1 -0
- package/dist/react/search/use-firestore-search.d.ts +1 -30
- 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/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 +3 -22
- 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 -39
- package/dist/search/types.d.ts.map +1 -1
- package/dist/search/types.js.map +1 -1
- package/package.json +2 -2
- package/dist/cache-helpers.d.ts +0 -48
- package/dist/cache-helpers.d.ts.map +0 -1
- package/dist/cache-helpers.js.map +0 -1
- package/dist/domain-events/cache-invalidation.d.ts +0 -70
- package/dist/domain-events/cache-invalidation.d.ts.map +0 -1
- package/dist/domain-events/cache-invalidation.js +0 -123
- package/dist/domain-events/cache-invalidation.js.map +0 -1
- package/dist/domain-events/create-domain-event-invalidator.d.ts +0 -46
- package/dist/domain-events/create-domain-event-invalidator.d.ts.map +0 -1
- package/dist/domain-events/create-domain-event-invalidator.js.map +0 -1
- package/dist/domain-events/index.d.ts +0 -5
- package/dist/domain-events/index.d.ts.map +0 -1
- package/dist/domain-events/index.js +0 -3
- package/dist/domain-events/index.js.map +0 -1
- package/dist/firestore/infinite-helpers.d.ts +0 -20
- package/dist/firestore/infinite-helpers.d.ts.map +0 -1
- package/dist/firestore/infinite-helpers.js +0 -23
- package/dist/firestore/infinite-helpers.js.map +0 -1
- package/dist/query-client.d.ts.map +0 -1
- package/dist/query-client.js.map +0 -1
|
@@ -1,150 +1,28 @@
|
|
|
1
1
|
import type { Firestore } from 'firebase/firestore';
|
|
2
2
|
import type { BatchFirestoreDocOutcome } from '../../firestore/types.js';
|
|
3
|
-
/** Transport used to resolve an id in one-shot mode. */
|
|
4
3
|
export type BatchFirestoreDocsTransport = 'batch' | 'get';
|
|
5
4
|
export type BatchFirestoreDocsOptions = {
|
|
6
|
-
/**
|
|
7
|
-
* Firestore instance to use for queries
|
|
8
|
-
*/
|
|
9
5
|
db: Firestore;
|
|
10
|
-
/**
|
|
11
|
-
* Full collection path (e.g., 'publicUsers', 'entities/public')
|
|
12
|
-
*/
|
|
13
6
|
collectionPath: string;
|
|
14
|
-
/**
|
|
15
|
-
* Array of document IDs to fetch
|
|
16
|
-
*/
|
|
17
7
|
ids: string[];
|
|
18
|
-
/**
|
|
19
|
-
* Query key prefix for individual document cache entries
|
|
20
|
-
* Each document is cached as [queryKeyPrefix, docId]
|
|
21
|
-
* @example 'publicUser' -> ['publicUser', 'user123']
|
|
22
|
-
*/
|
|
23
8
|
queryKeyPrefix: string;
|
|
24
|
-
/**
|
|
25
|
-
* Time in milliseconds a PRESENT document stays fresh (default: 30 minutes).
|
|
26
|
-
* An absent document uses `absentStaleTime` instead.
|
|
27
|
-
*/
|
|
28
9
|
staleTime?: number;
|
|
29
|
-
/**
|
|
30
|
-
* Time in milliseconds to keep unused data in cache (default: 1 hour)
|
|
31
|
-
*/
|
|
32
10
|
gcTime?: number;
|
|
33
|
-
/**
|
|
34
|
-
* Whether queries should be enabled (default: true)
|
|
35
|
-
*/
|
|
36
11
|
enabled?: boolean;
|
|
37
|
-
/**
|
|
38
|
-
* Real-time mode (default: false). When true, each id is resolved via a shared,
|
|
39
|
-
* reference-counted `onSnapshot` listener instead of a one-shot read:
|
|
40
|
-
* - a missing doc caches `null` (negative caching) and resolves the instant it appears,
|
|
41
|
-
* - identity edits propagate live, and every browser tab stays consistent,
|
|
42
|
-
* - `staleTime` is irrelevant (listeners keep the cache fresh) and `refetch()` is a no-op.
|
|
43
|
-
* Initial read cost matches the one-shot path; listeners then stay open while mounted.
|
|
44
|
-
* Use for small, frequently-rendered, change-sensitive docs owned by the signed-in user.
|
|
45
|
-
*/
|
|
46
12
|
subscribe?: boolean;
|
|
47
|
-
/**
|
|
48
|
-
* One-shot transport (default: `'batch'`).
|
|
49
|
-
*
|
|
50
|
-
* - `'batch'` — ids enqueued in the same microtask are coalesced into
|
|
51
|
-
* `where(documentId(), 'in', …)` queries of at most `FIRESTORE_IN_FILTER_LIMIT` (firebase-helpers) ids each. Fewest round-trips.
|
|
52
|
-
* - `'get'` — one `getDoc` per id, run concurrently. Required for a collection whose
|
|
53
|
-
* Firestore rules gate reads on `resource.data`: an unconstrained id-list query is
|
|
54
|
-
* denied WHOLESALE there, while per-document gets are evaluated per document, so one
|
|
55
|
-
* unreadable id errors alone instead of failing every id with it. On such a collection
|
|
56
|
-
* a get against a nonexistent doc is also denied, so "absent" and "hidden" are the same
|
|
57
|
-
* client-visible signal — the id surfaces as that query's error, never as a null.
|
|
58
|
-
*/
|
|
13
|
+
/** `'get'` is required where rules gate reads on `resource.data`: there an id-list query is denied wholesale. */
|
|
59
14
|
transport?: BatchFirestoreDocsTransport;
|
|
60
|
-
/**
|
|
61
|
-
* Time in milliseconds an ABSENT (`null`) result stays fresh (default: 30 seconds).
|
|
62
|
-
* Deliberately much shorter than `staleTime`: a missing doc is usually a doc that has
|
|
63
|
-
* not been mirrored/created YET, and holding "missing" as fresh for the full staleTime
|
|
64
|
-
* is what leaves a row blank long after the doc appears.
|
|
65
|
-
*/
|
|
66
15
|
absentStaleTime?: number;
|
|
67
|
-
/**
|
|
68
|
-
* Backoff ladder (ms) for re-reading an id that resolved absent, one re-read per rung
|
|
69
|
-
* per absence episode (default: {@link ABSENT_RETRY_DELAYS_MS}). Pass `[]` to ask for no
|
|
70
|
-
* re-reads.
|
|
71
|
-
*
|
|
72
|
-
* The timer is per cache key, so many consumers of the same absent id still cost one
|
|
73
|
-
* re-read per rung — and consumers that disagree combine by UNION, never by mount order:
|
|
74
|
-
* the key polls if ANY mounted consumer wants polling, on the LONGEST ladder among them.
|
|
75
|
-
* `[]` therefore only silences re-reads that no other mounted consumer asked for. The
|
|
76
|
-
* effective ladder is recomputed on every mount and unmount; an episode already in
|
|
77
|
-
* progress continues on the new ladder's remaining rungs, or stops if it is now empty.
|
|
78
|
-
*/
|
|
79
16
|
absentRetryDelaysMs?: readonly number[];
|
|
80
17
|
};
|
|
81
18
|
export type BatchFirestoreDocsResult<T> = {
|
|
82
|
-
/**
|
|
83
|
-
* Map of docId -> document data.
|
|
84
|
-
* Only includes documents that exist AND are currently readable: an id whose query is in
|
|
85
|
-
* an error state is excluded, so a previously cached doc is never presented as readable
|
|
86
|
-
* after access to it fails.
|
|
87
|
-
*/
|
|
88
19
|
data: Record<string, T>;
|
|
89
|
-
/**
|
|
90
|
-
* True while any requested id has not resolved yet
|
|
91
|
-
*/
|
|
92
20
|
isLoading: boolean;
|
|
93
|
-
/**
|
|
94
|
-
* True if any requested id is in an error state
|
|
95
|
-
*/
|
|
96
21
|
isError: boolean;
|
|
97
|
-
/**
|
|
98
|
-
* First error across the requested ids, if any
|
|
99
|
-
*/
|
|
100
22
|
error: Error | null;
|
|
101
|
-
/**
|
|
102
|
-
* One entry per requested id (empty ids and duplicates dropped): `loading`, `present` with
|
|
103
|
-
* its data, `absent`, or `failed` with that id's own error. A multi-id consumer reads which
|
|
104
|
-
* ids are missing and which failed here — `data` / `isError` / `error` are the aggregate
|
|
105
|
-
* view and cannot tell them apart. The map, and each unchanged entry, keep their identity
|
|
106
|
-
* across renders.
|
|
107
|
-
*/
|
|
108
23
|
outcomes: Record<string, BatchFirestoreDocOutcome<T>>;
|
|
109
|
-
/**
|
|
110
|
-
* True while a read of an id whose last settled read failed is fetching or waiting for the
|
|
111
|
-
* network — a retry of a failure. A first load, and the absence ladder's re-read of an id that
|
|
112
|
-
* answered absent, never count. TanStack clears a never-answered id's error as its refetch
|
|
113
|
-
* starts, so this is the running flag a retry state (`useReadRetryState`) is built from.
|
|
114
|
-
*/
|
|
115
24
|
failedReadsRunning: boolean;
|
|
116
|
-
/**
|
|
117
|
-
* Refetch every requested id, cached ones included. A no-op in subscribe mode.
|
|
118
|
-
*/
|
|
119
25
|
refetch: () => Promise<void>;
|
|
120
26
|
};
|
|
121
|
-
/**
|
|
122
|
-
* Resolve many document ids into individually observed, individually cached React Query
|
|
123
|
-
* entries at `[queryKeyPrefix, id]`.
|
|
124
|
-
*
|
|
125
|
-
* There is exactly ONE data cache: the per-id entries. Each id owns a real query with a
|
|
126
|
-
* real `queryFn`, so exact-key invalidation, `refetchQueries({ type: 'active' })`, and
|
|
127
|
-
* `refetchOnMount` all work on a single id the way they work on any other query — and the
|
|
128
|
-
* returned `data` is derived reactively from those query results, never read imperatively.
|
|
129
|
-
*
|
|
130
|
-
* A nonexistent document resolves to `null` (negative caching) and is excluded from `data`;
|
|
131
|
-
* an absent id is then re-read on a bounded ladder (see `absentRetryDelaysMs`) so a doc
|
|
132
|
-
* that appears shortly afterwards shows up without an invalidation.
|
|
133
|
-
*
|
|
134
|
-
* Each id's own answer — loading, present, absent, or failed with its own error — is in
|
|
135
|
-
* `outcomes`; the aggregate fields cannot tell an absent id from a failed one.
|
|
136
|
-
*
|
|
137
|
-
* @example
|
|
138
|
-
* ```typescript
|
|
139
|
-
* const { data: users, isLoading } = useBatchFirestoreDocs<PublicUser>({
|
|
140
|
-
* db,
|
|
141
|
-
* collectionPath: 'publicUsers',
|
|
142
|
-
* ids: ['user1', 'user2', 'user3'],
|
|
143
|
-
* queryKeyPrefix: 'publicUser',
|
|
144
|
-
* staleTime: 30 * 60 * 1000,
|
|
145
|
-
* });
|
|
146
|
-
* // users = { user1: {...}, user2: {...}, user3: {...} }
|
|
147
|
-
* ```
|
|
148
|
-
*/
|
|
149
27
|
export declare function useBatchFirestoreDocs<T extends Record<string, any>>({ db, collectionPath, ids, queryKeyPrefix, staleTime, gcTime, enabled, subscribe, transport, absentStaleTime, absentRetryDelaysMs, }: BatchFirestoreDocsOptions): BatchFirestoreDocsResult<T>;
|
|
150
28
|
//# sourceMappingURL=useBatchFirestoreDocs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBatchFirestoreDocs.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/useBatchFirestoreDocs.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAIpD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAGzE,
|
|
1
|
+
{"version":3,"file":"useBatchFirestoreDocs.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/useBatchFirestoreDocs.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAIpD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAGzE,MAAM,MAAM,2BAA2B,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1D,MAAM,MAAM,yBAAyB,GAAG;IACtC,EAAE,EAAE,SAAS,CAAC;IAEd,cAAc,EAAE,MAAM,CAAC;IAEvB,GAAG,EAAE,MAAM,EAAE,CAAC;IAEd,cAAc,EAAE,MAAM,CAAC;IAEvB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,iHAAiH;IACjH,SAAS,CAAC,EAAE,2BAA2B,CAAC;IAExC,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,wBAAwB,CAAC,CAAC,IAAI;IACxC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAExB,SAAS,EAAE,OAAO,CAAC;IAEnB,OAAO,EAAE,OAAO,CAAC;IAEjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAEpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC,CAAC,CAAC;IAEtD,kBAAkB,EAAE,OAAO,CAAC;IAE5B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAyBF,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACnE,EAAE,EACF,cAAc,EACd,GAAG,EACH,cAAc,EACd,SAA0B,EAC1B,MAAuB,EACvB,OAAc,EACd,SAAiB,EACjB,SAAmB,EACnB,eAA2B,EAC3B,mBAA4C,GAC7C,EAAE,yBAAyB,GAAG,wBAAwB,CAAC,CAAC,CAAC,CAiLzD"}
|
|
@@ -29,61 +29,25 @@ function shallowRecordEqual(a, b) {
|
|
|
29
29
|
}
|
|
30
30
|
return true;
|
|
31
31
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Resolve many document ids into individually observed, individually cached React Query
|
|
34
|
-
* entries at `[queryKeyPrefix, id]`.
|
|
35
|
-
*
|
|
36
|
-
* There is exactly ONE data cache: the per-id entries. Each id owns a real query with a
|
|
37
|
-
* real `queryFn`, so exact-key invalidation, `refetchQueries({ type: 'active' })`, and
|
|
38
|
-
* `refetchOnMount` all work on a single id the way they work on any other query — and the
|
|
39
|
-
* returned `data` is derived reactively from those query results, never read imperatively.
|
|
40
|
-
*
|
|
41
|
-
* A nonexistent document resolves to `null` (negative caching) and is excluded from `data`;
|
|
42
|
-
* an absent id is then re-read on a bounded ladder (see `absentRetryDelaysMs`) so a doc
|
|
43
|
-
* that appears shortly afterwards shows up without an invalidation.
|
|
44
|
-
*
|
|
45
|
-
* Each id's own answer — loading, present, absent, or failed with its own error — is in
|
|
46
|
-
* `outcomes`; the aggregate fields cannot tell an absent id from a failed one.
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* ```typescript
|
|
50
|
-
* const { data: users, isLoading } = useBatchFirestoreDocs<PublicUser>({
|
|
51
|
-
* db,
|
|
52
|
-
* collectionPath: 'publicUsers',
|
|
53
|
-
* ids: ['user1', 'user2', 'user3'],
|
|
54
|
-
* queryKeyPrefix: 'publicUser',
|
|
55
|
-
* staleTime: 30 * 60 * 1000,
|
|
56
|
-
* });
|
|
57
|
-
* // users = { user1: {...}, user2: {...}, user3: {...} }
|
|
58
|
-
* ```
|
|
59
|
-
*/
|
|
60
32
|
export function useBatchFirestoreDocs({ db, collectionPath, ids, queryKeyPrefix, staleTime = 30 * 60 * 1000, gcTime = 60 * 60 * 1000, enabled = true, subscribe = false, transport = 'batch', absentStaleTime = 30 * 1000, absentRetryDelaysMs = ABSENT_RETRY_DELAYS_MS, }) {
|
|
61
33
|
const queryClient = useQueryClient();
|
|
62
|
-
// Stabilize ids reference to prevent cascading re-renders
|
|
63
34
|
const idsKey = JSON.stringify(ids);
|
|
64
|
-
// Deduplicate and filter out empty IDs
|
|
65
35
|
const uniqueIds = useMemo(() => {
|
|
66
36
|
const parsed = JSON.parse(idsKey);
|
|
67
37
|
return Array.from(new Set(parsed.filter(Boolean)));
|
|
68
38
|
}, [idsKey]);
|
|
69
|
-
// ── Real-time subscribe mode ──────────────────────────────────────────────
|
|
70
|
-
// Listener errors cannot surface through the per-id queries (they are disabled while
|
|
71
|
-
// subscribed), so they are tracked here, per id, and merged into the returned result.
|
|
72
39
|
const [subscribeErrors, setSubscribeErrors] = useState({});
|
|
73
|
-
//
|
|
74
|
-
// cache write, but only on React Query's batched microtask; bumping state in the
|
|
75
|
-
// snapshot callback keeps a landed snapshot visible in the SAME tick it arrives.
|
|
40
|
+
// Query observers notify on React Query's batched microtask; bumping state here shows a snapshot in the tick it lands.
|
|
76
41
|
const [, setSnapshotVersion] = useState(0);
|
|
77
42
|
const results = useQueries({
|
|
78
43
|
queries: uniqueIds.map((id) => ({
|
|
79
44
|
queryKey: [queryKeyPrefix, id],
|
|
80
45
|
queryFn: async ({ signal }) => {
|
|
81
46
|
const loader = getDocLoader(queryClient, db, collectionPath);
|
|
82
|
-
const loaded = transport === 'get'
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// changes, so
|
|
86
|
-
// that errored left only a `null` placeholder; its error is the answer, never an absence.
|
|
47
|
+
const loaded = transport === 'get'
|
|
48
|
+
? await loader.loadOne(id, signal)
|
|
49
|
+
: await loader.loadBatched(id, signal);
|
|
50
|
+
// `onSnapshot` does not re-emit until the document changes, so a read that lands after a listener took the key yields to it.
|
|
87
51
|
if (isListenerOwned(queryClient, queryKeyPrefix, id)) {
|
|
88
52
|
const listenerError = listenerErrorOf(queryClient, queryKeyPrefix, id);
|
|
89
53
|
if (listenerError)
|
|
@@ -95,9 +59,7 @@ export function useBatchFirestoreDocs({ db, collectionPath, ids, queryKeyPrefix,
|
|
|
95
59
|
return loaded;
|
|
96
60
|
},
|
|
97
61
|
enabled: enabled && !subscribe,
|
|
98
|
-
//
|
|
99
|
-
// back in so a remount re-reads a STALE id (refetch-on-mount never fires on a fresh
|
|
100
|
-
// one, so an all-cache-hit mount still costs nothing).
|
|
62
|
+
// Opts back into refetch-on-mount (the package default is off) so a remount re-reads a stale id.
|
|
101
63
|
refetchOnMount: !subscribe,
|
|
102
64
|
staleTime: subscribe
|
|
103
65
|
? Infinity
|
|
@@ -124,16 +86,13 @@ export function useBatchFirestoreDocs({ db, collectionPath, ids, queryKeyPrefix,
|
|
|
124
86
|
});
|
|
125
87
|
setSnapshotVersion((version) => version + 1);
|
|
126
88
|
},
|
|
127
|
-
onError: (error) => setSubscribeErrors((errors) =>
|
|
89
|
+
onError: (error) => setSubscribeErrors((errors) => errors[id] === error ? errors : { ...errors, [id]: error }),
|
|
128
90
|
}));
|
|
129
91
|
return () => {
|
|
130
92
|
for (const unsubscribe of unsubscribers)
|
|
131
93
|
unsubscribe();
|
|
132
94
|
};
|
|
133
95
|
}, [uniqueIds, subscribe, enabled, collectionPath, queryKeyPrefix, queryClient, db]);
|
|
134
|
-
// Bounded re-read ladder for ids that resolved absent. Registration is per cache key, so
|
|
135
|
-
// overlapping consumers share one episode budget. The ladder is rebuilt from its own
|
|
136
|
-
// serialized form so an inline array literal cannot re-register (and reset it) every render.
|
|
137
96
|
const absentDelaysKey = absentRetryDelaysMs.join(',');
|
|
138
97
|
const absentDelays = useMemo(() => absentDelaysKey.split(',').filter(Boolean).map(Number), [absentDelaysKey]);
|
|
139
98
|
useEffect(() => {
|
|
@@ -145,14 +104,10 @@ export function useBatchFirestoreDocs({ db, collectionPath, ids, queryKeyPrefix,
|
|
|
145
104
|
release();
|
|
146
105
|
};
|
|
147
106
|
}, [uniqueIds, queryKeyPrefix, queryClient, absentDelays]);
|
|
148
|
-
//
|
|
149
|
-
// COMMITTED objects while nothing changed, so consumers can depend on their identity. Only
|
|
150
|
-
// the effect writes the refs, so a concurrent render React discards can never leave them
|
|
151
|
-
// pointing at an uncommitted map.
|
|
107
|
+
// Only the effect writes these refs, so a concurrent render React discards never leaves them on an uncommitted map.
|
|
152
108
|
const committedDataRef = useRef({});
|
|
153
109
|
const committedOutcomesRef = useRef({});
|
|
154
|
-
//
|
|
155
|
-
// read's error as its refetch starts, which would turn the id's failure into `loading`.
|
|
110
|
+
// TanStack clears a never-answered read's error as its refetch starts, so the failure is held through the retry.
|
|
156
111
|
const retryStates = useReadRetryStates(uniqueIds.map((id, index) => ({
|
|
157
112
|
source: (results[index] ?? IDLE_SOURCE),
|
|
158
113
|
queryKey: [queryKeyPrefix, id],
|
|
@@ -168,12 +123,12 @@ export function useBatchFirestoreDocs({ db, collectionPath, ids, queryKeyPrefix,
|
|
|
168
123
|
const id = uniqueIds[index];
|
|
169
124
|
const result = results[index];
|
|
170
125
|
let outcome = LOADING_OUTCOME;
|
|
171
|
-
// A one-shot reader of a key a failed shared listener owns sees that listener's `null`
|
|
172
|
-
// placeholder in the cache; the registry's error is what it stands for.
|
|
173
126
|
const listenerError = (subscribe ? subscribeErrors[id] : undefined) ??
|
|
174
127
|
listenerErrorOf(queryClient, queryKeyPrefix, id) ??
|
|
175
128
|
undefined;
|
|
176
|
-
if (result &&
|
|
129
|
+
if (result &&
|
|
130
|
+
(result.isFetching || result.isPaused) &&
|
|
131
|
+
result.errorUpdatedAt > result.dataUpdatedAt) {
|
|
177
132
|
failedReadsRunning = true;
|
|
178
133
|
}
|
|
179
134
|
const retryState = retryStates[index];
|
|
@@ -214,14 +169,10 @@ export function useBatchFirestoreDocs({ db, collectionPath, ids, queryKeyPrefix,
|
|
|
214
169
|
committedOutcomesRef.current = outcomes;
|
|
215
170
|
}, [data, outcomes]);
|
|
216
171
|
const refetch = useCallback(async () => {
|
|
217
|
-
// Subscribe mode owns its freshness through the shared listeners — refetching would
|
|
218
|
-
// issue reads against disabled queries and race the listener's value.
|
|
219
172
|
if (subscribe || !enabled)
|
|
220
173
|
return;
|
|
221
174
|
await Promise.all(uniqueIds.map((id) => queryClient.refetchQueries({ queryKey: [queryKeyPrefix, id], exact: true })));
|
|
222
175
|
}, [uniqueIds, subscribe, enabled, queryClient, queryKeyPrefix]);
|
|
223
|
-
// In subscribe mode "loading" means a subscribed id has no cache entry yet (`null`
|
|
224
|
-
// counts as resolved-missing).
|
|
225
176
|
const isLoading = subscribe ? enabled && anyUnresolved : oneShotLoading;
|
|
226
177
|
return {
|
|
227
178
|
data,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBatchFirestoreDocs.js","sourceRoot":"","sources":["../../../src/react/firestore/useBatchFirestoreDocs.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useQueries, useQueryClient } from '@tanstack/react-query';\nimport type { Firestore } from 'firebase/firestore';\nimport { isListenerOwned, listenerErrorOf, subscribeDoc } from './doc-subscription-registry.js';\nimport { getDocLoader } from './batch-doc-loader.js';\nimport { ABSENT_RETRY_DELAYS_MS, trackAbsentDoc } from './absence-scheduler.js';\nimport type { BatchFirestoreDocOutcome } from '../../firestore/types.js';\nimport { useReadRetryStates, type ReadRetrySource } from '../use-read-retry-state.js';\n\n/** Transport used to resolve an id in one-shot mode. */\nexport type BatchFirestoreDocsTransport = 'batch' | 'get';\n\nexport type BatchFirestoreDocsOptions = {\n /**\n * Firestore instance to use for queries\n */\n db: Firestore;\n\n /**\n * Full collection path (e.g., 'publicUsers', 'entities/public')\n */\n collectionPath: string;\n\n /**\n * Array of document IDs to fetch\n */\n ids: string[];\n\n /**\n * Query key prefix for individual document cache entries\n * Each document is cached as [queryKeyPrefix, docId]\n * @example 'publicUser' -> ['publicUser', 'user123']\n */\n queryKeyPrefix: string;\n\n /**\n * Time in milliseconds a PRESENT document stays fresh (default: 30 minutes).\n * An absent document uses `absentStaleTime` instead.\n */\n staleTime?: number;\n\n /**\n * Time in milliseconds to keep unused data in cache (default: 1 hour)\n */\n gcTime?: number;\n\n /**\n * Whether queries should be enabled (default: true)\n */\n enabled?: boolean;\n\n /**\n * Real-time mode (default: false). When true, each id is resolved via a shared,\n * reference-counted `onSnapshot` listener instead of a one-shot read:\n * - a missing doc caches `null` (negative caching) and resolves the instant it appears,\n * - identity edits propagate live, and every browser tab stays consistent,\n * - `staleTime` is irrelevant (listeners keep the cache fresh) and `refetch()` is a no-op.\n * Initial read cost matches the one-shot path; listeners then stay open while mounted.\n * Use for small, frequently-rendered, change-sensitive docs owned by the signed-in user.\n */\n subscribe?: boolean;\n\n /**\n * One-shot transport (default: `'batch'`).\n *\n * - `'batch'` — ids enqueued in the same microtask are coalesced into\n * `where(documentId(), 'in', …)` queries of at most `FIRESTORE_IN_FILTER_LIMIT` (firebase-helpers) ids each. Fewest round-trips.\n * - `'get'` — one `getDoc` per id, run concurrently. Required for a collection whose\n * Firestore rules gate reads on `resource.data`: an unconstrained id-list query is\n * denied WHOLESALE there, while per-document gets are evaluated per document, so one\n * unreadable id errors alone instead of failing every id with it. On such a collection\n * a get against a nonexistent doc is also denied, so \"absent\" and \"hidden\" are the same\n * client-visible signal — the id surfaces as that query's error, never as a null.\n */\n transport?: BatchFirestoreDocsTransport;\n\n /**\n * Time in milliseconds an ABSENT (`null`) result stays fresh (default: 30 seconds).\n * Deliberately much shorter than `staleTime`: a missing doc is usually a doc that has\n * not been mirrored/created YET, and holding \"missing\" as fresh for the full staleTime\n * is what leaves a row blank long after the doc appears.\n */\n absentStaleTime?: number;\n\n /**\n * Backoff ladder (ms) for re-reading an id that resolved absent, one re-read per rung\n * per absence episode (default: {@link ABSENT_RETRY_DELAYS_MS}). Pass `[]` to ask for no\n * re-reads.\n *\n * The timer is per cache key, so many consumers of the same absent id still cost one\n * re-read per rung — and consumers that disagree combine by UNION, never by mount order:\n * the key polls if ANY mounted consumer wants polling, on the LONGEST ladder among them.\n * `[]` therefore only silences re-reads that no other mounted consumer asked for. The\n * effective ladder is recomputed on every mount and unmount; an episode already in\n * progress continues on the new ladder's remaining rungs, or stops if it is now empty.\n */\n absentRetryDelaysMs?: readonly number[];\n};\n\nexport type BatchFirestoreDocsResult<T> = {\n /**\n * Map of docId -> document data.\n * Only includes documents that exist AND are currently readable: an id whose query is in\n * an error state is excluded, so a previously cached doc is never presented as readable\n * after access to it fails.\n */\n data: Record<string, T>;\n\n /**\n * True while any requested id has not resolved yet\n */\n isLoading: boolean;\n\n /**\n * True if any requested id is in an error state\n */\n isError: boolean;\n\n /**\n * First error across the requested ids, if any\n */\n error: Error | null;\n\n /**\n * One entry per requested id (empty ids and duplicates dropped): `loading`, `present` with\n * its data, `absent`, or `failed` with that id's own error. A multi-id consumer reads which\n * ids are missing and which failed here — `data` / `isError` / `error` are the aggregate\n * view and cannot tell them apart. The map, and each unchanged entry, keep their identity\n * across renders.\n */\n outcomes: Record<string, BatchFirestoreDocOutcome<T>>;\n\n /**\n * True while a read of an id whose last settled read failed is fetching or waiting for the\n * network — a retry of a failure. A first load, and the absence ladder's re-read of an id that\n * answered absent, never count. TanStack clears a never-answered id's error as its refetch\n * starts, so this is the running flag a retry state (`useReadRetryState`) is built from.\n */\n failedReadsRunning: boolean;\n\n /**\n * Refetch every requested id, cached ones included. A no-op in subscribe mode.\n */\n refetch: () => Promise<void>;\n};\n\nconst LOADING_OUTCOME = { status: 'loading' } as const;\nconst IDLE_SOURCE: ReadRetrySource<Error> = { error: null, isFetching: false, isLoading: false };\nconst ABSENT_OUTCOME = { status: 'absent' } as const;\n\nfunction sameOutcome<T>(a: BatchFirestoreDocOutcome<T>, b: BatchFirestoreDocOutcome<T>): boolean {\n if (a.status !== b.status) return false;\n if (a.status === 'present') return a.data === (b as { data: T }).data;\n if (a.status === 'failed') {\n const other = b as { error: Error; retrying: boolean };\n return a.error === other.error && a.retrying === other.retrying;\n }\n return true;\n}\n\nfunction shallowRecordEqual<T>(a: Record<string, T>, b: Record<string, T>): boolean {\n const aKeys = Object.keys(a);\n if (aKeys.length !== Object.keys(b).length) return false;\n for (const key of aKeys) {\n if (!Object.prototype.hasOwnProperty.call(b, key) || a[key] !== b[key]) return false;\n }\n return true;\n}\n\n/**\n * Resolve many document ids into individually observed, individually cached React Query\n * entries at `[queryKeyPrefix, id]`.\n *\n * There is exactly ONE data cache: the per-id entries. Each id owns a real query with a\n * real `queryFn`, so exact-key invalidation, `refetchQueries({ type: 'active' })`, and\n * `refetchOnMount` all work on a single id the way they work on any other query — and the\n * returned `data` is derived reactively from those query results, never read imperatively.\n *\n * A nonexistent document resolves to `null` (negative caching) and is excluded from `data`;\n * an absent id is then re-read on a bounded ladder (see `absentRetryDelaysMs`) so a doc\n * that appears shortly afterwards shows up without an invalidation.\n *\n * Each id's own answer — loading, present, absent, or failed with its own error — is in\n * `outcomes`; the aggregate fields cannot tell an absent id from a failed one.\n *\n * @example\n * ```typescript\n * const { data: users, isLoading } = useBatchFirestoreDocs<PublicUser>({\n * db,\n * collectionPath: 'publicUsers',\n * ids: ['user1', 'user2', 'user3'],\n * queryKeyPrefix: 'publicUser',\n * staleTime: 30 * 60 * 1000,\n * });\n * // users = { user1: {...}, user2: {...}, user3: {...} }\n * ```\n */\nexport function useBatchFirestoreDocs<T extends Record<string, any>>({\n db,\n collectionPath,\n ids,\n queryKeyPrefix,\n staleTime = 30 * 60 * 1000,\n gcTime = 60 * 60 * 1000,\n enabled = true,\n subscribe = false,\n transport = 'batch',\n absentStaleTime = 30 * 1000,\n absentRetryDelaysMs = ABSENT_RETRY_DELAYS_MS,\n}: BatchFirestoreDocsOptions): BatchFirestoreDocsResult<T> {\n const queryClient = useQueryClient();\n\n // Stabilize ids reference to prevent cascading re-renders\n const idsKey = JSON.stringify(ids);\n\n // Deduplicate and filter out empty IDs\n const uniqueIds = useMemo(() => {\n const parsed: string[] = JSON.parse(idsKey);\n return Array.from(new Set(parsed.filter(Boolean)));\n }, [idsKey]);\n\n // ── Real-time subscribe mode ──────────────────────────────────────────────\n // Listener errors cannot surface through the per-id queries (they are disabled while\n // subscribed), so they are tracked here, per id, and merged into the returned result.\n const [subscribeErrors, setSubscribeErrors] = useState<Record<string, Error>>({});\n // Snapshot re-render trigger. The per-id query observers do notify on the listener's\n // cache write, but only on React Query's batched microtask; bumping state in the\n // snapshot callback keeps a landed snapshot visible in the SAME tick it arrives.\n const [, setSnapshotVersion] = useState(0);\n\n const results = useQueries({\n queries: uniqueIds.map((id) => ({\n queryKey: [queryKeyPrefix, id],\n queryFn: async ({ signal }: { signal: AbortSignal }): Promise<T | null> => {\n const loader = getDocLoader(queryClient, db, collectionPath);\n const loaded =\n transport === 'get' ? await loader.loadOne(id, signal) : await loader.loadBatched(id, signal);\n // If a shared listener took ownership of this key while the read was in flight, the\n // listener's value is the newer truth — `onSnapshot` will not re-emit until the doc\n // changes, so returning the read result here would leave a stale value stuck. A listener\n // that errored left only a `null` placeholder; its error is the answer, never an absence.\n if (isListenerOwned(queryClient, queryKeyPrefix, id)) {\n const listenerError = listenerErrorOf(queryClient, queryKeyPrefix, id);\n if (listenerError) throw listenerError;\n const owned = queryClient.getQueryData([queryKeyPrefix, id]);\n if (owned !== undefined) return owned as T | null;\n }\n return loaded as T | null;\n },\n enabled: enabled && !subscribe,\n // The consuming app's global default is `refetchOnMount: false`; these lookups opt\n // back in so a remount re-reads a STALE id (refetch-on-mount never fires on a fresh\n // one, so an all-cache-hit mount still costs nothing).\n refetchOnMount: !subscribe,\n staleTime: subscribe\n ? Infinity\n : (query: { state: { data: unknown } }) =>\n query.state.data === null ? absentStaleTime : staleTime,\n gcTime,\n })),\n });\n\n useEffect(() => {\n if (!subscribe || !enabled || uniqueIds.length === 0) return;\n setSubscribeErrors({});\n const unsubscribers = uniqueIds.map((id) =>\n subscribeDoc({\n queryClient,\n db,\n collectionPath,\n queryKeyPrefix,\n id,\n onUpdate: () => {\n setSubscribeErrors((errors) => {\n if (!(id in errors)) return errors;\n const { [id]: _cleared, ...rest } = errors;\n return rest;\n });\n setSnapshotVersion((version) => version + 1);\n },\n onError: (error) =>\n setSubscribeErrors((errors) => (errors[id] === error ? errors : { ...errors, [id]: error })),\n }),\n );\n return () => {\n for (const unsubscribe of unsubscribers) unsubscribe();\n };\n }, [uniqueIds, subscribe, enabled, collectionPath, queryKeyPrefix, queryClient, db]);\n\n // Bounded re-read ladder for ids that resolved absent. Registration is per cache key, so\n // overlapping consumers share one episode budget. The ladder is rebuilt from its own\n // serialized form so an inline array literal cannot re-register (and reset it) every render.\n const absentDelaysKey = absentRetryDelaysMs.join(',');\n const absentDelays = useMemo(\n () => absentDelaysKey.split(',').filter(Boolean).map(Number),\n [absentDelaysKey],\n );\n\n useEffect(() => {\n if (uniqueIds.length === 0) return;\n const releases = uniqueIds.map((id) =>\n trackAbsentDoc({ queryClient, queryKey: [queryKeyPrefix, id], delays: absentDelays }),\n );\n return () => {\n for (const release of releases) release();\n };\n }, [uniqueIds, queryKeyPrefix, queryClient, absentDelays]);\n\n // The maps are rebuilt every render; the committed refs below hand back the last\n // COMMITTED objects while nothing changed, so consumers can depend on their identity. Only\n // the effect writes the refs, so a concurrent render React discards can never leave them\n // pointing at an uncommitted map.\n const committedDataRef = useRef<Record<string, T>>({});\n const committedOutcomesRef = useRef<Record<string, BatchFirestoreDocOutcome<T>>>({});\n\n // Each id's one-shot failure is held through its retry: TanStack clears a never-answered\n // read's error as its refetch starts, which would turn the id's failure into `loading`.\n const retryStates = useReadRetryStates(\n uniqueIds.map((id, index) => ({\n source: (results[index] ?? IDLE_SOURCE) as ReadRetrySource<Error>,\n queryKey: [queryKeyPrefix, id],\n })),\n );\n\n const nextData: Record<string, T> = {};\n const nextOutcomes: Record<string, BatchFirestoreDocOutcome<T>> = {};\n let anyError: Error | null = null;\n let anyListenerError: Error | null = null;\n let oneShotLoading = false;\n let anyUnresolved = false;\n let failedReadsRunning = false;\n\n for (let index = 0; index < uniqueIds.length; index += 1) {\n const id = uniqueIds[index];\n const result = results[index];\n let outcome: BatchFirestoreDocOutcome<T> = LOADING_OUTCOME;\n // A one-shot reader of a key a failed shared listener owns sees that listener's `null`\n // placeholder in the cache; the registry's error is what it stands for.\n const listenerError =\n (subscribe ? subscribeErrors[id] : undefined) ??\n listenerErrorOf(queryClient, queryKeyPrefix, id) ??\n undefined;\n if (result && (result.isFetching || result.isPaused) && result.errorUpdatedAt > result.dataUpdatedAt) {\n failedReadsRunning = true;\n }\n const retryState = retryStates[index];\n if (result?.isError) anyError ??= result.error as Error;\n if (retryState && retryState.error !== null) {\n outcome = { status: 'failed', error: retryState.error, retrying: retryState.isRetrying };\n } else if (result) {\n if (result.isLoading) oneShotLoading = true;\n const value = result.data as T | null | undefined;\n if (value === undefined) anyUnresolved = true;\n if (listenerError) {\n anyListenerError ??= listenerError;\n outcome = { status: 'failed', error: listenerError, retrying: false };\n } else if (value === null) {\n outcome = ABSENT_OUTCOME;\n } else if (value !== undefined) {\n nextData[id] = value;\n outcome = { status: 'present', data: value };\n }\n }\n const committed = committedOutcomesRef.current[id];\n nextOutcomes[id] = committed && sameOutcome(committed, outcome) ? committed : outcome;\n }\n\n const data = shallowRecordEqual(committedDataRef.current, nextData)\n ? committedDataRef.current\n : nextData;\n const outcomes = shallowRecordEqual(committedOutcomesRef.current, nextOutcomes)\n ? committedOutcomesRef.current\n : nextOutcomes;\n\n useEffect(() => {\n committedDataRef.current = data;\n committedOutcomesRef.current = outcomes;\n }, [data, outcomes]);\n\n const refetch = useCallback(async () => {\n // Subscribe mode owns its freshness through the shared listeners — refetching would\n // issue reads against disabled queries and race the listener's value.\n if (subscribe || !enabled) return;\n await Promise.all(\n uniqueIds.map((id) =>\n queryClient.refetchQueries({ queryKey: [queryKeyPrefix, id], exact: true }),\n ),\n );\n }, [uniqueIds, subscribe, enabled, queryClient, queryKeyPrefix]);\n\n // In subscribe mode \"loading\" means a subscribed id has no cache entry yet (`null`\n // counts as resolved-missing).\n const isLoading = subscribe ? enabled && anyUnresolved : oneShotLoading;\n\n return {\n data,\n isLoading,\n isError: anyError !== null || anyListenerError !== null,\n error: anyError ?? anyListenerError,\n outcomes,\n failedReadsRunning,\n refetch,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useBatchFirestoreDocs.js","sourceRoot":"","sources":["../../../src/react/firestore/useBatchFirestoreDocs.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useQueries, useQueryClient } from '@tanstack/react-query';\nimport type { Firestore } from 'firebase/firestore';\nimport { isListenerOwned, listenerErrorOf, subscribeDoc } from './doc-subscription-registry.js';\nimport { getDocLoader } from './batch-doc-loader.js';\nimport { ABSENT_RETRY_DELAYS_MS, trackAbsentDoc } from './absence-scheduler.js';\nimport type { BatchFirestoreDocOutcome } from '../../firestore/types.js';\nimport { useReadRetryStates, type ReadRetrySource } from '../use-read-retry-state.js';\n\nexport type BatchFirestoreDocsTransport = 'batch' | 'get';\n\nexport type BatchFirestoreDocsOptions = {\n db: Firestore;\n\n collectionPath: string;\n\n ids: string[];\n\n queryKeyPrefix: string;\n\n staleTime?: number;\n\n gcTime?: number;\n\n enabled?: boolean;\n\n subscribe?: boolean;\n\n /** `'get'` is required where rules gate reads on `resource.data`: there an id-list query is denied wholesale. */\n transport?: BatchFirestoreDocsTransport;\n\n absentStaleTime?: number;\n\n absentRetryDelaysMs?: readonly number[];\n};\n\nexport type BatchFirestoreDocsResult<T> = {\n data: Record<string, T>;\n\n isLoading: boolean;\n\n isError: boolean;\n\n error: Error | null;\n\n outcomes: Record<string, BatchFirestoreDocOutcome<T>>;\n\n failedReadsRunning: boolean;\n\n refetch: () => Promise<void>;\n};\n\nconst LOADING_OUTCOME = { status: 'loading' } as const;\nconst IDLE_SOURCE: ReadRetrySource<Error> = { error: null, isFetching: false, isLoading: false };\nconst ABSENT_OUTCOME = { status: 'absent' } as const;\n\nfunction sameOutcome<T>(a: BatchFirestoreDocOutcome<T>, b: BatchFirestoreDocOutcome<T>): boolean {\n if (a.status !== b.status) return false;\n if (a.status === 'present') return a.data === (b as { data: T }).data;\n if (a.status === 'failed') {\n const other = b as { error: Error; retrying: boolean };\n return a.error === other.error && a.retrying === other.retrying;\n }\n return true;\n}\n\nfunction shallowRecordEqual<T>(a: Record<string, T>, b: Record<string, T>): boolean {\n const aKeys = Object.keys(a);\n if (aKeys.length !== Object.keys(b).length) return false;\n for (const key of aKeys) {\n if (!Object.prototype.hasOwnProperty.call(b, key) || a[key] !== b[key]) return false;\n }\n return true;\n}\n\nexport function useBatchFirestoreDocs<T extends Record<string, any>>({\n db,\n collectionPath,\n ids,\n queryKeyPrefix,\n staleTime = 30 * 60 * 1000,\n gcTime = 60 * 60 * 1000,\n enabled = true,\n subscribe = false,\n transport = 'batch',\n absentStaleTime = 30 * 1000,\n absentRetryDelaysMs = ABSENT_RETRY_DELAYS_MS,\n}: BatchFirestoreDocsOptions): BatchFirestoreDocsResult<T> {\n const queryClient = useQueryClient();\n\n const idsKey = JSON.stringify(ids);\n\n const uniqueIds = useMemo(() => {\n const parsed: string[] = JSON.parse(idsKey);\n return Array.from(new Set(parsed.filter(Boolean)));\n }, [idsKey]);\n\n const [subscribeErrors, setSubscribeErrors] = useState<Record<string, Error>>({});\n // Query observers notify on React Query's batched microtask; bumping state here shows a snapshot in the tick it lands.\n const [, setSnapshotVersion] = useState(0);\n\n const results = useQueries({\n queries: uniqueIds.map((id) => ({\n queryKey: [queryKeyPrefix, id],\n queryFn: async ({ signal }: { signal: AbortSignal }): Promise<T | null> => {\n const loader = getDocLoader(queryClient, db, collectionPath);\n const loaded =\n transport === 'get'\n ? await loader.loadOne(id, signal)\n : await loader.loadBatched(id, signal);\n // `onSnapshot` does not re-emit until the document changes, so a read that lands after a listener took the key yields to it.\n if (isListenerOwned(queryClient, queryKeyPrefix, id)) {\n const listenerError = listenerErrorOf(queryClient, queryKeyPrefix, id);\n if (listenerError) throw listenerError;\n const owned = queryClient.getQueryData([queryKeyPrefix, id]);\n if (owned !== undefined) return owned as T | null;\n }\n return loaded as T | null;\n },\n enabled: enabled && !subscribe,\n // Opts back into refetch-on-mount (the package default is off) so a remount re-reads a stale id.\n refetchOnMount: !subscribe,\n staleTime: subscribe\n ? Infinity\n : (query: { state: { data: unknown } }) =>\n query.state.data === null ? absentStaleTime : staleTime,\n gcTime,\n })),\n });\n\n useEffect(() => {\n if (!subscribe || !enabled || uniqueIds.length === 0) return;\n setSubscribeErrors({});\n const unsubscribers = uniqueIds.map((id) =>\n subscribeDoc({\n queryClient,\n db,\n collectionPath,\n queryKeyPrefix,\n id,\n onUpdate: () => {\n setSubscribeErrors((errors) => {\n if (!(id in errors)) return errors;\n const { [id]: _cleared, ...rest } = errors;\n return rest;\n });\n setSnapshotVersion((version) => version + 1);\n },\n onError: (error) =>\n setSubscribeErrors((errors) =>\n errors[id] === error ? errors : { ...errors, [id]: error },\n ),\n }),\n );\n return () => {\n for (const unsubscribe of unsubscribers) unsubscribe();\n };\n }, [uniqueIds, subscribe, enabled, collectionPath, queryKeyPrefix, queryClient, db]);\n\n const absentDelaysKey = absentRetryDelaysMs.join(',');\n const absentDelays = useMemo(\n () => absentDelaysKey.split(',').filter(Boolean).map(Number),\n [absentDelaysKey],\n );\n\n useEffect(() => {\n if (uniqueIds.length === 0) return;\n const releases = uniqueIds.map((id) =>\n trackAbsentDoc({ queryClient, queryKey: [queryKeyPrefix, id], delays: absentDelays }),\n );\n return () => {\n for (const release of releases) release();\n };\n }, [uniqueIds, queryKeyPrefix, queryClient, absentDelays]);\n\n // Only the effect writes these refs, so a concurrent render React discards never leaves them on an uncommitted map.\n const committedDataRef = useRef<Record<string, T>>({});\n const committedOutcomesRef = useRef<Record<string, BatchFirestoreDocOutcome<T>>>({});\n\n // TanStack clears a never-answered read's error as its refetch starts, so the failure is held through the retry.\n const retryStates = useReadRetryStates(\n uniqueIds.map((id, index) => ({\n source: (results[index] ?? IDLE_SOURCE) as ReadRetrySource<Error>,\n queryKey: [queryKeyPrefix, id],\n })),\n );\n\n const nextData: Record<string, T> = {};\n const nextOutcomes: Record<string, BatchFirestoreDocOutcome<T>> = {};\n let anyError: Error | null = null;\n let anyListenerError: Error | null = null;\n let oneShotLoading = false;\n let anyUnresolved = false;\n let failedReadsRunning = false;\n\n for (let index = 0; index < uniqueIds.length; index += 1) {\n const id = uniqueIds[index];\n const result = results[index];\n let outcome: BatchFirestoreDocOutcome<T> = LOADING_OUTCOME;\n const listenerError =\n (subscribe ? subscribeErrors[id] : undefined) ??\n listenerErrorOf(queryClient, queryKeyPrefix, id) ??\n undefined;\n if (\n result &&\n (result.isFetching || result.isPaused) &&\n result.errorUpdatedAt > result.dataUpdatedAt\n ) {\n failedReadsRunning = true;\n }\n const retryState = retryStates[index];\n if (result?.isError) anyError ??= result.error as Error;\n if (retryState && retryState.error !== null) {\n outcome = { status: 'failed', error: retryState.error, retrying: retryState.isRetrying };\n } else if (result) {\n if (result.isLoading) oneShotLoading = true;\n const value = result.data as T | null | undefined;\n if (value === undefined) anyUnresolved = true;\n if (listenerError) {\n anyListenerError ??= listenerError;\n outcome = { status: 'failed', error: listenerError, retrying: false };\n } else if (value === null) {\n outcome = ABSENT_OUTCOME;\n } else if (value !== undefined) {\n nextData[id] = value;\n outcome = { status: 'present', data: value };\n }\n }\n const committed = committedOutcomesRef.current[id];\n nextOutcomes[id] = committed && sameOutcome(committed, outcome) ? committed : outcome;\n }\n\n const data = shallowRecordEqual(committedDataRef.current, nextData)\n ? committedDataRef.current\n : nextData;\n const outcomes = shallowRecordEqual(committedOutcomesRef.current, nextOutcomes)\n ? committedOutcomesRef.current\n : nextOutcomes;\n\n useEffect(() => {\n committedDataRef.current = data;\n committedOutcomesRef.current = outcomes;\n }, [data, outcomes]);\n\n const refetch = useCallback(async () => {\n if (subscribe || !enabled) return;\n await Promise.all(\n uniqueIds.map((id) =>\n queryClient.refetchQueries({ queryKey: [queryKeyPrefix, id], exact: true }),\n ),\n );\n }, [uniqueIds, subscribe, enabled, queryClient, queryKeyPrefix]);\n\n const isLoading = subscribe ? enabled && anyUnresolved : oneShotLoading;\n\n return {\n data,\n isLoading,\n isError: anyError !== null || anyListenerError !== null,\n error: anyError ?? anyListenerError,\n outcomes,\n failedReadsRunning,\n refetch,\n };\n}\n"]}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
export { createQueryClient } from '
|
|
2
|
-
export type { CreateQueryClientOptions } from '
|
|
1
|
+
export { createQueryClient } from './query-client.js';
|
|
2
|
+
export type { CreateQueryClientOptions } from './query-client.js';
|
|
3
3
|
export { QueryProvider } from './provider.js';
|
|
4
4
|
export type { QueryProviderProps } from './provider.js';
|
|
5
5
|
export { FirestoreProvider, useFirestoreDb } from './firestore/context.js';
|
|
6
6
|
export type { FirestoreProviderProps } from './firestore/context.js';
|
|
7
7
|
export { useFirestoreDoc } from './firestore/use-firestore-doc.js';
|
|
8
8
|
export { useFirestoreCollection } from './firestore/use-firestore-collection.js';
|
|
9
|
-
export type { FirestoreSourceState, WithSourceState, BatchFirestoreDocOutcome } from '../firestore/types.js';
|
|
10
9
|
export { useFirestoreInfinite } from './firestore/use-firestore-infinite.js';
|
|
11
10
|
export { useFirestorePaginated } from './firestore/use-firestore-paginated.js';
|
|
12
11
|
export type { UseFirestorePaginatedResult } from './firestore/use-firestore-paginated.js';
|
|
@@ -14,10 +13,20 @@ export { useFirestoreCount } from './firestore/use-firestore-count.js';
|
|
|
14
13
|
export { useFirestoreLiveInfinite } from './firestore/use-firestore-live-infinite.js';
|
|
15
14
|
export type { FirestoreLiveInfiniteResult } from './firestore/use-firestore-live-infinite.js';
|
|
16
15
|
export { useFirestoreSet, useFirestoreUpdate, useFirestoreDelete, useFirestoreBatch, } from './firestore/use-firestore-mutations.js';
|
|
17
|
-
export { useBatchFirestoreDocs
|
|
16
|
+
export { useBatchFirestoreDocs } from './firestore/useBatchFirestoreDocs.js';
|
|
18
17
|
export type { BatchFirestoreDocsOptions, BatchFirestoreDocsResult, BatchFirestoreDocsTransport, } from './firestore/useBatchFirestoreDocs.js';
|
|
19
18
|
export { ABSENT_RETRY_DELAYS_MS } from './firestore/absence-scheduler.js';
|
|
20
19
|
export { useFirestoreSearch } from './search/use-firestore-search.js';
|
|
20
|
+
export { invalidateByPrefix, removeByPrefix, updateQueryData, paginatedPageKey, mapPaginatedItems, prependToFirstPaginatedPage, placeRowInPages, } from './cache-helpers.js';
|
|
21
|
+
export type { PagedRows } from './cache-helpers.js';
|
|
22
|
+
export { createDomainEventInvalidator } from './domain-events/create-domain-event-invalidator.js';
|
|
23
|
+
export { exact, prefix, predicate, serializeInvalidation, applyInvalidations, } from './domain-events/cache-invalidation.js';
|
|
24
|
+
export type { DomainEventInvalidator, DomainEventInvalidationRegistry, } from './domain-events/create-domain-event-invalidator.js';
|
|
25
|
+
export type { CacheAction, CacheInvalidation, RefetchType, } from './domain-events/cache-invalidation.js';
|
|
26
|
+
export { docWithId } from './firestore/types.js';
|
|
27
|
+
export type { FirestoreDocOptions, FirestoreCollectionOptions, FirestoreInfiniteOptions, FirestorePaginatedOptions, FirestoreCountOptions, FirestoreLiveInfiniteOptions, InfinitePage, PaginatedPage, MutationOperation, } from './firestore/types.js';
|
|
28
|
+
export { flattenInfiniteData, getInfiniteDataCount } from './firestore/infinite-helpers.js';
|
|
29
|
+
export type { FirestoreSearchOptions } from './search/types.js';
|
|
21
30
|
export { useReadRetryState, useReadRetryStates } from './use-read-retry-state.js';
|
|
22
31
|
export type { ReadRetryEntry, ReadRetrySource, ReadRetryState } from './use-read-retry-state.js';
|
|
23
32
|
export { usePendingMutationVariables, pendingMutationVariables, usePendingMutationTargets, hasPendingMutation, duplicateSubmissionError, useDuplicateGuardedMutation, } from './pending-mutations.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAElE,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExD,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC3E,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAErE,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACjF,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAC/E,YAAY,EAAE,2BAA2B,EAAE,MAAM,wCAAwC,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,4CAA4C,CAAC;AACtF,YAAY,EAAE,2BAA2B,EAAE,MAAM,4CAA4C,CAAC;AAE9F,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,wCAAwC,CAAC;AAEhD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,YAAY,EACV,yBAAyB,EACzB,wBAAwB,EACxB,2BAA2B,GAC5B,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAE1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAEtE,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,2BAA2B,EAC3B,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAAE,4BAA4B,EAAE,MAAM,oDAAoD,CAAC;AAClG,OAAO,EACL,KAAK,EACL,MAAM,EACN,SAAS,EACT,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,uCAAuC,CAAC;AAC/C,YAAY,EACV,sBAAsB,EACtB,+BAA+B,GAChC,MAAM,oDAAoD,CAAC;AAC5D,YAAY,EACV,WAAW,EACX,iBAAiB,EACjB,WAAW,GACZ,MAAM,uCAAuC,CAAC;AAE/C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,YAAY,EACV,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,yBAAyB,EACzB,qBAAqB,EACrB,4BAA4B,EAC5B,YAAY,EACZ,aAAa,EACb,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC5F,YAAY,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAEhE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAClF,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEjG,OAAO,EACL,2BAA2B,EAC3B,wBAAwB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,2BAA2B,GAC5B,MAAM,wBAAwB,CAAC"}
|
package/dist/react/index.js
CHANGED
|
@@ -1,27 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export { createQueryClient } from '../query-client.js';
|
|
4
|
-
// Provider
|
|
1
|
+
'use client';
|
|
2
|
+
export { createQueryClient } from './query-client.js';
|
|
5
3
|
export { QueryProvider } from './provider.js';
|
|
6
|
-
// Firestore Provider
|
|
7
4
|
export { FirestoreProvider, useFirestoreDb } from './firestore/context.js';
|
|
8
|
-
// Firestore Query Hooks
|
|
9
5
|
export { useFirestoreDoc } from './firestore/use-firestore-doc.js';
|
|
10
6
|
export { useFirestoreCollection } from './firestore/use-firestore-collection.js';
|
|
11
7
|
export { useFirestoreInfinite } from './firestore/use-firestore-infinite.js';
|
|
12
8
|
export { useFirestorePaginated } from './firestore/use-firestore-paginated.js';
|
|
13
9
|
export { useFirestoreCount } from './firestore/use-firestore-count.js';
|
|
14
10
|
export { useFirestoreLiveInfinite } from './firestore/use-firestore-live-infinite.js';
|
|
15
|
-
// Firestore Mutation Hooks
|
|
16
11
|
export { useFirestoreSet, useFirestoreUpdate, useFirestoreDelete, useFirestoreBatch, } from './firestore/use-firestore-mutations.js';
|
|
17
|
-
|
|
18
|
-
export { useBatchFirestoreDocs, } from './firestore/useBatchFirestoreDocs.js';
|
|
12
|
+
export { useBatchFirestoreDocs } from './firestore/useBatchFirestoreDocs.js';
|
|
19
13
|
export { ABSENT_RETRY_DELAYS_MS } from './firestore/absence-scheduler.js';
|
|
20
|
-
// Search Hook
|
|
21
14
|
export { useFirestoreSearch } from './search/use-firestore-search.js';
|
|
22
|
-
|
|
15
|
+
export { invalidateByPrefix, removeByPrefix, updateQueryData, paginatedPageKey, mapPaginatedItems, prependToFirstPaginatedPage, placeRowInPages, } from './cache-helpers.js';
|
|
16
|
+
export { createDomainEventInvalidator } from './domain-events/create-domain-event-invalidator.js';
|
|
17
|
+
export { exact, prefix, predicate, serializeInvalidation, applyInvalidations, } from './domain-events/cache-invalidation.js';
|
|
18
|
+
export { docWithId } from './firestore/types.js';
|
|
19
|
+
export { flattenInfiniteData, getInfiniteDataCount } from './firestore/infinite-helpers.js';
|
|
23
20
|
export { useReadRetryState, useReadRetryStates } from './use-read-retry-state.js';
|
|
24
|
-
// Authoritative pending for interactive mutations: in-flight state and duplicate guards read from
|
|
25
|
-
// the shared MutationCache under the app's stable mutation keys.
|
|
26
21
|
export { usePendingMutationVariables, pendingMutationVariables, usePendingMutationTargets, hasPendingMutation, duplicateSubmissionError, useDuplicateGuardedMutation, } from './pending-mutations.js';
|
|
27
22
|
//# sourceMappingURL=index.js.map
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG9C,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAG3E,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACjF,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAE/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,4CAA4C,CAAC;AAGtF,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,wCAAwC,CAAC;AAEhD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAM7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAE1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAEtE,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,2BAA2B,EAC3B,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,4BAA4B,EAAE,MAAM,oDAAoD,CAAC;AAClG,OAAO,EACL,KAAK,EACL,MAAM,EACN,SAAS,EACT,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,uCAAuC,CAAC;AAW/C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAYjD,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAG5F,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAGlF,OAAO,EACL,2BAA2B,EAC3B,wBAAwB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,2BAA2B,GAC5B,MAAM,wBAAwB,CAAC","sourcesContent":["'use client';\n\nexport { createQueryClient } from './query-client.js';\nexport type { CreateQueryClientOptions } from './query-client.js';\n\nexport { QueryProvider } from './provider.js';\nexport type { QueryProviderProps } from './provider.js';\n\nexport { FirestoreProvider, useFirestoreDb } from './firestore/context.js';\nexport type { FirestoreProviderProps } from './firestore/context.js';\n\nexport { useFirestoreDoc } from './firestore/use-firestore-doc.js';\nexport { useFirestoreCollection } from './firestore/use-firestore-collection.js';\nexport { useFirestoreInfinite } from './firestore/use-firestore-infinite.js';\nexport { useFirestorePaginated } from './firestore/use-firestore-paginated.js';\nexport type { UseFirestorePaginatedResult } from './firestore/use-firestore-paginated.js';\nexport { useFirestoreCount } from './firestore/use-firestore-count.js';\nexport { useFirestoreLiveInfinite } from './firestore/use-firestore-live-infinite.js';\nexport type { FirestoreLiveInfiniteResult } from './firestore/use-firestore-live-infinite.js';\n\nexport {\n useFirestoreSet,\n useFirestoreUpdate,\n useFirestoreDelete,\n useFirestoreBatch,\n} from './firestore/use-firestore-mutations.js';\n\nexport { useBatchFirestoreDocs } from './firestore/useBatchFirestoreDocs.js';\nexport type {\n BatchFirestoreDocsOptions,\n BatchFirestoreDocsResult,\n BatchFirestoreDocsTransport,\n} from './firestore/useBatchFirestoreDocs.js';\nexport { ABSENT_RETRY_DELAYS_MS } from './firestore/absence-scheduler.js';\n\nexport { useFirestoreSearch } from './search/use-firestore-search.js';\n\nexport {\n invalidateByPrefix,\n removeByPrefix,\n updateQueryData,\n paginatedPageKey,\n mapPaginatedItems,\n prependToFirstPaginatedPage,\n placeRowInPages,\n} from './cache-helpers.js';\nexport type { PagedRows } from './cache-helpers.js';\n\nexport { createDomainEventInvalidator } from './domain-events/create-domain-event-invalidator.js';\nexport {\n exact,\n prefix,\n predicate,\n serializeInvalidation,\n applyInvalidations,\n} from './domain-events/cache-invalidation.js';\nexport type {\n DomainEventInvalidator,\n DomainEventInvalidationRegistry,\n} from './domain-events/create-domain-event-invalidator.js';\nexport type {\n CacheAction,\n CacheInvalidation,\n RefetchType,\n} from './domain-events/cache-invalidation.js';\n\nexport { docWithId } from './firestore/types.js';\nexport type {\n FirestoreDocOptions,\n FirestoreCollectionOptions,\n FirestoreInfiniteOptions,\n FirestorePaginatedOptions,\n FirestoreCountOptions,\n FirestoreLiveInfiniteOptions,\n InfinitePage,\n PaginatedPage,\n MutationOperation,\n} from './firestore/types.js';\nexport { flattenInfiniteData, getInfiniteDataCount } from './firestore/infinite-helpers.js';\nexport type { FirestoreSearchOptions } from './search/types.js';\n\nexport { useReadRetryState, useReadRetryStates } from './use-read-retry-state.js';\nexport type { ReadRetryEntry, ReadRetrySource, ReadRetryState } from './use-read-retry-state.js';\n\nexport {\n usePendingMutationVariables,\n pendingMutationVariables,\n usePendingMutationTargets,\n hasPendingMutation,\n duplicateSubmissionError,\n useDuplicateGuardedMutation,\n} from './pending-mutations.js';\n"]}
|
|
@@ -1,29 +1,9 @@
|
|
|
1
|
-
import { type MutationKey, type QueryClient, type UseMutationResult } from
|
|
2
|
-
/** The variables of every pending mutation under `mutationKey` (prefix-matched), live. */
|
|
1
|
+
import { type MutationKey, type QueryClient, type UseMutationResult } from '@tanstack/react-query';
|
|
3
2
|
export declare function usePendingMutationVariables<V>(mutationKey: MutationKey): ReadonlyArray<V | undefined>;
|
|
4
|
-
/** The variables of every pending mutation under `mutationKey`, read once — for a duplicate guard. */
|
|
5
3
|
export declare function pendingMutationVariables<V>(queryClient: QueryClient, mutationKey: MutationKey): Array<V | undefined>;
|
|
6
|
-
/**
|
|
7
|
-
* The targets of every pending mutation under `mutationKey`, live — for a list whose rows each
|
|
8
|
-
* ask "is mine pending?" through one hook call. `targetOf` names a variables' target, or returns
|
|
9
|
-
* nothing for variables without one.
|
|
10
|
-
*/
|
|
11
4
|
export declare function usePendingMutationTargets<V>(mutationKey: MutationKey, targetOf: (variables: V) => string | null | undefined): ReadonlySet<string>;
|
|
12
|
-
/**
|
|
13
|
-
* True when a mutation under `mutationKey` is pending — any one, or with `matches`, one whose
|
|
14
|
-
* variables are for the same target.
|
|
15
|
-
*/
|
|
16
5
|
export declare function hasPendingMutation<V>(queryClient: QueryClient, mutationKey: MutationKey, matches?: (variables: V) => boolean): boolean;
|
|
17
|
-
/**
|
|
18
|
-
* The rejection a guarded `mutateAsync` throws for a duplicate submission: an `AbortError`, the
|
|
19
|
-
* shape a caller already treats as "nothing happened" rather than a failure to report.
|
|
20
|
-
*/
|
|
21
6
|
export declare function duplicateSubmissionError(what: string): Error;
|
|
22
|
-
/**
|
|
23
|
-
* Wrap a keyed mutation's `mutate` / `mutateAsync` in the cache-consulting duplicate guard:
|
|
24
|
-
* `mutate` drops a duplicate silently, and `mutateAsync` rejects it with
|
|
25
|
-
* {@link duplicateSubmissionError}. `isDuplicate` must have a stable identity (declare it at module
|
|
26
|
-
* level) so the guarded callbacks keep theirs.
|
|
27
|
-
*/
|
|
7
|
+
/** `isDuplicate` must have a stable identity (declare it at module level) so the guarded callbacks keep theirs. */
|
|
28
8
|
export declare function useDuplicateGuardedMutation<TData, TError, TVariables, TContext>(mutation: UseMutationResult<TData, TError, TVariables, TContext>, isDuplicate: (queryClient: QueryClient, variables: TVariables) => boolean, what: string): UseMutationResult<TData, TError, TVariables, TContext>;
|
|
29
9
|
//# sourceMappingURL=pending-mutations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pending-mutations.d.ts","sourceRoot":"","sources":["../../src/react/pending-mutations.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pending-mutations.d.ts","sourceRoot":"","sources":["../../src/react/pending-mutations.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAE/B,wBAAgB,2BAA2B,CAAC,CAAC,EAC3C,WAAW,EAAE,WAAW,GACvB,aAAa,CAAC,CAAC,GAAG,SAAS,CAAC,CAK9B;AAED,wBAAgB,wBAAwB,CAAC,CAAC,EACxC,WAAW,EAAE,WAAW,EACxB,WAAW,EAAE,WAAW,GACvB,KAAK,CAAC,CAAC,GAAG,SAAS,CAAC,CAKtB;AAED,wBAAgB,yBAAyB,CAAC,CAAC,EACzC,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,CAAC,SAAS,EAAE,CAAC,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,WAAW,CAAC,MAAM,CAAC,CAQrB;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,WAAW,EAAE,WAAW,EACxB,WAAW,EAAE,WAAW,EACxB,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,KAAK,OAAO,GAClC,OAAO,CAIT;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAI5D;AAED,mHAAmH;AACnH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAC7E,QAAQ,EAAE,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAC,EAChE,WAAW,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,KAAK,OAAO,EACzE,IAAI,EAAE,MAAM,GACX,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAC,CAuBxD"}
|