@tanstack/react-db 0.5.12 → 1.0.0-rc.1.1

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 (40) hide show
  1. package/dist/cjs/live-query-internals.cjs.map +1 -1
  2. package/dist/cjs/live-query-internals.d.cts +3 -1
  3. package/dist/cjs/source-id-bindings.cjs +9 -42
  4. package/dist/cjs/source-id-bindings.cjs.map +1 -1
  5. package/dist/cjs/source-id-bindings.d.cts +6 -17
  6. package/dist/cjs/useLiveInfiniteQuery.cjs +93 -173
  7. package/dist/cjs/useLiveInfiniteQuery.cjs.map +1 -1
  8. package/dist/cjs/useLiveInfiniteQuery.d.cts +3 -14
  9. package/dist/cjs/useLiveQuery.cjs +187 -224
  10. package/dist/cjs/useLiveQuery.cjs.map +1 -1
  11. package/dist/cjs/useLiveQuery.d.cts +37 -136
  12. package/dist/cjs/useLiveSuspenseQuery.cjs +71 -64
  13. package/dist/cjs/useLiveSuspenseQuery.cjs.map +1 -1
  14. package/dist/cjs/useLiveSuspenseQuery.d.cts +10 -9
  15. package/dist/cjs/usePacedMutations.cjs.map +1 -1
  16. package/dist/cjs/usePacedMutations.d.cts +1 -1
  17. package/dist/esm/live-query-internals.d.ts +3 -1
  18. package/dist/esm/live-query-internals.js.map +1 -1
  19. package/dist/esm/source-id-bindings.d.ts +6 -17
  20. package/dist/esm/source-id-bindings.js +9 -40
  21. package/dist/esm/source-id-bindings.js.map +1 -1
  22. package/dist/esm/useLiveInfiniteQuery.d.ts +3 -14
  23. package/dist/esm/useLiveInfiniteQuery.js +95 -175
  24. package/dist/esm/useLiveInfiniteQuery.js.map +1 -1
  25. package/dist/esm/useLiveQuery.d.ts +37 -136
  26. package/dist/esm/useLiveQuery.js +189 -226
  27. package/dist/esm/useLiveQuery.js.map +1 -1
  28. package/dist/esm/useLiveSuspenseQuery.d.ts +10 -9
  29. package/dist/esm/useLiveSuspenseQuery.js +71 -64
  30. package/dist/esm/useLiveSuspenseQuery.js.map +1 -1
  31. package/dist/esm/usePacedMutations.d.ts +1 -1
  32. package/dist/esm/usePacedMutations.js.map +1 -1
  33. package/package.json +3 -3
  34. package/skills/react-db/SKILL.md +24 -15
  35. package/src/live-query-internals.ts +3 -0
  36. package/src/source-id-bindings.ts +16 -62
  37. package/src/useLiveInfiniteQuery.ts +138 -296
  38. package/src/useLiveQuery.ts +329 -495
  39. package/src/useLiveSuspenseQuery.ts +122 -132
  40. package/src/usePacedMutations.ts +1 -1
@@ -1,206 +1,127 @@
1
1
  "use client";
2
2
  import { useRef, useCallback, useSyncExternalStore } from "react";
3
- import { normalizeLiveQueryWindowPageSize, getLiveQueryWindowInputKind, compareLiveQueryWindowDependencies, shouldPreserveLiveQueryWindowPageCount, liveQueryWindowMatches, resolveLiveQueryWindowInput, createLiveQueryCollection, getLiveQueryWindowCollectionWarning, assertLiveQueryWindowManyResult, createLiveQueryWindowController, resumeDeferredLiveQueryCollections, fetchNextLiveQueryWindowPage } from "@tanstack/db";
3
+ import { readLiveQueryWindowPageShape, isCollection, getLiveQueryKey, bindLiveQueryWindow, rejectLiveQueryInput, releaseDeferredOnReclaim, resumeDeferredLiveQueryCollections, fetchNextLiveQueryWindowPage, createLiveQuerySourceIdClaims } from "@tanstack/db";
4
4
  import { useOptionalDbClient } from "./DbProvider.js";
5
- import { claimSourceIds, createSourceIdBindings } from "./source-id-bindings.js";
6
- import { warnDeprecatedDepsArray, prepareDerivedQuery, warnUnhashableDerivedIdentity, prepareQueryValue } from "./useLiveQuery.js";
7
- const DEFAULT_GC_TIME_MS = 1;
5
+ import { prepareDerivedQuery, prepareQueryValue, getIdentityScope } from "./useLiveQuery.js";
8
6
  const unpreparedQueryValue = /* @__PURE__ */ Symbol(`unpreparedQueryValue`);
