@ttt-productions/query-core 0.14.0 → 0.14.2
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/cache-helpers.js.map +1 -1
- package/dist/defaults.js.map +1 -1
- package/dist/domain-events/cache-invalidation.d.ts +21 -0
- package/dist/domain-events/cache-invalidation.d.ts.map +1 -1
- package/dist/domain-events/cache-invalidation.js +58 -19
- package/dist/domain-events/cache-invalidation.js.map +1 -1
- package/dist/domain-events/create-domain-event-invalidator.js.map +1 -1
- package/dist/domain-events/index.js.map +1 -1
- package/dist/firestore/infinite-helpers.js.map +1 -1
- package/dist/firestore/types.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keys.js.map +1 -1
- package/dist/query-client.js.map +1 -1
- package/dist/react/firestore/absence-scheduler.d.ts +39 -0
- package/dist/react/firestore/absence-scheduler.d.ts.map +1 -0
- package/dist/react/firestore/absence-scheduler.js +182 -0
- package/dist/react/firestore/absence-scheduler.js.map +1 -0
- package/dist/react/firestore/batch-doc-loader.d.ts +45 -0
- package/dist/react/firestore/batch-doc-loader.d.ts.map +1 -0
- package/dist/react/firestore/batch-doc-loader.js +179 -0
- package/dist/react/firestore/batch-doc-loader.js.map +1 -0
- package/dist/react/firestore/context.js.map +1 -1
- package/dist/react/firestore/doc-subscription-registry.d.ts +9 -0
- package/dist/react/firestore/doc-subscription-registry.d.ts.map +1 -1
- package/dist/react/firestore/doc-subscription-registry.js +0 -0
- package/dist/react/firestore/doc-subscription-registry.js.map +1 -1
- package/dist/react/firestore/resubscribe.js.map +1 -1
- package/dist/react/firestore/use-firestore-collection.js.map +1 -1
- package/dist/react/firestore/use-firestore-count.js.map +1 -1
- package/dist/react/firestore/use-firestore-doc.js.map +1 -1
- package/dist/react/firestore/use-firestore-infinite.js.map +1 -1
- package/dist/react/firestore/use-firestore-live-infinite.js.map +1 -1
- package/dist/react/firestore/use-firestore-mutations.js.map +1 -1
- package/dist/react/firestore/use-firestore-paginated.js.map +1 -1
- package/dist/react/firestore/useBatchFirestoreDocs.d.ts +63 -21
- package/dist/react/firestore/useBatchFirestoreDocs.d.ts.map +1 -1
- package/dist/react/firestore/useBatchFirestoreDocs.js +117 -109
- package/dist/react/firestore/useBatchFirestoreDocs.js.map +1 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/provider.js.map +1 -1
- package/dist/react/search/use-firestore-search.js.map +1 -1
- package/dist/search/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-helpers.js","sourceRoot":"","sources":["../src/cache-helpers.ts"],"names":[],"mappings":"AAEA,uDAAuD;AACvD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAmB,EAAE,MAAgB;IAC5E,OAAO,MAAM,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,cAAc,CAAC,MAAmB,EAAE,MAAgB;IAClE,OAAO,MAAM,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;AAClE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAmB,EACnB,GAAa,EACb,OAAmC;IAEnC,MAAM,CAAC,YAAY,CAAI,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AACvD,CAAC"}
|
|
1
|
+
{"version":3,"file":"cache-helpers.js","sourceRoot":"","sources":["../src/cache-helpers.ts"],"names":[],"mappings":"AAEA,uDAAuD;AACvD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAmB,EAAE,MAAgB;IAC5E,OAAO,MAAM,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,cAAc,CAAC,MAAmB,EAAE,MAAgB;IAClE,OAAO,MAAM,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;AAClE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAmB,EACnB,GAAa,EACb,OAAmC;IAEnC,MAAM,CAAC,YAAY,CAAI,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AACvD,CAAC","sourcesContent":["import type { QueryClient, QueryKey } from '@tanstack/react-query';\n\n/** Invalidate everything that matches a key prefix. */\nexport async function invalidateByPrefix(client: QueryClient, prefix: QueryKey) {\n return client.invalidateQueries({ queryKey: prefix, exact: false });\n}\n\n/** Remove everything that matches a key prefix (drops cached data). */\nexport function removeByPrefix(client: QueryClient, prefix: QueryKey) {\n return client.removeQueries({ queryKey: prefix, exact: false });\n}\n\n/**\n * Generic cache update helper.\n * Useful for optimistic updates without baking in business logic.\n */\nexport function updateQueryData<T>(\n client: QueryClient,\n key: QueryKey,\n updater: (prev: T | undefined) => T\n) {\n client.setQueryData<T>(key, (prev) => updater(prev));\n}\n"]}
|
package/dist/defaults.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAmB;IACjD,OAAO,EAAE;QACP,oBAAoB,EAAE,KAAK;QAC3B,kBAAkB,EAAE,IAAI;QACxB,cAAc,EAAE,KAAK;QAErB,SAAS,EAAE,MAAM,EAAE,MAAM;QACzB,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,KAAK;QAEzB,KAAK,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,GAAG,CAAC;KAC1C;IACD,SAAS,EAAE;QACT,KAAK,EAAE,CAAC;KACT;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,wBAAwB,GAAsB;IACzD,cAAc,EAAE,mBAAmB;CACpC,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,KAAK,EAAE,MAAM;IACb,MAAM,EAAE,CAAC,GAAG,MAAM;IAClB,IAAI,EAAE,EAAE,GAAG,MAAM;IACjB,OAAO,EAAE,MAAM,CAAC,iBAAiB;CACzB,CAAC"}
|
|
1
|
+
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAmB;IACjD,OAAO,EAAE;QACP,oBAAoB,EAAE,KAAK;QAC3B,kBAAkB,EAAE,IAAI;QACxB,cAAc,EAAE,KAAK;QAErB,SAAS,EAAE,MAAM,EAAE,MAAM;QACzB,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,KAAK;QAEzB,KAAK,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,GAAG,CAAC;KAC1C;IACD,SAAS,EAAE;QACT,KAAK,EAAE,CAAC;KACT;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,wBAAwB,GAAsB;IACzD,cAAc,EAAE,mBAAmB;CACpC,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,KAAK,EAAE,MAAM;IACb,MAAM,EAAE,CAAC,GAAG,MAAM;IAClB,IAAI,EAAE,EAAE,GAAG,MAAM;IACjB,OAAO,EAAE,MAAM,CAAC,iBAAiB;CACzB,CAAC","sourcesContent":["import type { DefaultOptions, QueryClientConfig } from '@tanstack/react-query';\n\n/**\n * Small, conservative defaults intended to work well across apps.\n * Apps can override via `createQueryClient({ defaultOptions: ... })`.\n */\nexport const defaultQueryOptions: DefaultOptions = {\n queries: {\n refetchOnWindowFocus: false,\n refetchOnReconnect: true,\n refetchOnMount: false,\n\n staleTime: 30_000, // 30s\n gcTime: 5 * 60_000, // 5m\n\n retry: (failureCount) => failureCount < 2,\n },\n mutations: {\n retry: 0,\n },\n};\n\nexport const defaultQueryClientConfig: QueryClientConfig = {\n defaultOptions: defaultQueryOptions,\n};\n\n/**\n * Named `staleTime` presets (milliseconds) for React Query call sites.\n *\n * Use a preset instead of a magic number so every query self-documents how\n * fresh its data must be:\n * - `short` (30s) — frequently-changing counts/lists; matches the client default.\n * - `medium` (5m) — semi-dynamic data that tolerates a few minutes of staleness.\n * - `long` (30m) — rarely-changing data (public profiles, detail docs).\n * - `forever` (∞) — immutable for the session, OR data whose freshness is driven\n * entirely by explicit cache invalidation (a domain event), never\n * by elapsed time. Do NOT use `forever` for data with no\n * invalidation path — it will never refetch.\n *\n * Generic time buckets, not app-specific presets — safe to live in this package.\n */\nexport const STALE_TIMES = {\n short: 30_000,\n medium: 5 * 60_000,\n long: 30 * 60_000,\n forever: Number.POSITIVE_INFINITY,\n} as const;\n"]}
|
|
@@ -24,5 +24,26 @@ export declare function predicate(description: string, match: (query: Query) =>
|
|
|
24
24
|
refetchType?: RefetchType;
|
|
25
25
|
}): CacheInvalidation;
|
|
26
26
|
export declare function serializeInvalidation(inv: CacheInvalidation): string;
|
|
27
|
+
/**
|
|
28
|
+
* Dispatch a deduplicated list of cache invalidations.
|
|
29
|
+
*
|
|
30
|
+
* Synchronous by contract: the underlying `invalidateQueries` / `cancelQueries`
|
|
31
|
+
* promises are fire-and-forget, so a mutation success path never awaits its own
|
|
32
|
+
* cache refresh.
|
|
33
|
+
*
|
|
34
|
+
* Before each invalidation, any query it matches that is mid-PENDING-INITIAL-READ
|
|
35
|
+
* is cancelled (see `cancelPendingInitialReads`) so a read that started before
|
|
36
|
+
* the write committed can never land as the query's first — and freshly-marked —
|
|
37
|
+
* value.
|
|
38
|
+
*
|
|
39
|
+
* `refetchType` governs whether that cancellation happens:
|
|
40
|
+
* - `'active'` (the default) and `'all'` cancel-then-invalidate every matched
|
|
41
|
+
* pending initial read — including one TanStack's refetch pass will not
|
|
42
|
+
* restart, since a stale first value would otherwise land and mark the query
|
|
43
|
+
* fresh, while a cancelled one simply refetches for its next observer.
|
|
44
|
+
* - `'none'` invalidates WITHOUT cancelling: the caller has asked for no
|
|
45
|
+
* refetch at all, so cancelling would abandon the only in-flight read the
|
|
46
|
+
* consumer has and leave it with no data and no pending request.
|
|
47
|
+
*/
|
|
27
48
|
export declare function applyInvalidations(queryClient: QueryClient, invalidations: ReadonlyArray<CacheInvalidation>): void;
|
|
28
49
|
//# sourceMappingURL=cache-invalidation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-invalidation.d.ts","sourceRoot":"","sources":["../../src/domain-events/cache-invalidation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"cache-invalidation.d.ts","sourceRoot":"","sources":["../../src/domain-events/cache-invalidation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAgB,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAExF,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAEpD,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,WAAW,CAAC,EAAE,WAAW,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,WAAW,CAAC,EAAE,WAAW,CAAA;CAAE,GACjE;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;IACjC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B,CAAC;AAEN,wBAAgB,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,WAAW,CAAA;CAAE,GAAG,iBAAiB,CAEjG;AAED,wBAAgB,MAAM,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,WAAW,CAAA;CAAE,GAAG,iBAAiB,CAElG;AAED,wBAAgB,SAAS,CACvB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,EAChC,IAAI,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,WAAW,CAAA;CAAE,GACnC,iBAAiB,CAEnB;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CAGpE;AAiCD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE,aAAa,CAAC,iBAAiB,CAAC,GAC9C,IAAI,CAgBN"}
|
|
@@ -12,6 +12,60 @@ export function serializeInvalidation(inv) {
|
|
|
12
12
|
return `predicate:${inv.description}`;
|
|
13
13
|
return `${inv.kind}:${JSON.stringify(inv.queryKey)}`;
|
|
14
14
|
}
|
|
15
|
+
/** The cache filter that selects exactly the queries one invalidation targets. */
|
|
16
|
+
function invalidationFilters(inv) {
|
|
17
|
+
if (inv.kind === "exact")
|
|
18
|
+
return { queryKey: inv.queryKey, exact: true };
|
|
19
|
+
if (inv.kind === "prefix")
|
|
20
|
+
return { queryKey: inv.queryKey, exact: false };
|
|
21
|
+
return { predicate: inv.match };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Cancel every query matched by `filters` that is in a PENDING INITIAL READ —
|
|
25
|
+
* currently fetching with no data yet.
|
|
26
|
+
*
|
|
27
|
+
* TanStack's `Query.fetch` only cancels a running request on refetch when the
|
|
28
|
+
* query already holds data; an initial fetch in flight is REUSED instead, so
|
|
29
|
+
* the invalidation's refetch resolves with the value the server returned BEFORE
|
|
30
|
+
* the mutation committed and then marks the query fresh. Cancelling first makes
|
|
31
|
+
* the invalidation's refetch a genuinely new request: the retryer is rejected
|
|
32
|
+
* synchronously (its `onCancel` reverts state to idle in the same tick), so the
|
|
33
|
+
* `invalidateQueries` call that follows starts a fresh fetch, and the abandoned
|
|
34
|
+
* promise's late, stale resolution is discarded.
|
|
35
|
+
*
|
|
36
|
+
* Cancellation is always scoped to one concrete query key — never a bare
|
|
37
|
+
* `cancelQueries()`.
|
|
38
|
+
*/
|
|
39
|
+
function cancelPendingInitialReads(queryClient, filters) {
|
|
40
|
+
for (const query of queryClient.getQueryCache().findAll(filters)) {
|
|
41
|
+
if (query.state.fetchStatus !== "fetching")
|
|
42
|
+
continue;
|
|
43
|
+
if (query.state.data !== undefined)
|
|
44
|
+
continue;
|
|
45
|
+
void queryClient.cancelQueries({ queryKey: query.queryKey, exact: true });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Dispatch a deduplicated list of cache invalidations.
|
|
50
|
+
*
|
|
51
|
+
* Synchronous by contract: the underlying `invalidateQueries` / `cancelQueries`
|
|
52
|
+
* promises are fire-and-forget, so a mutation success path never awaits its own
|
|
53
|
+
* cache refresh.
|
|
54
|
+
*
|
|
55
|
+
* Before each invalidation, any query it matches that is mid-PENDING-INITIAL-READ
|
|
56
|
+
* is cancelled (see `cancelPendingInitialReads`) so a read that started before
|
|
57
|
+
* the write committed can never land as the query's first — and freshly-marked —
|
|
58
|
+
* value.
|
|
59
|
+
*
|
|
60
|
+
* `refetchType` governs whether that cancellation happens:
|
|
61
|
+
* - `'active'` (the default) and `'all'` cancel-then-invalidate every matched
|
|
62
|
+
* pending initial read — including one TanStack's refetch pass will not
|
|
63
|
+
* restart, since a stale first value would otherwise land and mark the query
|
|
64
|
+
* fresh, while a cancelled one simply refetches for its next observer.
|
|
65
|
+
* - `'none'` invalidates WITHOUT cancelling: the caller has asked for no
|
|
66
|
+
* refetch at all, so cancelling would abandon the only in-flight read the
|
|
67
|
+
* consumer has and leave it with no data and no pending request.
|
|
68
|
+
*/
|
|
15
69
|
export function applyInvalidations(queryClient, invalidations) {
|
|
16
70
|
const seen = new Set();
|
|
17
71
|
for (const inv of invalidations) {
|
|
@@ -20,26 +74,11 @@ export function applyInvalidations(queryClient, invalidations) {
|
|
|
20
74
|
continue;
|
|
21
75
|
seen.add(sig);
|
|
22
76
|
const refetchType = inv.refetchType ?? "active";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
exact: true,
|
|
27
|
-
refetchType,
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
else if (inv.kind === "prefix") {
|
|
31
|
-
void queryClient.invalidateQueries({
|
|
32
|
-
queryKey: inv.queryKey,
|
|
33
|
-
exact: false,
|
|
34
|
-
refetchType,
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
void queryClient.invalidateQueries({
|
|
39
|
-
predicate: inv.match,
|
|
40
|
-
refetchType,
|
|
41
|
-
});
|
|
77
|
+
const filters = invalidationFilters(inv);
|
|
78
|
+
if (refetchType !== "none") {
|
|
79
|
+
cancelPendingInitialReads(queryClient, filters);
|
|
42
80
|
}
|
|
81
|
+
void queryClient.invalidateQueries({ ...filters, refetchType });
|
|
43
82
|
}
|
|
44
83
|
}
|
|
45
84
|
//# sourceMappingURL=cache-invalidation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-invalidation.js","sourceRoot":"","sources":["../../src/domain-events/cache-invalidation.ts"],"names":[],"mappings":"AAcA,MAAM,UAAU,KAAK,CAAC,QAAkB,EAAE,IAAoC;IAC5E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,QAAkB,EAAE,IAAoC;IAC7E,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,WAAmB,EACnB,KAAgC,EAChC,IAAoC;IAEpC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AACnF,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAsB;IAC1D,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW;QAAE,OAAO,aAAa,GAAG,CAAC,WAAW,EAAE,CAAC;IACpE,OAAO,GAAG,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,WAAwB,EACxB,aAA+C;IAE/C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QAChC,MAAM,GAAG,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEd,MAAM,WAAW,GAAgB,GAAG,CAAC,WAAW,IAAI,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"cache-invalidation.js","sourceRoot":"","sources":["../../src/domain-events/cache-invalidation.ts"],"names":[],"mappings":"AAcA,MAAM,UAAU,KAAK,CAAC,QAAkB,EAAE,IAAoC;IAC5E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,QAAkB,EAAE,IAAoC;IAC7E,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,WAAmB,EACnB,KAAgC,EAChC,IAAoC;IAEpC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AACnF,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAsB;IAC1D,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW;QAAE,OAAO,aAAa,GAAG,CAAC,WAAW,EAAE,CAAC;IACpE,OAAO,GAAG,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;AACvD,CAAC;AAED,kFAAkF;AAClF,SAAS,mBAAmB,CAAC,GAAsB;IACjD,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACzE,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAC3E,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC;AAClC,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,SAAS,yBAAyB,CAAC,WAAwB,EAAE,OAAqB;IAChF,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,aAAa,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACjE,IAAI,KAAK,CAAC,KAAK,CAAC,WAAW,KAAK,UAAU;YAAE,SAAS;QACrD,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS;YAAE,SAAS;QAC7C,KAAK,WAAW,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5E,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,kBAAkB,CAChC,WAAwB,EACxB,aAA+C;IAE/C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QAChC,MAAM,GAAG,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEd,MAAM,WAAW,GAAgB,GAAG,CAAC,WAAW,IAAI,QAAQ,CAAC;QAC7D,MAAM,OAAO,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;QAEzC,IAAI,WAAW,KAAK,MAAM,EAAE,CAAC;YAC3B,yBAAyB,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAClD,CAAC;QAED,KAAK,WAAW,CAAC,iBAAiB,CAAC,EAAE,GAAG,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC;IAClE,CAAC;AACH,CAAC","sourcesContent":["import type { Query, QueryClient, QueryFilters, QueryKey } from \"@tanstack/react-query\";\n\nexport type RefetchType = \"active\" | \"all\" | \"none\";\n\nexport type CacheInvalidation =\n | { kind: \"exact\"; queryKey: QueryKey; refetchType?: RefetchType }\n | { kind: \"prefix\"; queryKey: QueryKey; refetchType?: RefetchType }\n | {\n kind: \"predicate\";\n description: string;\n match: (query: Query) => boolean;\n refetchType?: RefetchType;\n };\n\nexport function exact(queryKey: QueryKey, opts?: { refetchType?: RefetchType }): CacheInvalidation {\n return { kind: \"exact\", queryKey, refetchType: opts?.refetchType };\n}\n\nexport function prefix(queryKey: QueryKey, opts?: { refetchType?: RefetchType }): CacheInvalidation {\n return { kind: \"prefix\", queryKey, refetchType: opts?.refetchType };\n}\n\nexport function predicate(\n description: string,\n match: (query: Query) => boolean,\n opts?: { refetchType?: RefetchType },\n): CacheInvalidation {\n return { kind: \"predicate\", description, match, refetchType: opts?.refetchType };\n}\n\nexport function serializeInvalidation(inv: CacheInvalidation): string {\n if (inv.kind === \"predicate\") return `predicate:${inv.description}`;\n return `${inv.kind}:${JSON.stringify(inv.queryKey)}`;\n}\n\n/** The cache filter that selects exactly the queries one invalidation targets. */\nfunction invalidationFilters(inv: CacheInvalidation): QueryFilters {\n if (inv.kind === \"exact\") return { queryKey: inv.queryKey, exact: true };\n if (inv.kind === \"prefix\") return { queryKey: inv.queryKey, exact: false };\n return { predicate: inv.match };\n}\n\n/**\n * Cancel every query matched by `filters` that is in a PENDING INITIAL READ —\n * currently fetching with no data yet.\n *\n * TanStack's `Query.fetch` only cancels a running request on refetch when the\n * query already holds data; an initial fetch in flight is REUSED instead, so\n * the invalidation's refetch resolves with the value the server returned BEFORE\n * the mutation committed and then marks the query fresh. Cancelling first makes\n * the invalidation's refetch a genuinely new request: the retryer is rejected\n * synchronously (its `onCancel` reverts state to idle in the same tick), so the\n * `invalidateQueries` call that follows starts a fresh fetch, and the abandoned\n * promise's late, stale resolution is discarded.\n *\n * Cancellation is always scoped to one concrete query key — never a bare\n * `cancelQueries()`.\n */\nfunction cancelPendingInitialReads(queryClient: QueryClient, filters: QueryFilters): void {\n for (const query of queryClient.getQueryCache().findAll(filters)) {\n if (query.state.fetchStatus !== \"fetching\") continue;\n if (query.state.data !== undefined) continue;\n void queryClient.cancelQueries({ queryKey: query.queryKey, exact: true });\n }\n}\n\n/**\n * Dispatch a deduplicated list of cache invalidations.\n *\n * Synchronous by contract: the underlying `invalidateQueries` / `cancelQueries`\n * promises are fire-and-forget, so a mutation success path never awaits its own\n * cache refresh.\n *\n * Before each invalidation, any query it matches that is mid-PENDING-INITIAL-READ\n * is cancelled (see `cancelPendingInitialReads`) so a read that started before\n * the write committed can never land as the query's first — and freshly-marked —\n * value.\n *\n * `refetchType` governs whether that cancellation happens:\n * - `'active'` (the default) and `'all'` cancel-then-invalidate every matched\n * pending initial read — including one TanStack's refetch pass will not\n * restart, since a stale first value would otherwise land and mark the query\n * fresh, while a cancelled one simply refetches for its next observer.\n * - `'none'` invalidates WITHOUT cancelling: the caller has asked for no\n * refetch at all, so cancelling would abandon the only in-flight read the\n * consumer has and leave it with no data and no pending request.\n */\nexport function applyInvalidations(\n queryClient: QueryClient,\n invalidations: ReadonlyArray<CacheInvalidation>,\n): void {\n const seen = new Set<string>();\n for (const inv of invalidations) {\n const sig = serializeInvalidation(inv);\n if (seen.has(sig)) continue;\n seen.add(sig);\n\n const refetchType: RefetchType = inv.refetchType ?? \"active\";\n const filters = invalidationFilters(inv);\n\n if (refetchType !== \"none\") {\n cancelPendingInitialReads(queryClient, filters);\n }\n\n void queryClient.invalidateQueries({ ...filters, refetchType });\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-domain-event-invalidator.js","sourceRoot":"","sources":["../../src/domain-events/create-domain-event-invalidator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAClB,KAAK,EACL,MAAM,EACN,SAAS,GAEV,MAAM,yBAAyB,CAAC;AAyBjC;;;;GAIG;AACH,MAAM,UAAU,4BAA4B,CAC1C,QAAiD;IAEjD,SAAS,MAAM,CAA+B,IAAW;QACvD,OAAO,QAAQ,CAAC,IAAI,CAEiB,CAAC;IACxC,CAAC;IAED,SAAS,MAAM,CAAC,WAAwB,EAAE,KAAa;QACrD,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,IAAsB,CAAC,CAAC;QAChD,MAAM,aAAa,GAAG,EAAE,CAAC,KAAK,CAAC,GAAuD,CAAC,CAAC;QACxF,kBAAkB,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IACjD,CAAC;IAED,SAAS,SAAS,CAAC,WAAwB,EAAE,MAA6B;QACxE,MAAM,GAAG,GAAwB,EAAE,CAAC;QACpC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,IAAsB,CAAC,CAAC;YAChD,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAuD,CAAC,CAAC,CAAC;QACjF,CAAC;QACD,kBAAkB,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IACvC,CAAC;IAED,OAAO;QACL,MAAM;QACN,SAAS;QACT,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE;QACrC,QAAQ;KACT,CAAC;AACJ,CAAC"}
|
|
1
|
+
{"version":3,"file":"create-domain-event-invalidator.js","sourceRoot":"","sources":["../../src/domain-events/create-domain-event-invalidator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAClB,KAAK,EACL,MAAM,EACN,SAAS,GAEV,MAAM,yBAAyB,CAAC;AAyBjC;;;;GAIG;AACH,MAAM,UAAU,4BAA4B,CAC1C,QAAiD;IAEjD,SAAS,MAAM,CAA+B,IAAW;QACvD,OAAO,QAAQ,CAAC,IAAI,CAEiB,CAAC;IACxC,CAAC;IAED,SAAS,MAAM,CAAC,WAAwB,EAAE,KAAa;QACrD,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,IAAsB,CAAC,CAAC;QAChD,MAAM,aAAa,GAAG,EAAE,CAAC,KAAK,CAAC,GAAuD,CAAC,CAAC;QACxF,kBAAkB,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IACjD,CAAC;IAED,SAAS,SAAS,CAAC,WAAwB,EAAE,MAA6B;QACxE,MAAM,GAAG,GAAwB,EAAE,CAAC;QACpC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,IAAsB,CAAC,CAAC;YAChD,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAuD,CAAC,CAAC,CAAC;QACjF,CAAC;QACD,kBAAkB,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IACvC,CAAC;IAED,OAAO;QACL,MAAM;QACN,SAAS;QACT,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE;QACrC,QAAQ;KACT,CAAC;AACJ,CAAC","sourcesContent":["import type { QueryClient } from \"@tanstack/react-query\";\nimport {\n applyInvalidations,\n exact,\n prefix,\n predicate,\n type CacheInvalidation,\n} from \"./cache-invalidation.js\";\n\n/**\n * Registry shape: each event type maps to a function from the event's `ids`\n * payload to the list of cache invalidations that event should trigger.\n */\nexport type DomainEventInvalidationRegistry<TEvent extends { type: string; ids: unknown }> = {\n [K in TEvent[\"type\"]]: (\n ids: Extract<TEvent, { type: K }>[\"ids\"],\n ) => ReadonlyArray<CacheInvalidation>;\n};\n\nexport interface DomainEventInvalidator<TEvent extends { type: string; ids: unknown }> {\n notify(queryClient: QueryClient, event: TEvent): void;\n notifyAll(queryClient: QueryClient, events: ReadonlyArray<TEvent>): void;\n /** Test/debug helpers. */\n readonly helpers: {\n exact: typeof exact;\n prefix: typeof prefix;\n predicate: typeof predicate;\n };\n /** Direct access to the registry, mainly for tests. */\n readonly registry: DomainEventInvalidationRegistry<TEvent>;\n}\n\n/**\n * Build a domain-event → cache-invalidation notifier from a typed registry.\n * The registry's keys are statically tied to `TEvent[\"type\"]`, so missing or\n * extra entries are compile errors.\n */\nexport function createDomainEventInvalidator<TEvent extends { type: string; ids: unknown }>(\n registry: DomainEventInvalidationRegistry<TEvent>,\n): DomainEventInvalidator<TEvent> {\n function lookup<TType extends TEvent[\"type\"]>(type: TType) {\n return registry[type] as (\n ids: Extract<TEvent, { type: TType }>[\"ids\"],\n ) => ReadonlyArray<CacheInvalidation>;\n }\n\n function notify(queryClient: QueryClient, event: TEvent): void {\n const fn = lookup(event.type as TEvent[\"type\"]);\n const invalidations = fn(event.ids as Extract<TEvent, { type: TEvent[\"type\"] }>[\"ids\"]);\n applyInvalidations(queryClient, invalidations);\n }\n\n function notifyAll(queryClient: QueryClient, events: ReadonlyArray<TEvent>): void {\n const all: CacheInvalidation[] = [];\n for (const event of events) {\n const fn = lookup(event.type as TEvent[\"type\"]);\n all.push(...fn(event.ids as Extract<TEvent, { type: TEvent[\"type\"] }>[\"ids\"]));\n }\n applyInvalidations(queryClient, all);\n }\n\n return {\n notify,\n notifyAll,\n helpers: { exact, prefix, predicate },\n registry,\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/domain-events/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,MAAM,EACN,SAAS,EACT,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,4BAA4B,EAAE,MAAM,sCAAsC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/domain-events/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,MAAM,EACN,SAAS,EACT,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,4BAA4B,EAAE,MAAM,sCAAsC,CAAC","sourcesContent":["export {\n exact,\n prefix,\n predicate,\n serializeInvalidation,\n applyInvalidations,\n} from \"./cache-invalidation.js\";\nexport type { CacheInvalidation, RefetchType } from \"./cache-invalidation.js\";\n\nexport { createDomainEventInvalidator } from \"./create-domain-event-invalidator.js\";\nexport type {\n DomainEventInvalidator,\n DomainEventInvalidationRegistry,\n} from \"./create-domain-event-invalidator.js\";\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"infinite-helpers.js","sourceRoot":"","sources":["../../src/firestore/infinite-helpers.ts"],"names":[],"mappings":"AAEA;;;;;;;;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,CAClC,IAA8C;IAE9C,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"}
|
|
1
|
+
{"version":3,"file":"infinite-helpers.js","sourceRoot":"","sources":["../../src/firestore/infinite-helpers.ts"],"names":[],"mappings":"AAEA;;;;;;;;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,CAClC,IAA8C;IAE9C,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 { InfinitePage, WithId } 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>(\n data: { pages: InfinitePage<T>[] } | undefined\n): number {\n if (!data) return 0;\n return data.pages.reduce((sum, page) => sum + page.items.length, 0);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/firestore/types.ts"],"names":[],"mappings":"AA8BA;;GAEG;AACH,MAAM,UAAU,SAAS,CACvB,IAA8B;IAE9B,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;AACzC,CAAC"}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/firestore/types.ts"],"names":[],"mappings":"AA8BA;;GAEG;AACH,MAAM,UAAU,SAAS,CACvB,IAA8B;IAE9B,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';\n\n/**\n * Document with ID included in the data object.\n */\nexport type WithId<T> = T & { id: string };\n\n/**\n * Metadata-derived liveness of a realtime subscription. Firestore serves cached\n * snapshots offline WITHOUT firing the error callback, so error-only detection\n * renders a stale cached empty result as \"live\". This four-state is derived from\n * `snapshot.metadata.fromCache` (server-confirmed vs cache) plus the listener\n * error callback:\n * - `connecting` — no server-confirmed snapshot yet (initial, or only cache-first data).\n * - `live` — at least one server-confirmed snapshot (`fromCache === false`).\n * - `offline` — only cached data after having been live (connectivity loss).\n * - `error` — the listener error callback fired (e.g. permission loss).\n *\n * Meaningful only for `subscribe: true`; a non-subscribed hook stays `connecting`.\n */\nexport type FirestoreSourceState = 'connecting' | 'live' | 'offline' | 'error';\n\n/** A query-hook result augmented with the metadata-derived subscription source state. */\nexport type WithSourceState<R> = R & { sourceState: FirestoreSourceState };\n\n/**\n * Extract document data with ID from a QueryDocumentSnapshot.\n */\nexport function docWithId<T extends DocumentData>(\n snap: QueryDocumentSnapshot<T>\n): WithId<T> {\n return { id: snap.id, ...snap.data() };\n}\n\n/**\n * Base options shared across all Firestore hooks.\n */\nexport interface FirestoreBaseOptions {\n /** React Query cache key */\n queryKey: readonly unknown[];\n /** Enable/disable the query */\n enabled?: boolean;\n /** Stale time override (ms) */\n staleTime?: number;\n /** Garbage collection time override (ms) */\n gcTime?: number;\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 /** 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 /** Numeric sort key from a raw doc (id included). Default: reads `orderByField` as a number. */\n getSortValue?: (data: DocumentData & { id: string }) => number;\n /** Output ordering of the merged list. 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 * Result for paginated queries.\n */\nexport interface PaginatedResult<T> {\n items: WithId<T>[];\n cursors: DocumentSnapshot[];\n hasMore: boolean;\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\n/**\n * Options for document mutations.\n */\nexport interface FirestoreMutationOptions<T> {\n /** Document path for single-doc mutations */\n docPath?: string;\n /** Query keys to invalidate on success */\n invalidateKeys?: readonly unknown[][];\n /** Enable optimistic updates */\n optimistic?: {\n /** Query key of the cache to update */\n queryKey: readonly unknown[];\n /** How to update the cached data */\n updater: (oldData: T | undefined, newData: Partial<T>) => T;\n };\n}\n\n/**\n * Options for batch mutations.\n */\nexport interface FirestoreBatchOptions {\n /** Query keys to invalidate on success */\n invalidateKeys?: readonly unknown[][];\n /** Max operations per batch (default: 450) */\n batchSize?: number;\n}\n"]}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,gBAAgB;AAChB,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAkBzF,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EACL,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,iCAAiC,CAAC;AASzC,iFAAiF;AACjF,OAAO,EACL,4BAA4B,EAC5B,KAAK,EACL,MAAM,EACN,SAAS,EACT,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,0BAA0B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,gBAAgB;AAChB,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAkBzF,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EACL,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,iCAAiC,CAAC;AASzC,iFAAiF;AACjF,OAAO,EACL,4BAA4B,EAC5B,KAAK,EACL,MAAM,EACN,SAAS,EACT,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,0BAA0B,CAAC","sourcesContent":["// Query defaults & staleTime presets (server-safe — plain constants, no React runtime)\nexport { STALE_TIMES } from './defaults.js';\n\n// Cache Helpers\nexport { invalidateByPrefix, removeByPrefix, updateQueryData } from './cache-helpers.js';\n\n// Firestore Types & Pure Helpers\nexport type {\n WithId,\n FirestoreSourceState,\n WithSourceState,\n FirestoreBaseOptions,\n FirestoreDocOptions,\n FirestoreCollectionOptions,\n FirestoreInfiniteOptions,\n FirestorePaginatedOptions,\n InfinitePage,\n PaginatedResult,\n MutationOperation,\n FirestoreMutationOptions,\n FirestoreBatchOptions,\n} from './firestore/types.js';\nexport { docWithId } from './firestore/types.js';\nexport {\n flattenInfiniteData,\n getInfiniteDataCount,\n} from './firestore/infinite-helpers.js';\n\n// Search Types\nexport type {\n FirestoreSearchConfig,\n FirestoreSearchOptions,\n SearchEqualityFilter,\n} from './search/types.js';\n\n// Domain-event invalidator (mechanism — consumers register their event registry)\nexport {\n createDomainEventInvalidator,\n exact,\n prefix,\n predicate,\n serializeInvalidation,\n applyInvalidations,\n} from \"./domain-events/index.js\";\nexport type {\n DomainEventInvalidator,\n DomainEventInvalidationRegistry,\n CacheInvalidation,\n RefetchType,\n} from \"./domain-events/index.js\";\n"]}
|
package/dist/keys.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Lightweight, stable, Firestore-safe query key helpers.\n *\n * All keys are arrays of `string | number | boolean | null` so they can\n * be written into Firestore docs (used by the unified upload pipeline's\n * `result.invalidate` field) and read back by the frontend hook to call\n * `queryClient.invalidateQueries`. Both sides produce structurally\n * identical arrays.\n *\n * Convention:\n * - `keys.user.all` => ['user']\n * - `keys.user.detail(id)` => ['user', 'detail', id]\n * - `keys.user.list(param?)` => ['user', 'list', param?]\n * - `keys.user.custom('accountStatus', id)` => ['user', 'accountStatus', id]\n *\n * For list filters that need multiple discriminators, use `custom`:\n * keys.feedback.custom('list', filter, sortBy)\n */\ntype SerializedKeyPart = string | number | boolean | null | undefined;\n\nexport type QueryKey = ReadonlyArray<string | number | boolean | null>;\n\nfunction withScope(scope: string, ...parts: SerializedKeyPart[]): QueryKey {\n const cleaned = parts.filter((p): p is Exclude<SerializedKeyPart, undefined> => p !== undefined);\n return [scope, ...cleaned] as const;\n}\n\nexport const keys = {\n user: {\n all: withScope('user'),\n detail: (id: string) => withScope('user', 'detail', id),\n list: (param?: SerializedKeyPart) => withScope('user', 'list', param),\n custom: (...parts: SerializedKeyPart[]) => withScope('user', ...parts),\n },\n follows: {\n all: withScope('follows'),\n detail: (id: string) => withScope('follows', 'detail', id),\n list: (param?: SerializedKeyPart) => withScope('follows', 'list', param),\n custom: (...parts: SerializedKeyPart[]) => withScope('follows', ...parts),\n },\n entities: {\n all: withScope('entities'),\n detail: (id: string) => withScope('entities', 'detail', id),\n list: (param?: SerializedKeyPart) => withScope('entities', 'list', param),\n custom: (...parts: SerializedKeyPart[]) => withScope('entities', ...parts),\n },\n messages: {\n all: withScope('messages'),\n detail: (id: string) => withScope('messages', 'detail', id),\n list: (param?: SerializedKeyPart) => withScope('messages', 'list', param),\n custom: (...parts: SerializedKeyPart[]) => withScope('messages', ...parts),\n },\n admin: {\n all: withScope('admin'),\n detail: (id: string) => withScope('admin', 'detail', id),\n list: (param?: SerializedKeyPart) => withScope('admin', 'list', param),\n custom: (...parts: SerializedKeyPart[]) => withScope('admin', ...parts),\n },\n chat: {\n all: withScope('chat'),\n detail: (id: string) => withScope('chat', 'detail', id),\n list: (param?: SerializedKeyPart) => withScope('chat', 'list', param),\n custom: (...parts: SerializedKeyPart[]) => withScope('chat', ...parts),\n },\n notifications: {\n all: withScope('notifications'),\n detail: (id: string) => withScope('notifications', 'detail', id),\n list: (param?: SerializedKeyPart) => withScope('notifications', 'list', param),\n custom: (...parts: SerializedKeyPart[]) => withScope('notifications', ...parts),\n },\n shortLinks: {\n all: withScope('shortLinks'),\n detail: (id: string) => withScope('shortLinks', 'detail', id),\n list: (param?: SerializedKeyPart) => withScope('shortLinks', 'list', param),\n custom: (...parts: SerializedKeyPart[]) => withScope('shortLinks', ...parts),\n },\n mentions: {\n all: withScope('mentions'),\n detail: (id: string) => withScope('mentions', 'detail', id),\n list: (param?: SerializedKeyPart) => withScope('mentions', 'list', param),\n custom: (...parts: SerializedKeyPart[]) => withScope('mentions', ...parts),\n },\n feedback: {\n all: withScope('feedback'),\n detail: (id: string) => withScope('feedback', 'detail', id),\n list: (param?: SerializedKeyPart) => withScope('feedback', 'list', param),\n custom: (...parts: SerializedKeyPart[]) => withScope('feedback', ...parts),\n },\n custom: (...parts: SerializedKeyPart[]) => withScope('custom', ...parts),\n} as const;\n\n/**\n * Utility for apps that want to define their own scoped key sets\n * while following the same conventions.\n */\nexport function createKeyScope(scope: string) {\n return {\n all: withScope(scope),\n detail: (id: string) => withScope(scope, 'detail', id),\n list: (param?: SerializedKeyPart) => withScope(scope, 'list', param),\n custom: (...parts: SerializedKeyPart[]) => withScope(scope, ...parts),\n } as const;\n}\n"]}
|
package/dist/query-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-client.js","sourceRoot":"","sources":["../src/query-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA0B,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAIzD;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,YAAsC,EAAE;IACxE,OAAO,IAAI,WAAW,CAAC;QACrB,GAAG,wBAAwB;QAC3B,GAAG,SAAS;QACZ,cAAc,EAAE;YACd,GAAG,wBAAwB,CAAC,cAAc;YAC1C,GAAG,SAAS,CAAC,cAAc;YAC3B,OAAO,EAAE;gBACP,GAAG,wBAAwB,CAAC,cAAc,EAAE,OAAO;gBACnD,GAAG,SAAS,CAAC,cAAc,EAAE,OAAO;aACrC;YACD,SAAS,EAAE;gBACT,GAAG,wBAAwB,CAAC,cAAc,EAAE,SAAS;gBACrD,GAAG,SAAS,CAAC,cAAc,EAAE,SAAS;aACvC;SACF;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
1
|
+
{"version":3,"file":"query-client.js","sourceRoot":"","sources":["../src/query-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA0B,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAIzD;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,YAAsC,EAAE;IACxE,OAAO,IAAI,WAAW,CAAC;QACrB,GAAG,wBAAwB;QAC3B,GAAG,SAAS;QACZ,cAAc,EAAE;YACd,GAAG,wBAAwB,CAAC,cAAc;YAC1C,GAAG,SAAS,CAAC,cAAc;YAC3B,OAAO,EAAE;gBACP,GAAG,wBAAwB,CAAC,cAAc,EAAE,OAAO;gBACnD,GAAG,SAAS,CAAC,cAAc,EAAE,OAAO;aACrC;YACD,SAAS,EAAE;gBACT,GAAG,wBAAwB,CAAC,cAAc,EAAE,SAAS;gBACrD,GAAG,SAAS,CAAC,cAAc,EAAE,SAAS;aACvC;SACF;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { QueryClient, type QueryClientConfig } from '@tanstack/react-query';\nimport { defaultQueryClientConfig } from './defaults.js';\n\nexport type CreateQueryClientOptions = QueryClientConfig;\n\n/**\n * Factory so every app uses the same baseline behavior.\n * Pass overrides to adjust defaults without forking.\n */\nexport function createQueryClient(overrides: CreateQueryClientOptions = {}) {\n return new QueryClient({\n ...defaultQueryClientConfig,\n ...overrides,\n defaultOptions: {\n ...defaultQueryClientConfig.defaultOptions,\n ...overrides.defaultOptions,\n queries: {\n ...defaultQueryClientConfig.defaultOptions?.queries,\n ...overrides.defaultOptions?.queries,\n },\n mutations: {\n ...defaultQueryClientConfig.defaultOptions?.mutations,\n ...overrides.defaultOptions?.mutations,\n },\n },\n });\n}\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { QueryClient, QueryKey } from '@tanstack/react-query';
|
|
2
|
+
/**
|
|
3
|
+
* Bounded re-read ladder for a document that resolved ABSENT.
|
|
4
|
+
*
|
|
5
|
+
* A one-shot lookup that lands `null` has no listener to tell it when the doc appears —
|
|
6
|
+
* the row would stay blank until something invalidates it. That is the real failure the
|
|
7
|
+
* realtime path was introduced to fix (a mirror doc that is missing or lagging at fetch
|
|
8
|
+
* time: just after a create-and-redirect, or in a second tab racing the server mirror).
|
|
9
|
+
* This scheduler restores the recovery without a listener: after an absent result it
|
|
10
|
+
* re-reads a few times on a widening ladder, then gives up.
|
|
11
|
+
*
|
|
12
|
+
* Delays (ms) between re-reads within one absence episode. After the last rung the episode
|
|
13
|
+
* is exhausted and never restarts on its own; only a later PRESENT→absent transition opens
|
|
14
|
+
* a new one.
|
|
15
|
+
*/
|
|
16
|
+
export declare const ABSENT_RETRY_DELAYS_MS: readonly [5000, 15000, 45000, 120000];
|
|
17
|
+
export interface TrackAbsentDocParams {
|
|
18
|
+
queryClient: QueryClient;
|
|
19
|
+
/** Exact key of the per-id lookup query. */
|
|
20
|
+
queryKey: QueryKey;
|
|
21
|
+
/**
|
|
22
|
+
* Ladder this registrant wants; an empty array means it wants no re-reads. Registrants
|
|
23
|
+
* on the same key combine by union — the longest ladder among them governs — so one
|
|
24
|
+
* consumer passing `[]` never disables polling another consumer asked for.
|
|
25
|
+
*/
|
|
26
|
+
delays?: readonly number[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Register interest in re-reading `queryKey` while it resolves absent. Returns an
|
|
30
|
+
* idempotent release function; releasing the last interested consumer pauses the ladder
|
|
31
|
+
* without spending the episode's remaining budget, and releasing the last registrant that
|
|
32
|
+
* wanted polling shrinks the effective ladder for the episode still in progress.
|
|
33
|
+
*/
|
|
34
|
+
export declare function trackAbsentDoc({ queryClient, queryKey, delays, }: TrackAbsentDocParams): () => void;
|
|
35
|
+
/** Test-only: number of absence episodes currently tracked for a QueryClient. */
|
|
36
|
+
export declare function __absenceEpisodeCount(queryClient: QueryClient): number;
|
|
37
|
+
/** Test-only: rungs already spent on a key's current episode, or `null` if there is none. */
|
|
38
|
+
export declare function __absenceAttempt(queryClient: QueryClient, queryKey: QueryKey): number | null;
|
|
39
|
+
//# sourceMappingURL=absence-scheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"absence-scheduler.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/absence-scheduler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEnE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,uCAA4C,CAAC;AAgLhF,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,WAAW,CAAC;IACzB,4CAA4C;IAC5C,QAAQ,EAAE,QAAQ,CAAC;IACnB;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAC7B,WAAW,EACX,QAAQ,EACR,MAA+B,GAChC,EAAE,oBAAoB,GAAG,MAAM,IAAI,CAmCnC;AAED,iFAAiF;AACjF,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,CAEtE;AAED,6FAA6F;AAC7F,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAE5F"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
/**
|
|
3
|
+
* Bounded re-read ladder for a document that resolved ABSENT.
|
|
4
|
+
*
|
|
5
|
+
* A one-shot lookup that lands `null` has no listener to tell it when the doc appears —
|
|
6
|
+
* the row would stay blank until something invalidates it. That is the real failure the
|
|
7
|
+
* realtime path was introduced to fix (a mirror doc that is missing or lagging at fetch
|
|
8
|
+
* time: just after a create-and-redirect, or in a second tab racing the server mirror).
|
|
9
|
+
* This scheduler restores the recovery without a listener: after an absent result it
|
|
10
|
+
* re-reads a few times on a widening ladder, then gives up.
|
|
11
|
+
*
|
|
12
|
+
* Delays (ms) between re-reads within one absence episode. After the last rung the episode
|
|
13
|
+
* is exhausted and never restarts on its own; only a later PRESENT→absent transition opens
|
|
14
|
+
* a new one.
|
|
15
|
+
*/
|
|
16
|
+
export const ABSENT_RETRY_DELAYS_MS = [5_000, 15_000, 45_000, 120_000];
|
|
17
|
+
const NO_DELAYS = [];
|
|
18
|
+
// Per-QueryClient, like the doc-subscription registry: a discarded client is GC'd with its
|
|
19
|
+
// bookkeeping, and each browser tab schedules independently.
|
|
20
|
+
const schedulers = new WeakMap();
|
|
21
|
+
/**
|
|
22
|
+
* Hash a key the way THIS client hashes it, so a consumer with a custom
|
|
23
|
+
* `queryKeyHashFn` still lines up with `QueryCache.get()`.
|
|
24
|
+
*/
|
|
25
|
+
function hashOf(queryClient, queryKey) {
|
|
26
|
+
return queryClient.defaultQueryOptions({ queryKey }).queryHash;
|
|
27
|
+
}
|
|
28
|
+
/** Union of the registrants' ladders: the longest one wins; none means no polling. */
|
|
29
|
+
function effectiveDelays(tracked) {
|
|
30
|
+
if (!tracked)
|
|
31
|
+
return NO_DELAYS;
|
|
32
|
+
let longest = NO_DELAYS;
|
|
33
|
+
for (const registration of tracked.registrations) {
|
|
34
|
+
if (registration.delays.length > longest.length)
|
|
35
|
+
longest = registration.delays;
|
|
36
|
+
}
|
|
37
|
+
return longest;
|
|
38
|
+
}
|
|
39
|
+
function clearTimer(episode) {
|
|
40
|
+
if (episode?.timer != null) {
|
|
41
|
+
clearTimeout(episode.timer);
|
|
42
|
+
episode.timer = null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function discardEpisode(state, hash) {
|
|
46
|
+
clearTimer(state.episodes.get(hash));
|
|
47
|
+
state.episodes.delete(hash);
|
|
48
|
+
}
|
|
49
|
+
function schedule(queryClient, state, hash) {
|
|
50
|
+
const tracked = state.tracked.get(hash);
|
|
51
|
+
const episode = state.episodes.get(hash);
|
|
52
|
+
if (!tracked || !episode || episode.timer !== null)
|
|
53
|
+
return;
|
|
54
|
+
const delays = effectiveDelays(tracked);
|
|
55
|
+
if (episode.attempt >= delays.length)
|
|
56
|
+
return;
|
|
57
|
+
episode.timer = setTimeout(() => {
|
|
58
|
+
episode.timer = null;
|
|
59
|
+
episode.attempt += 1;
|
|
60
|
+
const current = state.tracked.get(hash);
|
|
61
|
+
if (!current || !isAbsentAndActive(queryClient, hash))
|
|
62
|
+
return;
|
|
63
|
+
void queryClient.refetchQueries({ queryKey: current.queryKey, exact: true, type: 'active' });
|
|
64
|
+
// Lay the next rung; the ladder stops the moment the doc appears, the budget runs
|
|
65
|
+
// out, or the last enabled observer goes away.
|
|
66
|
+
evaluate(queryClient, state, hash);
|
|
67
|
+
}, delays[episode.attempt]);
|
|
68
|
+
}
|
|
69
|
+
function isAbsentAndActive(queryClient, hash) {
|
|
70
|
+
const query = queryClient.getQueryCache().get(hash);
|
|
71
|
+
if (!query)
|
|
72
|
+
return false;
|
|
73
|
+
return query.state.status === 'success' && query.state.data === null && query.isActive();
|
|
74
|
+
}
|
|
75
|
+
function evaluate(queryClient, state, hash) {
|
|
76
|
+
const query = queryClient.getQueryCache().get(hash);
|
|
77
|
+
if (!query) {
|
|
78
|
+
discardEpisode(state, hash);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const { status, data } = query.state;
|
|
82
|
+
if (status === 'success' && data !== null) {
|
|
83
|
+
// The doc is present — the episode is over.
|
|
84
|
+
discardEpisode(state, hash);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (status !== 'success') {
|
|
88
|
+
// Pending or errored: never polled, but the budget survives so an error mid-episode
|
|
89
|
+
// cannot hand the id a fresh ladder when it returns to absent.
|
|
90
|
+
clearTimer(state.episodes.get(hash));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const tracked = state.tracked.get(hash);
|
|
94
|
+
if (!tracked) {
|
|
95
|
+
clearTimer(state.episodes.get(hash));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
let episode = state.episodes.get(hash);
|
|
99
|
+
if (!episode) {
|
|
100
|
+
// No registrant wants polling — do not open an episode that could never spend a rung.
|
|
101
|
+
if (effectiveDelays(tracked).length === 0)
|
|
102
|
+
return;
|
|
103
|
+
episode = { attempt: 0, timer: null };
|
|
104
|
+
state.episodes.set(hash, episode);
|
|
105
|
+
}
|
|
106
|
+
if (!query.isActive()) {
|
|
107
|
+
clearTimer(episode);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
schedule(queryClient, state, hash);
|
|
111
|
+
}
|
|
112
|
+
function getState(queryClient) {
|
|
113
|
+
let state = schedulers.get(queryClient);
|
|
114
|
+
if (state)
|
|
115
|
+
return state;
|
|
116
|
+
state = { episodes: new Map(), tracked: new Map() };
|
|
117
|
+
schedulers.set(queryClient, state);
|
|
118
|
+
const scheduler = state;
|
|
119
|
+
// Exactly ONE cache listener per QueryClient, created with this state and never detached:
|
|
120
|
+
// it is WeakMap-scoped, so it is collected together with the client it belongs to.
|
|
121
|
+
queryClient.getQueryCache().subscribe((event) => {
|
|
122
|
+
const hash = event.query.queryHash;
|
|
123
|
+
if (!scheduler.tracked.has(hash) && !scheduler.episodes.has(hash))
|
|
124
|
+
return;
|
|
125
|
+
if (event.type === 'removed') {
|
|
126
|
+
discardEpisode(scheduler, hash);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
evaluate(queryClient, scheduler, hash);
|
|
130
|
+
});
|
|
131
|
+
return state;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Register interest in re-reading `queryKey` while it resolves absent. Returns an
|
|
135
|
+
* idempotent release function; releasing the last interested consumer pauses the ladder
|
|
136
|
+
* without spending the episode's remaining budget, and releasing the last registrant that
|
|
137
|
+
* wanted polling shrinks the effective ladder for the episode still in progress.
|
|
138
|
+
*/
|
|
139
|
+
export function trackAbsentDoc({ queryClient, queryKey, delays = ABSENT_RETRY_DELAYS_MS, }) {
|
|
140
|
+
const state = getState(queryClient);
|
|
141
|
+
const hash = hashOf(queryClient, queryKey);
|
|
142
|
+
const registration = { delays };
|
|
143
|
+
const existing = state.tracked.get(hash);
|
|
144
|
+
if (existing)
|
|
145
|
+
existing.registrations.push(registration);
|
|
146
|
+
else
|
|
147
|
+
state.tracked.set(hash, { queryKey, registrations: [registration] });
|
|
148
|
+
evaluate(queryClient, state, hash);
|
|
149
|
+
let released = false;
|
|
150
|
+
return () => {
|
|
151
|
+
if (released)
|
|
152
|
+
return;
|
|
153
|
+
released = true;
|
|
154
|
+
const current = state.tracked.get(hash);
|
|
155
|
+
if (!current)
|
|
156
|
+
return;
|
|
157
|
+
const before = effectiveDelays(current).length;
|
|
158
|
+
const index = current.registrations.indexOf(registration);
|
|
159
|
+
if (index >= 0)
|
|
160
|
+
current.registrations.splice(index, 1);
|
|
161
|
+
if (current.registrations.length === 0) {
|
|
162
|
+
state.tracked.delete(hash);
|
|
163
|
+
clearTimer(state.episodes.get(hash));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (effectiveDelays(current).length !== before) {
|
|
167
|
+
// The effective ladder changed: re-derive under the new one rather than letting a
|
|
168
|
+
// timer from the departed registrant's ladder fire.
|
|
169
|
+
clearTimer(state.episodes.get(hash));
|
|
170
|
+
evaluate(queryClient, state, hash);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/** Test-only: number of absence episodes currently tracked for a QueryClient. */
|
|
175
|
+
export function __absenceEpisodeCount(queryClient) {
|
|
176
|
+
return schedulers.get(queryClient)?.episodes.size ?? 0;
|
|
177
|
+
}
|
|
178
|
+
/** Test-only: rungs already spent on a key's current episode, or `null` if there is none. */
|
|
179
|
+
export function __absenceAttempt(queryClient, queryKey) {
|
|
180
|
+
return schedulers.get(queryClient)?.episodes.get(hashOf(queryClient, queryKey))?.attempt ?? null;
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=absence-scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"absence-scheduler.js","sourceRoot":"","sources":["../../../src/react/firestore/absence-scheduler.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport type { QueryClient, QueryKey } from '@tanstack/react-query';\n\n/**\n * Bounded re-read ladder for a document that resolved ABSENT.\n *\n * A one-shot lookup that lands `null` has no listener to tell it when the doc appears —\n * the row would stay blank until something invalidates it. That is the real failure the\n * realtime path was introduced to fix (a mirror doc that is missing or lagging at fetch\n * time: just after a create-and-redirect, or in a second tab racing the server mirror).\n * This scheduler restores the recovery without a listener: after an absent result it\n * re-reads a few times on a widening ladder, then gives up.\n *\n * Delays (ms) between re-reads within one absence episode. After the last rung the episode\n * is exhausted and never restarts on its own; only a later PRESENT→absent transition opens\n * a new one.\n */\nexport const ABSENT_RETRY_DELAYS_MS = [5_000, 15_000, 45_000, 120_000] as const;\n\n/**\n * The rules the scheduler holds to:\n *\n * - It polls ONLY a query in `status: 'success'` whose data is `null`. An error state is\n * never polled (a denial is not an absence), and an error mid-episode pauses the ladder\n * with its remaining budget intact.\n * - One timer per query key and one budget per absence episode: the first successful\n * `undefined → null` opens the episode, and neither a repeated `null` nor an additional\n * consumer resets it. A `present → null` transition opens a NEW episode.\n * - Several consumers may register the same key with DIFFERENT ladders. The effective\n * ladder is the union: poll if any registrant wants polling, using the LONGEST ladder\n * among them, recomputed on every register and release. Mount order never decides.\n * - Eligibility is TanStack's `query.isActive()` — at least one observer that is not\n * disabled. Subscribe-mode and disabled observers therefore never keep an episode\n * polling, while disabling ONE consumer cannot stop polling another still needs.\n * - Losing the last enabled observer PAUSES the ladder and retains the episode's remaining\n * rungs; a later enabled observer resumes from there. A new observer on an EXHAUSTED\n * episode does not restart it.\n * - Cache removal (gc) discards the episode outright, so a later re-fetch that lands absent\n * starts fresh.\n *\n * Because the timer is per key rather than per observer, N consumers of one absent id cost\n * one re-read per rung, not N.\n */\n\ntype Episode = {\n /** Rungs consumed so far; `>= effectiveDelays().length` means exhausted. */\n attempt: number;\n timer: ReturnType<typeof setTimeout> | null;\n};\n\ntype Registration = {\n delays: readonly number[];\n};\n\ntype Tracked = {\n queryKey: QueryKey;\n registrations: Registration[];\n};\n\ntype SchedulerState = {\n episodes: Map<string, Episode>;\n tracked: Map<string, Tracked>;\n};\n\nconst NO_DELAYS: readonly number[] = [];\n\n// Per-QueryClient, like the doc-subscription registry: a discarded client is GC'd with its\n// bookkeeping, and each browser tab schedules independently.\nconst schedulers = new WeakMap<QueryClient, SchedulerState>();\n\n/**\n * Hash a key the way THIS client hashes it, so a consumer with a custom\n * `queryKeyHashFn` still lines up with `QueryCache.get()`.\n */\nfunction hashOf(queryClient: QueryClient, queryKey: QueryKey): string {\n return queryClient.defaultQueryOptions({ queryKey }).queryHash;\n}\n\n/** Union of the registrants' ladders: the longest one wins; none means no polling. */\nfunction effectiveDelays(tracked: Tracked | undefined): readonly number[] {\n if (!tracked) return NO_DELAYS;\n let longest: readonly number[] = NO_DELAYS;\n for (const registration of tracked.registrations) {\n if (registration.delays.length > longest.length) longest = registration.delays;\n }\n return longest;\n}\n\nfunction clearTimer(episode: Episode | undefined): void {\n if (episode?.timer != null) {\n clearTimeout(episode.timer);\n episode.timer = null;\n }\n}\n\nfunction discardEpisode(state: SchedulerState, hash: string): void {\n clearTimer(state.episodes.get(hash));\n state.episodes.delete(hash);\n}\n\nfunction schedule(queryClient: QueryClient, state: SchedulerState, hash: string): void {\n const tracked = state.tracked.get(hash);\n const episode = state.episodes.get(hash);\n if (!tracked || !episode || episode.timer !== null) return;\n\n const delays = effectiveDelays(tracked);\n if (episode.attempt >= delays.length) return;\n\n episode.timer = setTimeout(() => {\n episode.timer = null;\n episode.attempt += 1;\n const current = state.tracked.get(hash);\n if (!current || !isAbsentAndActive(queryClient, hash)) return;\n void queryClient.refetchQueries({ queryKey: current.queryKey, exact: true, type: 'active' });\n // Lay the next rung; the ladder stops the moment the doc appears, the budget runs\n // out, or the last enabled observer goes away.\n evaluate(queryClient, state, hash);\n }, delays[episode.attempt]);\n}\n\nfunction isAbsentAndActive(queryClient: QueryClient, hash: string): boolean {\n const query = queryClient.getQueryCache().get(hash);\n if (!query) return false;\n return query.state.status === 'success' && query.state.data === null && query.isActive();\n}\n\nfunction evaluate(queryClient: QueryClient, state: SchedulerState, hash: string): void {\n const query = queryClient.getQueryCache().get(hash);\n if (!query) {\n discardEpisode(state, hash);\n return;\n }\n\n const { status, data } = query.state;\n\n if (status === 'success' && data !== null) {\n // The doc is present — the episode is over.\n discardEpisode(state, hash);\n return;\n }\n\n if (status !== 'success') {\n // Pending or errored: never polled, but the budget survives so an error mid-episode\n // cannot hand the id a fresh ladder when it returns to absent.\n clearTimer(state.episodes.get(hash));\n return;\n }\n\n const tracked = state.tracked.get(hash);\n if (!tracked) {\n clearTimer(state.episodes.get(hash));\n return;\n }\n\n let episode = state.episodes.get(hash);\n if (!episode) {\n // No registrant wants polling — do not open an episode that could never spend a rung.\n if (effectiveDelays(tracked).length === 0) return;\n episode = { attempt: 0, timer: null };\n state.episodes.set(hash, episode);\n }\n\n if (!query.isActive()) {\n clearTimer(episode);\n return;\n }\n\n schedule(queryClient, state, hash);\n}\n\nfunction getState(queryClient: QueryClient): SchedulerState {\n let state = schedulers.get(queryClient);\n if (state) return state;\n\n state = { episodes: new Map(), tracked: new Map() };\n schedulers.set(queryClient, state);\n\n const scheduler = state;\n // Exactly ONE cache listener per QueryClient, created with this state and never detached:\n // it is WeakMap-scoped, so it is collected together with the client it belongs to.\n queryClient.getQueryCache().subscribe((event) => {\n const hash = event.query.queryHash;\n if (!scheduler.tracked.has(hash) && !scheduler.episodes.has(hash)) return;\n if (event.type === 'removed') {\n discardEpisode(scheduler, hash);\n return;\n }\n evaluate(queryClient, scheduler, hash);\n });\n\n return state;\n}\n\nexport interface TrackAbsentDocParams {\n queryClient: QueryClient;\n /** Exact key of the per-id lookup query. */\n queryKey: QueryKey;\n /**\n * Ladder this registrant wants; an empty array means it wants no re-reads. Registrants\n * on the same key combine by union — the longest ladder among them governs — so one\n * consumer passing `[]` never disables polling another consumer asked for.\n */\n delays?: readonly number[];\n}\n\n/**\n * Register interest in re-reading `queryKey` while it resolves absent. Returns an\n * idempotent release function; releasing the last interested consumer pauses the ladder\n * without spending the episode's remaining budget, and releasing the last registrant that\n * wanted polling shrinks the effective ladder for the episode still in progress.\n */\nexport function trackAbsentDoc({\n queryClient,\n queryKey,\n delays = ABSENT_RETRY_DELAYS_MS,\n}: TrackAbsentDocParams): () => void {\n const state = getState(queryClient);\n const hash = hashOf(queryClient, queryKey);\n const registration: Registration = { delays };\n\n const existing = state.tracked.get(hash);\n if (existing) existing.registrations.push(registration);\n else state.tracked.set(hash, { queryKey, registrations: [registration] });\n\n evaluate(queryClient, state, hash);\n\n let released = false;\n return () => {\n if (released) return;\n released = true;\n const current = state.tracked.get(hash);\n if (!current) return;\n\n const before = effectiveDelays(current).length;\n const index = current.registrations.indexOf(registration);\n if (index >= 0) current.registrations.splice(index, 1);\n\n if (current.registrations.length === 0) {\n state.tracked.delete(hash);\n clearTimer(state.episodes.get(hash));\n return;\n }\n\n if (effectiveDelays(current).length !== before) {\n // The effective ladder changed: re-derive under the new one rather than letting a\n // timer from the departed registrant's ladder fire.\n clearTimer(state.episodes.get(hash));\n evaluate(queryClient, state, hash);\n }\n };\n}\n\n/** Test-only: number of absence episodes currently tracked for a QueryClient. */\nexport function __absenceEpisodeCount(queryClient: QueryClient): number {\n return schedulers.get(queryClient)?.episodes.size ?? 0;\n}\n\n/** Test-only: rungs already spent on a key's current episode, or `null` if there is none. */\nexport function __absenceAttempt(queryClient: QueryClient, queryKey: QueryKey): number | null {\n return schedulers.get(queryClient)?.episodes.get(hashOf(queryClient, queryKey))?.attempt ?? null;\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type Firestore } from 'firebase/firestore';
|
|
2
|
+
import type { QueryClient } from '@tanstack/react-query';
|
|
3
|
+
/**
|
|
4
|
+
* Coalescing document loader behind {@link useBatchFirestoreDocs}'s one-shot mode.
|
|
5
|
+
*
|
|
6
|
+
* Every id is its own observed React Query query, so without coalescing N ids would issue
|
|
7
|
+
* N Firestore round-trips. The loader collects the ids enqueued in a single microtask and
|
|
8
|
+
* dispatches them as `where(documentId(), 'in', chunk)` queries of at most 30 ids each
|
|
9
|
+
* (`'batch'` transport), or as concurrent per-document gets (`'get'` transport — required
|
|
10
|
+
* for collections whose rules gate reads on `resource.data`, where an unconstrained list
|
|
11
|
+
* query is denied wholesale but per-document gets are evaluated per doc).
|
|
12
|
+
*
|
|
13
|
+
* Two properties are load-bearing:
|
|
14
|
+
*
|
|
15
|
+
* - **The loader resolves promises only — it NEVER writes the query cache.** TanStack's
|
|
16
|
+
* per-query fetch identity is therefore the sole guard against a late batch response
|
|
17
|
+
* overwriting a newer per-id result.
|
|
18
|
+
* - **A dispatched batch is closed.** A later enqueue of the same id (a refetch after a
|
|
19
|
+
* cancellation, say) always joins a NEW batch, never the in-flight one — so a cancelled
|
|
20
|
+
* read can never be served by the request it was cancelled out of.
|
|
21
|
+
*
|
|
22
|
+
* Cancelling one per-id query detaches only that waiter; the shared request still completes
|
|
23
|
+
* for its batch-mates.
|
|
24
|
+
*
|
|
25
|
+
* Loaders are scoped per `(QueryClient, Firestore instance, collectionPath)`. The Firestore
|
|
26
|
+
* instance is part of the scope on purpose: two `db` instances sharing a collection path
|
|
27
|
+
* must never share a request.
|
|
28
|
+
*/
|
|
29
|
+
/** Firestore's hard cap on the number of values in an `in` filter. */
|
|
30
|
+
export declare const FIRESTORE_IN_LIMIT = 30;
|
|
31
|
+
/** Maximum `in`-query requests in flight at once, per loader scope. */
|
|
32
|
+
export declare const MAX_CONCURRENT_BATCH_REQUESTS = 4;
|
|
33
|
+
/** Maximum per-document gets in flight at once, per loader scope. */
|
|
34
|
+
export declare const MAX_CONCURRENT_DOC_GETS = 12;
|
|
35
|
+
export type LoadedDoc = Record<string, unknown> & {
|
|
36
|
+
id: string;
|
|
37
|
+
};
|
|
38
|
+
export type DocLoader = {
|
|
39
|
+
/** Resolve one id through the coalescing `in`-query batcher. */
|
|
40
|
+
loadBatched(id: string, signal?: AbortSignal): Promise<LoadedDoc | null>;
|
|
41
|
+
/** Resolve one id through its own `getDoc`, bounded by the get concurrency limit. */
|
|
42
|
+
loadOne(id: string, signal?: AbortSignal): Promise<LoadedDoc | null>;
|
|
43
|
+
};
|
|
44
|
+
export declare function getDocLoader(queryClient: QueryClient, db: Firestore, collectionPath: string): DocLoader;
|
|
45
|
+
//# sourceMappingURL=batch-doc-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch-doc-loader.d.ts","sourceRoot":"","sources":["../../../src/react/firestore/batch-doc-loader.ts"],"names":[],"mappings":"AAEA,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,sEAAsE;AACtE,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,uEAAuE;AACvE,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAE/C,qEAAqE;AACrE,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAOjE,MAAM,MAAM,SAAS,GAAG;IACtB,gEAAgE;IAChE,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IACzE,qFAAqF;IACrF,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;CACtE,CAAC;AAoJF,wBAAgB,YAAY,CAC1B,WAAW,EAAE,WAAW,EACxB,EAAE,EAAE,SAAS,EACb,cAAc,EAAE,MAAM,GACrB,SAAS,CAiBX"}
|