@ttt-productions/query-core 0.14.22 → 0.14.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +6 -6
  2. package/dist/defaults.d.ts +0 -15
  3. package/dist/defaults.d.ts.map +1 -1
  4. package/dist/defaults.js +0 -15
  5. package/dist/defaults.js.map +1 -1
  6. package/dist/firestore/types.d.ts +1 -49
  7. package/dist/firestore/types.d.ts.map +1 -1
  8. package/dist/firestore/types.js.map +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +0 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/keys.d.ts +0 -22
  13. package/dist/keys.d.ts.map +1 -1
  14. package/dist/keys.js +0 -4
  15. package/dist/keys.js.map +1 -1
  16. package/dist/react/cache-helpers.d.ts +0 -32
  17. package/dist/react/cache-helpers.d.ts.map +1 -1
  18. package/dist/react/cache-helpers.js +0 -28
  19. package/dist/react/cache-helpers.js.map +1 -1
  20. package/dist/react/defaults.d.ts +0 -4
  21. package/dist/react/defaults.d.ts.map +1 -1
  22. package/dist/react/defaults.js +2 -6
  23. package/dist/react/defaults.js.map +1 -1
  24. package/dist/react/domain-events/cache-invalidation.d.ts +0 -37
  25. package/dist/react/domain-events/cache-invalidation.d.ts.map +1 -1
  26. package/dist/react/domain-events/cache-invalidation.js +4 -53
  27. package/dist/react/domain-events/cache-invalidation.js.map +1 -1
  28. package/dist/react/domain-events/create-domain-event-invalidator.d.ts +0 -18
  29. package/dist/react/domain-events/create-domain-event-invalidator.d.ts.map +1 -1
  30. package/dist/react/domain-events/create-domain-event-invalidator.js +0 -5
  31. package/dist/react/domain-events/create-domain-event-invalidator.js.map +1 -1
  32. package/dist/react/firestore/absence-scheduler.d.ts +0 -28
  33. package/dist/react/firestore/absence-scheduler.d.ts.map +1 -1
  34. package/dist/react/firestore/absence-scheduler.js +1 -39
  35. package/dist/react/firestore/absence-scheduler.js.map +1 -1
  36. package/dist/react/firestore/batch-doc-loader.d.ts +0 -30
  37. package/dist/react/firestore/batch-doc-loader.d.ts.map +1 -1
  38. package/dist/react/firestore/batch-doc-loader.js +0 -34
  39. package/dist/react/firestore/batch-doc-loader.js.map +1 -1
  40. package/dist/react/firestore/context.d.ts +0 -19
  41. package/dist/react/firestore/context.d.ts.map +1 -1
  42. package/dist/react/firestore/context.js +0 -19
  43. package/dist/react/firestore/context.js.map +1 -1
  44. package/dist/react/firestore/doc-subscription-registry.d.ts +0 -23
  45. package/dist/react/firestore/doc-subscription-registry.d.ts.map +1 -1
  46. package/dist/react/firestore/doc-subscription-registry.js +0 -25
  47. package/dist/react/firestore/doc-subscription-registry.js.map +1 -1
  48. package/dist/react/firestore/infinite-helpers.d.ts +0 -12
  49. package/dist/react/firestore/infinite-helpers.d.ts.map +1 -1
  50. package/dist/react/firestore/infinite-helpers.js +0 -12
  51. package/dist/react/firestore/infinite-helpers.js.map +1 -1
  52. package/dist/react/firestore/resubscribe.d.ts +0 -20
  53. package/dist/react/firestore/resubscribe.d.ts.map +1 -1
  54. package/dist/react/firestore/resubscribe.js +0 -20
  55. package/dist/react/firestore/resubscribe.js.map +1 -1
  56. package/dist/react/firestore/subscription-status.d.ts +0 -29
  57. package/dist/react/firestore/subscription-status.d.ts.map +1 -1
  58. package/dist/react/firestore/subscription-status.js +1 -34
  59. package/dist/react/firestore/subscription-status.js.map +1 -1
  60. package/dist/react/firestore/types.d.ts +0 -64
  61. package/dist/react/firestore/types.d.ts.map +1 -1
  62. package/dist/react/firestore/types.js +0 -3
  63. package/dist/react/firestore/types.js.map +1 -1
  64. package/dist/react/firestore/use-firestore-collection.d.ts +0 -20
  65. package/dist/react/firestore/use-firestore-collection.d.ts.map +1 -1
  66. package/dist/react/firestore/use-firestore-collection.js +1 -35
  67. package/dist/react/firestore/use-firestore-collection.js.map +1 -1
  68. package/dist/react/firestore/use-firestore-count.d.ts +0 -15
  69. package/dist/react/firestore/use-firestore-count.d.ts.map +1 -1
  70. package/dist/react/firestore/use-firestore-count.js +0 -15
  71. package/dist/react/firestore/use-firestore-count.js.map +1 -1
  72. package/dist/react/firestore/use-firestore-doc.d.ts +0 -23
  73. package/dist/react/firestore/use-firestore-doc.d.ts.map +1 -1
  74. package/dist/react/firestore/use-firestore-doc.js +3 -40
  75. package/dist/react/firestore/use-firestore-doc.js.map +1 -1
  76. package/dist/react/firestore/use-firestore-infinite.d.ts +0 -29
  77. package/dist/react/firestore/use-firestore-infinite.d.ts.map +1 -1
  78. package/dist/react/firestore/use-firestore-infinite.js +0 -32
  79. package/dist/react/firestore/use-firestore-infinite.js.map +1 -1
  80. package/dist/react/firestore/use-firestore-live-infinite.d.ts +0 -34
  81. package/dist/react/firestore/use-firestore-live-infinite.d.ts.map +1 -1
  82. package/dist/react/firestore/use-firestore-live-infinite.js +1 -35
  83. package/dist/react/firestore/use-firestore-live-infinite.js.map +1 -1
  84. package/dist/react/firestore/use-firestore-paginated.d.ts +0 -30
  85. package/dist/react/firestore/use-firestore-paginated.d.ts.map +1 -1
  86. package/dist/react/firestore/use-firestore-paginated.js +2 -33
  87. package/dist/react/firestore/use-firestore-paginated.js.map +1 -1
  88. package/dist/react/firestore/useBatchFirestoreDocs.d.ts +1 -123
  89. package/dist/react/firestore/useBatchFirestoreDocs.d.ts.map +1 -1
  90. package/dist/react/firestore/useBatchFirestoreDocs.js +5 -58
  91. package/dist/react/firestore/useBatchFirestoreDocs.js.map +1 -1
  92. package/dist/react/index.d.ts.map +1 -1
  93. package/dist/react/index.js +0 -13
  94. package/dist/react/index.js.map +1 -1
  95. package/dist/react/pending-mutations.d.ts +1 -21
  96. package/dist/react/pending-mutations.d.ts.map +1 -1
  97. package/dist/react/pending-mutations.js +1 -32
  98. package/dist/react/pending-mutations.js.map +1 -1
  99. package/dist/react/provider.d.ts +0 -5
  100. package/dist/react/provider.d.ts.map +1 -1
  101. package/dist/react/provider.js +0 -4
  102. package/dist/react/provider.js.map +1 -1
  103. package/dist/react/query-client.d.ts +0 -4
  104. package/dist/react/query-client.d.ts.map +1 -1
  105. package/dist/react/query-client.js +0 -4
  106. package/dist/react/query-client.js.map +1 -1
  107. package/dist/react/search/types.d.ts +0 -12
  108. package/dist/react/search/types.d.ts.map +1 -1
  109. package/dist/react/search/types.js.map +1 -1
  110. package/dist/react/search/use-firestore-search.d.ts +0 -29
  111. package/dist/react/search/use-firestore-search.d.ts.map +1 -1
  112. package/dist/react/search/use-firestore-search.js +3 -41
  113. package/dist/react/search/use-firestore-search.js.map +1 -1
  114. package/dist/react/use-read-retry-state.d.ts +1 -21
  115. package/dist/react/use-read-retry-state.d.ts.map +1 -1
  116. package/dist/react/use-read-retry-state.js +2 -21
  117. package/dist/react/use-read-retry-state.js.map +1 -1
  118. package/dist/search/rule.d.ts +0 -6
  119. package/dist/search/rule.d.ts.map +1 -1
  120. package/dist/search/rule.js +0 -6
  121. package/dist/search/rule.js.map +1 -1
  122. package/dist/search/types.d.ts +1 -13
  123. package/dist/search/types.d.ts.map +1 -1
  124. package/dist/search/types.js.map +1 -1
  125. package/package.json +2 -2
