@ttt-productions/query-core 0.14.20 → 0.14.22
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/dist/defaults.d.ts +0 -7
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +0 -20
- package/dist/defaults.js.map +1 -1
- package/dist/firestore/types.d.ts +0 -142
- 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 -7
- package/dist/index.js.map +1 -1
- package/dist/react/cache-helpers.d.ts.map +1 -0
- package/dist/react/cache-helpers.js.map +1 -0
- package/dist/react/defaults.d.ts +8 -0
- package/dist/react/defaults.d.ts.map +1 -0
- package/dist/react/defaults.js +21 -0
- package/dist/react/defaults.js.map +1 -0
- package/dist/{domain-events → react/domain-events}/cache-invalidation.d.ts +8 -8
- package/dist/react/domain-events/cache-invalidation.d.ts.map +1 -0
- package/dist/{domain-events → react/domain-events}/cache-invalidation.js +28 -17
- package/dist/react/domain-events/cache-invalidation.js.map +1 -0
- package/dist/{domain-events → react/domain-events}/create-domain-event-invalidator.d.ts +4 -4
- 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 -1
- package/dist/react/domain-events/create-domain-event-invalidator.js.map +1 -0
- package/dist/react/firestore/context.d.ts.map +1 -1
- package/dist/react/firestore/context.js +1 -1
- package/dist/react/firestore/context.js.map +1 -1
- package/dist/react/firestore/doc-subscription-registry.d.ts.map +1 -1
- package/dist/react/firestore/doc-subscription-registry.js +5 -1
- package/dist/react/firestore/doc-subscription-registry.js.map +1 -1
- package/dist/{firestore → react/firestore}/infinite-helpers.d.ts +2 -1
- package/dist/react/firestore/infinite-helpers.d.ts.map +1 -0
- package/dist/react/firestore/infinite-helpers.js.map +1 -0
- package/dist/react/firestore/types.d.ts +144 -0
- package/dist/react/firestore/types.d.ts.map +1 -0
- package/dist/react/firestore/types.js +7 -0
- package/dist/react/firestore/types.js.map +1 -0
- package/dist/react/firestore/use-firestore-collection.d.ts +2 -1
- package/dist/react/firestore/use-firestore-collection.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-collection.js +22 -9
- package/dist/react/firestore/use-firestore-collection.js.map +1 -1
- package/dist/react/firestore/use-firestore-count.d.ts +1 -1
- package/dist/react/firestore/use-firestore-count.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-count.js.map +1 -1
- package/dist/react/firestore/use-firestore-doc.d.ts +2 -1
- package/dist/react/firestore/use-firestore-doc.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-doc.js +18 -2
- package/dist/react/firestore/use-firestore-doc.js.map +1 -1
- package/dist/react/firestore/use-firestore-infinite.d.ts +1 -1
- package/dist/react/firestore/use-firestore-infinite.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-infinite.js.map +1 -1
- package/dist/react/firestore/use-firestore-live-infinite.d.ts +2 -1
- package/dist/react/firestore/use-firestore-live-infinite.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-live-infinite.js +9 -1
- 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 -1
- package/dist/react/firestore/use-firestore-paginated.d.ts.map +1 -1
- package/dist/react/firestore/use-firestore-paginated.js +6 -3
- package/dist/react/firestore/use-firestore-paginated.js.map +1 -1
- package/dist/react/firestore/useBatchFirestoreDocs.d.ts.map +1 -1
- package/dist/react/firestore/useBatchFirestoreDocs.js +7 -3
- 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 +11 -3
- package/dist/react/index.js.map +1 -1
- package/dist/react/pending-mutations.d.ts +1 -1
- package/dist/react/pending-mutations.d.ts.map +1 -1
- package/dist/react/pending-mutations.js +6 -6
- package/dist/react/pending-mutations.js.map +1 -1
- package/dist/react/provider.d.ts +1 -1
- package/dist/react/provider.d.ts.map +1 -1
- package/dist/react/provider.js +1 -1
- package/dist/react/provider.js.map +1 -1
- package/dist/react/query-client.d.ts.map +1 -0
- package/dist/react/query-client.js.map +1 -0
- package/dist/react/search/types.d.ts +28 -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 -1
- package/dist/react/search/use-firestore-search.d.ts.map +1 -1
- package/dist/react/search/use-firestore-search.js.map +1 -1
- package/dist/react/use-read-retry-state.d.ts.map +1 -1
- package/dist/react/use-read-retry-state.js +1 -1
- package/dist/react/use-read-retry-state.js.map +1 -1
- package/dist/search/types.d.ts +0 -26
- 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.map +0 -1
- package/dist/cache-helpers.js.map +0 -1
- package/dist/domain-events/cache-invalidation.d.ts.map +0 -1
- package/dist/domain-events/cache-invalidation.js.map +0 -1
- 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.map +0 -1
- 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
- /package/dist/{cache-helpers.d.ts → react/cache-helpers.d.ts} +0 -0
- /package/dist/{cache-helpers.js → react/cache-helpers.js} +0 -0
- /package/dist/{firestore → react/firestore}/infinite-helpers.js +0 -0
- /package/dist/{query-client.d.ts → react/query-client.d.ts} +0 -0
- /package/dist/{query-client.js → react/query-client.js} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type DocumentData } from 'firebase/firestore';
|
|
2
|
-
import type {
|
|
2
|
+
import type { FirestoreSourceState } from '../../firestore/types.js';
|
|
3
|
+
import type { FirestoreLiveInfiniteOptions } from './types.js';
|
|
3
4
|
export interface FirestoreLiveInfiniteResult<T> {
|
|
4
5
|
/** The current subscription identity's rows — live set plus loaded older pages, ordered per `sort`. */
|
|
5
6
|
items: T[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-firestore-live-infinite.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-live-infinite.ts"],"names":[],"mappings":"AAIA,OAAO,EASL,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"use-firestore-live-infinite.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-live-infinite.ts"],"names":[],"mappings":"AAIA,OAAO,EASL,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAI/D,MAAM,WAAW,2BAA2B,CAAC,CAAC;IAC5C,uGAAuG;IACvG,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,gGAAgG;IAChG,gBAAgB,EAAE,OAAO,CAAC;IAC1B,mGAAmG;IACnG,WAAW,EAAE,oBAAoB,CAAC;IAClC,kGAAkG;IAClG,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,iIAAiI;IACjI,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,wGAAwG;IACxG,QAAQ,EAAE,OAAO,CAAC;IAClB,wEAAwE;IACxE,eAAe,EAAE,OAAO,CAAC;IACzB,2EAA2E;IAC3E,UAAU,EAAE,KAAK,GAAG,IAAI,CAAC;IACzB,sFAAsF;IACtF,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AA2BD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,GAAG,YAAY,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,EAC1E,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,WAAgB,EAChB,QAAQ,EAAE,iBAAsB,EAChC,OAAc,EACd,MAAM,EACN,IAAY,GACb,EAAE,4BAA4B,CAAC,CAAC,CAAC,GAAG,2BAA2B,CAAC,CAAC,CAAC,CAuQlE"}
|
|
@@ -232,7 +232,15 @@ export function useFirestoreLiveInfinite({ collectionPath, queryKey, orderByFiel
|
|
|
232
232
|
else
|
|
233
233
|
void refetchOlder();
|
|
234
234
|
}
|
|
235
|
-
}, [
|
|
235
|
+
}, [
|
|
236
|
+
listenerFailed,
|
|
237
|
+
identity,
|
|
238
|
+
olderError,
|
|
239
|
+
olderFetching,
|
|
240
|
+
olderPages,
|
|
241
|
+
fetchNextPage,
|
|
242
|
+
refetchOlder,
|
|
243
|
+
]);
|
|
236
244
|
const olderEntries = React.useMemo(() => (olderPages ?? []).flatMap((p) => p.entries), [olderPages]);
|
|
237
245
|
const items = React.useMemo(() => {
|
|
238
246
|
if (!enabled)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-firestore-live-infinite.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-live-infinite.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useInfiniteQuery, useQueryClient } from '@tanstack/react-query';\nimport {\n collection,\n query,\n orderBy,\n limit,\n startAfter,\n endAt,\n getDocs,\n onSnapshot,\n type DocumentData,\n type QueryDocumentSnapshot,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport { RESUBSCRIBE_DELAYS_MS, isPermissionDeniedError } from './resubscribe.js';\nimport type { FirestoreLiveInfiniteOptions, FirestoreSourceState } from '../../firestore/types.js';\n\nconst MAX_PAGE_SIZE = 100;\n\nexport interface FirestoreLiveInfiniteResult<T> {\n /** The current subscription identity's rows — live set plus loaded older pages, ordered per `sort`. */\n items: T[];\n /** True until the current identity's first snapshot arrives, unless its listener has failed. */\n isInitialLoading: boolean;\n /** Liveness of the current identity's listener; `connecting` from that identity's first render. */\n sourceState: FirestoreSourceState;\n /** The listener's failure for the current identity; `null` while connecting, live, or offline. */\n error: Error | null;\n /** Load the next older page; nothing older is read until this is called. Never rejects — a failed read lands in `olderError`. */\n fetchOlder: () => Promise<void>;\n /** Whether older rows exist that are not loaded yet (a one-doc look-ahead, never a full-page guess). */\n hasOlder: boolean;\n /** Whether an older page is being read (including a `retry` of one). */\n isFetchingOlder: boolean;\n /** The last older-page read failed; `null` once an older read succeeds. */\n olderError: Error | null;\n /** Re-run what failed: resubscribe a failed listener, re-read a failed older page. */\n retry: () => void;\n}\n\ntype Entry<T> = { docId: string; item: T };\ntype Snap = QueryDocumentSnapshot<DocumentData>;\n\ninterface WindowState<T> {\n identity: string;\n ready: boolean;\n entries: Entry<T>[];\n anchor: Snap | null;\n olderExists: boolean;\n sourceState: FirestoreSourceState;\n error: Error | null;\n}\n\nfunction freshWindow<T>(identity: string): WindowState<T> {\n return {\n identity,\n ready: false,\n entries: [],\n anchor: null,\n olderExists: false,\n sourceState: 'connecting',\n error: null,\n };\n}\n\n/**\n * A live list (newest rows realtime) with older rows paged in on request, merged into\n * one ordered list.\n *\n * Until the first server-confirmed snapshot that has rows, the listener reads the newest\n * `pageSize` rows plus one look-ahead row. That snapshot fixes an anchor — the oldest row\n * of the window — and from then on the listener covers anchor → newest with no limit, so\n * it grows only with rows that arrive while the list is open, and every older page starts\n * after the anchor. Nothing can fall between the live set and the older pages, however\n * many rows arrive. Older pages carry the same one-row look-ahead, so `hasOlder` is false\n * exactly when nothing older exists. No older page is read until `fetchOlder` asks for one.\n *\n * Both reads arrive newest first and meet at the anchor, so the merged list is their\n * concatenation: a snapshot never re-sorts the rows already loaded.\n *\n * Everything read is tagged to its subscription identity (path, key, ordering,\n * constraints, page size, enabled): a new identity renders empty and connecting in its\n * first render, and a retired identity's late callback or older page never lands.\n *\n * A `permission-denied` listener error resubscribes on the `RESUBSCRIBE_DELAYS_MS` ladder\n * before surfacing; any other listener error surfaces at once. Rows already shown are\n * kept beside the error — except beside a surfaced `permission-denied`: the reader lost\n * access, so the live rows, the anchor, and the loaded older pages are all dropped, and a\n * retry reads the list afresh. `db` comes from `<FirestoreProvider>`.\n */\nexport function useFirestoreLiveInfinite<T = DocumentData & { id: string }>({\n collectionPath,\n queryKey,\n orderByField,\n constraints = [],\n pageSize: requestedPageSize = 20,\n enabled = true,\n select,\n sort = 'asc',\n}: FirestoreLiveInfiniteOptions<T>): FirestoreLiveInfiniteResult<T> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n const pageSize = Math.min(requestedPageSize, MAX_PAGE_SIZE);\n\n // Latest-callback ref: an inline select must not re-subscribe the listener on every render.\n const selectRef = React.useRef(select);\n selectRef.current = select;\n\n const toEntry = React.useCallback((snap: Snap): Entry<T> => {\n const dataWithId: DocumentData & { id: string } = { id: snap.id, ...snap.data() };\n const item = selectRef.current ? selectRef.current(dataWithId) : (dataWithId as unknown as T);\n return { docId: snap.id, item };\n }, []);\n\n // Constraint objects are fresh every render; their serialized form is the identity.\n const queryKeyMemo = JSON.stringify(queryKey);\n const constraintsMemo = JSON.stringify(constraints);\n const identity = JSON.stringify([\n collectionPath,\n queryKeyMemo,\n orderByField,\n constraintsMemo,\n pageSize,\n enabled,\n ]);\n\n const [windowState, setWindowState] = React.useState<WindowState<T>>(() => freshWindow(identity));\n const windowRef = React.useRef(windowState);\n windowRef.current = windowState;\n const [retryNonce, setRetryNonce] = React.useState(0);\n\n const current: WindowState<T> =\n windowState.identity === identity ? windowState : freshWindow<T>(identity);\n\n React.useEffect(() => {\n if (!enabled) {\n // Retire the enabled identity's rows, so re-enabling starts afresh rather than resuming.\n setWindowState(freshWindow<T>(identity));\n return;\n }\n\n // A retry of the same identity resumes from its anchor and keeps its rows.\n const prior = windowRef.current;\n const resumed = prior.identity === identity;\n setWindowState((prev) =>\n prev.identity === identity\n ? { ...prev, error: null, sourceState: 'connecting' }\n : freshWindow<T>(identity),\n );\n\n let disposed = false;\n let unsubscribe: (() => void) | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let attempt = 0;\n let currentAnchor: Snap | null = resumed ? prior.anchor : null;\n\n const update = (fn: (prev: WindowState<T>) => WindowState<T>) => {\n if (disposed) return;\n setWindowState((prev) => (prev.identity === identity ? fn(prev) : prev));\n };\n\n const listen = (anchor: Snap | null) => {\n if (disposed) return;\n currentAnchor = anchor;\n const colRef = collection(db, collectionPath);\n const q = anchor\n ? query(colRef, ...constraints, orderBy(orderByField, 'desc'), endAt(anchor))\n : query(colRef, ...constraints, orderBy(orderByField, 'desc'), limit(pageSize + 1));\n let serverConfirmed = false;\n\n unsubscribe = onSnapshot(\n q,\n { includeMetadataChanges: true },\n (snap) => {\n if (disposed) return;\n attempt = 0;\n const docs = snap.docs as Snap[];\n const fromCache = snap.metadata?.fromCache ?? false;\n const nextSource = (prev: FirestoreSourceState): FirestoreSourceState => {\n if (!fromCache) return 'live';\n if (!serverConfirmed) return prev === 'error' ? 'connecting' : prev;\n return 'offline';\n };\n if (!fromCache) serverConfirmed = true;\n\n if (anchor) {\n update((prev) => ({\n ...prev,\n ready: true,\n entries: docs.map(toEntry),\n sourceState: nextSource(prev.sourceState),\n error: null,\n }));\n return;\n }\n\n const windowDocs = docs.slice(0, pageSize);\n if (fromCache || windowDocs.length === 0) {\n update((prev) => ({\n ...prev,\n ready: true,\n entries: windowDocs.map(toEntry),\n sourceState: nextSource(prev.sourceState),\n error: null,\n }));\n return;\n }\n\n const newAnchor = windowDocs[windowDocs.length - 1];\n update((prev) => ({\n ...prev,\n ready: true,\n entries: windowDocs.map(toEntry),\n anchor: newAnchor,\n olderExists: docs.length > pageSize,\n sourceState: 'live',\n error: null,\n }));\n unsubscribe?.();\n unsubscribe = null;\n listen(newAnchor);\n },\n (error) => {\n if (disposed) return;\n unsubscribe?.();\n unsubscribe = null;\n if (isPermissionDeniedError(error) && attempt < RESUBSCRIBE_DELAYS_MS.length) {\n const delay = RESUBSCRIBE_DELAYS_MS[attempt];\n attempt += 1;\n update((prev) => ({ ...prev, sourceState: 'connecting' }));\n retryTimer = setTimeout(() => listen(currentAnchor), delay);\n return;\n }\n console.error('[useFirestoreLiveInfinite] Subscription error:', error);\n if (isPermissionDeniedError(error)) {\n currentAnchor = null;\n queryClient.removeQueries({ queryKey: [...queryKey, 'older', identity] });\n update(() => ({ ...freshWindow<T>(identity), sourceState: 'error', error }));\n return;\n }\n update((prev) => ({ ...prev, sourceState: 'error', error }));\n },\n );\n };\n\n listen(currentAnchor);\n\n return () => {\n disposed = true;\n if (retryTimer) clearTimeout(retryTimer);\n unsubscribe?.();\n };\n // `identity` carries collectionPath, queryKey, orderByField, constraints, pageSize, and\n // enabled; constraints are read from the render that produced it.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [db, identity, retryNonce, toEntry, queryClient]);\n\n const anchor = enabled ? current.anchor : null;\n const olderEnabled = anchor !== null && current.olderExists;\n\n // Never fetched on its own: `fetchOlder` reads the first page (`refetch`) and each one after it.\n const older = useInfiniteQuery({\n queryKey: [...queryKey, 'older', identity, anchor?.id ?? null],\n enabled: false,\n initialPageParam: undefined as Snap | undefined,\n queryFn: async ({ pageParam }) => {\n const cursor = pageParam ?? anchor;\n if (!cursor) {\n return { entries: [] as Entry<T>[], nextCursor: undefined as Snap | undefined };\n }\n const colRef = collection(db, collectionPath);\n const q = query(\n colRef,\n ...constraints,\n orderBy(orderByField, 'desc'),\n startAfter(cursor),\n limit(pageSize + 1),\n );\n const snap = await getDocs(q);\n const docs = snap.docs as Snap[];\n const page = docs.slice(0, pageSize);\n return {\n entries: page.map(toEntry),\n nextCursor: docs.length > pageSize ? page[page.length - 1] : undefined,\n };\n },\n getNextPageParam: (last) => last.nextCursor,\n });\n\n const {\n data: olderData,\n isFetching: olderFetching,\n isError: olderIsError,\n error: olderErr,\n hasNextPage: olderHasNext,\n fetchNextPage,\n refetch: refetchOlder,\n } = older;\n\n const olderPages = olderEnabled ? olderData?.pages : undefined;\n const hasOlder = olderEnabled && (olderPages ? Boolean(olderHasNext) : true);\n const olderError = olderEnabled && olderIsError ? olderErr : null;\n\n const fetchOlder = React.useCallback(async () => {\n if (!olderEnabled || olderFetching) return;\n if (!olderPages) {\n await refetchOlder();\n return;\n }\n if (olderHasNext) await fetchNextPage();\n }, [olderEnabled, olderFetching, olderPages, olderHasNext, refetchOlder, fetchNextPage]);\n\n const listenerFailed = enabled && current.error !== null;\n const retry = React.useCallback(() => {\n if (listenerFailed) {\n setWindowState((prev) =>\n prev.identity === identity ? { ...prev, error: null, sourceState: 'connecting' } : prev,\n );\n setRetryNonce((n) => n + 1);\n }\n if (olderError && !olderFetching) {\n if (olderPages) void fetchNextPage();\n else void refetchOlder();\n }\n }, [listenerFailed, identity, olderError, olderFetching, olderPages, fetchNextPage, refetchOlder]);\n\n const olderEntries = React.useMemo(() => (olderPages ?? []).flatMap((p) => p.entries), [olderPages]);\n\n const items = React.useMemo(() => {\n if (!enabled) return [];\n // A row edited so its ordering value moved into the live range is in both reads; its live\n // copy is the fresh one.\n const liveIds = new Set(current.entries.map((entry) => entry.docId));\n const newestFirst = [\n ...current.entries.map((entry) => entry.item),\n ...olderEntries.filter((entry) => !liveIds.has(entry.docId)).map((entry) => entry.item),\n ];\n return sort === 'desc' ? newestFirst : newestFirst.reverse();\n }, [enabled, olderEntries, current.entries, sort]);\n\n return {\n items,\n isInitialLoading: enabled ? !current.ready && current.error === null : false,\n sourceState: current.sourceState,\n error: enabled ? current.error : null,\n fetchOlder,\n hasOlder,\n isFetchingOlder: olderEnabled && olderFetching,\n olderError,\n retry,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"use-firestore-live-infinite.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-live-infinite.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useInfiniteQuery, useQueryClient } from '@tanstack/react-query';\nimport {\n collection,\n query,\n orderBy,\n limit,\n startAfter,\n endAt,\n getDocs,\n onSnapshot,\n type DocumentData,\n type QueryDocumentSnapshot,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport { RESUBSCRIBE_DELAYS_MS, isPermissionDeniedError } from './resubscribe.js';\nimport type { FirestoreSourceState } from '../../firestore/types.js';\nimport type { FirestoreLiveInfiniteOptions } from './types.js';\n\nconst MAX_PAGE_SIZE = 100;\n\nexport interface FirestoreLiveInfiniteResult<T> {\n /** The current subscription identity's rows — live set plus loaded older pages, ordered per `sort`. */\n items: T[];\n /** True until the current identity's first snapshot arrives, unless its listener has failed. */\n isInitialLoading: boolean;\n /** Liveness of the current identity's listener; `connecting` from that identity's first render. */\n sourceState: FirestoreSourceState;\n /** The listener's failure for the current identity; `null` while connecting, live, or offline. */\n error: Error | null;\n /** Load the next older page; nothing older is read until this is called. Never rejects — a failed read lands in `olderError`. */\n fetchOlder: () => Promise<void>;\n /** Whether older rows exist that are not loaded yet (a one-doc look-ahead, never a full-page guess). */\n hasOlder: boolean;\n /** Whether an older page is being read (including a `retry` of one). */\n isFetchingOlder: boolean;\n /** The last older-page read failed; `null` once an older read succeeds. */\n olderError: Error | null;\n /** Re-run what failed: resubscribe a failed listener, re-read a failed older page. */\n retry: () => void;\n}\n\ntype Entry<T> = { docId: string; item: T };\ntype Snap = QueryDocumentSnapshot<DocumentData>;\n\ninterface WindowState<T> {\n identity: string;\n ready: boolean;\n entries: Entry<T>[];\n anchor: Snap | null;\n olderExists: boolean;\n sourceState: FirestoreSourceState;\n error: Error | null;\n}\n\nfunction freshWindow<T>(identity: string): WindowState<T> {\n return {\n identity,\n ready: false,\n entries: [],\n anchor: null,\n olderExists: false,\n sourceState: 'connecting',\n error: null,\n };\n}\n\n/**\n * A live list (newest rows realtime) with older rows paged in on request, merged into\n * one ordered list.\n *\n * Until the first server-confirmed snapshot that has rows, the listener reads the newest\n * `pageSize` rows plus one look-ahead row. That snapshot fixes an anchor — the oldest row\n * of the window — and from then on the listener covers anchor → newest with no limit, so\n * it grows only with rows that arrive while the list is open, and every older page starts\n * after the anchor. Nothing can fall between the live set and the older pages, however\n * many rows arrive. Older pages carry the same one-row look-ahead, so `hasOlder` is false\n * exactly when nothing older exists. No older page is read until `fetchOlder` asks for one.\n *\n * Both reads arrive newest first and meet at the anchor, so the merged list is their\n * concatenation: a snapshot never re-sorts the rows already loaded.\n *\n * Everything read is tagged to its subscription identity (path, key, ordering,\n * constraints, page size, enabled): a new identity renders empty and connecting in its\n * first render, and a retired identity's late callback or older page never lands.\n *\n * A `permission-denied` listener error resubscribes on the `RESUBSCRIBE_DELAYS_MS` ladder\n * before surfacing; any other listener error surfaces at once. Rows already shown are\n * kept beside the error — except beside a surfaced `permission-denied`: the reader lost\n * access, so the live rows, the anchor, and the loaded older pages are all dropped, and a\n * retry reads the list afresh. `db` comes from `<FirestoreProvider>`.\n */\nexport function useFirestoreLiveInfinite<T = DocumentData & { id: string }>({\n collectionPath,\n queryKey,\n orderByField,\n constraints = [],\n pageSize: requestedPageSize = 20,\n enabled = true,\n select,\n sort = 'asc',\n}: FirestoreLiveInfiniteOptions<T>): FirestoreLiveInfiniteResult<T> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n const pageSize = Math.min(requestedPageSize, MAX_PAGE_SIZE);\n\n // Latest-callback ref: an inline select must not re-subscribe the listener on every render.\n const selectRef = React.useRef(select);\n selectRef.current = select;\n\n const toEntry = React.useCallback((snap: Snap): Entry<T> => {\n const dataWithId: DocumentData & { id: string } = { id: snap.id, ...snap.data() };\n const item = selectRef.current ? selectRef.current(dataWithId) : (dataWithId as unknown as T);\n return { docId: snap.id, item };\n }, []);\n\n // Constraint objects are fresh every render; their serialized form is the identity.\n const queryKeyMemo = JSON.stringify(queryKey);\n const constraintsMemo = JSON.stringify(constraints);\n const identity = JSON.stringify([\n collectionPath,\n queryKeyMemo,\n orderByField,\n constraintsMemo,\n pageSize,\n enabled,\n ]);\n\n const [windowState, setWindowState] = React.useState<WindowState<T>>(() => freshWindow(identity));\n const windowRef = React.useRef(windowState);\n windowRef.current = windowState;\n const [retryNonce, setRetryNonce] = React.useState(0);\n\n const current: WindowState<T> =\n windowState.identity === identity ? windowState : freshWindow<T>(identity);\n\n React.useEffect(() => {\n if (!enabled) {\n // Retire the enabled identity's rows, so re-enabling starts afresh rather than resuming.\n setWindowState(freshWindow<T>(identity));\n return;\n }\n\n // A retry of the same identity resumes from its anchor and keeps its rows.\n const prior = windowRef.current;\n const resumed = prior.identity === identity;\n setWindowState((prev) =>\n prev.identity === identity\n ? { ...prev, error: null, sourceState: 'connecting' }\n : freshWindow<T>(identity),\n );\n\n let disposed = false;\n let unsubscribe: (() => void) | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let attempt = 0;\n let currentAnchor: Snap | null = resumed ? prior.anchor : null;\n\n const update = (fn: (prev: WindowState<T>) => WindowState<T>) => {\n if (disposed) return;\n setWindowState((prev) => (prev.identity === identity ? fn(prev) : prev));\n };\n\n const listen = (anchor: Snap | null) => {\n if (disposed) return;\n currentAnchor = anchor;\n const colRef = collection(db, collectionPath);\n const q = anchor\n ? query(colRef, ...constraints, orderBy(orderByField, 'desc'), endAt(anchor))\n : query(colRef, ...constraints, orderBy(orderByField, 'desc'), limit(pageSize + 1));\n let serverConfirmed = false;\n\n unsubscribe = onSnapshot(\n q,\n { includeMetadataChanges: true },\n (snap) => {\n if (disposed) return;\n attempt = 0;\n const docs = snap.docs as Snap[];\n const fromCache = snap.metadata?.fromCache ?? false;\n const nextSource = (prev: FirestoreSourceState): FirestoreSourceState => {\n if (!fromCache) return 'live';\n if (!serverConfirmed) return prev === 'error' ? 'connecting' : prev;\n return 'offline';\n };\n if (!fromCache) serverConfirmed = true;\n\n if (anchor) {\n update((prev) => ({\n ...prev,\n ready: true,\n entries: docs.map(toEntry),\n sourceState: nextSource(prev.sourceState),\n error: null,\n }));\n return;\n }\n\n const windowDocs = docs.slice(0, pageSize);\n if (fromCache || windowDocs.length === 0) {\n update((prev) => ({\n ...prev,\n ready: true,\n entries: windowDocs.map(toEntry),\n sourceState: nextSource(prev.sourceState),\n error: null,\n }));\n return;\n }\n\n const newAnchor = windowDocs[windowDocs.length - 1];\n update((prev) => ({\n ...prev,\n ready: true,\n entries: windowDocs.map(toEntry),\n anchor: newAnchor,\n olderExists: docs.length > pageSize,\n sourceState: 'live',\n error: null,\n }));\n unsubscribe?.();\n unsubscribe = null;\n listen(newAnchor);\n },\n (error) => {\n if (disposed) return;\n unsubscribe?.();\n unsubscribe = null;\n if (isPermissionDeniedError(error) && attempt < RESUBSCRIBE_DELAYS_MS.length) {\n const delay = RESUBSCRIBE_DELAYS_MS[attempt];\n attempt += 1;\n update((prev) => ({ ...prev, sourceState: 'connecting' }));\n retryTimer = setTimeout(() => listen(currentAnchor), delay);\n return;\n }\n console.error('[useFirestoreLiveInfinite] Subscription error:', error);\n if (isPermissionDeniedError(error)) {\n currentAnchor = null;\n queryClient.removeQueries({ queryKey: [...queryKey, 'older', identity] });\n update(() => ({ ...freshWindow<T>(identity), sourceState: 'error', error }));\n return;\n }\n update((prev) => ({ ...prev, sourceState: 'error', error }));\n },\n );\n };\n\n listen(currentAnchor);\n\n return () => {\n disposed = true;\n if (retryTimer) clearTimeout(retryTimer);\n unsubscribe?.();\n };\n // `identity` carries collectionPath, queryKey, orderByField, constraints, pageSize, and\n // enabled; constraints are read from the render that produced it.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [db, identity, retryNonce, toEntry, queryClient]);\n\n const anchor = enabled ? current.anchor : null;\n const olderEnabled = anchor !== null && current.olderExists;\n\n // Never fetched on its own: `fetchOlder` reads the first page (`refetch`) and each one after it.\n const older = useInfiniteQuery({\n queryKey: [...queryKey, 'older', identity, anchor?.id ?? null],\n enabled: false,\n initialPageParam: undefined as Snap | undefined,\n queryFn: async ({ pageParam }) => {\n const cursor = pageParam ?? anchor;\n if (!cursor) {\n return { entries: [] as Entry<T>[], nextCursor: undefined as Snap | undefined };\n }\n const colRef = collection(db, collectionPath);\n const q = query(\n colRef,\n ...constraints,\n orderBy(orderByField, 'desc'),\n startAfter(cursor),\n limit(pageSize + 1),\n );\n const snap = await getDocs(q);\n const docs = snap.docs as Snap[];\n const page = docs.slice(0, pageSize);\n return {\n entries: page.map(toEntry),\n nextCursor: docs.length > pageSize ? page[page.length - 1] : undefined,\n };\n },\n getNextPageParam: (last) => last.nextCursor,\n });\n\n const {\n data: olderData,\n isFetching: olderFetching,\n isError: olderIsError,\n error: olderErr,\n hasNextPage: olderHasNext,\n fetchNextPage,\n refetch: refetchOlder,\n } = older;\n\n const olderPages = olderEnabled ? olderData?.pages : undefined;\n const hasOlder = olderEnabled && (olderPages ? Boolean(olderHasNext) : true);\n const olderError = olderEnabled && olderIsError ? olderErr : null;\n\n const fetchOlder = React.useCallback(async () => {\n if (!olderEnabled || olderFetching) return;\n if (!olderPages) {\n await refetchOlder();\n return;\n }\n if (olderHasNext) await fetchNextPage();\n }, [olderEnabled, olderFetching, olderPages, olderHasNext, refetchOlder, fetchNextPage]);\n\n const listenerFailed = enabled && current.error !== null;\n const retry = React.useCallback(() => {\n if (listenerFailed) {\n setWindowState((prev) =>\n prev.identity === identity ? { ...prev, error: null, sourceState: 'connecting' } : prev,\n );\n setRetryNonce((n) => n + 1);\n }\n if (olderError && !olderFetching) {\n if (olderPages) void fetchNextPage();\n else void refetchOlder();\n }\n }, [\n listenerFailed,\n identity,\n olderError,\n olderFetching,\n olderPages,\n fetchNextPage,\n refetchOlder,\n ]);\n\n const olderEntries = React.useMemo(\n () => (olderPages ?? []).flatMap((p) => p.entries),\n [olderPages],\n );\n\n const items = React.useMemo(() => {\n if (!enabled) return [];\n // A row edited so its ordering value moved into the live range is in both reads; its live\n // copy is the fresh one.\n const liveIds = new Set(current.entries.map((entry) => entry.docId));\n const newestFirst = [\n ...current.entries.map((entry) => entry.item),\n ...olderEntries.filter((entry) => !liveIds.has(entry.docId)).map((entry) => entry.item),\n ];\n return sort === 'desc' ? newestFirst : newestFirst.reverse();\n }, [enabled, olderEntries, current.entries, sort]);\n\n return {\n items,\n isInitialLoading: enabled ? !current.ready && current.error === null : false,\n sourceState: current.sourceState,\n error: enabled ? current.error : null,\n fetchOlder,\n hasOlder,\n isFetchingOlder: olderEnabled && olderFetching,\n olderError,\n retry,\n };\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type QueryKey, type UseMutationResult } from '@tanstack/react-query';
|
|
2
2
|
import { type DocumentData } from 'firebase/firestore';
|
|
3
|
-
import type { FirestoreMutationOptions, FirestoreBatchOptions
|
|
3
|
+
import type { FirestoreMutationOptions, FirestoreBatchOptions } from '../../firestore/types.js';
|
|
4
|
+
import type { MutationOperation } from './types.js';
|
|
4
5
|
interface SetResult<T> {
|
|
5
6
|
id: string;
|
|
6
7
|
data: T;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-firestore-mutations.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-mutations.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"use-firestore-mutations.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-mutations.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,QAAQ,EACb,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAML,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAChG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAIpD,UAAU,SAAS,CAAC,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,CAAC,CAAC;CACT;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,YAAY,EAAE,EACtD,cAAmB,GACpB,GAAE,IAAI,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAAE,gBAAgB,CAAM,GAAG,iBAAiB,CAC7E,SAAS,CAAC,CAAC,CAAC,EACZ,KAAK,EACL;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAC9C,CAwBA;AAED,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,YAAY,EAAE,EACzD,cAAmB,EACnB,UAAU,GACX,GAAE,wBAAwB,CAAC,CAAC,CAAM,GAAG,iBAAiB,CACrD,OAAO,CAAC,CAAC,CAAC,EACV,KAAK,EACL;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;CAAE,EACrC;IAAE,YAAY,EAAE,CAAC,GAAG,SAAS,CAAA;CAAE,GAAG,SAAS,CAC5C,CAoCA;AAED,wBAAgB,kBAAkB,CAAC,CAAC,GAAG,OAAO,EAAE,EAC9C,cAAmB,EACnB,UAAU,GACX,GAAE;IACD,cAAc,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;IACrC,UAAU,CAAC,EAAE;QACX,QAAQ,EAAE,QAAQ,CAAC;QACnB,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC,CAAC;KACzD,CAAC;CACE,GAAG,iBAAiB,CACxB,MAAM,EACN,KAAK,EACL;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,EACnB;IAAE,YAAY,EAAE,CAAC,GAAG,SAAS,CAAA;CAAE,GAAG,SAAS,CAC5C,CAkCA;AAED,UAAU,WAAW;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,cAAmB,EACnB,SAA8B,GAC/B,GAAE,qBAA0B,GAAG,iBAAiB,CAC/C,WAAW,EACX,KAAK,EACL;IAAE,UAAU,EAAE,iBAAiB,EAAE,CAAA;CAAE,CACpC,CAmDA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
2
|
+
import { useMutation, useQueryClient, } from '@tanstack/react-query';
|
|
3
3
|
import { doc, setDoc, updateDoc, deleteDoc, writeBatch, } from 'firebase/firestore';
|
|
4
4
|
import { useFirestoreDb } from './context.js';
|
|
5
5
|
const DEFAULT_BATCH_SIZE = 450;
|
|
@@ -23,7 +23,7 @@ export function useFirestoreUpdate({ invalidateKeys = [], optimistic, } = {}) {
|
|
|
23
23
|
const db = useFirestoreDb();
|
|
24
24
|
const queryClient = useQueryClient();
|
|
25
25
|
return useMutation({
|
|
26
|
-
mutationFn: async ({ docPath, data
|
|
26
|
+
mutationFn: async ({ docPath, data }) => {
|
|
27
27
|
const docRef = doc(db, docPath);
|
|
28
28
|
await updateDoc(docRef, data);
|
|
29
29
|
return data;
|
|
@@ -87,7 +87,7 @@ export function useFirestoreBatch({ invalidateKeys = [], batchSize = DEFAULT_BAT
|
|
|
87
87
|
const db = useFirestoreDb();
|
|
88
88
|
const queryClient = useQueryClient();
|
|
89
89
|
return useMutation({
|
|
90
|
-
mutationFn: async ({ operations
|
|
90
|
+
mutationFn: async ({ operations }) => {
|
|
91
91
|
const chunks = [];
|
|
92
92
|
for (let i = 0; i < operations.length; i += batchSize) {
|
|
93
93
|
chunks.push(operations.slice(i, i + batchSize));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-firestore-mutations.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-mutations.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useMutation, useQueryClient, type QueryKey, type UseMutationResult } from '@tanstack/react-query';\nimport {\n doc,\n setDoc,\n updateDoc,\n deleteDoc,\n writeBatch,\n type DocumentData,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport type { FirestoreMutationOptions, FirestoreBatchOptions, MutationOperation } from '../../firestore/types.js';\n\nconst DEFAULT_BATCH_SIZE = 450;\n\ninterface SetResult<T> {\n id: string;\n data: T;\n}\n\nexport function useFirestoreSet<T extends DocumentData>({\n invalidateKeys = [],\n}: Pick<FirestoreMutationOptions<T>, 'invalidateKeys'> = {}): UseMutationResult<\n SetResult<T>,\n Error,\n { docPath: string; data: T; merge?: boolean }\n> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({\n docPath,\n data,\n merge = false,\n }: {\n docPath: string;\n data: T;\n merge?: boolean;\n }) => {\n const docRef = doc(db, docPath);\n await setDoc(docRef, data, { merge });\n return { id: docRef.id, data };\n },\n onSuccess: () => {\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: key });\n });\n },\n });\n}\n\nexport function useFirestoreUpdate<T extends DocumentData>({\n invalidateKeys = [],\n optimistic,\n}: FirestoreMutationOptions<T> = {}): UseMutationResult<\n Partial<T>,\n Error,\n { docPath: string; data: Partial<T> },\n { previousData: T | undefined } | undefined\n> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({\n docPath,\n data,\n }: {\n docPath: string;\n data: Partial<T>;\n }) => {\n const docRef = doc(db, docPath);\n await updateDoc(docRef, data as DocumentData);\n return data;\n },\n onMutate: async ({ data }) => {\n if (!optimistic) return undefined;\n\n await queryClient.cancelQueries({ queryKey: optimistic.queryKey });\n const previousData = queryClient.getQueryData<T>(optimistic.queryKey);\n\n queryClient.setQueryData<T>(optimistic.queryKey, (old) =>\n optimistic.updater(old, data as Partial<T>)\n );\n\n return { previousData };\n },\n onError: (_error, _variables, context) => {\n if (optimistic && context?.previousData !== undefined) {\n queryClient.setQueryData(optimistic.queryKey, context.previousData);\n }\n },\n onSettled: () => {\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: key });\n });\n if (optimistic) {\n queryClient.invalidateQueries({ queryKey: optimistic.queryKey });\n }\n },\n });\n}\n\nexport function useFirestoreDelete<T = unknown>({\n invalidateKeys = [],\n optimistic,\n}: {\n invalidateKeys?: readonly QueryKey[];\n optimistic?: {\n queryKey: QueryKey;\n updater: (oldData: T | undefined, docPath: string) => T;\n };\n} = {}): UseMutationResult<\n string,\n Error,\n { docPath: string },\n { previousData: T | undefined } | undefined\n> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({ docPath }: { docPath: string }) => {\n const docRef = doc(db, docPath);\n await deleteDoc(docRef);\n return docPath;\n },\n onMutate: async ({ docPath }) => {\n if (!optimistic) return undefined;\n\n await queryClient.cancelQueries({ queryKey: optimistic.queryKey });\n const previousData = queryClient.getQueryData<T>(optimistic.queryKey);\n\n queryClient.setQueryData<T>(optimistic.queryKey, (old) =>\n optimistic.updater(old, docPath)\n );\n\n return { previousData };\n },\n onError: (_error, _variables, context) => {\n if (optimistic && context?.previousData !== undefined) {\n queryClient.setQueryData(optimistic.queryKey, context.previousData);\n }\n },\n onSettled: () => {\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: key });\n });\n if (optimistic) {\n queryClient.invalidateQueries({ queryKey: optimistic.queryKey });\n }\n },\n });\n}\n\ninterface BatchResult {\n committed: number;\n batches: number;\n}\n\nexport function useFirestoreBatch({\n invalidateKeys = [],\n batchSize = DEFAULT_BATCH_SIZE,\n}: FirestoreBatchOptions = {}): UseMutationResult<\n BatchResult,\n Error,\n { operations: MutationOperation[] }\n> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({\n operations,\n }: {\n operations: MutationOperation[];\n }) => {\n const chunks: MutationOperation[][] = [];\n for (let i = 0; i < operations.length; i += batchSize) {\n chunks.push(operations.slice(i, i + batchSize));\n }\n\n let totalCommitted = 0;\n\n for (const chunk of chunks) {\n const batch = writeBatch(db);\n\n for (const op of chunk) {\n const docRef = doc(db, op.docPath);\n\n switch (op.type) {\n case 'set':\n if (op.merge) {\n batch.set(docRef, op.data, { merge: true });\n } else {\n batch.set(docRef, op.data);\n }\n break;\n case 'update':\n batch.update(docRef, op.data);\n break;\n case 'delete':\n batch.delete(docRef);\n break;\n }\n }\n\n await batch.commit();\n totalCommitted += chunk.length;\n }\n\n return {\n committed: totalCommitted,\n batches: chunks.length,\n };\n },\n onSuccess: () => {\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: key });\n });\n },\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"use-firestore-mutations.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-mutations.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport {\n useMutation,\n useQueryClient,\n type QueryKey,\n type UseMutationResult,\n} from '@tanstack/react-query';\nimport {\n doc,\n setDoc,\n updateDoc,\n deleteDoc,\n writeBatch,\n type DocumentData,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport type { FirestoreMutationOptions, FirestoreBatchOptions } from '../../firestore/types.js';\nimport type { MutationOperation } from './types.js';\n\nconst DEFAULT_BATCH_SIZE = 450;\n\ninterface SetResult<T> {\n id: string;\n data: T;\n}\n\nexport function useFirestoreSet<T extends DocumentData>({\n invalidateKeys = [],\n}: Pick<FirestoreMutationOptions<T>, 'invalidateKeys'> = {}): UseMutationResult<\n SetResult<T>,\n Error,\n { docPath: string; data: T; merge?: boolean }\n> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({\n docPath,\n data,\n merge = false,\n }: {\n docPath: string;\n data: T;\n merge?: boolean;\n }) => {\n const docRef = doc(db, docPath);\n await setDoc(docRef, data, { merge });\n return { id: docRef.id, data };\n },\n onSuccess: () => {\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: key });\n });\n },\n });\n}\n\nexport function useFirestoreUpdate<T extends DocumentData>({\n invalidateKeys = [],\n optimistic,\n}: FirestoreMutationOptions<T> = {}): UseMutationResult<\n Partial<T>,\n Error,\n { docPath: string; data: Partial<T> },\n { previousData: T | undefined } | undefined\n> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({ docPath, data }: { docPath: string; data: Partial<T> }) => {\n const docRef = doc(db, docPath);\n await updateDoc(docRef, data as DocumentData);\n return data;\n },\n onMutate: async ({ data }) => {\n if (!optimistic) return undefined;\n\n await queryClient.cancelQueries({ queryKey: optimistic.queryKey });\n const previousData = queryClient.getQueryData<T>(optimistic.queryKey);\n\n queryClient.setQueryData<T>(optimistic.queryKey, (old) =>\n optimistic.updater(old, data as Partial<T>),\n );\n\n return { previousData };\n },\n onError: (_error, _variables, context) => {\n if (optimistic && context?.previousData !== undefined) {\n queryClient.setQueryData(optimistic.queryKey, context.previousData);\n }\n },\n onSettled: () => {\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: key });\n });\n if (optimistic) {\n queryClient.invalidateQueries({ queryKey: optimistic.queryKey });\n }\n },\n });\n}\n\nexport function useFirestoreDelete<T = unknown>({\n invalidateKeys = [],\n optimistic,\n}: {\n invalidateKeys?: readonly QueryKey[];\n optimistic?: {\n queryKey: QueryKey;\n updater: (oldData: T | undefined, docPath: string) => T;\n };\n} = {}): UseMutationResult<\n string,\n Error,\n { docPath: string },\n { previousData: T | undefined } | undefined\n> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({ docPath }: { docPath: string }) => {\n const docRef = doc(db, docPath);\n await deleteDoc(docRef);\n return docPath;\n },\n onMutate: async ({ docPath }) => {\n if (!optimistic) return undefined;\n\n await queryClient.cancelQueries({ queryKey: optimistic.queryKey });\n const previousData = queryClient.getQueryData<T>(optimistic.queryKey);\n\n queryClient.setQueryData<T>(optimistic.queryKey, (old) => optimistic.updater(old, docPath));\n\n return { previousData };\n },\n onError: (_error, _variables, context) => {\n if (optimistic && context?.previousData !== undefined) {\n queryClient.setQueryData(optimistic.queryKey, context.previousData);\n }\n },\n onSettled: () => {\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: key });\n });\n if (optimistic) {\n queryClient.invalidateQueries({ queryKey: optimistic.queryKey });\n }\n },\n });\n}\n\ninterface BatchResult {\n committed: number;\n batches: number;\n}\n\nexport function useFirestoreBatch({\n invalidateKeys = [],\n batchSize = DEFAULT_BATCH_SIZE,\n}: FirestoreBatchOptions = {}): UseMutationResult<\n BatchResult,\n Error,\n { operations: MutationOperation[] }\n> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({ operations }: { operations: MutationOperation[] }) => {\n const chunks: MutationOperation[][] = [];\n for (let i = 0; i < operations.length; i += batchSize) {\n chunks.push(operations.slice(i, i + batchSize));\n }\n\n let totalCommitted = 0;\n\n for (const chunk of chunks) {\n const batch = writeBatch(db);\n\n for (const op of chunk) {\n const docRef = doc(db, op.docPath);\n\n switch (op.type) {\n case 'set':\n if (op.merge) {\n batch.set(docRef, op.data, { merge: true });\n } else {\n batch.set(docRef, op.data);\n }\n break;\n case 'update':\n batch.update(docRef, op.data);\n break;\n case 'delete':\n batch.delete(docRef);\n break;\n }\n }\n\n await batch.commit();\n totalCommitted += chunk.length;\n }\n\n return {\n committed: totalCommitted,\n batches: chunks.length,\n };\n },\n onSuccess: () => {\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: key });\n });\n },\n });\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type UseQueryResult } from '@tanstack/react-query';
|
|
2
2
|
import { type DocumentData } from 'firebase/firestore';
|
|
3
|
-
import type {
|
|
3
|
+
import type { WithId } from '../../firestore/types.js';
|
|
4
|
+
import type { FirestorePaginatedOptions, PaginatedPage } from './types.js';
|
|
4
5
|
/**
|
|
5
6
|
* Return type for useFirestorePaginated hook. `data` is the displayed page's rows; the
|
|
6
7
|
* page's cache entry (rows, cursor, has-more) lives at `paginatedPageKey(queryKey, page)`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-firestore-paginated.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-paginated.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAML,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"use-firestore-paginated.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-paginated.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAML,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,KAAK,EAAE,yBAAyB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAI3E;;;GAGG;AACH,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI,IAAI,CAC/C,cAAc,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAC/C,MAAM,CACP,GAAG;IACF,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC3E,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,QAA4B,EAC5B,WAAe,EACf,OAAc,EACd,SAAS,EACT,MAAM,EACN,eAAe,EACf,MAAM,GACP,EAAE,yBAAyB,CAAC,CAAC,CAAC,GAAG,2BAA2B,CAAC,CAAC,CAAC,CAsH/D"}
|
|
@@ -3,7 +3,7 @@ import { useState, useCallback, useEffect, useRef } from 'react';
|
|
|
3
3
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
4
4
|
import { collection, query, getDocs, limit, startAfter, } from 'firebase/firestore';
|
|
5
5
|
import { useFirestoreDb } from './context.js';
|
|
6
|
-
import { paginatedPageKey } from '
|
|
6
|
+
import { paginatedPageKey } from '../cache-helpers.js';
|
|
7
7
|
const DEFAULT_PAGE_SIZE = 10;
|
|
8
8
|
/**
|
|
9
9
|
* Page-based pagination for Firestore collections (Previous / Next, one page at a time).
|
|
@@ -90,7 +90,8 @@ export function useFirestorePaginated({ collectionPath, queryKey, constraints =
|
|
|
90
90
|
const cachedEntry = queryResult.data;
|
|
91
91
|
const outOfChain = page > 1 &&
|
|
92
92
|
cachedEntry !== undefined &&
|
|
93
|
-
(previousPage.data === undefined ||
|
|
93
|
+
(previousPage.data === undefined ||
|
|
94
|
+
cachedEntry.after !== (previousPage.data.cursor?.id ?? null));
|
|
94
95
|
const { refetch, isFetching } = queryResult;
|
|
95
96
|
useEffect(() => {
|
|
96
97
|
if (enabled && outOfChain && !isFetching)
|
|
@@ -118,7 +119,9 @@ export function useFirestorePaginated({ collectionPath, queryKey, constraints =
|
|
|
118
119
|
const prevPage = useCallback(() => setPage(page - 1), [page, setPage]);
|
|
119
120
|
return {
|
|
120
121
|
...queryResult,
|
|
121
|
-
...(outOfChain
|
|
122
|
+
...(outOfChain
|
|
123
|
+
? { isLoading: true, isPending: true, isSuccess: false, status: 'pending' }
|
|
124
|
+
: {}),
|
|
122
125
|
data: entry?.items,
|
|
123
126
|
page,
|
|
124
127
|
pageSize,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-firestore-paginated.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-paginated.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useState, useCallback, useEffect, useRef } from 'react';\nimport { useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';\nimport {\n collection,\n query,\n getDocs,\n limit,\n startAfter,\n type DocumentData,\n type DocumentSnapshot,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport { paginatedPageKey } from '../../cache-helpers.js';\nimport type { FirestorePaginatedOptions, PaginatedPage, WithId } from '../../firestore/types.js';\n\nconst DEFAULT_PAGE_SIZE = 10;\n\n/**\n * Return type for useFirestorePaginated hook. `data` is the displayed page's rows; the\n * page's cache entry (rows, cursor, has-more) lives at `paginatedPageKey(queryKey, page)`.\n */\nexport type UseFirestorePaginatedResult<T> = Omit<UseQueryResult<PaginatedPage<WithId<T>>, Error>, 'data'> & {\n data: WithId<T>[] | undefined;\n page: number;\n pageSize: number;\n hasNextPage: boolean;\n hasPrevPage: boolean;\n setPage: (page: number) => void;\n nextPage: () => void;\n prevPage: () => void;\n};\n\n/**\n * Page-based pagination for Firestore collections (Previous / Next, one page at a time).\n *\n * Each page is ONE cache entry at `[...queryKey, 'page', n]` holding its rows, its last\n * row as the next page's cursor, and whether a further page exists — read with a one-row\n * look-ahead, so `hasNextPage` is false exactly when nothing follows the displayed page.\n * Page n reads page n-1's cursor from the cache (loading the chain first if it is\n * missing) and records it as `after`. A displayed page whose `after` no longer matches\n * page n-1's cached cursor (page n-1 was re-read and its last row moved) is re-read before\n * it is shown, so every instance, remount, and refetch of a key agrees on where a page\n * starts. The page number belongs to the query identity (path, key, constraints, page\n * size): a new identity renders `initialPage` in its first render.\n *\n * `queryKey` must encode everything that changes the read — path, filters, ordering, and\n * page size — because the cache entries are shared by key.\n *\n * @example\n * ```tsx\n * const { data, page, nextPage, prevPage, hasNextPage, hasPrevPage } = useFirestorePaginated<Task>({\n * collectionPath: 'tasks',\n * queryKey: ['tasks', 'active', { pageSize: 10 }],\n * constraints: [where('status', '==', 'active'), orderBy('createdAt', 'desc')],\n * pageSize: 10,\n * });\n * ```\n */\nexport function useFirestorePaginated<T extends DocumentData = DocumentData>({\n collectionPath,\n queryKey,\n constraints = [],\n pageSize = DEFAULT_PAGE_SIZE,\n initialPage = 1,\n enabled = true,\n staleTime,\n gcTime,\n refetchInterval,\n select,\n}: FirestorePaginatedOptions<T>): UseFirestorePaginatedResult<T> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n const queryKeyMemo = JSON.stringify(queryKey);\n const constraintsMemo = JSON.stringify(constraints);\n const identity = JSON.stringify([collectionPath, queryKeyMemo, constraintsMemo, pageSize]);\n const firstPage = Math.max(1, Math.floor(initialPage));\n\n const [pageState, setPageState] = useState(() => ({ identity, page: firstPage }));\n const page = pageState.identity === identity ? pageState.page : firstPage;\n\n const selectRef = useRef(select);\n selectRef.current = select;\n\n const fetchPage = useCallback(\n async (n: number): Promise<PaginatedPage<WithId<T>>> => {\n let cursor: DocumentSnapshot | null = null;\n if (n > 1) {\n const previous = await queryClient.ensureQueryData<PaginatedPage<WithId<T>>>({\n queryKey: paginatedPageKey(queryKey, n - 1),\n queryFn: () => fetchPage(n - 1),\n });\n if (!previous.hasMore || !previous.cursor) {\n return { items: [], cursor: null, hasMore: false, after: previous.cursor?.id ?? null };\n }\n cursor = previous.cursor;\n }\n const q = query(\n collection(db, collectionPath),\n ...constraints,\n ...(cursor ? [startAfter(cursor)] : []),\n limit(pageSize + 1),\n );\n const snapshot = await getDocs(q);\n const pageDocs = snapshot.docs.slice(0, pageSize);\n return {\n items: pageDocs.map((docSnap) => {\n const dataWithId = { id: docSnap.id, ...docSnap.data() };\n const mapped = selectRef.current ? selectRef.current(dataWithId) : dataWithId;\n return mapped as WithId<T>;\n }),\n cursor: pageDocs[pageDocs.length - 1] ?? null,\n hasMore: snapshot.docs.length > pageSize,\n after: cursor?.id ?? null,\n };\n },\n // queryKey and constraints are captured through their serialized identity.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [db, queryClient, identity, selectRef],\n );\n\n const queryResult = useQuery({\n queryKey: paginatedPageKey(queryKey, page),\n queryFn: () => fetchPage(page),\n enabled,\n staleTime,\n gcTime,\n refetchInterval,\n });\n\n // The page before the displayed one, observed from the cache only (never fetched here).\n // When it is re-read and its last row moves, the displayed entry no longer starts where the\n // chain says it must: it is re-read before it is shown as current.\n const previousPage = useQuery({\n queryKey: paginatedPageKey(queryKey, Math.max(1, page - 1)),\n queryFn: () => fetchPage(Math.max(1, page - 1)),\n enabled: false,\n });\n const cachedEntry = queryResult.data;\n const outOfChain =\n page > 1 &&\n cachedEntry !== undefined &&\n (previousPage.data === undefined || cachedEntry.after !== (previousPage.data.cursor?.id ?? null));\n const { refetch, isFetching } = queryResult;\n useEffect(() => {\n if (enabled && outOfChain && !isFetching) void refetch();\n }, [enabled, outOfChain, isFetching, refetch]);\n\n const entry = outOfChain ? undefined : cachedEntry;\n const hasNextPage = Boolean(entry?.hasMore && entry.cursor);\n\n const setPage = useCallback(\n (target: number) => {\n const next = Math.floor(target);\n if (next < 1 || next === page) return;\n if (next > page + 1) {\n const before = queryClient.getQueryData<PaginatedPage<unknown>>(\n paginatedPageKey(queryKey, next - 1),\n );\n if (!before?.hasMore) return;\n }\n if (next === page + 1 && !hasNextPage) return;\n setPageState({ identity, page: next });\n },\n // queryKey is captured through its serialized identity.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [page, hasNextPage, identity, queryClient],\n );\n\n const nextPage = useCallback(() => setPage(page + 1), [page, setPage]);\n const prevPage = useCallback(() => setPage(page - 1), [page, setPage]);\n\n return {\n ...queryResult,\n ...(outOfChain ? { isLoading: true, isPending: true, isSuccess: false, status: 'pending' as const } : {}),\n data: entry?.items,\n page,\n pageSize,\n hasNextPage,\n hasPrevPage: page > 1,\n setPage,\n nextPage,\n prevPage,\n } as UseFirestorePaginatedResult<T>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"use-firestore-paginated.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-paginated.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useState, useCallback, useEffect, useRef } from 'react';\nimport { useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';\nimport {\n collection,\n query,\n getDocs,\n limit,\n startAfter,\n type DocumentData,\n type DocumentSnapshot,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport { paginatedPageKey } from '../cache-helpers.js';\nimport type { WithId } from '../../firestore/types.js';\nimport type { FirestorePaginatedOptions, PaginatedPage } from './types.js';\n\nconst DEFAULT_PAGE_SIZE = 10;\n\n/**\n * Return type for useFirestorePaginated hook. `data` is the displayed page's rows; the\n * page's cache entry (rows, cursor, has-more) lives at `paginatedPageKey(queryKey, page)`.\n */\nexport type UseFirestorePaginatedResult<T> = Omit<\n UseQueryResult<PaginatedPage<WithId<T>>, Error>,\n 'data'\n> & {\n data: WithId<T>[] | undefined;\n page: number;\n pageSize: number;\n hasNextPage: boolean;\n hasPrevPage: boolean;\n setPage: (page: number) => void;\n nextPage: () => void;\n prevPage: () => void;\n};\n\n/**\n * Page-based pagination for Firestore collections (Previous / Next, one page at a time).\n *\n * Each page is ONE cache entry at `[...queryKey, 'page', n]` holding its rows, its last\n * row as the next page's cursor, and whether a further page exists — read with a one-row\n * look-ahead, so `hasNextPage` is false exactly when nothing follows the displayed page.\n * Page n reads page n-1's cursor from the cache (loading the chain first if it is\n * missing) and records it as `after`. A displayed page whose `after` no longer matches\n * page n-1's cached cursor (page n-1 was re-read and its last row moved) is re-read before\n * it is shown, so every instance, remount, and refetch of a key agrees on where a page\n * starts. The page number belongs to the query identity (path, key, constraints, page\n * size): a new identity renders `initialPage` in its first render.\n *\n * `queryKey` must encode everything that changes the read — path, filters, ordering, and\n * page size — because the cache entries are shared by key.\n *\n * @example\n * ```tsx\n * const { data, page, nextPage, prevPage, hasNextPage, hasPrevPage } = useFirestorePaginated<Task>({\n * collectionPath: 'tasks',\n * queryKey: ['tasks', 'active', { pageSize: 10 }],\n * constraints: [where('status', '==', 'active'), orderBy('createdAt', 'desc')],\n * pageSize: 10,\n * });\n * ```\n */\nexport function useFirestorePaginated<T extends DocumentData = DocumentData>({\n collectionPath,\n queryKey,\n constraints = [],\n pageSize = DEFAULT_PAGE_SIZE,\n initialPage = 1,\n enabled = true,\n staleTime,\n gcTime,\n refetchInterval,\n select,\n}: FirestorePaginatedOptions<T>): UseFirestorePaginatedResult<T> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n const queryKeyMemo = JSON.stringify(queryKey);\n const constraintsMemo = JSON.stringify(constraints);\n const identity = JSON.stringify([collectionPath, queryKeyMemo, constraintsMemo, pageSize]);\n const firstPage = Math.max(1, Math.floor(initialPage));\n\n const [pageState, setPageState] = useState(() => ({ identity, page: firstPage }));\n const page = pageState.identity === identity ? pageState.page : firstPage;\n\n const selectRef = useRef(select);\n selectRef.current = select;\n\n const fetchPage = useCallback(\n async (n: number): Promise<PaginatedPage<WithId<T>>> => {\n let cursor: DocumentSnapshot | null = null;\n if (n > 1) {\n const previous = await queryClient.ensureQueryData<PaginatedPage<WithId<T>>>({\n queryKey: paginatedPageKey(queryKey, n - 1),\n queryFn: () => fetchPage(n - 1),\n });\n if (!previous.hasMore || !previous.cursor) {\n return { items: [], cursor: null, hasMore: false, after: previous.cursor?.id ?? null };\n }\n cursor = previous.cursor;\n }\n const q = query(\n collection(db, collectionPath),\n ...constraints,\n ...(cursor ? [startAfter(cursor)] : []),\n limit(pageSize + 1),\n );\n const snapshot = await getDocs(q);\n const pageDocs = snapshot.docs.slice(0, pageSize);\n return {\n items: pageDocs.map((docSnap) => {\n const dataWithId = { id: docSnap.id, ...docSnap.data() };\n const mapped = selectRef.current ? selectRef.current(dataWithId) : dataWithId;\n return mapped as WithId<T>;\n }),\n cursor: pageDocs[pageDocs.length - 1] ?? null,\n hasMore: snapshot.docs.length > pageSize,\n after: cursor?.id ?? null,\n };\n },\n // queryKey and constraints are captured through their serialized identity.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [db, queryClient, identity, selectRef],\n );\n\n const queryResult = useQuery({\n queryKey: paginatedPageKey(queryKey, page),\n queryFn: () => fetchPage(page),\n enabled,\n staleTime,\n gcTime,\n refetchInterval,\n });\n\n // The page before the displayed one, observed from the cache only (never fetched here).\n // When it is re-read and its last row moves, the displayed entry no longer starts where the\n // chain says it must: it is re-read before it is shown as current.\n const previousPage = useQuery({\n queryKey: paginatedPageKey(queryKey, Math.max(1, page - 1)),\n queryFn: () => fetchPage(Math.max(1, page - 1)),\n enabled: false,\n });\n const cachedEntry = queryResult.data;\n const outOfChain =\n page > 1 &&\n cachedEntry !== undefined &&\n (previousPage.data === undefined ||\n cachedEntry.after !== (previousPage.data.cursor?.id ?? null));\n const { refetch, isFetching } = queryResult;\n useEffect(() => {\n if (enabled && outOfChain && !isFetching) void refetch();\n }, [enabled, outOfChain, isFetching, refetch]);\n\n const entry = outOfChain ? undefined : cachedEntry;\n const hasNextPage = Boolean(entry?.hasMore && entry.cursor);\n\n const setPage = useCallback(\n (target: number) => {\n const next = Math.floor(target);\n if (next < 1 || next === page) return;\n if (next > page + 1) {\n const before = queryClient.getQueryData<PaginatedPage<unknown>>(\n paginatedPageKey(queryKey, next - 1),\n );\n if (!before?.hasMore) return;\n }\n if (next === page + 1 && !hasNextPage) return;\n setPageState({ identity, page: next });\n },\n // queryKey is captured through its serialized identity.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [page, hasNextPage, identity, queryClient],\n );\n\n const nextPage = useCallback(() => setPage(page + 1), [page, setPage]);\n const prevPage = useCallback(() => setPage(page - 1), [page, setPage]);\n\n return {\n ...queryResult,\n ...(outOfChain\n ? { isLoading: true, isPending: true, isSuccess: false, status: 'pending' as const }\n : {}),\n data: entry?.items,\n page,\n pageSize,\n hasNextPage,\n hasPrevPage: page > 1,\n setPage,\n nextPage,\n prevPage,\n } as UseFirestorePaginatedResult<T>;\n}\n"]}
|
|
@@ -1 +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,
|
|
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"}
|
|
@@ -79,7 +79,9 @@ export function useBatchFirestoreDocs({ db, collectionPath, ids, queryKeyPrefix,
|
|
|
79
79
|
queryKey: [queryKeyPrefix, id],
|
|
80
80
|
queryFn: async ({ signal }) => {
|
|
81
81
|
const loader = getDocLoader(queryClient, db, collectionPath);
|
|
82
|
-
const loaded = transport === 'get'
|
|
82
|
+
const loaded = transport === 'get'
|
|
83
|
+
? await loader.loadOne(id, signal)
|
|
84
|
+
: await loader.loadBatched(id, signal);
|
|
83
85
|
// If a shared listener took ownership of this key while the read was in flight, the
|
|
84
86
|
// listener's value is the newer truth — `onSnapshot` will not re-emit until the doc
|
|
85
87
|
// changes, so returning the read result here would leave a stale value stuck. A listener
|
|
@@ -124,7 +126,7 @@ export function useBatchFirestoreDocs({ db, collectionPath, ids, queryKeyPrefix,
|
|
|
124
126
|
});
|
|
125
127
|
setSnapshotVersion((version) => version + 1);
|
|
126
128
|
},
|
|
127
|
-
onError: (error) => setSubscribeErrors((errors) =>
|
|
129
|
+
onError: (error) => setSubscribeErrors((errors) => errors[id] === error ? errors : { ...errors, [id]: error }),
|
|
128
130
|
}));
|
|
129
131
|
return () => {
|
|
130
132
|
for (const unsubscribe of unsubscribers)
|
|
@@ -173,7 +175,9 @@ export function useBatchFirestoreDocs({ db, collectionPath, ids, queryKeyPrefix,
|
|
|
173
175
|
const listenerError = (subscribe ? subscribeErrors[id] : undefined) ??
|
|
174
176
|
listenerErrorOf(queryClient, queryKeyPrefix, id) ??
|
|
175
177
|
undefined;
|
|
176
|
-
if (result &&
|
|
178
|
+
if (result &&
|
|
179
|
+
(result.isFetching || result.isPaused) &&
|
|
180
|
+
result.errorUpdatedAt > result.dataUpdatedAt) {
|
|
177
181
|
failedReadsRunning = true;
|
|
178
182
|
}
|
|
179
183
|
const retryState = retryStates[index];
|
|
@@ -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\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"]}
|
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":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,
|
|
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"}
|