@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.
Files changed (115) hide show
  1. package/dist/defaults.d.ts +0 -7
  2. package/dist/defaults.d.ts.map +1 -1
  3. package/dist/defaults.js +0 -20
  4. package/dist/defaults.js.map +1 -1
  5. package/dist/firestore/types.d.ts +0 -142
  6. package/dist/firestore/types.d.ts.map +1 -1
  7. package/dist/firestore/types.js +1 -6
  8. package/dist/firestore/types.js.map +1 -1
  9. package/dist/index.d.ts +2 -9
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -7
  12. package/dist/index.js.map +1 -1
  13. package/dist/react/cache-helpers.d.ts.map +1 -0
  14. package/dist/react/cache-helpers.js.map +1 -0
  15. package/dist/react/defaults.d.ts +8 -0
  16. package/dist/react/defaults.d.ts.map +1 -0
  17. package/dist/react/defaults.js +21 -0
  18. package/dist/react/defaults.js.map +1 -0
  19. package/dist/{domain-events → react/domain-events}/cache-invalidation.d.ts +8 -8
  20. package/dist/react/domain-events/cache-invalidation.d.ts.map +1 -0
  21. package/dist/{domain-events → react/domain-events}/cache-invalidation.js +28 -17
  22. package/dist/react/domain-events/cache-invalidation.js.map +1 -0
  23. package/dist/{domain-events → react/domain-events}/create-domain-event-invalidator.d.ts +4 -4
  24. package/dist/react/domain-events/create-domain-event-invalidator.d.ts.map +1 -0
  25. package/dist/{domain-events → react/domain-events}/create-domain-event-invalidator.js +1 -1
  26. package/dist/react/domain-events/create-domain-event-invalidator.js.map +1 -0
  27. package/dist/react/firestore/context.d.ts.map +1 -1
  28. package/dist/react/firestore/context.js +1 -1
  29. package/dist/react/firestore/context.js.map +1 -1
  30. package/dist/react/firestore/doc-subscription-registry.d.ts.map +1 -1
  31. package/dist/react/firestore/doc-subscription-registry.js +5 -1
  32. package/dist/react/firestore/doc-subscription-registry.js.map +1 -1
  33. package/dist/{firestore → react/firestore}/infinite-helpers.d.ts +2 -1
  34. package/dist/react/firestore/infinite-helpers.d.ts.map +1 -0
  35. package/dist/react/firestore/infinite-helpers.js.map +1 -0
  36. package/dist/react/firestore/types.d.ts +144 -0
  37. package/dist/react/firestore/types.d.ts.map +1 -0
  38. package/dist/react/firestore/types.js +7 -0
  39. package/dist/react/firestore/types.js.map +1 -0
  40. package/dist/react/firestore/use-firestore-collection.d.ts +2 -1
  41. package/dist/react/firestore/use-firestore-collection.d.ts.map +1 -1
  42. package/dist/react/firestore/use-firestore-collection.js +22 -9
  43. package/dist/react/firestore/use-firestore-collection.js.map +1 -1
  44. package/dist/react/firestore/use-firestore-count.d.ts +1 -1
  45. package/dist/react/firestore/use-firestore-count.d.ts.map +1 -1
  46. package/dist/react/firestore/use-firestore-count.js.map +1 -1
  47. package/dist/react/firestore/use-firestore-doc.d.ts +2 -1
  48. package/dist/react/firestore/use-firestore-doc.d.ts.map +1 -1
  49. package/dist/react/firestore/use-firestore-doc.js +18 -2
  50. package/dist/react/firestore/use-firestore-doc.js.map +1 -1
  51. package/dist/react/firestore/use-firestore-infinite.d.ts +1 -1
  52. package/dist/react/firestore/use-firestore-infinite.d.ts.map +1 -1
  53. package/dist/react/firestore/use-firestore-infinite.js.map +1 -1
  54. package/dist/react/firestore/use-firestore-live-infinite.d.ts +2 -1
  55. package/dist/react/firestore/use-firestore-live-infinite.d.ts.map +1 -1
  56. package/dist/react/firestore/use-firestore-live-infinite.js +9 -1
  57. package/dist/react/firestore/use-firestore-live-infinite.js.map +1 -1
  58. package/dist/react/firestore/use-firestore-mutations.d.ts +2 -1
  59. package/dist/react/firestore/use-firestore-mutations.d.ts.map +1 -1
  60. package/dist/react/firestore/use-firestore-mutations.js +3 -3
  61. package/dist/react/firestore/use-firestore-mutations.js.map +1 -1
  62. package/dist/react/firestore/use-firestore-paginated.d.ts +2 -1
  63. package/dist/react/firestore/use-firestore-paginated.d.ts.map +1 -1
  64. package/dist/react/firestore/use-firestore-paginated.js +6 -3
  65. package/dist/react/firestore/use-firestore-paginated.js.map +1 -1
  66. package/dist/react/firestore/useBatchFirestoreDocs.d.ts.map +1 -1
  67. package/dist/react/firestore/useBatchFirestoreDocs.js +7 -3
  68. package/dist/react/firestore/useBatchFirestoreDocs.js.map +1 -1
  69. package/dist/react/index.d.ts +13 -4
  70. package/dist/react/index.d.ts.map +1 -1
  71. package/dist/react/index.js +11 -3
  72. package/dist/react/index.js.map +1 -1
  73. package/dist/react/pending-mutations.d.ts +1 -1
  74. package/dist/react/pending-mutations.d.ts.map +1 -1
  75. package/dist/react/pending-mutations.js +6 -6
  76. package/dist/react/pending-mutations.js.map +1 -1
  77. package/dist/react/provider.d.ts +1 -1
  78. package/dist/react/provider.d.ts.map +1 -1
  79. package/dist/react/provider.js +1 -1
  80. package/dist/react/provider.js.map +1 -1
  81. package/dist/react/query-client.d.ts.map +1 -0
  82. package/dist/react/query-client.js.map +1 -0
  83. package/dist/react/search/types.d.ts +28 -0
  84. package/dist/react/search/types.d.ts.map +1 -0
  85. package/dist/react/search/types.js +2 -0
  86. package/dist/react/search/types.js.map +1 -0
  87. package/dist/react/search/use-firestore-search.d.ts +1 -1
  88. package/dist/react/search/use-firestore-search.d.ts.map +1 -1
  89. package/dist/react/search/use-firestore-search.js.map +1 -1
  90. package/dist/react/use-read-retry-state.d.ts.map +1 -1
  91. package/dist/react/use-read-retry-state.js +1 -1
  92. package/dist/react/use-read-retry-state.js.map +1 -1
  93. package/dist/search/types.d.ts +0 -26
  94. package/dist/search/types.d.ts.map +1 -1
  95. package/dist/search/types.js.map +1 -1
  96. package/package.json +2 -2
  97. package/dist/cache-helpers.d.ts.map +0 -1
  98. package/dist/cache-helpers.js.map +0 -1
  99. package/dist/domain-events/cache-invalidation.d.ts.map +0 -1
  100. package/dist/domain-events/cache-invalidation.js.map +0 -1
  101. package/dist/domain-events/create-domain-event-invalidator.d.ts.map +0 -1
  102. package/dist/domain-events/create-domain-event-invalidator.js.map +0 -1
  103. package/dist/domain-events/index.d.ts +0 -5
  104. package/dist/domain-events/index.d.ts.map +0 -1
  105. package/dist/domain-events/index.js +0 -3
  106. package/dist/domain-events/index.js.map +0 -1
  107. package/dist/firestore/infinite-helpers.d.ts.map +0 -1
  108. package/dist/firestore/infinite-helpers.js.map +0 -1
  109. package/dist/query-client.d.ts.map +0 -1
  110. package/dist/query-client.js.map +0 -1
  111. /package/dist/{cache-helpers.d.ts → react/cache-helpers.d.ts} +0 -0
  112. /package/dist/{cache-helpers.js → react/cache-helpers.js} +0 -0
  113. /package/dist/{firestore → react/firestore}/infinite-helpers.js +0 -0
  114. /package/dist/{query-client.d.ts → react/query-client.d.ts} +0 -0
  115. /package/dist/{query-client.js → react/query-client.js} +0 -0