@@ -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,wDAAwD;AACxD,MAAM,MAAM,2BAA2B,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1D,MAAM,MAAM,yBAAyB,GAAG;IACtC;;OAEG;IACH,EAAE,EAAE,SAAS,CAAC;IAEd;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,GAAG,EAAE,MAAM,EAAE,CAAC;IAEd;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,2BAA2B,CAAC;IAExC;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;;;;;;;;;OAWG;IACH,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,wBAAwB,CAAC,CAAC,IAAI;IACxC;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAExB;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAEpB;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC,CAAC,CAAC;IAEtD;;;;;OAKG;IACH,kBAAkB,EAAE,OAAO,CAAC;IAE5B;;OAEG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAyBF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,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,CA0MzD"}
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,50 +29,15 @@ 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
- // Snapshot re-render trigger. The per-id query observers do notify on the listener's
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) => ({
@@ -82,10 +47,7 @@ export function useBatchFirestoreDocs({ db, collectionPath, ids, queryKeyPrefix,
82
47
  const loaded = transport === 'get'
83
48
  ? await loader.loadOne(id, signal)
84
49
  : await loader.loadBatched(id, signal);
85
- // If a shared listener took ownership of this key while the read was in flight, the
86
- // listener's value is the newer truth — `onSnapshot` will not re-emit until the doc
87
- // changes, so returning the read result here would leave a stale value stuck. A listener
88
- // that errored left only a `null` placeholder; its error is the answer, never an absence.
50
+ // `onSnapshot` does not re-emit until the document changes, so a read that lands after a listener took the key yields to it.
89
51
  if (isListenerOwned(queryClient, queryKeyPrefix, id)) {
90
52
  const listenerError = listenerErrorOf(queryClient, queryKeyPrefix, id);
91
53
  if (listenerError)
@@ -97,9 +59,7 @@ export function useBatchFirestoreDocs({ db, collectionPath, ids, queryKeyPrefix,
97
59
  return loaded;
98
60
  },
99
61
  enabled: enabled && !subscribe,
100
- // The consuming app's global default is `refetchOnMount: false`; these lookups opt
101
- // back in so a remount re-reads a STALE id (refetch-on-mount never fires on a fresh
102
- // 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.
103
63
  refetchOnMount: !subscribe,
104
64
  staleTime: subscribe
105
65
  ? Infinity
@@ -133,9 +93,6 @@ export function useBatchFirestoreDocs({ db, collectionPath, ids, queryKeyPrefix,
133
93
  unsubscribe();
134
94
  };
135
95
  }, [uniqueIds, subscribe, enabled, collectionPath, queryKeyPrefix, queryClient, db]);
136
- // Bounded re-read ladder for ids that resolved absent. Registration is per cache key, so
137
- // overlapping consumers share one episode budget. The ladder is rebuilt from its own
138
- // serialized form so an inline array literal cannot re-register (and reset it) every render.
139
96
  const absentDelaysKey = absentRetryDelaysMs.join(',');
140
97
  const absentDelays = useMemo(() => absentDelaysKey.split(',').filter(Boolean).map(Number), [absentDelaysKey]);
141
98
  useEffect(() => {
@@ -147,14 +104,10 @@ export function useBatchFirestoreDocs({ db, collectionPath, ids, queryKeyPrefix,
147
104
  release();
148
105
  };
149
106
  }, [uniqueIds, queryKeyPrefix, queryClient, absentDelays]);
150
- // The maps are rebuilt every render; the committed refs below hand back the last
151
- // COMMITTED objects while nothing changed, so consumers can depend on their identity. Only
152
- // the effect writes the refs, so a concurrent render React discards can never leave them
153
- // 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.
154
108
  const committedDataRef = useRef({});
155
109
  const committedOutcomesRef = useRef({});
156
- // Each id's one-shot failure is held through its retry: TanStack clears a never-answered
157
- // 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.
158
111
  const retryStates = useReadRetryStates(uniqueIds.map((id, index) => ({
159
112
  source: (results[index] ?? IDLE_SOURCE),
160
113
  queryKey: [queryKeyPrefix, id],
@@ -170,8 +123,6 @@ export function useBatchFirestoreDocs({ db, collectionPath, ids, queryKeyPrefix,
170
123
  const id = uniqueIds[index];
171
124
  const result = results[index];
172
125
  let outcome = LOADING_OUTCOME;
173
- // A one-shot reader of a key a failed shared listener owns sees that listener's `null`
174
- // placeholder in the cache; the registry's error is what it stands for.
175
126
  const listenerError = (subscribe ? subscribeErrors[id] : undefined) ??
176
127
  listenerErrorOf(queryClient, queryKeyPrefix, id) ??
177
128
  undefined;
@@ -218,14 +169,10 @@ export function useBatchFirestoreDocs({ db, collectionPath, ids, queryKeyPrefix,
218
169
  committedOutcomesRef.current = outcomes;
219
170
  }, [data, outcomes]);
220
171
  const refetch = useCallback(async () => {
221
- // Subscribe mode owns its freshness through the shared listeners — refetching would
222
- // issue reads against disabled queries and race the listener's value.
223
172
  if (subscribe || !enabled)
224
173
  return;
225
174
  await Promise.all(uniqueIds.map((id) => queryClient.refetchQueries({ queryKey: [queryKeyPrefix, id], exact: true })));
226
175
  }, [uniqueIds, subscribe, enabled, queryClient, queryKeyPrefix]);
227
- // In subscribe mode "loading" means a subscribed id has no cache entry yet (`null`
228
- // counts as resolved-missing).
229
176
  const isLoading = subscribe ? enabled && anyUnresolved : oneShotLoading;
230
177
  return {
231
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'\n ? await loader.loadOne(id, signal)\n : 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) =>\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 // 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 (\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 // 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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAGlE,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGxD,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC3E,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAGrE,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;AAG9F,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,wCAAwC,CAAC;AAGhD,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;AAG1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAGtE,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;AAGpD,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;AAG/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;AAGhE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAClF,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAIjG,OAAO,EACL,2BAA2B,EAC3B,wBAAwB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,2BAA2B,GAC5B,MAAM,wBAAwB,CAAC"}
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"}
@@ -1,35 +1,22 @@
1
1
  'use client';
2
- // Query Client factory (client-only; requires @tanstack/react-query)
3
2
  export { createQueryClient } from './query-client.js';
4
- // Provider
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
- // Batch Firestore Docs
18
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
- // Cache Helpers
23
15
  export { invalidateByPrefix, removeByPrefix, updateQueryData, paginatedPageKey, mapPaginatedItems, prependToFirstPaginatedPage, placeRowInPages, } from './cache-helpers.js';
24
- // Domain-event invalidator (mechanism — consumers register their event registry)
25
16
  export { createDomainEventInvalidator } from './domain-events/create-domain-event-invalidator.js';
26
17
  export { exact, prefix, predicate, serializeInvalidation, applyInvalidations, } from './domain-events/cache-invalidation.js';
27
- // Firestore hook option and page types, and the snapshot helpers
28
18
  export { docWithId } from './firestore/types.js';
29
19
  export { flattenInfiniteData, getInfiniteDataCount } from './firestore/infinite-helpers.js';
30
- // Read retry state
31
20
  export { useReadRetryState, useReadRetryStates } from './use-read-retry-state.js';
32
- // Authoritative pending for interactive mutations: in-flight state and duplicate guards read from
33
- // the shared MutationCache under the app's stable mutation keys.
34
21
  export { usePendingMutationVariables, pendingMutationVariables, usePendingMutationTargets, hasPendingMutation, duplicateSubmissionError, useDuplicateGuardedMutation, } from './pending-mutations.js';
35
22
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,qEAAqE;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,WAAW;AACX,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG9C,qBAAqB;AACrB,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAG3E,wBAAwB;AACxB,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,2BAA2B;AAC3B,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,wCAAwC,CAAC;AAEhD,uBAAuB;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAM7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAE1E,cAAc;AACd,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAEtE,gBAAgB;AAChB,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,2BAA2B,EAC3B,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAG5B,iFAAiF;AACjF,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,iEAAiE;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAYjD,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAG5F,mBAAmB;AACnB,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAGlF,kGAAkG;AAClG,iEAAiE;AACjE,OAAO,EACL,2BAA2B,EAC3B,wBAAwB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,2BAA2B,GAC5B,MAAM,wBAAwB,CAAC","sourcesContent":["'use client';\n\n// Query Client factory (client-only; requires @tanstack/react-query)\nexport { createQueryClient } from './query-client.js';\nexport type { CreateQueryClientOptions } from './query-client.js';\n\n// Provider\nexport { QueryProvider } from './provider.js';\nexport type { QueryProviderProps } from './provider.js';\n\n// Firestore Provider\nexport { FirestoreProvider, useFirestoreDb } from './firestore/context.js';\nexport type { FirestoreProviderProps } from './firestore/context.js';\n\n// Firestore Query Hooks\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\n// Firestore Mutation Hooks\nexport {\n useFirestoreSet,\n useFirestoreUpdate,\n useFirestoreDelete,\n useFirestoreBatch,\n} from './firestore/use-firestore-mutations.js';\n\n// Batch Firestore Docs\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\n// Search Hook\nexport { useFirestoreSearch } from './search/use-firestore-search.js';\n\n// Cache Helpers\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\n// Domain-event invalidator (mechanism — consumers register their event registry)\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\n// Firestore hook option and page types, and the snapshot helpers\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\n// Read retry state\nexport { useReadRetryState, useReadRetryStates } from './use-read-retry-state.js';\nexport type { ReadRetryEntry, ReadRetrySource, ReadRetryState } from './use-read-retry-state.js';\n\n// Authoritative pending for interactive mutations: in-flight state and duplicate guards read from\n// the shared MutationCache under the app's stable mutation keys.\nexport {\n usePendingMutationVariables,\n pendingMutationVariables,\n usePendingMutationTargets,\n hasPendingMutation,\n duplicateSubmissionError,\n useDuplicateGuardedMutation,\n} from './pending-mutations.js';\n"]}
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
1
  import { type MutationKey, type QueryClient, type UseMutationResult } from '@tanstack/react-query';
2
- /** The variables of every pending mutation under `mutationKey` (prefix-matched), live. */
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":"AAeA,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAE/B,0FAA0F;AAC1F,wBAAgB,2BAA2B,CAAC,CAAC,EAC3C,WAAW,EAAE,WAAW,GACvB,aAAa,CAAC,CAAC,GAAG,SAAS,CAAC,CAK9B;AAED,sGAAsG;AACtG,wBAAgB,wBAAwB,CAAC,CAAC,EACxC,WAAW,EAAE,WAAW,EACxB,WAAW,EAAE,WAAW,GACvB,KAAK,CAAC,CAAC,GAAG,SAAS,CAAC,CAKtB;AAED;;;;GAIG;AACH,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;;;GAGG;AACH,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;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAI5D;AAED;;;;;GAKG;AACH,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"}
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"}
@@ -1,36 +1,18 @@
1
1
  'use client';
2
- // Authoritative pending for interactive mutations. A mutation registers in React Query's shared
3
- // MutationCache under a stable `mutationKey` the app declares (one key per action), and its target
4
- // travels in the mutation's variables. Pending is read from that cache, never from one mounted
5
- // control's `isPending`, which gives three guarantees:
6
- //
7
- // 1. an in-flight request survives its originating control unmounting;
8
- // 2. a remounted control still renders pending for that target;
9
- // 3. a second submission for the same target is refused while the first is pending.
10
- //
11
- // The app owns its keys, its variable shapes and what counts as the same target; this module owns
12
- // only the mechanics.
13
2
  import { useCallback } from 'react';
14
3
  import { useMutationState, useQueryClient, } from '@tanstack/react-query';
15
- /** The variables of every pending mutation under `mutationKey` (prefix-matched), live. */
16
4
  export function usePendingMutationVariables(mutationKey) {
17
5
  return useMutationState({
18
6
  filters: { mutationKey, status: 'pending' },
19
7
  select: (mutation) => mutation.state.variables,
20
8
  });
21
9
  }
22
- /** The variables of every pending mutation under `mutationKey`, read once — for a duplicate guard. */
23
10
  export function pendingMutationVariables(queryClient, mutationKey) {
24
11
  return queryClient
25
12
  .getMutationCache()
26
13
  .findAll({ mutationKey, status: 'pending' })
27
14
  .map((mutation) => mutation.state.variables);
28
15
  }
29
- /**
30
- * The targets of every pending mutation under `mutationKey`, live — for a list whose rows each
31
- * ask "is mine pending?" through one hook call. `targetOf` names a variables' target, or returns
32
- * nothing for variables without one.
33
- */
34
16
  export function usePendingMutationTargets(mutationKey, targetOf) {
35
17
  const targets = new Set();
36
18
  for (const variables of usePendingMutationVariables(mutationKey)) {
@@ -42,31 +24,18 @@ export function usePendingMutationTargets(mutationKey, targetOf) {
42
24
  }
43
25
  return targets;
44
26
  }
45
- /**
46
- * True when a mutation under `mutationKey` is pending — any one, or with `matches`, one whose
47
- * variables are for the same target.
48
- */
49
27
  export function hasPendingMutation(queryClient, mutationKey, matches) {
50
28
  const pending = pendingMutationVariables(queryClient, mutationKey);
51
29
  if (!matches)
52
30
  return pending.length > 0;
53
31
  return pending.some((variables) => variables !== undefined && matches(variables));
54
32
  }
55
- /**
56
- * The rejection a guarded `mutateAsync` throws for a duplicate submission: an `AbortError`, the
57
- * shape a caller already treats as "nothing happened" rather than a failure to report.
58
- */
59
33
  export function duplicateSubmissionError(what) {
60
34
  const duplicate = new Error(`${what} is already in flight.`);
61
35
  duplicate.name = 'AbortError';
62
36
  return duplicate;
63
37
  }
64
- /**
65
- * Wrap a keyed mutation's `mutate` / `mutateAsync` in the cache-consulting duplicate guard:
66
- * `mutate` drops a duplicate silently, and `mutateAsync` rejects it with
67
- * {@link duplicateSubmissionError}. `isDuplicate` must have a stable identity (declare it at module
68
- * level) so the guarded callbacks keep theirs.
69
- */
38
+ /** `isDuplicate` must have a stable identity (declare it at module level) so the guarded callbacks keep theirs. */
70
39
  export function useDuplicateGuardedMutation(mutation, isDuplicate, what) {
71
40
  const queryClient = useQueryClient();
72
41
  const { mutate: rawMutate, mutateAsync: rawMutateAsync } = mutation;
@@ -1 +1 @@
1
- {"version":3,"file":"pending-mutations.js","sourceRoot":"","sources":["../../src/react/pending-mutations.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\n// Authoritative pending for interactive mutations. A mutation registers in React Query's shared\n// MutationCache under a stable `mutationKey` the app declares (one key per action), and its target\n// travels in the mutation's variables. Pending is read from that cache, never from one mounted\n// control's `isPending`, which gives three guarantees:\n//\n// 1. an in-flight request survives its originating control unmounting;\n// 2. a remounted control still renders pending for that target;\n// 3. a second submission for the same target is refused while the first is pending.\n//\n// The app owns its keys, its variable shapes and what counts as the same target; this module owns\n// only the mechanics.\n\nimport { useCallback } from 'react';\nimport {\n useMutationState,\n useQueryClient,\n type MutateOptions,\n type MutationKey,\n type QueryClient,\n type UseMutationResult,\n} from '@tanstack/react-query';\n\n/** The variables of every pending mutation under `mutationKey` (prefix-matched), live. */\nexport function usePendingMutationVariables<V>(\n mutationKey: MutationKey,\n): ReadonlyArray<V | undefined> {\n return useMutationState({\n filters: { mutationKey, status: 'pending' },\n select: (mutation) => mutation.state.variables as V | undefined,\n });\n}\n\n/** The variables of every pending mutation under `mutationKey`, read once — for a duplicate guard. */\nexport function pendingMutationVariables<V>(\n queryClient: QueryClient,\n mutationKey: MutationKey,\n): Array<V | undefined> {\n return queryClient\n .getMutationCache()\n .findAll({ mutationKey, status: 'pending' })\n .map((mutation) => mutation.state.variables as V | undefined);\n}\n\n/**\n * The targets of every pending mutation under `mutationKey`, live — for a list whose rows each\n * ask \"is mine pending?\" through one hook call. `targetOf` names a variables' target, or returns\n * nothing for variables without one.\n */\nexport function usePendingMutationTargets<V>(\n mutationKey: MutationKey,\n targetOf: (variables: V) => string | null | undefined,\n): ReadonlySet<string> {\n const targets = new Set<string>();\n for (const variables of usePendingMutationVariables<V>(mutationKey)) {\n if (variables === undefined) continue;\n const target = targetOf(variables);\n if (target) targets.add(target);\n }\n return targets;\n}\n\n/**\n * True when a mutation under `mutationKey` is pending — any one, or with `matches`, one whose\n * variables are for the same target.\n */\nexport function hasPendingMutation<V>(\n queryClient: QueryClient,\n mutationKey: MutationKey,\n matches?: (variables: V) => boolean,\n): boolean {\n const pending = pendingMutationVariables<V>(queryClient, mutationKey);\n if (!matches) return pending.length > 0;\n return pending.some((variables) => variables !== undefined && matches(variables));\n}\n\n/**\n * The rejection a guarded `mutateAsync` throws for a duplicate submission: an `AbortError`, the\n * shape a caller already treats as \"nothing happened\" rather than a failure to report.\n */\nexport function duplicateSubmissionError(what: string): Error {\n const duplicate = new Error(`${what} is already in flight.`);\n duplicate.name = 'AbortError';\n return duplicate;\n}\n\n/**\n * Wrap a keyed mutation's `mutate` / `mutateAsync` in the cache-consulting duplicate guard:\n * `mutate` drops a duplicate silently, and `mutateAsync` rejects it with\n * {@link duplicateSubmissionError}. `isDuplicate` must have a stable identity (declare it at module\n * level) so the guarded callbacks keep theirs.\n */\nexport function useDuplicateGuardedMutation<TData, TError, TVariables, TContext>(\n mutation: UseMutationResult<TData, TError, TVariables, TContext>,\n isDuplicate: (queryClient: QueryClient, variables: TVariables) => boolean,\n what: string,\n): UseMutationResult<TData, TError, TVariables, TContext> {\n const queryClient = useQueryClient();\n const { mutate: rawMutate, mutateAsync: rawMutateAsync } = mutation;\n const mutate = useCallback(\n (variables: TVariables, options?: MutateOptions<TData, TError, TVariables, TContext>) => {\n if (isDuplicate(queryClient, variables)) return;\n rawMutate(variables, options);\n },\n [queryClient, rawMutate, isDuplicate],\n );\n const mutateAsync = useCallback(\n async (variables: TVariables, options?: MutateOptions<TData, TError, TVariables, TContext>) => {\n if (isDuplicate(queryClient, variables)) throw duplicateSubmissionError(what);\n return rawMutateAsync(variables, options);\n },\n [queryClient, rawMutateAsync, isDuplicate, what],\n );\n return { ...mutation, mutate, mutateAsync } as UseMutationResult<\n TData,\n TError,\n TVariables,\n TContext\n >;\n}\n"]}
1
+ {"version":3,"file":"pending-mutations.js","sourceRoot":"","sources":["../../src/react/pending-mutations.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback } from 'react';\nimport {\n useMutationState,\n useQueryClient,\n type MutateOptions,\n type MutationKey,\n type QueryClient,\n type UseMutationResult,\n} from '@tanstack/react-query';\n\nexport function usePendingMutationVariables<V>(\n mutationKey: MutationKey,\n): ReadonlyArray<V | undefined> {\n return useMutationState({\n filters: { mutationKey, status: 'pending' },\n select: (mutation) => mutation.state.variables as V | undefined,\n });\n}\n\nexport function pendingMutationVariables<V>(\n queryClient: QueryClient,\n mutationKey: MutationKey,\n): Array<V | undefined> {\n return queryClient\n .getMutationCache()\n .findAll({ mutationKey, status: 'pending' })\n .map((mutation) => mutation.state.variables as V | undefined);\n}\n\nexport function usePendingMutationTargets<V>(\n mutationKey: MutationKey,\n targetOf: (variables: V) => string | null | undefined,\n): ReadonlySet<string> {\n const targets = new Set<string>();\n for (const variables of usePendingMutationVariables<V>(mutationKey)) {\n if (variables === undefined) continue;\n const target = targetOf(variables);\n if (target) targets.add(target);\n }\n return targets;\n}\n\nexport function hasPendingMutation<V>(\n queryClient: QueryClient,\n mutationKey: MutationKey,\n matches?: (variables: V) => boolean,\n): boolean {\n const pending = pendingMutationVariables<V>(queryClient, mutationKey);\n if (!matches) return pending.length > 0;\n return pending.some((variables) => variables !== undefined && matches(variables));\n}\n\nexport function duplicateSubmissionError(what: string): Error {\n const duplicate = new Error(`${what} is already in flight.`);\n duplicate.name = 'AbortError';\n return duplicate;\n}\n\n/** `isDuplicate` must have a stable identity (declare it at module level) so the guarded callbacks keep theirs. */\nexport function useDuplicateGuardedMutation<TData, TError, TVariables, TContext>(\n mutation: UseMutationResult<TData, TError, TVariables, TContext>,\n isDuplicate: (queryClient: QueryClient, variables: TVariables) => boolean,\n what: string,\n): UseMutationResult<TData, TError, TVariables, TContext> {\n const queryClient = useQueryClient();\n const { mutate: rawMutate, mutateAsync: rawMutateAsync } = mutation;\n const mutate = useCallback(\n (variables: TVariables, options?: MutateOptions<TData, TError, TVariables, TContext>) => {\n if (isDuplicate(queryClient, variables)) return;\n rawMutate(variables, options);\n },\n [queryClient, rawMutate, isDuplicate],\n );\n const mutateAsync = useCallback(\n async (variables: TVariables, options?: MutateOptions<TData, TError, TVariables, TContext>) => {\n if (isDuplicate(queryClient, variables)) throw duplicateSubmissionError(what);\n return rawMutateAsync(variables, options);\n },\n [queryClient, rawMutateAsync, isDuplicate, what],\n );\n return { ...mutation, mutate, mutateAsync } as UseMutationResult<\n TData,\n TError,\n TVariables,\n TContext\n >;\n}\n"]}