9
- function queryIdentityMatches(comparison, sameClient, usesLegacyDeps) {
10
- return sameClient && (usesLegacyDeps ? !comparison.changed : comparison.structurallyEqual);
11
- }
12
- function useLiveInfiniteQuery(queryFnOrCollection, config, deps) {
13
- if (`getNextPageParam` in config) {
14
- throw new Error(
15
- `getNextPageParam is not supported by useLiveInfiniteQuery. Use an on-demand collection and fulfill meta.loadSubsetOptions in queryFn for server pagination.`
16
- );
17
- }
18
- const pageSize = normalizeLiveQueryWindowPageSize(config.pageSize);
19
- const initialPageParam = config.initialPageParam ?? 0;
7
+ function useLiveInfiniteQuery(queryFnOrCollection, config) {
8
+ const pageShape = readLiveQueryWindowPageShape(config);
20
9
  const contextDbClient = useOptionalDbClient();
21
10
  const dbClient = config.client ?? contextDbClient;
22
- const inputIsCollection = getLiveQueryWindowInputKind(queryFnOrCollection) === `collection`;
11
+ const inputIsCollection = isCollection(queryFnOrCollection);
23
12
  const committedRef = useRef(null);
24
13
  const committed = committedRef.current;
25
- const renderedCollectionRef = useRef(null);
14
+ const renderedRef = useRef(null);
26
15
  const sourceIdsRef = useRef(null);
27
- const inputKind = inputIsCollection ? `collection` : `query`;
28
16
  const derivedIdentityProfilerRef = useRef({
29
17
  renderCount: 0,
30
18
  totalMs: 0,
31
19
  maxMs: 0,
32
20
  warned: false
33
21
  });
34
- const legacyUnhashableIdentityRef = useRef([
35
- `legacy-unhashable`
36
- ]);
37
22
  const deferredCollections = /* @__PURE__ */ new Set();
23
+ const identityScope = () => getIdentityScope(
24
+ sourceIdsRef.current ??= createLiveQuerySourceIdClaims(),
25
+ dbClient,
26
+ `useLiveInfiniteQuery`
27
+ );
28
+ const queryKey = getLiveQueryKey(config);
38
29
  let preparedQueryValue = unpreparedQueryValue;
39
- let identityDeps = [];
40
- let identityMode = `collection`;
41
- if (!inputIsCollection) {
42
- if (config.queryKey !== void 0) {
43
- identityMode = `queryKey`;
44
- identityDeps = config.queryKey;
45
- } else if (deps !== void 0) {
46
- identityMode = `legacyDeps`;
47
- identityDeps = deps;
48
- warnDeprecatedDepsArray(`useLiveInfiniteQuery`);
49
- } else if (committed?.identityMode === `derived` && committed.inputQuery === queryFnOrCollection && committed.client === dbClient) {
50
- identityMode = `derived`;
51
- identityDeps = committed.dependencies ?? [];
52
- } else {
53
- identityMode = `derived`;
54
- const preparation = prepareDerivedQuery(
55
- queryFnOrCollection,
56
- dbClient,
57
- derivedIdentityProfilerRef.current,
58
- deferredCollections
59
- );
60
- preparedQueryValue = preparation.value;
61
- claimSourceIds(
62
- sourceIdsRef.current ??= createSourceIdBindings(),
63
- preparation.value,
64
- dbClient,
65
- `useLiveInfiniteQuery`,
66
- () => resumeDeferredLiveQueryCollections(deferredCollections)
67
- );
68
- if (preparation.status === `hashable`) {
69
- identityDeps = preparation.identityDeps;
30
+ let dependencies;
31
+ let derivedQuery = void 0;
32
+ const rendered = renderedRef.current;
33
+ let binding;
34
+ try {
35
+ if (!inputIsCollection) {
36
+ if (queryKey !== void 0) {
37
+ dependencies = queryKey;
38
+ } else if (committed !== null && committed.derivedQuery === queryFnOrCollection && committed.binding.scope === dbClient) {
39
+ derivedQuery = queryFnOrCollection;
40
+ dependencies = committed.binding.dependencies;
70
41
  } else {
71
- warnUnhashableDerivedIdentity(preparation.error);
72
- identityDeps = legacyUnhashableIdentityRef.current;
42
+ const preparation = prepareDerivedQuery(
43
+ queryFnOrCollection,
44
+ derivedIdentityProfilerRef.current,
45
+ identityScope(),
46
+ deferredCollections
47
+ );
48
+ preparedQueryValue = preparation.value;
49
+ derivedQuery = queryFnOrCollection;
50
+ dependencies = preparation.identityDeps;
73
51
  }
74
52
  }
75
- }
76
- const usesLegacyDeps = !inputIsCollection && config.queryKey === void 0 && deps !== void 0;
77
- const dependencyComparison = compareLiveQueryWindowDependencies(
78
- committed?.dependencies,
79
- identityDeps
80
- );
81
- const sameClient = committed?.client === dbClient;
82
- const dependenciesChanged = !inputIsCollection && !queryIdentityMatches(dependencyComparison, sameClient, usesLegacyDeps);
83
- const dependenciesStructurallyEqual = usesLegacyDeps && sameClient && dependencyComparison.structurallyEqual;
84
- const needsNewCollection = committed === null || committed.inputKind !== inputKind || inputIsCollection && committed.inputCollection !== queryFnOrCollection || dependenciesChanged;
85
- const pageShapeChanged = committed === null || committed.pageSize !== pageSize || committed.initialPageParam !== initialPageParam;
86
- const needsNewController = committed === null || needsNewCollection || pageShapeChanged;
87
- let renderState = committed;
88
- if (needsNewController) {
89
- let collection = committed?.collection;
90
- let warning = null;
91
- let startInRender = false;
92
- let suppliedCollection = false;
93
- let createdCollection = false;
94
- const canPreservePageCount = shouldPreserveLiveQueryWindowPageCount({
95
- hasPreviousController: committed !== null,
96
- previousInputKind: committed?.inputKind,
97
- inputKind,
98
- sameCollection: inputIsCollection && committed?.inputCollection === queryFnOrCollection,
99
- dependenciesChanged,
100
- dependenciesStructurallyEqual,
101
- pageShapeChanged
102
- });
103
- const previousPageCount = committed ? Math.max(1, committed.controller.getSnapshot().pages.length) : 1;
104
- const initialPageCount = canPreservePageCount ? previousPageCount : 1;
105
- const requiredLimit = initialPageCount * pageSize + 1;
106
- if (needsNewCollection) {
107
- let inputValue = queryFnOrCollection;
108
- if (!inputIsCollection) {
109
- if (preparedQueryValue === unpreparedQueryValue) {
110
- preparedQueryValue = prepareQueryValue(
111
- queryFnOrCollection,
112
- dbClient,
113
- deferredCollections
114
- );
115
- }
116
- inputValue = () => preparedQueryValue;
117
- }
118
- const rendered = renderedCollectionRef.current;
119
- if (!inputIsCollection && rendered !== null && queryIdentityMatches(
120
- compareLiveQueryWindowDependencies(
121
- rendered.dependencies,
122
- identityDeps
123
- ),
124
- rendered.client === dbClient,
125
- usesLegacyDeps
126
- ) && rendered.collection.status !== `cleaned-up` && rendered.collection.status !== `error` && liveQueryWindowMatches(rendered.collection, requiredLimit)) {
127
- collection = rendered.collection;
128
- for (const deferred of rendered.deferredCollections) {
129
- deferredCollections.add(deferred);
130
- }
131
- } else {
132
- const input = resolveLiveQueryWindowInput(inputValue);
133
- if (input.kind === `collection`) {
134
- collection = input.collection;
135
- suppliedCollection = true;
136
- renderedCollectionRef.current = null;
137
- } else {
138
- collection = createLiveQueryCollection({
139
- query: input.query.limit(requiredLimit).offset(0),
140
- gcTime: DEFAULT_GC_TIME_MS
141
- });
142
- startInRender = true;
143
- createdCollection = true;
144
- }
53
+ const compileQuery = () => {
54
+ if (preparedQueryValue === unpreparedQueryValue) {
55
+ preparedQueryValue = prepareQueryValue(
56
+ queryFnOrCollection,
57
+ dbClient,
58
+ deferredCollections
59
+ );
145
60
  }
146
- }
147
- if (!collection) {
148
- throw new Error(`useLiveInfiniteQuery: Failed to create a collection.`);
149
- }
150
- if (inputIsCollection) {
151
- warning = getLiveQueryWindowCollectionWarning(collection, pageSize + 1) ?? null;
152
- } else {
153
- assertLiveQueryWindowManyResult(collection);
154
- }
155
- if (suppliedCollection) {
156
- startInRender = liveQueryWindowMatches(collection, requiredLimit);
157
- }
158
- if (startInRender) collection.startSyncImmediate();
159
- if (createdCollection) {
160
- renderedCollectionRef.current = {
61
+ return preparedQueryValue;
62
+ };
63
+ binding = bindLiveQueryWindow(
64
+ committed?.binding ?? null,
65
+ {
66
+ ...pageShape,
67
+ input: typeof queryFnOrCollection === `function` ? compileQuery : queryFnOrCollection,
68
+ dependencies,
69
+ scope: dbClient,
161
70
  client: dbClient,
162
- dependencies: [...identityDeps],
163
- collection,
164
- deferredCollections
165
- };
71
+ queryKey,
72
+ recompile: `identity`
73
+ },
74
+ rendered?.binding
75
+ );
76
+ } catch (error) {
77
+ rejectLiveQueryInput(deferredCollections, error);
78
+ }
79
+ let currentRenderState = committed;
80
+ if (binding !== committed?.binding) {
81
+ const newCollection = binding.collection !== committed?.binding.collection;
82
+ if (newCollection && binding.collection === rendered?.binding.collection) {
83
+ for (const deferred of rendered.deferredCollections) {
84
+ deferredCollections.add(deferred);
85
+ }
166
86
  }
167
- renderState = {
168
- inputKind,
169
- inputCollection: inputIsCollection ? collection : null,
170
- inputQuery: inputIsCollection ? null : queryFnOrCollection,
171
- client: dbClient,
172
- identityMode,
173
- dependencies: inputIsCollection ? null : [...identityDeps],
174
- pageSize,
175
- initialPageParam,
176
- collection,
177
- controller: createLiveQueryWindowController(collection, {
178
- pageSize,
179
- initialPageParam,
180
- initialPageCount
181
- }),
182
- warning,
183
- warned: false,
184
- deferredCollections
87
+ const reusesRendered = newCollection && binding.collection === rendered?.binding.collection;
88
+ currentRenderState = {
89
+ binding,
90
+ derivedQuery,
91
+ deferredCollections,
92
+ // A render that never commits releases its deferred sync starts when
93
+ // garbage collection reclaims the window collection it built. A reused
94
+ // collection keeps the release its first render registered.
95
+ cancelReclaimRelease: reusesRendered ? rendered.cancelReclaimRelease : newCollection && binding.ownsCollection ? releaseDeferredOnReclaim(binding.collection, deferredCollections) : void 0,
96
+ warned: false
185
97
  };
98
+ if (binding.supplied) renderedRef.current = null;
99
+ else if (newCollection && binding.ownsCollection) {
100
+ renderedRef.current = currentRenderState;
101
+ }
186
102
  }
187
- const currentRenderState = renderState;
188
- const controller = currentRenderState.controller;
103
+ const renderState = currentRenderState;
104
+ const controller = renderState.binding.controller;
189
105
  const subscribe = useCallback(
190
106
  (onStoreChange) => {
191
107
  const unsubscribe = controller.subscribe(onStoreChange);
192
- committedRef.current = currentRenderState;
193
- if (currentRenderState.warning && !currentRenderState.warned) {
194
- currentRenderState.warned = true;
195
- console.warn(currentRenderState.warning);
108
+ committedRef.current = renderState;
109
+ if (renderState.binding.warning && !renderState.warned) {
110
+ renderState.warned = true;
111
+ console.warn(renderState.binding.warning);
196
112
  }
197
- resumeDeferredLiveQueryCollections(currentRenderState.deferredCollections);
113
+ renderState.cancelReclaimRelease?.();
114
+ resumeDeferredLiveQueryCollections(renderState.deferredCollections);
198
115
  return unsubscribe;
199
116
  },
200
- [controller, currentRenderState]
117
+ [controller, renderState]
201
118
  );
202
119
  const getSnapshot = useCallback(() => controller.getSnapshot(), [controller]);
203
- const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
120
+ const snapshot = useSyncExternalStore(
121
+ subscribe,
122
+ getSnapshot,
123
+ () => controller.getServerSnapshot()
124
+ );
204
125
  const fetchNextPage = useCallback(
205
126
  () => fetchNextLiveQueryWindowPage(controller),
206
127
  [controller]
@@ -213,12 +134,11 @@ function useLiveInfiniteQuery(queryFnOrCollection, config, deps) {
213
134
  status: snapshot.status,
214
135
  isLoading: snapshot.isLoading,
215
136
  isReady: snapshot.isReady,
216
- persistedStatus: snapshot.persistedStatus,
217
- isPersistedReady: snapshot.isPersistedReady,
218
- persistedError: snapshot.persistedError,
219
137
  isIdle: snapshot.isIdle,
220
- isError: snapshot.isError,
138
+ isLoadingError: snapshot.isLoadingError,
139
+ isCrashed: snapshot.isCrashed,
221
140
  isCleanedUp: snapshot.isCleanedUp,
141
+ isSyncError: snapshot.isSyncError,
222
142
  collection: snapshot.collection,
223
143
  isEnabled: snapshot.isEnabled,
224
144
  pages: snapshot.pages,
@@ -226,7 +146,7 @@ function useLiveInfiniteQuery(queryFnOrCollection, config, deps) {
226
146
  fetchNextPage,
227
147
  hasNextPage: snapshot.hasNextPage,
228
148
  isFetchingNextPage: snapshot.isFetchingNextPage,
229
- error: snapshot.error
149
+ health: snapshot.health
230
150
  };
231
151
  }
232
152
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveInfiniteQuery.js","sources":["../../src/useLiveInfiniteQuery.ts"],"sourcesContent":["'use client'\n\nimport { useCallback, useRef, useSyncExternalStore } from 'react'\nimport {\n assertLiveQueryWindowManyResult,\n compareLiveQueryWindowDependencies,\n createLiveQueryCollection,\n createLiveQueryWindowController,\n fetchNextLiveQueryWindowPage,\n getLiveQueryWindowCollectionWarning,\n getLiveQueryWindowInputKind,\n liveQueryWindowMatches,\n normalizeLiveQueryWindowPageSize,\n resolveLiveQueryWindowInput,\n shouldPreserveLiveQueryWindowPageCount,\n} from '@tanstack/db'\nimport { useOptionalDbClient } from './DbProvider'\nimport {\n claimSourceIds,\n createSourceIdBindings,\n resumeSyncStarts,\n} from './source-id-bindings'\nimport {\n prepareDerivedQuery,\n prepareQueryValue,\n warnDeprecatedDepsArray,\n warnUnhashableDerivedIdentity,\n} from './useLiveQuery'\nimport type { SourceIdBindings } from './source-id-bindings'\nimport type {\n DerivedIdentityProfiler,\n LiveQueryKey,\n useLiveQuery,\n} from './useLiveQuery'\nimport type {\n Collection,\n CollectionImpl as CollectionImplType,\n CollectionStatus,\n Context,\n DbClient,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryPersistedStatus,\n LiveQueryWindowController,\n NonSingleResult,\n QueryBuilder,\n} from '@tanstack/db'\n\n// Live queries created here are cleaned up immediately (0 disables GC).\nconst DEFAULT_GC_TIME_MS = 1\nconst unpreparedQueryValue = Symbol(`unpreparedQueryValue`)\n\n// Keep the generic parameter for existing typed config wrappers.\nexport type UseLiveInfiniteQueryConfig<_TContext extends Context> = {\n /**\n * Explicit identity for queries that contain opaque functional variants or\n * are hot enough that deriving identity from structured IR is too expensive.\n * Structured queries should omit this so DB can derive identity directly.\n */\n queryKey?: LiveQueryKey\n /** Override the nearest DbProvider for this query. */\n client?: DbClient\n pageSize?: number\n /** First result-page label, not a server cursor or remote offset. */\n initialPageParam?: number\n}\n\nexport type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<\n ReturnType<typeof useLiveQuery<TContext>>,\n `data`\n> & {\n data: InferResultType<TContext>\n pages: Array<Array<InferResultType<TContext>[number]>>\n pageParams: Array<number>\n fetchNextPage: () => Promise<void>\n hasNextPage: boolean\n isFetchingNextPage: boolean\n error: unknown\n}\n\nexport type UseLiveInfiniteQueryReturnWithCollection<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n> = {\n data: Array<TResult>\n state: Map<TKey, TResult>\n collection: Collection<TResult, TKey, TUtils> & NonSingleResult\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n persistedStatus: LiveQueryPersistedStatus\n isPersistedReady: boolean\n persistedError: unknown | undefined\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n isEnabled: true\n pages: Array<Array<TResult>>\n pageParams: Array<number>\n fetchNextPage: () => Promise<void>\n hasNextPage: boolean\n isFetchingNextPage: boolean\n error: unknown\n}\n\ntype EnabledLiveQueryReturn<TContext extends Context> = ReturnType<\n typeof useLiveQuery<TContext>\n>\n\ntype InfiniteQueryRenderState = {\n inputKind: `collection` | `query`\n inputCollection: Collection<any, any, any> | null\n inputQuery: unknown\n client: DbClient | undefined\n identityMode: `collection` | `queryKey` | `legacyDeps` | `derived`\n dependencies: Array<unknown> | null\n pageSize: number\n initialPageParam: number\n collection: Collection<any, any, any>\n controller: LiveQueryWindowController<any, any>\n warning: string | null\n warned: boolean\n deferredCollections: Set<\n CollectionImplType<any, string | number, any, any, any>\n >\n}\n\ntype RenderedWindowCollection = {\n client: DbClient | undefined\n dependencies: Array<unknown>\n collection: Collection<any, any, any>\n deferredCollections: Set<\n CollectionImplType<any, string | number, any, any, any>\n >\n}\n\n/**\n * Whether a recorded query identity matches the current one. Legacy deps\n * compare by reference. Derived identities and query keys compare by structure.\n */\nfunction queryIdentityMatches(\n comparison: { changed: boolean; structurallyEqual: boolean },\n sameClient: boolean,\n usesLegacyDeps: boolean,\n): boolean {\n return (\n sameClient &&\n (usesLegacyDeps ? !comparison.changed : comparison.structurallyEqual)\n )\n}\n\n/**\n * Create an infinite query using a query function with live updates.\n *\n * Uses `utils.setWindow()` to dynamically adjust the limit/offset window\n * without recreating the live query collection on each page change.\n *\n * @param queryFn - Query function that defines what data to fetch. Must include `.orderBy()` for setWindow to work.\n * @param config - Configuration including pageSize and an optional initial page label\n * @param deps - Deprecated array of dependencies that trigger query re-execution when changed\n * @returns Object with pages, data, and pagination controls\n */\n\n// Overload for pre-created collection (non-single result)\nexport function useLiveInfiniteQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult,\n config: UseLiveInfiniteQueryConfig<any>,\n): UseLiveInfiniteQueryReturnWithCollection<TResult, TKey, TUtils>\n\n// Overload for query function\nexport function useLiveInfiniteQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n config: UseLiveInfiniteQueryConfig<TContext>,\n deps?: Array<unknown>,\n): UseLiveInfiniteQueryReturn<TContext>\n\n// Implementation\nexport function useLiveInfiniteQuery<TContext extends Context>(\n queryFnOrCollection: any,\n config: UseLiveInfiniteQueryConfig<TContext>,\n deps?: Array<unknown>,\n): UseLiveInfiniteQueryReturn<TContext> {\n if (`getNextPageParam` in config) {\n throw new Error(\n `getNextPageParam is not supported by useLiveInfiniteQuery. Use an on-demand collection and fulfill meta.loadSubsetOptions in queryFn for server pagination.`,\n )\n }\n const pageSize = normalizeLiveQueryWindowPageSize(config.pageSize)\n const initialPageParam = config.initialPageParam ?? 0\n const contextDbClient = useOptionalDbClient()\n const dbClient = config.client ?? contextDbClient\n\n const inputIsCollection =\n getLiveQueryWindowInputKind(queryFnOrCollection) === `collection`\n\n const committedRef = useRef<InfiniteQueryRenderState | null>(null)\n const committed = committedRef.current\n // Like useLiveQuery's instance memo, keep the window collection built by the\n // latest render. A second render before commit, such as a StrictMode double\n // render, reuses it instead of starting another one. Only the collection is\n // kept. Each render still builds its own controller and page count from the\n // committed state, which an uncommitted render never changes.\n const renderedCollectionRef = useRef<RenderedWindowCollection | null>(null)\n const sourceIdsRef = useRef<SourceIdBindings | null>(null)\n const inputKind = inputIsCollection ? `collection` : `query`\n const derivedIdentityProfilerRef = useRef<DerivedIdentityProfiler>({\n renderCount: 0,\n totalMs: 0,\n maxMs: 0,\n warned: false,\n })\n const legacyUnhashableIdentityRef = useRef<Array<unknown>>([\n `legacy-unhashable`,\n ])\n const deferredCollections = new Set<\n CollectionImplType<any, string | number, any, any, any>\n >()\n\n let preparedQueryValue: unknown | typeof unpreparedQueryValue =\n unpreparedQueryValue\n let identityDeps: ReadonlyArray<unknown> = []\n let identityMode: InfiniteQueryRenderState[`identityMode`] = `collection`\n\n if (!inputIsCollection) {\n if (config.queryKey !== undefined) {\n identityMode = `queryKey`\n identityDeps = config.queryKey\n } else if (deps !== undefined) {\n identityMode = `legacyDeps`\n identityDeps = deps\n warnDeprecatedDepsArray(`useLiveInfiniteQuery`)\n } else if (\n committed?.identityMode === `derived` &&\n committed.inputQuery === queryFnOrCollection &&\n committed.client === dbClient\n ) {\n identityMode = `derived`\n identityDeps = committed.dependencies ?? []\n } else {\n identityMode = `derived`\n const preparation = prepareDerivedQuery(\n queryFnOrCollection,\n dbClient,\n derivedIdentityProfilerRef.current,\n deferredCollections,\n )\n preparedQueryValue = preparation.value\n claimSourceIds(\n (sourceIdsRef.current ??= createSourceIdBindings()),\n preparation.value,\n dbClient,\n `useLiveInfiniteQuery`,\n () => resumeSyncStarts(deferredCollections),\n )\n if (preparation.status === `hashable`) {\n identityDeps = preparation.identityDeps\n } else {\n warnUnhashableDerivedIdentity(preparation.error)\n identityDeps = legacyUnhashableIdentityRef.current\n }\n }\n }\n\n const usesLegacyDeps =\n !inputIsCollection && config.queryKey === undefined && deps !== undefined\n const dependencyComparison = compareLiveQueryWindowDependencies(\n committed?.dependencies,\n identityDeps,\n )\n const sameClient = committed?.client === dbClient\n const dependenciesChanged =\n !inputIsCollection &&\n !queryIdentityMatches(dependencyComparison, sameClient, usesLegacyDeps)\n const dependenciesStructurallyEqual =\n usesLegacyDeps && sameClient && dependencyComparison.structurallyEqual\n const needsNewCollection =\n committed === null ||\n committed.inputKind !== inputKind ||\n (inputIsCollection && committed.inputCollection !== queryFnOrCollection) ||\n dependenciesChanged\n const pageShapeChanged =\n committed === null ||\n committed.pageSize !== pageSize ||\n committed.initialPageParam !== initialPageParam\n const needsNewController =\n committed === null || needsNewCollection || pageShapeChanged\n\n let renderState = committed\n if (needsNewController) {\n let collection = committed?.collection\n let warning: string | null = null\n let startInRender = false\n let suppliedCollection = false\n let createdCollection = false\n\n const canPreservePageCount = shouldPreserveLiveQueryWindowPageCount({\n hasPreviousController: committed !== null,\n previousInputKind: committed?.inputKind,\n inputKind,\n sameCollection:\n inputIsCollection && committed?.inputCollection === queryFnOrCollection,\n dependenciesChanged,\n dependenciesStructurallyEqual,\n pageShapeChanged,\n })\n const previousPageCount = committed\n ? Math.max(1, committed.controller.getSnapshot().pages.length)\n : 1\n const initialPageCount = canPreservePageCount ? previousPageCount : 1\n // The peek-ahead window for every retained page.\n const requiredLimit = initialPageCount * pageSize + 1\n\n if (needsNewCollection) {\n let inputValue = queryFnOrCollection\n if (!inputIsCollection) {\n if (preparedQueryValue === unpreparedQueryValue) {\n preparedQueryValue = prepareQueryValue(\n queryFnOrCollection,\n dbClient,\n deferredCollections,\n )\n }\n inputValue = () => preparedQueryValue\n }\n const rendered = renderedCollectionRef.current\n if (\n !inputIsCollection &&\n rendered !== null &&\n queryIdentityMatches(\n compareLiveQueryWindowDependencies(\n rendered.dependencies,\n identityDeps,\n ),\n rendered.client === dbClient,\n usesLegacyDeps,\n ) &&\n rendered.collection.status !== `cleaned-up` &&\n rendered.collection.status !== `error` &&\n liveQueryWindowMatches(rendered.collection, requiredLimit)\n ) {\n // An earlier render built this collection and nothing has committed\n // since. Its window is exactly the retained pages, so its first\n // rows are correct. Sources it deferred still resume at commit. A\n // collection in terminal error, such as after its source restarted,\n // cannot serve the query again, so a new one replaces it.\n collection = rendered.collection\n for (const deferred of rendered.deferredCollections) {\n deferredCollections.add(deferred)\n }\n } else {\n const input = resolveLiveQueryWindowInput<TContext>(inputValue)\n if (input.kind === `collection`) {\n collection = input.collection\n suppliedCollection = true\n // A supplied collection is never reused, so release the last one.\n renderedCollectionRef.current = null\n } else {\n // Wrap the query with the peek-ahead window for every retained page,\n // so a collection that starts syncing now never publishes fewer rows\n // than the controller's pages. The controller grows the limit via\n // setWindow.\n collection = createLiveQueryCollection({\n query: input.query.limit(requiredLimit).offset(0),\n gcTime: DEFAULT_GC_TIME_MS,\n })\n startInRender = true\n createdCollection = true\n }\n }\n }\n\n if (!collection) {\n throw new Error(`useLiveInfiniteQuery: Failed to create a collection.`)\n }\n\n if (inputIsCollection) {\n warning =\n getLiveQueryWindowCollectionWarning(collection, pageSize + 1) ?? null\n } else {\n assertLiveQueryWindowManyResult(collection)\n }\n // Like useLiveQuery, start sync during render once the input is valid. A\n // render reads only rows the sources already hold, such as a preloaded\n // collection's, and sends no request until the subscription commits. GC\n // reclaims a render that never commits. A supplied window that the\n // controller must still adjust waits for the subscription.\n if (suppliedCollection) {\n startInRender = liveQueryWindowMatches(collection, requiredLimit)\n }\n if (startInRender) collection.startSyncImmediate()\n // Keep a created collection for the next render only once it validated and\n // started, so a retry after a startup error builds a new one and throws.\n if (createdCollection) {\n renderedCollectionRef.current = {\n client: dbClient,\n dependencies: [...identityDeps],\n collection,\n deferredCollections,\n }\n }\n\n renderState = {\n inputKind,\n inputCollection: inputIsCollection ? collection : null,\n inputQuery: inputIsCollection ? null : queryFnOrCollection,\n client: dbClient,\n identityMode,\n dependencies: inputIsCollection ? null : [...identityDeps],\n pageSize,\n initialPageParam,\n collection,\n controller: createLiveQueryWindowController(collection, {\n pageSize,\n initialPageParam,\n initialPageCount,\n }),\n warning,\n warned: false,\n deferredCollections,\n }\n }\n const currentRenderState = renderState!\n const controller = currentRenderState.controller\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n const unsubscribe = controller.subscribe(onStoreChange)\n committedRef.current = currentRenderState\n if (currentRenderState.warning && !currentRenderState.warned) {\n currentRenderState.warned = true\n console.warn(currentRenderState.warning)\n }\n resumeSyncStarts(currentRenderState.deferredCollections)\n return unsubscribe\n },\n [controller, currentRenderState],\n )\n const getSnapshot = useCallback(() => controller.getSnapshot(), [controller])\n const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\n const fetchNextPage = useCallback(\n () => fetchNextLiveQueryWindowPage(controller),\n [controller],\n )\n\n return {\n data: snapshot.data as InferResultType<TContext>,\n get state() {\n return snapshot.state as EnabledLiveQueryReturn<TContext>[`state`]\n },\n status: snapshot.status as EnabledLiveQueryReturn<TContext>[`status`],\n isLoading: snapshot.isLoading,\n isReady: snapshot.isReady,\n persistedStatus: snapshot.persistedStatus,\n isPersistedReady: snapshot.isPersistedReady,\n persistedError: snapshot.persistedError,\n isIdle: snapshot.isIdle,\n isError: snapshot.isError,\n isCleanedUp: snapshot.isCleanedUp,\n collection:\n snapshot.collection as EnabledLiveQueryReturn<TContext>[`collection`],\n isEnabled: snapshot.isEnabled,\n pages: snapshot.pages as Array<Array<InferResultType<TContext>[number]>>,\n pageParams: snapshot.pageParams as Array<number>,\n fetchNextPage,\n hasNextPage: snapshot.hasNextPage,\n isFetchingNextPage: snapshot.isFetchingNextPage,\n error: snapshot.error,\n }\n}\n"],"names":["resumeSyncStarts"],"mappings":";;;;;;AAiDA;AACA;AA2FA;AAKE;AAIF;AAgCO;AAKL;AACE;AAAU;AACR;AAAA;AAGJ;AACA;AACA;AACA;AAEA;AAGA;AACA;AAMA;AACA;AACA;AACA;AAAmE;AACpD;AACJ;AACF;AACC;AAEV;AAA2D;AACzD;AAEF;AAIA;AAEA;AACA;AAEA;AACE;AACE;AACA;AAAsB;AAEtB;AACA;AACA;AAA8C;AAM9C;AACA;AAAyC;AAEzC;AACA;AAAoB;AAClB;AACA;AAC2B;AAC3B;AAEF;AACA;AAAA;AAC4B;AACd;AACZ;AACA;AAC0C;AAE5C;AACE;AAA2B;AAE3B;AACA;AAA2C;AAC7C;AACF;AAGF;AAEA;AAA6B;AAChB;AACX;AAEF;AACA;AAGA;AAEA;AAKA;AAIA;AAGA;AACA;AACE;AACA;AACA;AACA;AACA;AAEA;AAAoE;AAC7B;AACP;AAC9B;AAEsD;AACtD;AACA;AACA;AAEF;AAGA;AAEA;AAEA;AACE;AACA;AACE;AACE;AAAqB;AACnB;AACA;AACA;AAAA;AAGJ;AAAmB;AAErB;AACA;AAGE;AACE;AACW;AACT;AAAA;AAEkB;AACpB;AAWF;AACA;AACE;AAAgC;AAClC;AAEA;AACA;AACE;AACA;AAEA;AAAgC;AAMhC;AAAuC;AACW;AACxC;AAEV;AACA;AAAoB;AACtB;AACF;AAGF;AACE;AAAsE;AAGxE;AACE;AACmE;AAEnE;AAA0C;AAO5C;AACE;AAAgE;AAElE;AAGA;AACE;AAAgC;AACtB;AACsB;AAC9B;AACA;AAAA;AAIJ;AAAc;AACZ;AACkD;AACX;AAC/B;AACR;AACyD;AACzD;AACA;AACA;AACwD;AACtD;AACA;AACA;AACD;AACD;AACQ;AACR;AAAA;AAGJ;AACA;AAEA;AAAkB;AAEd;AACA;AACA;AACE;AACA;AAAuC;AAEzCA;AACA;AAAO;AACT;AAC+B;AAEjC;AACA;AAEA;AAAsB;AACyB;AAClC;AAGb;AAAO;AACU;AAEb;AAAgB;AAClB;AACiB;AACG;AACF;AACQ;AACC;AACF;AACR;AACC;AACI;AAEX;AACS;AACJ;AACK;AACrB;AACsB;AACO;AACb;AAEpB;;;;"}
1
+ {"version":3,"file":"useLiveInfiniteQuery.js","sources":["../../src/useLiveInfiniteQuery.ts"],"sourcesContent":["'use client'\n\nimport { useCallback, useRef, useSyncExternalStore } from 'react'\nimport {\n bindLiveQueryWindow,\n createLiveQuerySourceIdClaims,\n fetchNextLiveQueryWindowPage,\n getLiveQueryKey,\n isCollection,\n readLiveQueryWindowPageShape,\n rejectLiveQueryInput,\n releaseDeferredOnReclaim,\n resumeDeferredLiveQueryCollections,\n} from '@tanstack/db'\nimport { useOptionalDbClient } from './DbProvider'\nimport {\n getIdentityScope,\n prepareDerivedQuery,\n prepareQueryValue,\n} from './useLiveQuery'\nimport type {\n DerivedIdentityProfiler,\n LiveQueryKey,\n useLiveQuery,\n} from './useLiveQuery'\nimport type {\n Collection,\n CollectionImpl as CollectionImplType,\n CollectionStatus,\n Context,\n DbClient,\n InferResultType,\n InitialQueryBuilder,\n LiveQuerySourceIdClaims,\n LiveQueryStatusFlags,\n LiveQueryWindowBinding,\n NonSingleResult,\n QueryBuilder,\n} from '@tanstack/db'\n\nconst unpreparedQueryValue = Symbol(`unpreparedQueryValue`)\n\n// Keep the generic parameter for existing typed config wrappers.\nexport type UseLiveInfiniteQueryConfig<_TContext extends Context> = {\n /**\n * Explicit identity for queries that contain opaque functional variants or\n * are hot enough that deriving identity from structured IR is too expensive.\n * Structured queries should omit this so DB can derive identity directly.\n */\n queryKey?: LiveQueryKey\n /** Override the nearest DbProvider for this query. */\n client?: DbClient\n pageSize?: number\n /** First result-page label, not a server cursor or remote offset. */\n initialPageParam?: number\n}\n\nexport type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<\n ReturnType<typeof useLiveQuery<TContext>>,\n `data`\n> & {\n data: InferResultType<TContext>\n pages: Array<Array<InferResultType<TContext>[number]>>\n pageParams: Array<number>\n fetchNextPage: () => Promise<void>\n hasNextPage: boolean\n isFetchingNextPage: boolean\n}\n\nexport type UseLiveInfiniteQueryReturnWithCollection<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n> = LiveQueryStatusFlags & {\n data: Array<TResult>\n state: Map<TKey, TResult>\n collection: Collection<TResult, TKey, TUtils> & NonSingleResult\n status: CollectionStatus\n isEnabled: true\n pages: Array<Array<TResult>>\n pageParams: Array<number>\n fetchNextPage: () => Promise<void>\n hasNextPage: boolean\n isFetchingNextPage: boolean\n}\n\ntype EnabledLiveQueryReturn<TContext extends Context> = ReturnType<\n typeof useLiveQuery<TContext>\n>\n\n/** One render's window binding and what its commit still owes. */\ntype InfiniteQueryRender = {\n binding: LiveQueryWindowBinding\n /** The query callback whose identity this render derived, if any. */\n derivedQuery: unknown\n deferredCollections: Set<\n CollectionImplType<any, string | number, any, any, any>\n >\n /** Drops the reclaim release when this render commits. */\n cancelReclaimRelease: (() => void) | undefined\n warned: boolean\n}\n\n/**\n * Create an infinite query using a query function with live updates.\n *\n * Uses `utils.setWindow()` to dynamically adjust the limit/offset window\n * without recreating the live query collection on each page change.\n *\n * @param queryFn - Query function that defines what data to fetch. Must include `.orderBy()` for setWindow to work.\n * @param config - Configuration including pageSize and an optional initial page label\n * @returns Object with pages, data, and pagination controls\n */\n\n// Overload for pre-created collection (non-single result)\nexport function useLiveInfiniteQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult,\n config: UseLiveInfiniteQueryConfig<any>,\n): UseLiveInfiniteQueryReturnWithCollection<TResult, TKey, TUtils>\n\n// Overload for query function\nexport function useLiveInfiniteQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n config: UseLiveInfiniteQueryConfig<TContext>,\n): UseLiveInfiniteQueryReturn<TContext>\n\n// Implementation\nexport function useLiveInfiniteQuery<TContext extends Context>(\n queryFnOrCollection: any,\n config: UseLiveInfiniteQueryConfig<TContext>,\n): UseLiveInfiniteQueryReturn<TContext> {\n const pageShape = readLiveQueryWindowPageShape(config)\n const contextDbClient = useOptionalDbClient()\n const dbClient = config.client ?? contextDbClient\n const inputIsCollection = isCollection(queryFnOrCollection)\n\n const committedRef = useRef<InfiniteQueryRender | null>(null)\n const committed = committedRef.current\n // Like useLiveQuery's instance memo, keep the latest render whose binding\n // built a window collection. A second render before commit, such as a\n // StrictMode double render, reuses that collection instead of starting\n // another. Each render still builds its own controller and page count from\n // the committed binding, which an uncommitted render never changes.\n const renderedRef = useRef<InfiniteQueryRender | null>(null)\n const sourceIdsRef = useRef<LiveQuerySourceIdClaims | null>(null)\n const derivedIdentityProfilerRef = useRef<DerivedIdentityProfiler>({\n renderCount: 0,\n totalMs: 0,\n maxMs: 0,\n warned: false,\n })\n const deferredCollections = new Set<\n CollectionImplType<any, string | number, any, any, any>\n >()\n const identityScope = () =>\n getIdentityScope(\n (sourceIdsRef.current ??= createLiveQuerySourceIdClaims()),\n dbClient,\n `useLiveInfiniteQuery`,\n )\n\n const queryKey = getLiveQueryKey(config)\n let preparedQueryValue: unknown | typeof unpreparedQueryValue =\n unpreparedQueryValue\n let dependencies: ReadonlyArray<unknown> | undefined\n let derivedQuery: unknown = undefined\n\n const rendered = renderedRef.current\n let binding: LiveQueryWindowBinding\n // The render boundary: a render that throws anywhere after preparation,\n // such as an unhashable, single-result, or uncompilable query, never\n // commits, so it releases the sync starts it deferred here.\n try {\n if (!inputIsCollection) {\n if (queryKey !== undefined) {\n dependencies = queryKey\n } else if (\n committed !== null &&\n committed.derivedQuery === queryFnOrCollection &&\n committed.binding.scope === dbClient\n ) {\n // The same callback in the same client scope keeps its identity.\n derivedQuery = queryFnOrCollection\n dependencies = committed.binding.dependencies\n } else {\n const preparation = prepareDerivedQuery(\n queryFnOrCollection,\n derivedIdentityProfilerRef.current,\n identityScope(),\n deferredCollections,\n )\n preparedQueryValue = preparation.value\n derivedQuery = queryFnOrCollection\n dependencies = preparation.identityDeps\n }\n }\n\n // The binding calls this only when it compiles a new window collection.\n const compileQuery = () => {\n if (preparedQueryValue === unpreparedQueryValue) {\n preparedQueryValue = prepareQueryValue(\n queryFnOrCollection,\n dbClient,\n deferredCollections,\n )\n }\n return preparedQueryValue\n }\n\n binding = bindLiveQueryWindow(\n committed?.binding ?? null,\n {\n ...pageShape,\n input:\n typeof queryFnOrCollection === `function`\n ? compileQuery\n : queryFnOrCollection,\n dependencies,\n scope: dbClient,\n client: dbClient,\n queryKey,\n recompile: `identity`,\n },\n rendered?.binding,\n )\n } catch (error) {\n rejectLiveQueryInput(deferredCollections, error)\n }\n\n let currentRenderState = committed\n if (binding !== committed?.binding) {\n const newCollection = binding.collection !== committed?.binding.collection\n if (newCollection && binding.collection === rendered?.binding.collection) {\n // Sources the earlier render deferred still resume at this commit.\n for (const deferred of rendered.deferredCollections) {\n deferredCollections.add(deferred)\n }\n }\n const reusesRendered =\n newCollection && binding.collection === rendered?.binding.collection\n currentRenderState = {\n binding,\n derivedQuery,\n deferredCollections,\n // A render that never commits releases its deferred sync starts when\n // garbage collection reclaims the window collection it built. A reused\n // collection keeps the release its first render registered.\n cancelReclaimRelease: reusesRendered\n ? rendered.cancelReclaimRelease\n : newCollection && binding.ownsCollection\n ? releaseDeferredOnReclaim(binding.collection, deferredCollections)\n : undefined,\n warned: false,\n }\n // Keep a created collection for the next render only once it validated\n // and started, so a retry after a startup error builds a new one.\n if (binding.supplied) renderedRef.current = null\n else if (newCollection && binding.ownsCollection) {\n renderedRef.current = currentRenderState\n }\n }\n const renderState = currentRenderState!\n const controller = renderState.binding.controller\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n const unsubscribe = controller.subscribe(onStoreChange)\n committedRef.current = renderState\n if (renderState.binding.warning && !renderState.warned) {\n renderState.warned = true\n console.warn(renderState.binding.warning)\n }\n // A commit releases the starts itself, so it drops the reclaim release.\n renderState.cancelReclaimRelease?.()\n resumeDeferredLiveQueryCollections(renderState.deferredCollections)\n return unsubscribe\n },\n [controller, renderState],\n )\n const getSnapshot = useCallback(() => controller.getSnapshot(), [controller])\n const snapshot = useSyncExternalStore(subscribe, getSnapshot, () =>\n controller.getServerSnapshot(),\n )\n\n const fetchNextPage = useCallback(\n () => fetchNextLiveQueryWindowPage(controller),\n [controller],\n )\n\n return {\n data: snapshot.data as InferResultType<TContext>,\n get state() {\n return snapshot.state as EnabledLiveQueryReturn<TContext>[`state`]\n },\n status: snapshot.status as EnabledLiveQueryReturn<TContext>[`status`],\n isLoading: snapshot.isLoading,\n isReady: snapshot.isReady,\n isIdle: snapshot.isIdle,\n isLoadingError: snapshot.isLoadingError,\n isCrashed: snapshot.isCrashed,\n isCleanedUp: snapshot.isCleanedUp,\n isSyncError: snapshot.isSyncError,\n collection:\n snapshot.collection as EnabledLiveQueryReturn<TContext>[`collection`],\n isEnabled: snapshot.isEnabled,\n pages: snapshot.pages as Array<Array<InferResultType<TContext>[number]>>,\n pageParams: snapshot.pageParams as Array<number>,\n fetchNextPage,\n hasNextPage: snapshot.hasNextPage,\n isFetchingNextPage: snapshot.isFetchingNextPage,\n health: snapshot.health,\n }\n}\n"],"names":[],"mappings":";;;;;AAwCA;AA2FO;AAIL;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AAAmE;AACpD;AACJ;AACF;AACC;AAEV;AAGA;AACE;AAC4B;AAC1B;AACA;AAGJ;AACA;AAEA;AACA;AAEA;AACA;AAIA;AACE;AACE;AACE;AAAe;AAOf;AACA;AAAiC;AAEjC;AAAoB;AAClB;AAC2B;AAC3B;AACA;AAEF;AACA;AACA;AAA2B;AAC7B;AAIF;AACE;AACE;AAAqB;AACnB;AACA;AACA;AAAA;AAGJ;AAAO;AAGT;AAAU;AACc;AACtB;AACK;AAIG;AACN;AACO;AACC;AACR;AACW;AAAA;AAEH;AAAA;AAGZ;AAA+C;AAGjD;AACA;AACE;AACA;AAEE;AACE;AAAgC;AAClC;AAEF;AAEA;AAAqB;AACnB;AACA;AACA;AAAA;AAAA;AAAA;AAQM;AACE;AAIV;AAA4C;AAE1C;AAAsB;AACxB;AAEF;AACA;AAEA;AAAkB;AAEd;AACA;AACA;AACE;AACA;AAAwC;AAG1C;AACA;AACA;AAAO;AACT;AACwB;AAE1B;AACA;AAAiB;AAAqB;AAAW;AACpC;AAGb;AAAsB;AACyB;AAClC;AAGb;AAAO;AACU;AAEb;AAAgB;AAClB;AACiB;AACG;AACF;AACD;AACQ;AACL;AACE;AACA;AAEX;AACS;AACJ;AACK;AACrB;AACsB;AACO;AACZ;AAErB;;;;"}