@@ -18,7 +18,7 @@ const FirestoreContext = createContext(null);
18
18
  * ```
19
19
  */
20
20
  export function FirestoreProvider({ db, children }) {
21
- return (_jsx(FirestoreContext.Provider, { value: { db }, children: children }));
21
+ return _jsx(FirestoreContext.Provider, { value: { db }, children: children });
22
22
  }
23
23
  /**
24
24
  * Access the Firestore instance from context.
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/react/firestore/context.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,aAAa,EAAE,UAAU,EAAkB,MAAM,OAAO,CAAC;AAOlE,MAAM,gBAAgB,GAAG,aAAa,CAA+B,IAAI,CAAC,CAAC;AAO3E;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAAE,EAAE,EAAE,QAAQ,EAA0B;IACxE,OAAO,CACL,KAAC,gBAAgB,CAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,EAAE,EAAE,YACrC,QAAQ,GACiB,CAC7B,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,OAAO,GAAG,UAAU,CAAC,gBAAgB,CAAC,CAAC;IAC7C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,0DAA0D;YAC1D,iDAAiD,CAClD,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC,EAAE,CAAC;AACpB,CAAC","sourcesContent":["'use client';\n\nimport { createContext, useContext, type ReactNode } from 'react';\nimport type { Firestore } from 'firebase/firestore';\n\ninterface FirestoreContextValue {\n db: Firestore;\n}\n\nconst FirestoreContext = createContext<FirestoreContextValue | null>(null);\n\nexport interface FirestoreProviderProps {\n db: Firestore;\n children: ReactNode;\n}\n\n/**\n * Provides Firestore instance to all useFirestore* hooks.\n * Wrap your app with this provider alongside QueryProvider.\n *\n * @example\n * ```tsx\n * import { db } from '@/lib/firebase';\n *\n * <QueryProvider>\n * <FirestoreProvider db={db}>\n * {children}\n * </FirestoreProvider>\n * </QueryProvider>\n * ```\n */\nexport function FirestoreProvider({ db, children }: FirestoreProviderProps) {\n return (\n <FirestoreContext.Provider value={{ db }}>\n {children}\n </FirestoreContext.Provider>\n );\n}\n\n/**\n * Access the Firestore instance from context.\n * Throws if used outside of FirestoreProvider.\n */\nexport function useFirestoreDb(): Firestore {\n const context = useContext(FirestoreContext);\n if (!context) {\n throw new Error(\n 'useFirestoreDb must be used within a FirestoreProvider. ' +\n 'Wrap your app with <FirestoreProvider db={db}>.'\n );\n }\n return context.db;\n}\n"]}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/react/firestore/context.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,aAAa,EAAE,UAAU,EAAkB,MAAM,OAAO,CAAC;AAOlE,MAAM,gBAAgB,GAAG,aAAa,CAA+B,IAAI,CAAC,CAAC;AAO3E;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAAE,EAAE,EAAE,QAAQ,EAA0B;IACxE,OAAO,KAAC,gBAAgB,CAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,EAAE,EAAE,YAAG,QAAQ,GAA6B,CAAC;AAC1F,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,OAAO,GAAG,UAAU,CAAC,gBAAgB,CAAC,CAAC;IAC7C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,0DAA0D;YACxD,iDAAiD,CACpD,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC,EAAE,CAAC;AACpB,CAAC","sourcesContent":["'use client';\n\nimport { createContext, useContext, type ReactNode } from 'react';\nimport type { Firestore } from 'firebase/firestore';\n\ninterface FirestoreContextValue {\n db: Firestore;\n}\n\nconst FirestoreContext = createContext<FirestoreContextValue | null>(null);\n\nexport interface FirestoreProviderProps {\n db: Firestore;\n children: ReactNode;\n}\n\n/**\n * Provides Firestore instance to all useFirestore* hooks.\n * Wrap your app with this provider alongside QueryProvider.\n *\n * @example\n * ```tsx\n * import { db } from '@/lib/firebase';\n *\n * <QueryProvider>\n * <FirestoreProvider db={db}>\n * {children}\n * </FirestoreProvider>\n * </QueryProvider>\n * ```\n */\nexport function FirestoreProvider({ db, children }: FirestoreProviderProps) {\n return <FirestoreContext.Provider value={{ db }}>{children}</FirestoreContext.Provider>;\n}\n\n/**\n * Access the Firestore instance from context.\n * Throws if used outside of FirestoreProvider.\n */\nexport function useFirestoreDb(): Firestore {\n const context = useContext(FirestoreContext);\n if (!context) {\n throw new Error(\n 'useFirestoreDb must be used within a FirestoreProvider. ' +\n 'Wrap your app with <FirestoreProvider db={db}>.',\n );\n }\n return context.db;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"doc-subscription-registry.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/doc-subscription-registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AA8CzD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,WAAW,EACxB,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,GACT,OAAO,CAET;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,WAAW,EACxB,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,GACT,KAAK,GAAG,IAAI,CAEd;AAWD,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,WAAW,CAAC;IACzB,EAAE,EAAE,SAAS,CAAC;IACd,4EAA4E;IAC5E,cAAc,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,cAAc,EAAE,MAAM,CAAC;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,uFAAuF;IACvF,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,IAAI,CAwDnE;AAED,sEAAsE;AACtE,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,CAEtE"}
1
+ {"version":3,"file":"doc-subscription-registry.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/doc-subscription-registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AA8CzD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,WAAW,EACxB,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,GACT,OAAO,CAET;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,WAAW,EACxB,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,GACT,KAAK,GAAG,IAAI,CAEd;AAWD,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,WAAW,CAAC;IACzB,EAAE,EAAE,SAAS,CAAC;IACd,4EAA4E;IAC5E,cAAc,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,cAAc,EAAE,MAAM,CAAC;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,uFAAuF;IACvF,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,IAAI,CA4DnE;AAED,sEAAsE;AACtE,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,CAEtE"}
@@ -43,7 +43,11 @@ export function subscribeDoc(params) {
43
43
  const key = registryKey(queryKeyPrefix, id);
44
44
  let entry = registry.get(key);
45
45
  if (!entry) {
46
- const created = { unsubscribe: () => { }, subscribers: new Set(), error: null };
46
+ const created = {
47
+ unsubscribe: () => { },
48
+ subscribers: new Set(),
49
+ error: null,
50
+ };
47
51
  const subscribers = created.subscribers;
48
52
  const ref = doc(db, collectionPath, id);
49
53
  created.unsubscribe = onSnapshot(ref, (snapshot) => {
@@ -1 +1 @@
1
- {"version":3,"file":"doc-subscription-registry.js","sourceRoot":"","sources":["../../../src/react/firestore/doc-subscription-registry.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { doc, onSnapshot, type Firestore } from 'firebase/firestore';\nimport type { QueryClient } from '@tanstack/react-query';\n\n/**\n * Reference-counted, real-time document subscription registry.\n *\n * Backs the `subscribe` mode of {@link useBatchFirestoreDocs}. The problem it solves:\n * many components on a page resolve the SAME document id (e.g. the same author rendered\n * in a feed card, a comment, and a mention). Without sharing, each would open its own\n * `onSnapshot` listener. This registry keeps exactly ONE Firestore listener per\n * `(queryClient, queryKeyPrefix, id)` and fans its updates out to every subscriber, so N\n * components showing the same id cost one listener.\n *\n * The single listener writes the document straight into the React Query cache at\n * `[queryKeyPrefix, id]` — `{ id, ...data }` when the doc exists, or `null` when it does\n * not (negative caching, so a missing/lagging doc resolves the instant it appears instead\n * of staying blank until staleTime). A listener ERROR also negative-caches the id: a\n * rules-denied doc (hidden, or non-existent when the rule references `resource.data`) is\n * resolved, not forever-loading — consumers see `data: null` plus the error instead\n * of an eternal spinner. Firestore ends a listener that errors, so the entry keeps that error\n * for as long as the entry lives (until its last subscriber leaves; a later snapshot, were one\n * to arrive, clears it): a subscriber that joins an errored listener receives the error too,\n * and a one-shot reader of the same key reads it through `listenerErrorOf`, so neither takes\n * the cached `null` as a missing document. The registry is keyed per `QueryClient` (a WeakMap),\n * which in practice means per browser tab, so each tab listens independently and stays\n * consistent without cross-tab plumbing.\n */\n\ntype Subscriber = {\n onUpdate: () => void;\n onError?: (error: Error) => void;\n};\n\ntype Entry = {\n unsubscribe: () => void;\n subscribers: Set<Subscriber>;\n /** The listener's last error, cleared by the next snapshot. */\n error: Error | null;\n};\n\n// Per-QueryClient registry. WeakMap so a discarded QueryClient (and its entries) is GC'd.\nconst registries = new WeakMap<QueryClient, Map<string, Entry>>();\n\nfunction registryKey(queryKeyPrefix: string, id: string): string {\n return `${queryKeyPrefix} ${id}`;\n}\n\n/**\n * True while a shared listener owns `[queryKeyPrefix, id]` for this client.\n *\n * A one-shot read that was already in flight when a consumer entered subscribe mode would\n * otherwise resolve AFTER the listener's first snapshot and overwrite it — and `onSnapshot`\n * does not re-emit until the document actually changes, so the stale value would stick.\n * The one-shot `queryFn` consults this after its read and yields to the listener's value.\n */\nexport function isListenerOwned(\n queryClient: QueryClient,\n queryKeyPrefix: string,\n id: string,\n): boolean {\n return registries.get(queryClient)?.has(registryKey(queryKeyPrefix, id)) ?? false;\n}\n\n/**\n * The error of the shared listener that owns `[queryKeyPrefix, id]`, or `null` when no listener\n * owns the key or it has not errored. The listener writes `null` to the key when it errors, so a\n * one-shot reader of the same key consults this before taking that `null` as a missing document.\n */\nexport function listenerErrorOf(\n queryClient: QueryClient,\n queryKeyPrefix: string,\n id: string,\n): Error | null {\n return registries.get(queryClient)?.get(registryKey(queryKeyPrefix, id))?.error ?? null;\n}\n\nfunction getRegistry(queryClient: QueryClient): Map<string, Entry> {\n let registry = registries.get(queryClient);\n if (!registry) {\n registry = new Map();\n registries.set(queryClient, registry);\n }\n return registry;\n}\n\nexport interface SubscribeDocParams {\n queryClient: QueryClient;\n db: Firestore;\n /** Top-level/full collection path the doc lives in (e.g. 'publicUsers'). */\n collectionPath: string;\n /** Cache key prefix; the doc is cached at `[queryKeyPrefix, id]`. */\n queryKeyPrefix: string;\n id: string;\n /** Called whenever a snapshot (data or missing) lands, so the caller can re-render. */\n onUpdate: () => void;\n /** Called on listener error — and at once on joining a listener that has already errored. */\n onError?: (error: Error) => void;\n}\n\n/**\n * Subscribe to a single document via a shared, reference-counted listener.\n * Returns an idempotent unsubscribe function; the underlying Firestore listener is\n * detached only when the last subscriber for that id unsubscribes.\n */\nexport function subscribeDoc(params: SubscribeDocParams): () => void {\n const { queryClient, db, collectionPath, queryKeyPrefix, id, onUpdate, onError } = params;\n const registry = getRegistry(queryClient);\n const key = registryKey(queryKeyPrefix, id);\n\n let entry = registry.get(key);\n if (!entry) {\n const created: Entry = { unsubscribe: () => {}, subscribers: new Set<Subscriber>(), error: null };\n const subscribers = created.subscribers;\n const ref = doc(db, collectionPath, id);\n created.unsubscribe = onSnapshot(\n ref,\n (snapshot) => {\n created.error = null;\n queryClient.setQueryData(\n [queryKeyPrefix, id],\n snapshot.exists() ? { id: snapshot.id, ...snapshot.data() } : null,\n { updatedAt: Date.now() },\n );\n // Copy before iterating: an onUpdate could (re-entrantly) unsubscribe.\n for (const sub of [...subscribers]) sub.onUpdate();\n },\n (error) => {\n // Write `null` so the id resolves — as a failure, through the kept error — instead\n // of loading forever: subscribe-mode loading (\"no cache entry yet\") would otherwise\n // stay true for ids whose listener can never land a snapshot.\n created.error = error;\n queryClient.setQueryData([queryKeyPrefix, id], null, { updatedAt: Date.now() });\n for (const sub of [...subscribers]) sub.onError?.(error);\n },\n );\n entry = created;\n registry.set(key, entry);\n }\n\n const subscriber: Subscriber = { onUpdate, onError };\n entry.subscribers.add(subscriber);\n if (entry.error) onError?.(entry.error);\n\n let released = false;\n return () => {\n if (released) return;\n released = true;\n const current = registry.get(key);\n if (!current) return;\n current.subscribers.delete(subscriber);\n if (current.subscribers.size === 0) {\n current.unsubscribe();\n registry.delete(key);\n // A failed listener's `null` is a placeholder, not a read: once nothing owns the key,\n // mark it stale so a remaining one-shot reader reads the document for itself.\n if (current.error) {\n void queryClient.invalidateQueries({ queryKey: [queryKeyPrefix, id], exact: true });\n }\n }\n };\n}\n\n/** Test-only: number of active shared listeners for a QueryClient. */\nexport function __activeListenerCount(queryClient: QueryClient): number {\n return registries.get(queryClient)?.size ?? 0;\n}\n"]}
1
+ {"version":3,"file":"doc-subscription-registry.js","sourceRoot":"","sources":["../../../src/react/firestore/doc-subscription-registry.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { doc, onSnapshot, type Firestore } from 'firebase/firestore';\nimport type { QueryClient } from '@tanstack/react-query';\n\n/**\n * Reference-counted, real-time document subscription registry.\n *\n * Backs the `subscribe` mode of {@link useBatchFirestoreDocs}. The problem it solves:\n * many components on a page resolve the SAME document id (e.g. the same author rendered\n * in a feed card, a comment, and a mention). Without sharing, each would open its own\n * `onSnapshot` listener. This registry keeps exactly ONE Firestore listener per\n * `(queryClient, queryKeyPrefix, id)` and fans its updates out to every subscriber, so N\n * components showing the same id cost one listener.\n *\n * The single listener writes the document straight into the React Query cache at\n * `[queryKeyPrefix, id]` — `{ id, ...data }` when the doc exists, or `null` when it does\n * not (negative caching, so a missing/lagging doc resolves the instant it appears instead\n * of staying blank until staleTime). A listener ERROR also negative-caches the id: a\n * rules-denied doc (hidden, or non-existent when the rule references `resource.data`) is\n * resolved, not forever-loading — consumers see `data: null` plus the error instead\n * of an eternal spinner. Firestore ends a listener that errors, so the entry keeps that error\n * for as long as the entry lives (until its last subscriber leaves; a later snapshot, were one\n * to arrive, clears it): a subscriber that joins an errored listener receives the error too,\n * and a one-shot reader of the same key reads it through `listenerErrorOf`, so neither takes\n * the cached `null` as a missing document. The registry is keyed per `QueryClient` (a WeakMap),\n * which in practice means per browser tab, so each tab listens independently and stays\n * consistent without cross-tab plumbing.\n */\n\ntype Subscriber = {\n onUpdate: () => void;\n onError?: (error: Error) => void;\n};\n\ntype Entry = {\n unsubscribe: () => void;\n subscribers: Set<Subscriber>;\n /** The listener's last error, cleared by the next snapshot. */\n error: Error | null;\n};\n\n// Per-QueryClient registry. WeakMap so a discarded QueryClient (and its entries) is GC'd.\nconst registries = new WeakMap<QueryClient, Map<string, Entry>>();\n\nfunction registryKey(queryKeyPrefix: string, id: string): string {\n return `${queryKeyPrefix} ${id}`;\n}\n\n/**\n * True while a shared listener owns `[queryKeyPrefix, id]` for this client.\n *\n * A one-shot read that was already in flight when a consumer entered subscribe mode would\n * otherwise resolve AFTER the listener's first snapshot and overwrite it — and `onSnapshot`\n * does not re-emit until the document actually changes, so the stale value would stick.\n * The one-shot `queryFn` consults this after its read and yields to the listener's value.\n */\nexport function isListenerOwned(\n queryClient: QueryClient,\n queryKeyPrefix: string,\n id: string,\n): boolean {\n return registries.get(queryClient)?.has(registryKey(queryKeyPrefix, id)) ?? false;\n}\n\n/**\n * The error of the shared listener that owns `[queryKeyPrefix, id]`, or `null` when no listener\n * owns the key or it has not errored. The listener writes `null` to the key when it errors, so a\n * one-shot reader of the same key consults this before taking that `null` as a missing document.\n */\nexport function listenerErrorOf(\n queryClient: QueryClient,\n queryKeyPrefix: string,\n id: string,\n): Error | null {\n return registries.get(queryClient)?.get(registryKey(queryKeyPrefix, id))?.error ?? null;\n}\n\nfunction getRegistry(queryClient: QueryClient): Map<string, Entry> {\n let registry = registries.get(queryClient);\n if (!registry) {\n registry = new Map();\n registries.set(queryClient, registry);\n }\n return registry;\n}\n\nexport interface SubscribeDocParams {\n queryClient: QueryClient;\n db: Firestore;\n /** Top-level/full collection path the doc lives in (e.g. 'publicUsers'). */\n collectionPath: string;\n /** Cache key prefix; the doc is cached at `[queryKeyPrefix, id]`. */\n queryKeyPrefix: string;\n id: string;\n /** Called whenever a snapshot (data or missing) lands, so the caller can re-render. */\n onUpdate: () => void;\n /** Called on listener error — and at once on joining a listener that has already errored. */\n onError?: (error: Error) => void;\n}\n\n/**\n * Subscribe to a single document via a shared, reference-counted listener.\n * Returns an idempotent unsubscribe function; the underlying Firestore listener is\n * detached only when the last subscriber for that id unsubscribes.\n */\nexport function subscribeDoc(params: SubscribeDocParams): () => void {\n const { queryClient, db, collectionPath, queryKeyPrefix, id, onUpdate, onError } = params;\n const registry = getRegistry(queryClient);\n const key = registryKey(queryKeyPrefix, id);\n\n let entry = registry.get(key);\n if (!entry) {\n const created: Entry = {\n unsubscribe: () => {},\n subscribers: new Set<Subscriber>(),\n error: null,\n };\n const subscribers = created.subscribers;\n const ref = doc(db, collectionPath, id);\n created.unsubscribe = onSnapshot(\n ref,\n (snapshot) => {\n created.error = null;\n queryClient.setQueryData(\n [queryKeyPrefix, id],\n snapshot.exists() ? { id: snapshot.id, ...snapshot.data() } : null,\n { updatedAt: Date.now() },\n );\n // Copy before iterating: an onUpdate could (re-entrantly) unsubscribe.\n for (const sub of [...subscribers]) sub.onUpdate();\n },\n (error) => {\n // Write `null` so the id resolves — as a failure, through the kept error — instead\n // of loading forever: subscribe-mode loading (\"no cache entry yet\") would otherwise\n // stay true for ids whose listener can never land a snapshot.\n created.error = error;\n queryClient.setQueryData([queryKeyPrefix, id], null, { updatedAt: Date.now() });\n for (const sub of [...subscribers]) sub.onError?.(error);\n },\n );\n entry = created;\n registry.set(key, entry);\n }\n\n const subscriber: Subscriber = { onUpdate, onError };\n entry.subscribers.add(subscriber);\n if (entry.error) onError?.(entry.error);\n\n let released = false;\n return () => {\n if (released) return;\n released = true;\n const current = registry.get(key);\n if (!current) return;\n current.subscribers.delete(subscriber);\n if (current.subscribers.size === 0) {\n current.unsubscribe();\n registry.delete(key);\n // A failed listener's `null` is a placeholder, not a read: once nothing owns the key,\n // mark it stale so a remaining one-shot reader reads the document for itself.\n if (current.error) {\n void queryClient.invalidateQueries({ queryKey: [queryKeyPrefix, id], exact: true });\n }\n }\n };\n}\n\n/** Test-only: number of active shared listeners for a QueryClient. */\nexport function __activeListenerCount(queryClient: QueryClient): number {\n return registries.get(queryClient)?.size ?? 0;\n}\n"]}
@@ -1,4 +1,5 @@
1
- import type { InfinitePage, WithId } from './types.js';
1
+ import type { WithId } from '../../firestore/types.js';
2
+ import type { InfinitePage } from './types.js';
2
3
  /**
3
4
  * Flatten infinite query pages into a single array of items.
4
5
  *
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infinite-helpers.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/infinite-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,IAAI,EAAE;IAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAA;CAAE,GAAG,SAAS,GAC7C,MAAM,CAAC,CAAC,CAAC,EAAE,CAGb;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,IAAI,EAAE;IAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAA;CAAE,GAAG,SAAS,GAAG,MAAM,CAG9F"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infinite-helpers.js","sourceRoot":"","sources":["../../../src/react/firestore/infinite-helpers.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAA8C;IAE9C,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAClD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAI,IAA8C;IACpF,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,CAAC;IACpB,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AACtE,CAAC","sourcesContent":["import type { WithId } from '../../firestore/types.js';\nimport type { InfinitePage } from './types.js';\n\n/**\n * Flatten infinite query pages into a single array of items.\n *\n * @example\n * ```tsx\n * const { data } = useFirestoreInfinite<Post>({ ... });\n * const posts = flattenInfiniteData(data);\n * ```\n */\nexport function flattenInfiniteData<T>(\n data: { pages: InfinitePage<T>[] } | undefined,\n): WithId<T>[] {\n if (!data) return [];\n return data.pages.flatMap((page) => page.items);\n}\n\n/**\n * Get total count of items across all loaded pages.\n */\nexport function getInfiniteDataCount<T>(data: { pages: InfinitePage<T>[] } | undefined): number {\n if (!data) return 0;\n return data.pages.reduce((sum, page) => sum + page.items.length, 0);\n}\n"]}
@@ -0,0 +1,144 @@
1
+ import type { DocumentData, DocumentSnapshot, QueryDocumentSnapshot, QueryConstraint } from 'firebase/firestore';
2
+ import type { WithId, FirestoreBaseOptions } from '../../firestore/types.js';
3
+ /**
4
+ * Extract document data with ID from a QueryDocumentSnapshot.
5
+ */
6
+ export declare function docWithId<T extends DocumentData>(snap: QueryDocumentSnapshot<T>): WithId<T>;
7
+ /**
8
+ * Options for single document queries.
9
+ */
10
+ export interface FirestoreDocOptions<T> extends FirestoreBaseOptions {
11
+ /** Full document path (e.g., 'users/abc123') */
12
+ docPath: string;
13
+ /** Enable realtime updates via onSnapshot (default: false) */
14
+ subscribe?: boolean;
15
+ /** Transform function applied to document data. Receives data WITH id included. */
16
+ select?: (data: DocumentData & {
17
+ id: string;
18
+ }) => T;
19
+ }
20
+ /**
21
+ * Options for collection queries.
22
+ */
23
+ export interface FirestoreCollectionOptions<T> extends FirestoreBaseOptions {
24
+ /** Collection path (e.g., 'users' or 'entities/abc/members') */
25
+ collectionPath: string;
26
+ /** Firestore query constraints (where, orderBy, etc.) */
27
+ constraints?: QueryConstraint[];
28
+ /** Enable realtime updates via onSnapshot (default: false) */
29
+ subscribe?: boolean;
30
+ /** Transform function applied to each document. Receives data WITH id included. */
31
+ select?: (data: DocumentData & {
32
+ id: string;
33
+ }) => T;
34
+ }
35
+ /**
36
+ * Options for infinite scroll queries.
37
+ */
38
+ export interface FirestoreInfiniteOptions<T> extends FirestoreBaseOptions {
39
+ /** Collection path */
40
+ collectionPath: string;
41
+ /** Firestore query constraints (where, orderBy - required for pagination) */
42
+ constraints?: QueryConstraint[];
43
+ /** Number of items per page (default: 20) */
44
+ pageSize?: number;
45
+ /** Transform function applied to each document. Receives data WITH id included. */
46
+ select?: (data: DocumentData & {
47
+ id: string;
48
+ }) => T;
49
+ }
50
+ /**
51
+ * Options for paginated queries with page numbers.
52
+ */
53
+ export interface FirestorePaginatedOptions<T> extends FirestoreBaseOptions {
54
+ /** Collection path */
55
+ collectionPath: string;
56
+ /** Firestore query constraints */
57
+ constraints?: QueryConstraint[];
58
+ /** Number of items per page (default: 10) */
59
+ pageSize?: number;
60
+ /** Initial page number (default: 1) */
61
+ initialPage?: number;
62
+ /** Re-read the displayed page every `refetchInterval` ms while it is mounted (omit for none). */
63
+ refetchInterval?: number;
64
+ /** Transform function applied to each document. Receives data WITH id included. */
65
+ select?: (data: DocumentData & {
66
+ id: string;
67
+ }) => T;
68
+ }
69
+ /**
70
+ * Options for count() aggregation queries.
71
+ */
72
+ export interface FirestoreCountOptions extends FirestoreBaseOptions {
73
+ /** Collection path */
74
+ collectionPath: string;
75
+ /** Firestore query constraints (where, etc.) applied before counting */
76
+ constraints?: QueryConstraint[];
77
+ /** Polling interval in ms (omit for no polling) */
78
+ refetchInterval?: number;
79
+ }
80
+ /**
81
+ * Options for a live newest-window + cursor-bridged older-pages list
82
+ * (e.g. chat messages, live comment threads, live activity feeds). The newest
83
+ * page is a realtime onSnapshot window; older pages load on demand via getDocs,
84
+ * seeded from the window's oldest document so there is no gap or re-read.
85
+ */
86
+ export interface FirestoreLiveInfiniteOptions<T = DocumentData & {
87
+ id: string;
88
+ }> {
89
+ /** Collection path (slash-joined for nested paths, e.g. 'chats/t1/messages'). */
90
+ collectionPath: string;
91
+ /** React Query cache key. The older-pages query is keyed under `[...queryKey, 'older']`. */
92
+ queryKey: readonly unknown[];
93
+ /** Field both the live window and older pages order by (descending under the hood). */
94
+ orderByField: string;
95
+ /** Equality/filter constraints. Do NOT pass orderBy/limit/startAfter — the hook adds those. */
96
+ constraints?: QueryConstraint[];
97
+ /** Items per page / live-window size (default 20, capped at 100). */
98
+ pageSize?: number;
99
+ /** When false, no listener is opened and no pages are fetched. */
100
+ enabled?: boolean;
101
+ /** Map each raw doc (id included) to the item shape. Default: identity (WithId). */
102
+ select?: (data: DocumentData & {
103
+ id: string;
104
+ }) => T;
105
+ /** Output ordering of the merged list, by `orderByField` as Firestore orders it. Default 'asc' (oldest → newest). */
106
+ sort?: 'asc' | 'desc';
107
+ }
108
+ /**
109
+ * Result page for infinite queries.
110
+ */
111
+ export interface InfinitePage<T> {
112
+ items: WithId<T>[];
113
+ lastDoc: DocumentSnapshot | null;
114
+ size: number;
115
+ }
116
+ /**
117
+ * One page's cache entry for `useFirestorePaginated`, stored at `[...queryKey, 'page', n]`:
118
+ * the page's rows, its last row (the next page's cursor; `null` for an empty page), and
119
+ * whether a further page exists (read with a one-row look-ahead), and the id of the row it
120
+ * was read after (`null` for page 1), checked against the previous page's cursor.
121
+ */
122
+ export interface PaginatedPage<T> {
123
+ items: T[];
124
+ cursor: DocumentSnapshot | null;
125
+ hasMore: boolean;
126
+ after: string | null;
127
+ }
128
+ /**
129
+ * Mutation operation types for batch mutations.
130
+ */
131
+ export type MutationOperation = {
132
+ type: 'set';
133
+ docPath: string;
134
+ data: DocumentData;
135
+ merge?: boolean;
136
+ } | {
137
+ type: 'update';
138
+ docPath: string;
139
+ data: DocumentData;
140
+ } | {
141
+ type: 'delete';
142
+ docPath: string;
143
+ };
144
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Extract document data with ID from a QueryDocumentSnapshot.
3
+ */
4
+ export function docWithId(snap) {
5
+ return { id: snap.id, ...snap.data() };
6
+ }
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/react/firestore/types.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,MAAM,UAAU,SAAS,CAAyB,IAA8B;IAC9E,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;AACzC,CAAC","sourcesContent":["import type {\n DocumentData,\n DocumentSnapshot,\n QueryDocumentSnapshot,\n QueryConstraint,\n} from 'firebase/firestore';\nimport type { WithId, FirestoreBaseOptions } from '../../firestore/types.js';\n\n/**\n * Extract document data with ID from a QueryDocumentSnapshot.\n */\nexport function docWithId<T extends DocumentData>(snap: QueryDocumentSnapshot<T>): WithId<T> {\n return { id: snap.id, ...snap.data() };\n}\n\n/**\n * Options for single document queries.\n */\nexport interface FirestoreDocOptions<T> extends FirestoreBaseOptions {\n /** Full document path (e.g., 'users/abc123') */\n docPath: string;\n /** Enable realtime updates via onSnapshot (default: false) */\n subscribe?: boolean;\n /** Transform function applied to document data. Receives data WITH id included. */\n select?: (data: DocumentData & { id: string }) => T;\n}\n\n/**\n * Options for collection queries.\n */\nexport interface FirestoreCollectionOptions<T> extends FirestoreBaseOptions {\n /** Collection path (e.g., 'users' or 'entities/abc/members') */\n collectionPath: string;\n /** Firestore query constraints (where, orderBy, etc.) */\n constraints?: QueryConstraint[];\n /** Enable realtime updates via onSnapshot (default: false) */\n subscribe?: boolean;\n /** Transform function applied to each document. Receives data WITH id included. */\n select?: (data: DocumentData & { id: string }) => T;\n}\n\n/**\n * Options for infinite scroll queries.\n */\nexport interface FirestoreInfiniteOptions<T> extends FirestoreBaseOptions {\n /** Collection path */\n collectionPath: string;\n /** Firestore query constraints (where, orderBy - required for pagination) */\n constraints?: QueryConstraint[];\n /** Number of items per page (default: 20) */\n pageSize?: number;\n /** Transform function applied to each document. Receives data WITH id included. */\n select?: (data: DocumentData & { id: string }) => T;\n}\n\n/**\n * Options for paginated queries with page numbers.\n */\nexport interface FirestorePaginatedOptions<T> extends FirestoreBaseOptions {\n /** Collection path */\n collectionPath: string;\n /** Firestore query constraints */\n constraints?: QueryConstraint[];\n /** Number of items per page (default: 10) */\n pageSize?: number;\n /** Initial page number (default: 1) */\n initialPage?: number;\n /** Re-read the displayed page every `refetchInterval` ms while it is mounted (omit for none). */\n refetchInterval?: number;\n /** Transform function applied to each document. Receives data WITH id included. */\n select?: (data: DocumentData & { id: string }) => T;\n}\n\n/**\n * Options for count() aggregation queries.\n */\nexport interface FirestoreCountOptions extends FirestoreBaseOptions {\n /** Collection path */\n collectionPath: string;\n /** Firestore query constraints (where, etc.) applied before counting */\n constraints?: QueryConstraint[];\n /** Polling interval in ms (omit for no polling) */\n refetchInterval?: number;\n}\n\n/**\n * Options for a live newest-window + cursor-bridged older-pages list\n * (e.g. chat messages, live comment threads, live activity feeds). The newest\n * page is a realtime onSnapshot window; older pages load on demand via getDocs,\n * seeded from the window's oldest document so there is no gap or re-read.\n */\nexport interface FirestoreLiveInfiniteOptions<T = DocumentData & { id: string }> {\n /** Collection path (slash-joined for nested paths, e.g. 'chats/t1/messages'). */\n collectionPath: string;\n /** React Query cache key. The older-pages query is keyed under `[...queryKey, 'older']`. */\n queryKey: readonly unknown[];\n /** Field both the live window and older pages order by (descending under the hood). */\n orderByField: string;\n /** Equality/filter constraints. Do NOT pass orderBy/limit/startAfter — the hook adds those. */\n constraints?: QueryConstraint[];\n /** Items per page / live-window size (default 20, capped at 100). */\n pageSize?: number;\n /** When false, no listener is opened and no pages are fetched. */\n enabled?: boolean;\n /** Map each raw doc (id included) to the item shape. Default: identity (WithId). */\n select?: (data: DocumentData & { id: string }) => T;\n /** Output ordering of the merged list, by `orderByField` as Firestore orders it. Default 'asc' (oldest → newest). */\n sort?: 'asc' | 'desc';\n}\n\n/**\n * Result page for infinite queries.\n */\nexport interface InfinitePage<T> {\n items: WithId<T>[];\n lastDoc: DocumentSnapshot | null;\n size: number;\n}\n\n/**\n * One page's cache entry for `useFirestorePaginated`, stored at `[...queryKey, 'page', n]`:\n * the page's rows, its last row (the next page's cursor; `null` for an empty page), and\n * whether a further page exists (read with a one-row look-ahead), and the id of the row it\n * was read after (`null` for page 1), checked against the previous page's cursor.\n */\nexport interface PaginatedPage<T> {\n items: T[];\n cursor: DocumentSnapshot | null;\n hasMore: boolean;\n after: string | null;\n}\n\n/**\n * Mutation operation types for batch mutations.\n */\nexport type MutationOperation =\n | { type: 'set'; docPath: string; data: DocumentData; merge?: boolean }\n | { type: 'update'; docPath: string; data: DocumentData }\n | { type: 'delete'; docPath: string };\n"]}
@@ -1,6 +1,7 @@
1
1
  import { type UseQueryResult } from '@tanstack/react-query';
2
2
  import { type DocumentData } from 'firebase/firestore';
3
- import type { FirestoreCollectionOptions, WithId, WithSourceState } from '../../firestore/types.js';
3
+ import type { WithId, WithSourceState } from '../../firestore/types.js';
4
+ import type { FirestoreCollectionOptions } from './types.js';
4
5
  /**
5
6
  * Fetch all documents from a Firestore collection with optional realtime updates.
6
7
  * Use this for small collections where you need all documents at once.
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-collection.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-collection.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,KAAK,EACV,0BAA0B,EAC1B,MAAM,EACN,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAElC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC5E,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,OAAc,EACd,SAAiB,EACjB,SAAS,EACT,MAAM,EACN,MAAM,GACP,EAAE,0BAA0B,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CA4GrF"}
1
+ {"version":3,"file":"use-firestore-collection.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-collection.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAA0C,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAI/F,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC5E,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,OAAc,EACd,SAAiB,EACjB,SAAS,EACT,MAAM,EACN,MAAM,GACP,EAAE,0BAA0B,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAyHrF"}
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { useEffect } from 'react';
3
3
  import { useQuery, useQueryClient } from '@tanstack/react-query';
4
- import { collection, query, getDocs, onSnapshot, } from 'firebase/firestore';
4
+ import { collection, query, getDocs, onSnapshot } from 'firebase/firestore';
5
5
  import { useFirestoreDb } from './context.js';
6
6
  import { RESUBSCRIBE_DELAYS_MS, isPermissionDeniedError } from './resubscribe.js';
7
7
  import { useSubscribedResult, useSubscriptionStatus } from './subscription-status.js';
@@ -52,9 +52,7 @@ export function useFirestoreCollection({ collectionPath, queryKey, constraints =
52
52
  if (disposed)
53
53
  return;
54
54
  const collectionRef = collection(db, collectionPath);
55
- const q = constraints.length > 0
56
- ? query(collectionRef, ...constraints)
57
- : collectionRef;
55
+ const q = constraints.length > 0 ? query(collectionRef, ...constraints) : collectionRef;
58
56
  currentUnsubscribe = onSnapshot(q, { includeMetadataChanges: true }, (snapshot) => {
59
57
  attempt = 0;
60
58
  updateStatus({ error: null, restart: null });
@@ -66,7 +64,13 @@ export function useFirestoreCollection({ collectionPath, queryKey, constraints =
66
64
  });
67
65
  queryClient.setQueryData(queryKey, items);
68
66
  const fromCache = snapshot.metadata?.fromCache ?? false;
69
- updateStatus((prev) => ({ sourceState: fromCache ? (prev.sourceState === 'connecting' ? 'connecting' : 'offline') : 'live' }));
67
+ updateStatus((prev) => ({
68
+ sourceState: fromCache
69
+ ? prev.sourceState === 'connecting'
70
+ ? 'connecting'
71
+ : 'offline'
72
+ : 'live',
73
+ }));
70
74
  }, (error) => {
71
75
  // Heal a transient permission-denied by resubscribing on the backoff ladder,
72
76
  // keeping the last-known data visible. Only surface the error once the ladder
@@ -95,14 +99,23 @@ export function useFirestoreCollection({ collectionPath, queryKey, constraints =
95
99
  // queryKey & constraints are tracked via their stringified forms above; select is intentionally
96
100
  // excluded so an inline caller function doesn't force a re-subscribe on every render.
97
101
  // eslint-disable-next-line react-hooks/exhaustive-deps
98
- }, [db, collectionPath, queryKeyMemo, constraintsMemo, enabled, subscribe, restarts, queryClient, resetStatus, updateStatus]);
102
+ }, [
103
+ db,
104
+ collectionPath,
105
+ queryKeyMemo,
106
+ constraintsMemo,
107
+ enabled,
108
+ subscribe,
109
+ restarts,
110
+ queryClient,
111
+ resetStatus,
112
+ updateStatus,
113
+ ]);
99
114
  const queryResult = useQuery({
100
115
  queryKey,
101
116
  queryFn: async () => {
102
117
  const collectionRef = collection(db, collectionPath);
103
- const q = constraints.length > 0
104
- ? query(collectionRef, ...constraints)
105
- : collectionRef;
118
+ const q = constraints.length > 0 ? query(collectionRef, ...constraints) : collectionRef;
106
119
  const snapshot = await getDocs(q);
107
120
  return snapshot.docs.map((docSnap) => {
108
121
  const rawData = docSnap.data();
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-collection.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-collection.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\nimport { useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';\nimport {\n collection,\n query,\n getDocs,\n onSnapshot,\n type DocumentData,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport { RESUBSCRIBE_DELAYS_MS, isPermissionDeniedError } from './resubscribe.js';\nimport { useSubscribedResult, useSubscriptionStatus } from './subscription-status.js';\nimport type {\n FirestoreCollectionOptions,\n WithId,\n WithSourceState,\n} from '../../firestore/types.js';\n\n/**\n * Fetch all documents from a Firestore collection with optional realtime updates.\n * Use this for small collections where you need all documents at once.\n * For large collections, use useFirestoreInfinite or useFirestorePaginated.\n * * @example\n * ```tsx\n * // Fetch all channels in an entity\n * const { data: channels } = useFirestoreCollection<Channel>({\n * collectionPath: `entities/${entityId}/channels`,\n * queryKey: ['channels', entityId],\n * constraints: [orderBy('createdAt', 'asc')],\n * });\n * * // With realtime updates\n * const { data: members } = useFirestoreCollection<Member>({\n * collectionPath: `entities/${entityId}/members`,\n * queryKey: ['members', entityId],\n * subscribe: true,\n * });\n * ```\n */\nexport function useFirestoreCollection<T extends DocumentData = DocumentData>({\n collectionPath,\n queryKey,\n constraints = [],\n enabled = true,\n subscribe = false,\n staleTime,\n gcTime,\n select,\n}: FirestoreCollectionOptions<T>): WithSourceState<UseQueryResult<WithId<T>[], Error>> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n // STABILIZE DEPENDENCIES:\n // We stringify these because [where('a', '==', 'b')] !== [where('a', '==', 'b')] in JS.\n const constraintsMemo = JSON.stringify(constraints);\n const queryKeyMemo = JSON.stringify(queryKey);\n\n // Listener errors can't surface through useQuery (the queryFn is disabled while\n // subscribed), so they're tracked per subscription identity and merged into the result.\n const subscription = useSubscriptionStatus(\n JSON.stringify([collectionPath, queryKeyMemo, constraintsMemo, enabled, subscribe]),\n );\n const { reset: resetStatus, update: updateStatus, restarts } = subscription;\n\n useEffect(() => {\n if (!subscribe || !enabled) return;\n\n resetStatus();\n\n // Bounded resubscribe: a transient `permission-denied` (e.g. a failed mid-session\n // App Check token refresh) is retried on the RESUBSCRIBE_DELAYS_MS ladder before the\n // error is surfaced. `attempt` resets on any healthy snapshot; `disposed`/timers are\n // cleaned up on unmount so a pending retry never fires after teardown.\n let currentUnsubscribe: (() => void) | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let attempt = 0;\n let disposed = false;\n\n const start = () => {\n if (disposed) return;\n const collectionRef = collection(db, collectionPath);\n const q = constraints.length > 0\n ? query(collectionRef, ...constraints)\n : collectionRef;\n\n currentUnsubscribe = onSnapshot(\n q,\n { includeMetadataChanges: true },\n (snapshot) => {\n attempt = 0;\n updateStatus({ error: null, restart: null });\n const items = snapshot.docs.map((docSnap) => {\n const rawData = docSnap.data();\n const dataWithId = { id: docSnap.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n return data as WithId<T>;\n });\n queryClient.setQueryData(queryKey, items);\n const fromCache = snapshot.metadata?.fromCache ?? false;\n updateStatus((prev) => ({ sourceState: fromCache ? (prev.sourceState === 'connecting' ? 'connecting' : 'offline') : 'live' }));\n },\n (error) => {\n // Heal a transient permission-denied by resubscribing on the backoff ladder,\n // keeping the last-known data visible. Only surface the error once the ladder\n // is exhausted; any other error surfaces immediately, as before.\n if (isPermissionDeniedError(error) && attempt < RESUBSCRIBE_DELAYS_MS.length) {\n currentUnsubscribe?.();\n currentUnsubscribe = null;\n const delay = RESUBSCRIBE_DELAYS_MS[attempt];\n attempt += 1;\n updateStatus({ sourceState: 'connecting' });\n retryTimer = setTimeout(start, delay);\n return;\n }\n console.error('[useFirestoreCollection] Subscription error:', error);\n updateStatus({ error, sourceState: 'error', restart: null });\n queryClient.setQueryData(queryKey, undefined);\n }\n );\n };\n\n start();\n\n return () => {\n disposed = true;\n if (retryTimer) clearTimeout(retryTimer);\n currentUnsubscribe?.();\n };\n // queryKey & constraints are tracked via their stringified forms above; select is intentionally\n // excluded so an inline caller function doesn't force a re-subscribe on every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [db, collectionPath, queryKeyMemo, constraintsMemo, enabled, subscribe, restarts, queryClient, resetStatus, updateStatus]);\n\n const queryResult = useQuery({\n queryKey,\n queryFn: async (): Promise<WithId<T>[]> => {\n const collectionRef = collection(db, collectionPath);\n const q = constraints.length > 0\n ? query(collectionRef, ...constraints)\n : collectionRef;\n\n const snapshot = await getDocs(q);\n return snapshot.docs.map((docSnap) => {\n const rawData = docSnap.data();\n // Include id in data passed to select, so it can be renamed/transformed\n const dataWithId = { id: docSnap.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n return data as WithId<T>;\n });\n },\n enabled: enabled && !subscribe,\n staleTime: subscribe ? Infinity : staleTime,\n gcTime,\n });\n\n return useSubscribedResult(queryResult, subscribe, enabled, subscription);\n}\n"]}
1
+ {"version":3,"file":"use-firestore-collection.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-collection.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\nimport { useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';\nimport { collection, query, getDocs, onSnapshot, type DocumentData } from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport { RESUBSCRIBE_DELAYS_MS, isPermissionDeniedError } from './resubscribe.js';\nimport { useSubscribedResult, useSubscriptionStatus } from './subscription-status.js';\nimport type { WithId, WithSourceState } from '../../firestore/types.js';\nimport type { FirestoreCollectionOptions } from './types.js';\n\n/**\n * Fetch all documents from a Firestore collection with optional realtime updates.\n * Use this for small collections where you need all documents at once.\n * For large collections, use useFirestoreInfinite or useFirestorePaginated.\n * * @example\n * ```tsx\n * // Fetch all channels in an entity\n * const { data: channels } = useFirestoreCollection<Channel>({\n * collectionPath: `entities/${entityId}/channels`,\n * queryKey: ['channels', entityId],\n * constraints: [orderBy('createdAt', 'asc')],\n * });\n * * // With realtime updates\n * const { data: members } = useFirestoreCollection<Member>({\n * collectionPath: `entities/${entityId}/members`,\n * queryKey: ['members', entityId],\n * subscribe: true,\n * });\n * ```\n */\nexport function useFirestoreCollection<T extends DocumentData = DocumentData>({\n collectionPath,\n queryKey,\n constraints = [],\n enabled = true,\n subscribe = false,\n staleTime,\n gcTime,\n select,\n}: FirestoreCollectionOptions<T>): WithSourceState<UseQueryResult<WithId<T>[], Error>> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n\n // STABILIZE DEPENDENCIES:\n // We stringify these because [where('a', '==', 'b')] !== [where('a', '==', 'b')] in JS.\n const constraintsMemo = JSON.stringify(constraints);\n const queryKeyMemo = JSON.stringify(queryKey);\n\n // Listener errors can't surface through useQuery (the queryFn is disabled while\n // subscribed), so they're tracked per subscription identity and merged into the result.\n const subscription = useSubscriptionStatus(\n JSON.stringify([collectionPath, queryKeyMemo, constraintsMemo, enabled, subscribe]),\n );\n const { reset: resetStatus, update: updateStatus, restarts } = subscription;\n\n useEffect(() => {\n if (!subscribe || !enabled) return;\n\n resetStatus();\n\n // Bounded resubscribe: a transient `permission-denied` (e.g. a failed mid-session\n // App Check token refresh) is retried on the RESUBSCRIBE_DELAYS_MS ladder before the\n // error is surfaced. `attempt` resets on any healthy snapshot; `disposed`/timers are\n // cleaned up on unmount so a pending retry never fires after teardown.\n let currentUnsubscribe: (() => void) | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let attempt = 0;\n let disposed = false;\n\n const start = () => {\n if (disposed) return;\n const collectionRef = collection(db, collectionPath);\n const q = constraints.length > 0 ? query(collectionRef, ...constraints) : collectionRef;\n\n currentUnsubscribe = onSnapshot(\n q,\n { includeMetadataChanges: true },\n (snapshot) => {\n attempt = 0;\n updateStatus({ error: null, restart: null });\n const items = snapshot.docs.map((docSnap) => {\n const rawData = docSnap.data();\n const dataWithId = { id: docSnap.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n return data as WithId<T>;\n });\n queryClient.setQueryData(queryKey, items);\n const fromCache = snapshot.metadata?.fromCache ?? false;\n updateStatus((prev) => ({\n sourceState: fromCache\n ? prev.sourceState === 'connecting'\n ? 'connecting'\n : 'offline'\n : 'live',\n }));\n },\n (error) => {\n // Heal a transient permission-denied by resubscribing on the backoff ladder,\n // keeping the last-known data visible. Only surface the error once the ladder\n // is exhausted; any other error surfaces immediately, as before.\n if (isPermissionDeniedError(error) && attempt < RESUBSCRIBE_DELAYS_MS.length) {\n currentUnsubscribe?.();\n currentUnsubscribe = null;\n const delay = RESUBSCRIBE_DELAYS_MS[attempt];\n attempt += 1;\n updateStatus({ sourceState: 'connecting' });\n retryTimer = setTimeout(start, delay);\n return;\n }\n console.error('[useFirestoreCollection] Subscription error:', error);\n updateStatus({ error, sourceState: 'error', restart: null });\n queryClient.setQueryData(queryKey, undefined);\n },\n );\n };\n\n start();\n\n return () => {\n disposed = true;\n if (retryTimer) clearTimeout(retryTimer);\n currentUnsubscribe?.();\n };\n // queryKey & constraints are tracked via their stringified forms above; select is intentionally\n // excluded so an inline caller function doesn't force a re-subscribe on every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n db,\n collectionPath,\n queryKeyMemo,\n constraintsMemo,\n enabled,\n subscribe,\n restarts,\n queryClient,\n resetStatus,\n updateStatus,\n ]);\n\n const queryResult = useQuery({\n queryKey,\n queryFn: async (): Promise<WithId<T>[]> => {\n const collectionRef = collection(db, collectionPath);\n const q = constraints.length > 0 ? query(collectionRef, ...constraints) : collectionRef;\n\n const snapshot = await getDocs(q);\n return snapshot.docs.map((docSnap) => {\n const rawData = docSnap.data();\n // Include id in data passed to select, so it can be renamed/transformed\n const dataWithId = { id: docSnap.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n return data as WithId<T>;\n });\n },\n enabled: enabled && !subscribe,\n staleTime: subscribe ? Infinity : staleTime,\n gcTime,\n });\n\n return useSubscribedResult(queryResult, subscribe, enabled, subscription);\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { type UseQueryResult } from '@tanstack/react-query';
2
- import type { FirestoreCountOptions } from '../../firestore/types.js';
2
+ import type { FirestoreCountOptions } from './types.js';
3
3
  /**
4
4
  * Count documents in a Firestore collection via a server-side `count()`
5
5
  * aggregation (cheap — it does not read the matching documents). Optional
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-count.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-count.ts"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEtE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,OAAc,EACd,SAAS,EACT,MAAM,EACN,eAAe,GAChB,EAAE,qBAAqB,GAAG,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,CAgBvD"}
1
+ {"version":3,"file":"use-firestore-count.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-count.ts"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,OAAc,EACd,SAAS,EACT,MAAM,EACN,eAAe,GAChB,EAAE,qBAAqB,GAAG,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,CAgBvD"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-count.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-count.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,QAAQ,EAAuB,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAChC,cAAc,EACd,QAAQ,EACR,WAAW,GAAG,EAAE,EAChB,OAAO,GAAG,IAAI,EACd,SAAS,EACT,MAAM,EACN,eAAe,GACO;IACtB,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IAE5B,OAAO,QAAQ,CAAC;QACd,QAAQ;QACR,OAAO,EAAE,KAAK,IAAqB,EAAE;YACnC,MAAM,aAAa,GAAG,UAAU,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;YACrD,MAAM,CAAC,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;YACxF,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,CAAC,CAAC,CAAC;YAC7C,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;QAC/B,CAAC;QACD,OAAO;QACP,SAAS;QACT,MAAM;QACN,eAAe;KAChB,CAAC,CAAC;AACL,CAAC","sourcesContent":["'use client';\n\nimport { useQuery, type UseQueryResult } from '@tanstack/react-query';\nimport { collection, query, getCountFromServer } from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport type { FirestoreCountOptions } from '../../firestore/types.js';\n\n/**\n * Count documents in a Firestore collection via a server-side `count()`\n * aggregation (cheap — it does not read the matching documents). Optional\n * polling via `refetchInterval`.\n *\n * @example\n * ```tsx\n * const { data: count } = useFirestoreCount({\n * collectionPath: 'activeUserNotifications',\n * queryKey: ['notifications', 'unread-count', userId],\n * constraints: [where('targetUserId', '==', userId), where('seenAt', '==', 0)],\n * refetchInterval: 30_000,\n * });\n * ```\n */\nexport function useFirestoreCount({\n collectionPath,\n queryKey,\n constraints = [],\n enabled = true,\n staleTime,\n gcTime,\n refetchInterval,\n}: FirestoreCountOptions): UseQueryResult<number, Error> {\n const db = useFirestoreDb();\n\n return useQuery({\n queryKey,\n queryFn: async (): Promise<number> => {\n const collectionRef = collection(db, collectionPath);\n const q = constraints.length > 0 ? query(collectionRef, ...constraints) : collectionRef;\n const snapshot = await getCountFromServer(q);\n return snapshot.data().count;\n },\n enabled,\n staleTime,\n gcTime,\n refetchInterval,\n });\n}\n"]}
1
+ {"version":3,"file":"use-firestore-count.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-count.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,QAAQ,EAAuB,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAChC,cAAc,EACd,QAAQ,EACR,WAAW,GAAG,EAAE,EAChB,OAAO,GAAG,IAAI,EACd,SAAS,EACT,MAAM,EACN,eAAe,GACO;IACtB,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IAE5B,OAAO,QAAQ,CAAC;QACd,QAAQ;QACR,OAAO,EAAE,KAAK,IAAqB,EAAE;YACnC,MAAM,aAAa,GAAG,UAAU,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;YACrD,MAAM,CAAC,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;YACxF,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,CAAC,CAAC,CAAC;YAC7C,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;QAC/B,CAAC;QACD,OAAO;QACP,SAAS;QACT,MAAM;QACN,eAAe;KAChB,CAAC,CAAC;AACL,CAAC","sourcesContent":["'use client';\n\nimport { useQuery, type UseQueryResult } from '@tanstack/react-query';\nimport { collection, query, getCountFromServer } from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport type { FirestoreCountOptions } from './types.js';\n\n/**\n * Count documents in a Firestore collection via a server-side `count()`\n * aggregation (cheap — it does not read the matching documents). Optional\n * polling via `refetchInterval`.\n *\n * @example\n * ```tsx\n * const { data: count } = useFirestoreCount({\n * collectionPath: 'activeUserNotifications',\n * queryKey: ['notifications', 'unread-count', userId],\n * constraints: [where('targetUserId', '==', userId), where('seenAt', '==', 0)],\n * refetchInterval: 30_000,\n * });\n * ```\n */\nexport function useFirestoreCount({\n collectionPath,\n queryKey,\n constraints = [],\n enabled = true,\n staleTime,\n gcTime,\n refetchInterval,\n}: FirestoreCountOptions): UseQueryResult<number, Error> {\n const db = useFirestoreDb();\n\n return useQuery({\n queryKey,\n queryFn: async (): Promise<number> => {\n const collectionRef = collection(db, collectionPath);\n const q = constraints.length > 0 ? query(collectionRef, ...constraints) : collectionRef;\n const snapshot = await getCountFromServer(q);\n return snapshot.data().count;\n },\n enabled,\n staleTime,\n gcTime,\n refetchInterval,\n });\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import { type UseQueryResult } from '@tanstack/react-query';
2
2
  import { type DocumentData } from 'firebase/firestore';
3
- import type { FirestoreDocOptions, WithId, WithSourceState } from '../../firestore/types.js';
3
+ import type { WithId, WithSourceState } from '../../firestore/types.js';
4
+ import type { FirestoreDocOptions } from './types.js';
4
5
  /**
5
6
  * Fetch a single Firestore document with optional realtime updates.
6
7
  * * @example
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-doc.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-doc.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAA2B,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIhF,OAAO,KAAK,EACV,mBAAmB,EACnB,MAAM,EACN,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAElC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EACrE,OAAO,EACP,QAAQ,EACR,OAAc,EACd,SAAiB,EACjB,SAAS,EACT,MAAM,EACN,MAAM,GACP,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,CAAC,CAoGnF"}
1
+ {"version":3,"file":"use-firestore-doc.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-doc.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAA2B,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIhF,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EACrE,OAAO,EACP,QAAQ,EACR,OAAc,EACd,SAAiB,EACjB,SAAS,EACT,MAAM,EACN,MAAM,GACP,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,CAAC,CAsHnF"}
@@ -67,7 +67,13 @@ export function useFirestoreDoc({ docPath, queryKey, enabled = true, subscribe =
67
67
  queryClient.setQueryData(queryKey, null);
68
68
  }
69
69
  const fromCache = snapshot.metadata?.fromCache ?? false;
70
- updateStatus((prev) => ({ sourceState: fromCache ? (prev.sourceState === 'connecting' ? 'connecting' : 'offline') : 'live' }));
70
+ updateStatus((prev) => ({
71
+ sourceState: fromCache
72
+ ? prev.sourceState === 'connecting'
73
+ ? 'connecting'
74
+ : 'offline'
75
+ : 'live',
76
+ }));
71
77
  }, (error) => {
72
78
  // Heal a transient permission-denied by resubscribing on the backoff ladder,
73
79
  // keeping the last-known data visible. Only surface the error once the ladder
@@ -96,7 +102,17 @@ export function useFirestoreDoc({ docPath, queryKey, enabled = true, subscribe =
96
102
  // Note: select intentionally excluded to prevent re-subscribing on every render if the caller
97
103
  // passes an inline function. queryKey is tracked via queryKeyMemo above.
98
104
  // eslint-disable-next-line react-hooks/exhaustive-deps
99
- }, [db, docPath, queryKeyMemo, enabled, subscribe, restarts, queryClient, resetStatus, updateStatus]);
105
+ }, [
106
+ db,
107
+ docPath,
108
+ queryKeyMemo,
109
+ enabled,
110
+ subscribe,
111
+ restarts,
112
+ queryClient,
113
+ resetStatus,
114
+ updateStatus,
115
+ ]);
100
116
  const query = useQuery({
101
117
  queryKey,
102
118
  queryFn: async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-doc.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-doc.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\nimport { useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';\nimport { doc, getDoc, onSnapshot, type DocumentData } from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport { RESUBSCRIBE_DELAYS_MS, isPermissionDeniedError } from './resubscribe.js';\nimport { useSubscribedResult, useSubscriptionStatus } from './subscription-status.js';\nimport type {\n FirestoreDocOptions,\n WithId,\n WithSourceState,\n} from '../../firestore/types.js';\n\n/**\n * Fetch a single Firestore document with optional realtime updates.\n * * @example\n * ```tsx\n * // Simple fetch\n * const { data: user } = useFirestoreDoc<User>({\n * docPath: `users/${userId}`,\n * queryKey: ['user', userId],\n * });\n * * // With realtime updates\n * const { data: entity } = useFirestoreDoc<Entity>({\n * docPath: `entities/${entityId}`,\n * queryKey: ['entity', entityId],\n * subscribe: true,\n * });\n * * // Conditional fetch\n * const { data: profile } = useFirestoreDoc<Profile>({\n * docPath: `profiles/${userId}`,\n * queryKey: ['profile', userId],\n * enabled: !!userId,\n * });\n * ```\n */\nexport function useFirestoreDoc<T extends DocumentData = DocumentData>({\n docPath,\n queryKey,\n enabled = true,\n subscribe = false,\n staleTime,\n gcTime,\n select,\n}: FirestoreDocOptions<T>): WithSourceState<UseQueryResult<WithId<T> | null, Error>> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n const queryKeyMemo = JSON.stringify(queryKey);\n\n // Listener errors can't surface through useQuery (the queryFn is disabled while\n // subscribed), so they're tracked per subscription identity and merged into the result.\n const subscription = useSubscriptionStatus(JSON.stringify([docPath, queryKeyMemo, enabled, subscribe]));\n const { reset: resetStatus, update: updateStatus, restarts } = subscription;\n\n // Set up realtime subscription\n useEffect(() => {\n if (!subscribe || !enabled) return;\n\n resetStatus();\n\n // Bounded resubscribe: a transient `permission-denied` (e.g. a failed mid-session\n // App Check token refresh) is retried on the RESUBSCRIBE_DELAYS_MS ladder before the\n // error is surfaced. `attempt` resets on any healthy snapshot; `disposed`/timers are\n // cleaned up on unmount so a pending retry never fires after teardown.\n let currentUnsubscribe: (() => void) | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let attempt = 0;\n let disposed = false;\n\n const start = () => {\n if (disposed) return;\n const docRef = doc(db, docPath);\n currentUnsubscribe = onSnapshot(\n docRef,\n { includeMetadataChanges: true },\n (snapshot) => {\n attempt = 0;\n updateStatus({ error: null, restart: null });\n if (snapshot.exists()) {\n const rawData = snapshot.data();\n // Include id in data passed to select, so it can be renamed/transformed\n const dataWithId = { id: snapshot.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n queryClient.setQueryData(queryKey, data as WithId<T>);\n } else {\n queryClient.setQueryData(queryKey, null);\n }\n const fromCache = snapshot.metadata?.fromCache ?? false;\n updateStatus((prev) => ({ sourceState: fromCache ? (prev.sourceState === 'connecting' ? 'connecting' : 'offline') : 'live' }));\n },\n (error) => {\n // Heal a transient permission-denied by resubscribing on the backoff ladder,\n // keeping the last-known data visible. Only surface the error once the ladder\n // is exhausted; any other error surfaces immediately, as before.\n if (isPermissionDeniedError(error) && attempt < RESUBSCRIBE_DELAYS_MS.length) {\n currentUnsubscribe?.();\n currentUnsubscribe = null;\n const delay = RESUBSCRIBE_DELAYS_MS[attempt];\n attempt += 1;\n updateStatus({ sourceState: 'connecting' });\n retryTimer = setTimeout(start, delay);\n return;\n }\n console.error('[useFirestoreDoc] Subscription error:', error);\n updateStatus({ error, sourceState: 'error', restart: null });\n queryClient.setQueryData(queryKey, undefined);\n }\n );\n };\n\n start();\n\n return () => {\n disposed = true;\n if (retryTimer) clearTimeout(retryTimer);\n currentUnsubscribe?.();\n };\n // Note: select intentionally excluded to prevent re-subscribing on every render if the caller\n // passes an inline function. queryKey is tracked via queryKeyMemo above.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [db, docPath, queryKeyMemo, enabled, subscribe, restarts, queryClient, resetStatus, updateStatus]);\n\n const query = useQuery({\n queryKey,\n queryFn: async (): Promise<WithId<T> | null> => {\n const docRef = doc(db, docPath);\n const snapshot = await getDoc(docRef);\n\n if (!snapshot.exists()) {\n return null;\n }\n\n const rawData = snapshot.data();\n // Include id in data passed to select, so it can be renamed/transformed\n const dataWithId = { id: snapshot.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n return data as WithId<T>;\n },\n enabled: enabled && !subscribe, // Don't fetch if subscribing (snapshot handles it)\n staleTime: subscribe ? Infinity : staleTime, // Realtime data is always fresh\n gcTime,\n });\n\n return useSubscribedResult(query, subscribe, enabled, subscription);\n}\n"]}
1
+ {"version":3,"file":"use-firestore-doc.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-doc.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\nimport { useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';\nimport { doc, getDoc, onSnapshot, type DocumentData } from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport { RESUBSCRIBE_DELAYS_MS, isPermissionDeniedError } from './resubscribe.js';\nimport { useSubscribedResult, useSubscriptionStatus } from './subscription-status.js';\nimport type { WithId, WithSourceState } from '../../firestore/types.js';\nimport type { FirestoreDocOptions } from './types.js';\n\n/**\n * Fetch a single Firestore document with optional realtime updates.\n * * @example\n * ```tsx\n * // Simple fetch\n * const { data: user } = useFirestoreDoc<User>({\n * docPath: `users/${userId}`,\n * queryKey: ['user', userId],\n * });\n * * // With realtime updates\n * const { data: entity } = useFirestoreDoc<Entity>({\n * docPath: `entities/${entityId}`,\n * queryKey: ['entity', entityId],\n * subscribe: true,\n * });\n * * // Conditional fetch\n * const { data: profile } = useFirestoreDoc<Profile>({\n * docPath: `profiles/${userId}`,\n * queryKey: ['profile', userId],\n * enabled: !!userId,\n * });\n * ```\n */\nexport function useFirestoreDoc<T extends DocumentData = DocumentData>({\n docPath,\n queryKey,\n enabled = true,\n subscribe = false,\n staleTime,\n gcTime,\n select,\n}: FirestoreDocOptions<T>): WithSourceState<UseQueryResult<WithId<T> | null, Error>> {\n const db = useFirestoreDb();\n const queryClient = useQueryClient();\n const queryKeyMemo = JSON.stringify(queryKey);\n\n // Listener errors can't surface through useQuery (the queryFn is disabled while\n // subscribed), so they're tracked per subscription identity and merged into the result.\n const subscription = useSubscriptionStatus(\n JSON.stringify([docPath, queryKeyMemo, enabled, subscribe]),\n );\n const { reset: resetStatus, update: updateStatus, restarts } = subscription;\n\n // Set up realtime subscription\n useEffect(() => {\n if (!subscribe || !enabled) return;\n\n resetStatus();\n\n // Bounded resubscribe: a transient `permission-denied` (e.g. a failed mid-session\n // App Check token refresh) is retried on the RESUBSCRIBE_DELAYS_MS ladder before the\n // error is surfaced. `attempt` resets on any healthy snapshot; `disposed`/timers are\n // cleaned up on unmount so a pending retry never fires after teardown.\n let currentUnsubscribe: (() => void) | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let attempt = 0;\n let disposed = false;\n\n const start = () => {\n if (disposed) return;\n const docRef = doc(db, docPath);\n currentUnsubscribe = onSnapshot(\n docRef,\n { includeMetadataChanges: true },\n (snapshot) => {\n attempt = 0;\n updateStatus({ error: null, restart: null });\n if (snapshot.exists()) {\n const rawData = snapshot.data();\n // Include id in data passed to select, so it can be renamed/transformed\n const dataWithId = { id: snapshot.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n queryClient.setQueryData(queryKey, data as WithId<T>);\n } else {\n queryClient.setQueryData(queryKey, null);\n }\n const fromCache = snapshot.metadata?.fromCache ?? false;\n updateStatus((prev) => ({\n sourceState: fromCache\n ? prev.sourceState === 'connecting'\n ? 'connecting'\n : 'offline'\n : 'live',\n }));\n },\n (error) => {\n // Heal a transient permission-denied by resubscribing on the backoff ladder,\n // keeping the last-known data visible. Only surface the error once the ladder\n // is exhausted; any other error surfaces immediately, as before.\n if (isPermissionDeniedError(error) && attempt < RESUBSCRIBE_DELAYS_MS.length) {\n currentUnsubscribe?.();\n currentUnsubscribe = null;\n const delay = RESUBSCRIBE_DELAYS_MS[attempt];\n attempt += 1;\n updateStatus({ sourceState: 'connecting' });\n retryTimer = setTimeout(start, delay);\n return;\n }\n console.error('[useFirestoreDoc] Subscription error:', error);\n updateStatus({ error, sourceState: 'error', restart: null });\n queryClient.setQueryData(queryKey, undefined);\n },\n );\n };\n\n start();\n\n return () => {\n disposed = true;\n if (retryTimer) clearTimeout(retryTimer);\n currentUnsubscribe?.();\n };\n // Note: select intentionally excluded to prevent re-subscribing on every render if the caller\n // passes an inline function. queryKey is tracked via queryKeyMemo above.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n db,\n docPath,\n queryKeyMemo,\n enabled,\n subscribe,\n restarts,\n queryClient,\n resetStatus,\n updateStatus,\n ]);\n\n const query = useQuery({\n queryKey,\n queryFn: async (): Promise<WithId<T> | null> => {\n const docRef = doc(db, docPath);\n const snapshot = await getDoc(docRef);\n\n if (!snapshot.exists()) {\n return null;\n }\n\n const rawData = snapshot.data();\n // Include id in data passed to select, so it can be renamed/transformed\n const dataWithId = { id: snapshot.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n return data as WithId<T>;\n },\n enabled: enabled && !subscribe, // Don't fetch if subscribing (snapshot handles it)\n staleTime: subscribe ? Infinity : staleTime, // Realtime data is always fresh\n gcTime,\n });\n\n return useSubscribedResult(query, subscribe, enabled, subscription);\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { type UseInfiniteQueryResult } from '@tanstack/react-query';
2
2
  import { type DocumentData, type DocumentSnapshot } from 'firebase/firestore';
3
- import type { FirestoreInfiniteOptions, InfinitePage } from '../../firestore/types.js';
3
+ import type { FirestoreInfiniteOptions, InfinitePage } from './types.js';
4
4
  /**
5
5
  * Infinite scroll pagination for Firestore collections.
6
6
  * Automatically handles cursor-based pagination with React Query.
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-infinite.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-infinite.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAML,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,wBAAwB,EAAE,YAAY,EAAU,MAAM,0BAA0B,CAAC;AAI/F;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC1E,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,QAA4B,EAC5B,OAAc,EACd,SAAS,EACT,MAAM,EACN,MAAM,GACP,EAAE,wBAAwB,CAAC,CAAC,CAAC,GAAG,sBAAsB,CAAC;IAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAAC,UAAU,EAAE,CAAC,gBAAgB,GAAG,IAAI,CAAC,EAAE,CAAA;CAAE,EAAE,KAAK,CAAC,CA0CpI"}
1
+ {"version":3,"file":"use-firestore-infinite.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-infinite.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAML,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,wBAAwB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC1E,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,QAA4B,EAC5B,OAAc,EACd,SAAS,EACT,MAAM,EACN,MAAM,GACP,EAAE,wBAAwB,CAAC,CAAC,CAAC,GAAG,sBAAsB,CACrD;IAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAAC,UAAU,EAAE,CAAC,gBAAgB,GAAG,IAAI,CAAC,EAAE,CAAA;CAAE,EACrE,KAAK,CACN,CA0CA"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-infinite.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-infinite.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,gBAAgB,EAA+B,MAAM,uBAAuB,CAAC;AACtF,OAAO,EACL,UAAU,EACV,KAAK,EACL,OAAO,EACP,KAAK,EACL,UAAU,GAGX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,oBAAoB,CAAwC,EAC1E,cAAc,EACd,QAAQ,EACR,WAAW,GAAG,EAAE,EAChB,QAAQ,GAAG,iBAAiB,EAC5B,OAAO,GAAG,IAAI,EACd,SAAS,EACT,MAAM,EACN,MAAM,GACsB;IAC5B,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IAE5B,OAAO,gBAAgB,CAAC;QACtB,QAAQ;QACR,OAAO,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,EAA4B,EAAE;YACzD,MAAM,aAAa,GAAG,UAAU,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;YAErD,+BAA+B;YAC/B,MAAM,gBAAgB,GAAG;gBACvB,GAAG,WAAW;gBACd,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,SAA6B,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjE,KAAK,CAAC,QAAQ,CAAC;aAChB,CAAC;YAEF,MAAM,CAAC,GAAG,KAAK,CAAC,aAAa,EAAE,GAAG,gBAAgB,CAAC,CAAC;YACpD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;YAElC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;gBAC1C,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;gBAC/B,wEAAwE;gBACxE,MAAM,UAAU,GAAG,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC;gBAClD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;gBACtD,OAAO,IAAiB,CAAC;YAC3B,CAAC,CAAC,CAAC;YAEH,OAAO;gBACL,KAAK;gBACL,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI;gBACxD,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM;aAC3B,CAAC;QACJ,CAAC;QACD,gBAAgB,EAAE,IAA+B;QACjD,gBAAgB,EAAE,CAAC,QAAQ,EAAE,EAAE;YAC7B,gEAAgE;YAChE,IAAI,QAAQ,CAAC,IAAI,GAAG,QAAQ;gBAAE,OAAO,SAAS,CAAC;YAC/C,OAAO,QAAQ,CAAC,OAAO,CAAC;QAC1B,CAAC;QACD,OAAO;QACP,SAAS;QACT,MAAM;KACP,CAAC,CAAC;AACL,CAAC","sourcesContent":["'use client';\n\nimport { useInfiniteQuery, type UseInfiniteQueryResult } from '@tanstack/react-query';\nimport {\n collection,\n query,\n getDocs,\n limit,\n startAfter,\n type DocumentData,\n type DocumentSnapshot,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport type { FirestoreInfiniteOptions, InfinitePage, WithId } from '../../firestore/types.js';\n\nconst DEFAULT_PAGE_SIZE = 20;\n\n/**\n * Infinite scroll pagination for Firestore collections.\n * Automatically handles cursor-based pagination with React Query.\n *\n * @example\n * ```tsx\n * const {\n * data,\n * fetchNextPage,\n * hasNextPage,\n * isFetchingNextPage,\n * isLoading,\n * } = useFirestoreInfinite<Post>({\n * collectionPath: 'posts',\n * queryKey: ['posts', 'feed'],\n * constraints: [\n * where('status', '==', 'published'),\n * orderBy('createdAt', 'desc'),\n * ],\n * pageSize: 20,\n * });\n *\n * // Flatten pages for rendering\n * const posts = data?.pages.flatMap(page => page.items) ?? [];\n *\n * // Load more on scroll\n * <InfiniteScroll onLoadMore={fetchNextPage} hasMore={hasNextPage} />\n * ```\n */\nexport function useFirestoreInfinite<T extends DocumentData = DocumentData>({\n collectionPath,\n queryKey,\n constraints = [],\n pageSize = DEFAULT_PAGE_SIZE,\n enabled = true,\n staleTime,\n gcTime,\n select,\n}: FirestoreInfiniteOptions<T>): UseInfiniteQueryResult<{ pages: InfinitePage<T>[]; pageParams: (DocumentSnapshot | null)[] }, Error> {\n const db = useFirestoreDb();\n\n return useInfiniteQuery({\n queryKey,\n queryFn: async ({ pageParam }): Promise<InfinitePage<T>> => {\n const collectionRef = collection(db, collectionPath);\n\n // Build query with constraints\n const queryConstraints = [\n ...constraints,\n ...(pageParam ? [startAfter(pageParam as DocumentSnapshot)] : []),\n limit(pageSize),\n ];\n\n const q = query(collectionRef, ...queryConstraints);\n const snapshot = await getDocs(q);\n\n const items = snapshot.docs.map((docSnap) => {\n const rawData = docSnap.data();\n // Include id in data passed to select, so it can be renamed/transformed\n const dataWithId = { id: docSnap.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n return data as WithId<T>;\n });\n\n return {\n items,\n lastDoc: snapshot.docs[snapshot.docs.length - 1] ?? null,\n size: snapshot.docs.length,\n };\n },\n initialPageParam: null as DocumentSnapshot | null,\n getNextPageParam: (lastPage) => {\n // If we got fewer items than requested, there are no more pages\n if (lastPage.size < pageSize) return undefined;\n return lastPage.lastDoc;\n },\n enabled,\n staleTime,\n gcTime,\n });\n}\n"]}
1
+ {"version":3,"file":"use-firestore-infinite.js","sourceRoot":"","sources":["../../../src/react/firestore/use-firestore-infinite.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,gBAAgB,EAA+B,MAAM,uBAAuB,CAAC;AACtF,OAAO,EACL,UAAU,EACV,KAAK,EACL,OAAO,EACP,KAAK,EACL,UAAU,GAGX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAI9C,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,oBAAoB,CAAwC,EAC1E,cAAc,EACd,QAAQ,EACR,WAAW,GAAG,EAAE,EAChB,QAAQ,GAAG,iBAAiB,EAC5B,OAAO,GAAG,IAAI,EACd,SAAS,EACT,MAAM,EACN,MAAM,GACsB;IAI5B,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IAE5B,OAAO,gBAAgB,CAAC;QACtB,QAAQ;QACR,OAAO,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,EAA4B,EAAE;YACzD,MAAM,aAAa,GAAG,UAAU,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;YAErD,+BAA+B;YAC/B,MAAM,gBAAgB,GAAG;gBACvB,GAAG,WAAW;gBACd,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,SAA6B,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjE,KAAK,CAAC,QAAQ,CAAC;aAChB,CAAC;YAEF,MAAM,CAAC,GAAG,KAAK,CAAC,aAAa,EAAE,GAAG,gBAAgB,CAAC,CAAC;YACpD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;YAElC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;gBAC1C,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;gBAC/B,wEAAwE;gBACxE,MAAM,UAAU,GAAG,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC;gBAClD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;gBACtD,OAAO,IAAiB,CAAC;YAC3B,CAAC,CAAC,CAAC;YAEH,OAAO;gBACL,KAAK;gBACL,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI;gBACxD,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM;aAC3B,CAAC;QACJ,CAAC;QACD,gBAAgB,EAAE,IAA+B;QACjD,gBAAgB,EAAE,CAAC,QAAQ,EAAE,EAAE;YAC7B,gEAAgE;YAChE,IAAI,QAAQ,CAAC,IAAI,GAAG,QAAQ;gBAAE,OAAO,SAAS,CAAC;YAC/C,OAAO,QAAQ,CAAC,OAAO,CAAC;QAC1B,CAAC;QACD,OAAO;QACP,SAAS;QACT,MAAM;KACP,CAAC,CAAC;AACL,CAAC","sourcesContent":["'use client';\n\nimport { useInfiniteQuery, type UseInfiniteQueryResult } from '@tanstack/react-query';\nimport {\n collection,\n query,\n getDocs,\n limit,\n startAfter,\n type DocumentData,\n type DocumentSnapshot,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from './context.js';\nimport type { WithId } from '../../firestore/types.js';\nimport type { FirestoreInfiniteOptions, InfinitePage } from './types.js';\n\nconst DEFAULT_PAGE_SIZE = 20;\n\n/**\n * Infinite scroll pagination for Firestore collections.\n * Automatically handles cursor-based pagination with React Query.\n *\n * @example\n * ```tsx\n * const {\n * data,\n * fetchNextPage,\n * hasNextPage,\n * isFetchingNextPage,\n * isLoading,\n * } = useFirestoreInfinite<Post>({\n * collectionPath: 'posts',\n * queryKey: ['posts', 'feed'],\n * constraints: [\n * where('status', '==', 'published'),\n * orderBy('createdAt', 'desc'),\n * ],\n * pageSize: 20,\n * });\n *\n * // Flatten pages for rendering\n * const posts = data?.pages.flatMap(page => page.items) ?? [];\n *\n * // Load more on scroll\n * <InfiniteScroll onLoadMore={fetchNextPage} hasMore={hasNextPage} />\n * ```\n */\nexport function useFirestoreInfinite<T extends DocumentData = DocumentData>({\n collectionPath,\n queryKey,\n constraints = [],\n pageSize = DEFAULT_PAGE_SIZE,\n enabled = true,\n staleTime,\n gcTime,\n select,\n}: FirestoreInfiniteOptions<T>): UseInfiniteQueryResult<\n { pages: InfinitePage<T>[]; pageParams: (DocumentSnapshot | null)[] },\n Error\n> {\n const db = useFirestoreDb();\n\n return useInfiniteQuery({\n queryKey,\n queryFn: async ({ pageParam }): Promise<InfinitePage<T>> => {\n const collectionRef = collection(db, collectionPath);\n\n // Build query with constraints\n const queryConstraints = [\n ...constraints,\n ...(pageParam ? [startAfter(pageParam as DocumentSnapshot)] : []),\n limit(pageSize),\n ];\n\n const q = query(collectionRef, ...queryConstraints);\n const snapshot = await getDocs(q);\n\n const items = snapshot.docs.map((docSnap) => {\n const rawData = docSnap.data();\n // Include id in data passed to select, so it can be renamed/transformed\n const dataWithId = { id: docSnap.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n return data as WithId<T>;\n });\n\n return {\n items,\n lastDoc: snapshot.docs[snapshot.docs.length - 1] ?? null,\n size: snapshot.docs.length,\n };\n },\n initialPageParam: null as DocumentSnapshot | null,\n getNextPageParam: (lastPage) => {\n // If we got fewer items than requested, there are no more pages\n if (lastPage.size < pageSize) return undefined;\n return lastPage.lastDoc;\n },\n enabled,\n staleTime,\n gcTime,\n });\n}\n"]}