@firtoz/collection-sync 4.0.0 → 6.0.0

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 (130) hide show
  1. package/README.md +46 -0
  2. package/dist/cache-manager.d.ts +52 -0
  3. package/dist/cache-manager.js +5 -0
  4. package/dist/cache-manager.js.map +1 -0
  5. package/dist/chunk-3EHHMLSV.js +57 -0
  6. package/dist/chunk-3EHHMLSV.js.map +1 -0
  7. package/dist/chunk-43KYAIKY.js +46 -0
  8. package/dist/chunk-43KYAIKY.js.map +1 -0
  9. package/dist/chunk-4BEXLBCH.js +64 -0
  10. package/dist/chunk-4BEXLBCH.js.map +1 -0
  11. package/dist/chunk-5V6BSQAB.js +148 -0
  12. package/dist/chunk-5V6BSQAB.js.map +1 -0
  13. package/dist/chunk-5VMFQT5Z.js +112 -0
  14. package/dist/chunk-5VMFQT5Z.js.map +1 -0
  15. package/dist/chunk-6EHROJFY.js +111 -0
  16. package/dist/chunk-6EHROJFY.js.map +1 -0
  17. package/dist/chunk-6X3434GJ.js +21 -0
  18. package/dist/chunk-6X3434GJ.js.map +1 -0
  19. package/dist/chunk-BGJH6PH2.js +175 -0
  20. package/dist/chunk-BGJH6PH2.js.map +1 -0
  21. package/dist/chunk-BJJEAKXL.js +252 -0
  22. package/dist/chunk-BJJEAKXL.js.map +1 -0
  23. package/dist/chunk-GWIOC5CP.js +51 -0
  24. package/dist/chunk-GWIOC5CP.js.map +1 -0
  25. package/dist/chunk-HMLY7DHA.js +12 -0
  26. package/dist/chunk-HMLY7DHA.js.map +1 -0
  27. package/dist/chunk-I6RJWBGF.js +112 -0
  28. package/dist/chunk-I6RJWBGF.js.map +1 -0
  29. package/dist/chunk-M5MJHS6A.js +10 -0
  30. package/dist/chunk-M5MJHS6A.js.map +1 -0
  31. package/dist/chunk-O3KBDCEI.js +615 -0
  32. package/dist/chunk-O3KBDCEI.js.map +1 -0
  33. package/dist/chunk-OP53UBPN.js +19 -0
  34. package/dist/chunk-OP53UBPN.js.map +1 -0
  35. package/dist/chunk-P3JOTUAB.js +802 -0
  36. package/dist/chunk-P3JOTUAB.js.map +1 -0
  37. package/dist/chunk-QJP4GSJH.js +373 -0
  38. package/dist/chunk-QJP4GSJH.js.map +1 -0
  39. package/dist/chunk-RDDS7JQW.js +623 -0
  40. package/dist/chunk-RDDS7JQW.js.map +1 -0
  41. package/dist/chunk-TEH7V76G.js +209 -0
  42. package/dist/chunk-TEH7V76G.js.map +1 -0
  43. package/dist/chunk-UJ24XW52.js +20 -0
  44. package/dist/chunk-UJ24XW52.js.map +1 -0
  45. package/dist/chunk-UVZJL6QV.js +18 -0
  46. package/dist/chunk-UVZJL6QV.js.map +1 -0
  47. package/dist/chunk-XC4QNFSQ.js +238 -0
  48. package/dist/chunk-XC4QNFSQ.js.map +1 -0
  49. package/dist/chunk-YD5LVGWX.js +125 -0
  50. package/dist/chunk-YD5LVGWX.js.map +1 -0
  51. package/dist/chunk-YYGPIHHJ.js +166 -0
  52. package/dist/chunk-YYGPIHHJ.js.map +1 -0
  53. package/dist/connect-partial-sync.d.ts +41 -0
  54. package/dist/connect-partial-sync.js +6 -0
  55. package/dist/connect-partial-sync.js.map +1 -0
  56. package/dist/connect-sync.d.ts +26 -0
  57. package/dist/connect-sync.js +5 -0
  58. package/dist/connect-sync.js.map +1 -0
  59. package/dist/create-partial-synced-collection.d.ts +24 -0
  60. package/dist/create-partial-synced-collection.js +8 -0
  61. package/dist/create-partial-synced-collection.js.map +1 -0
  62. package/dist/create-synced-collection.d.ts +26 -0
  63. package/dist/create-synced-collection.js +8 -0
  64. package/dist/create-synced-collection.js.map +1 -0
  65. package/dist/index.d.ts +18 -0
  66. package/dist/index.js +18 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/partial-sync-client-bridge.d.ts +157 -0
  69. package/dist/partial-sync-client-bridge.js +6 -0
  70. package/dist/partial-sync-client-bridge.js.map +1 -0
  71. package/dist/partial-sync-interest.d.ts +48 -0
  72. package/dist/partial-sync-interest.js +6 -0
  73. package/dist/partial-sync-interest.js.map +1 -0
  74. package/dist/partial-sync-mutation-handler.d.ts +31 -0
  75. package/dist/partial-sync-mutation-handler.js +6 -0
  76. package/dist/partial-sync-mutation-handler.js.map +1 -0
  77. package/dist/partial-sync-predicate-match.d.ts +8 -0
  78. package/dist/partial-sync-predicate-match.js +4 -0
  79. package/dist/partial-sync-predicate-match.js.map +1 -0
  80. package/dist/partial-sync-row-key.d.ts +41 -0
  81. package/dist/partial-sync-row-key.js +4 -0
  82. package/dist/partial-sync-row-key.js.map +1 -0
  83. package/dist/partial-sync-server-bridge.d.ts +102 -0
  84. package/dist/partial-sync-server-bridge.js +8 -0
  85. package/dist/partial-sync-server-bridge.js.map +1 -0
  86. package/dist/react/constants.d.ts +12 -0
  87. package/dist/react/constants.js +4 -0
  88. package/dist/react/constants.js.map +1 -0
  89. package/dist/react/index.d.ts +19 -0
  90. package/dist/react/index.js +17 -0
  91. package/dist/react/index.js.map +1 -0
  92. package/dist/react/partial-sync-adapter.d.ts +40 -0
  93. package/dist/react/partial-sync-adapter.js +4 -0
  94. package/dist/react/partial-sync-adapter.js.map +1 -0
  95. package/dist/react/partial-sync-utils.d.ts +42 -0
  96. package/dist/react/partial-sync-utils.js +5 -0
  97. package/dist/react/partial-sync-utils.js.map +1 -0
  98. package/dist/react/range-conditions-expression.d.ts +49 -0
  99. package/dist/react/range-conditions-expression.js +4 -0
  100. package/dist/react/range-conditions-expression.js.map +1 -0
  101. package/dist/react/types.d.ts +196 -0
  102. package/dist/react/types.js +3 -0
  103. package/dist/react/types.js.map +1 -0
  104. package/dist/react/usePartialSyncCollection.d.ts +20 -0
  105. package/dist/react/usePartialSyncCollection.js +10 -0
  106. package/dist/react/usePartialSyncCollection.js.map +1 -0
  107. package/dist/react/usePartialSyncViewport.d.ts +20 -0
  108. package/dist/react/usePartialSyncViewport.js +7 -0
  109. package/dist/react/usePartialSyncViewport.js.map +1 -0
  110. package/dist/react/usePartialSyncWindow.d.ts +17 -0
  111. package/dist/react/usePartialSyncWindow.js +12 -0
  112. package/dist/react/usePartialSyncWindow.js.map +1 -0
  113. package/dist/react/usePredicateFilteredRows.d.ts +20 -0
  114. package/dist/react/usePredicateFilteredRows.js +6 -0
  115. package/dist/react/usePredicateFilteredRows.js.map +1 -0
  116. package/dist/sync-client-bridge.d.ts +48 -0
  117. package/dist/sync-client-bridge.js +6 -0
  118. package/dist/sync-client-bridge.js.map +1 -0
  119. package/dist/sync-protocol.d.ts +378 -0
  120. package/dist/sync-protocol.js +4 -0
  121. package/dist/sync-protocol.js.map +1 -0
  122. package/dist/sync-server-bridge.d.ts +35 -0
  123. package/dist/sync-server-bridge.js +6 -0
  124. package/dist/sync-server-bridge.js.map +1 -0
  125. package/dist/with-sync.d.ts +107 -0
  126. package/dist/with-sync.js +7 -0
  127. package/dist/with-sync.js.map +1 -0
  128. package/package.json +27 -21
  129. package/src/connect-partial-sync.ts +16 -12
  130. package/src/connect-sync.ts +12 -10
@@ -0,0 +1,615 @@
1
+ import { DEFAULT_PAGE_LIMIT, DEFAULT_SEEK_COOLDOWN_MS } from './chunk-M5MJHS6A.js';
2
+ import { defaultPartialSyncVersionMs, assertSyncUtils, getPartialSyncRowByMapId, tryIdsForIndexWindow, computeFingerprintForIndexWindow } from './chunk-4BEXLBCH.js';
3
+ import { CacheManager } from './chunk-5V6BSQAB.js';
4
+ import { connectPartialSync } from './chunk-XC4QNFSQ.js';
5
+ import { PartialSyncClientBridge } from './chunk-P3JOTUAB.js';
6
+ import { partialSyncRowKey } from './chunk-UJ24XW52.js';
7
+ import { useState, useRef, useCallback, useMemo, useLayoutEffect, useSyncExternalStore, useEffect } from 'react';
8
+
9
+ function usePartialSyncWindow({
10
+ collection,
11
+ sort,
12
+ getSortValue,
13
+ wsUrl,
14
+ wsTransport = "json",
15
+ serializeJson = JSON.stringify,
16
+ deserializeJson = JSON.parse,
17
+ getVersionMs = defaultPartialSyncVersionMs,
18
+ getSortPositions,
19
+ pageLimit = DEFAULT_PAGE_LIMIT,
20
+ seekCooldownMs = DEFAULT_SEEK_COOLDOWN_MS,
21
+ partialWindowResetKey,
22
+ mutationBridge,
23
+ mergeTransportSend,
24
+ collectionId,
25
+ cacheDisplayMode = "immediate"
26
+ }) {
27
+ const [windowStartIndex, setWindowStartIndex] = useState(0);
28
+ const [totalCount, setTotalCount] = useState(0);
29
+ const [nextCursor, setNextCursor] = useState(null);
30
+ const [hasMore, setHasMore] = useState(true);
31
+ const [rangeRequestInFlight, setRangeRequestInFlight] = useState(false);
32
+ const [pendingServerRange, setPendingServerRange] = useState(null);
33
+ const [bridgeState, setBridgeState] = useState({
34
+ status: "offline"
35
+ });
36
+ const [viewportInfo, setViewportInfo] = useState({
37
+ firstVisibleIndex: 0,
38
+ lastVisibleIndex: 0
39
+ });
40
+ const viewportInfoRef = useRef(viewportInfo);
41
+ viewportInfoRef.current = viewportInfo;
42
+ const [lastSeekMeta, setLastSeekMeta] = useState(null);
43
+ const [collectionVersion, setCollectionVersion] = useState(0);
44
+ const [indexMapVersion, setIndexMapVersion] = useState(0);
45
+ const globalIndexMapRef = useRef(/* @__PURE__ */ new Map());
46
+ const denseRowsRef = useRef([]);
47
+ const windowStartRef = useRef(windowStartIndex);
48
+ windowStartRef.current = windowStartIndex;
49
+ const sortRef = useRef(sort);
50
+ sortRef.current = sort;
51
+ const totalCountRef = useRef(totalCount);
52
+ totalCountRef.current = totalCount;
53
+ const fetchGenRef = useRef(0);
54
+ const seekCooldownUntilRef = useRef(0);
55
+ const seekToViewportRef = useRef(() => {
56
+ });
57
+ const invalidateSeekTimerRef = useRef(
58
+ null
59
+ );
60
+ const getSortValueRef = useRef(getSortValue);
61
+ getSortValueRef.current = getSortValue;
62
+ const getSortPositionsRef = useRef(getSortPositions);
63
+ getSortPositionsRef.current = getSortPositions;
64
+ const getVersionMsRef = useRef(getVersionMs);
65
+ getVersionMsRef.current = getVersionMs;
66
+ const serializeJsonRef = useRef(serializeJson);
67
+ serializeJsonRef.current = serializeJson;
68
+ const deserializeJsonRef = useRef(deserializeJson);
69
+ deserializeJsonRef.current = deserializeJson;
70
+ const bumpIndexMap = useCallback(() => {
71
+ setIndexMapVersion((v) => v + 1);
72
+ }, []);
73
+ const syncUtils = useMemo(
74
+ () => assertSyncUtils(collection.utils),
75
+ [collection]
76
+ );
77
+ const syncUtilsRef = useRef(syncUtils);
78
+ syncUtilsRef.current = syncUtils;
79
+ const collectionRef = useRef(collection);
80
+ collectionRef.current = collection;
81
+ const cacheManager = useMemo(
82
+ () => new CacheManager({
83
+ getStorageEstimate: async () => {
84
+ if (typeof navigator === "undefined" || !navigator.storage?.estimate) {
85
+ const usageBytes2 = 0;
86
+ const quotaBytes2 = 50 * 1024 * 1024;
87
+ return {
88
+ usageBytes: usageBytes2,
89
+ quotaBytes: quotaBytes2,
90
+ utilizationRatio: usageBytes2 / quotaBytes2
91
+ };
92
+ }
93
+ const estimate = await navigator.storage.estimate();
94
+ const usageBytes = estimate.usage ?? 0;
95
+ const quotaBytes = estimate.quota ?? 1;
96
+ return {
97
+ usageBytes,
98
+ quotaBytes,
99
+ utilizationRatio: usageBytes / quotaBytes
100
+ };
101
+ },
102
+ deleteRows: async (keys) => {
103
+ const keySet = /* @__PURE__ */ new Set();
104
+ for (const k of keys) {
105
+ keySet.add(k);
106
+ keySet.add(String(k));
107
+ }
108
+ for (const [idx, id] of globalIndexMapRef.current) {
109
+ if (keySet.has(id) || keySet.has(String(id)) || typeof id === "string" && /^-?\d+$/.test(id) && keySet.has(Number(id))) {
110
+ globalIndexMapRef.current.delete(idx);
111
+ }
112
+ }
113
+ bumpIndexMap();
114
+ await syncUtilsRef.current.receiveSync(
115
+ keys.map((key) => ({ type: "delete", key }))
116
+ );
117
+ }
118
+ }),
119
+ [bumpIndexMap]
120
+ );
121
+ const cacheManagerRef = useRef(cacheManager);
122
+ cacheManagerRef.current = cacheManager;
123
+ const partialClientId = mutationBridge?.clientId;
124
+ const bridge = useMemo(
125
+ () => new PartialSyncClientBridge({
126
+ ...partialClientId !== void 0 ? { clientId: partialClientId } : {},
127
+ ...collectionId !== void 0 ? { collectionId } : {},
128
+ collection: {
129
+ get: (key) => getPartialSyncRowByMapId(collectionRef.current, key),
130
+ utils: {
131
+ receiveSync: (messages) => syncUtilsRef.current.receiveSync(messages)
132
+ }
133
+ },
134
+ send: () => {
135
+ },
136
+ onStateChange: (state) => setBridgeState(state),
137
+ beforeApplyRows: async (incomingRows) => {
138
+ const sortNow = sortRef.current;
139
+ const rowsNow = denseRowsRef.current;
140
+ const sortPos = getSortPositionsRef.current;
141
+ const gsv = getSortValueRef.current;
142
+ const cm = cacheManagerRef.current;
143
+ cm.recordFetchedRows(
144
+ incomingRows,
145
+ (row) => sortPos !== void 0 ? sortPos(row) : {
146
+ [sortNow.column]: gsv(row, sortNow.column)
147
+ }
148
+ );
149
+ const firstRow = rowsNow[0] ?? incomingRows[0];
150
+ const lastRow = rowsNow[rowsNow.length - 1] ?? incomingRows[incomingRows.length - 1];
151
+ const result = await cm.evictIfNeeded({
152
+ sortColumn: sortNow.column,
153
+ sortDirection: sortNow.direction,
154
+ fromValue: firstRow !== void 0 ? gsv(firstRow, sortNow.column) : "",
155
+ toValue: lastRow !== void 0 ? gsv(lastRow, sortNow.column) : ""
156
+ });
157
+ setBridgeState((previous) => {
158
+ if (previous.status === "partial" || previous.status === "realtime") {
159
+ return {
160
+ ...previous,
161
+ cacheUtilization: result.estimate.utilizationRatio
162
+ };
163
+ }
164
+ return previous;
165
+ });
166
+ },
167
+ onViewTransition: (e) => {
168
+ if (e.type === "exitView" && e.change.type === "update") {
169
+ const key = partialSyncRowKey(e.change.value.id);
170
+ let removed = false;
171
+ for (const [idx, mappedId] of [
172
+ ...globalIndexMapRef.current.entries()
173
+ ]) {
174
+ if (mappedId === key) {
175
+ globalIndexMapRef.current.delete(idx);
176
+ removed = true;
177
+ }
178
+ }
179
+ if (removed) {
180
+ bumpIndexMap();
181
+ }
182
+ }
183
+ },
184
+ onRangePatchApplied: ({
185
+ change,
186
+ viewTransition
187
+ }) => {
188
+ if (change.type !== "update" || viewTransition !== void 0) {
189
+ return;
190
+ }
191
+ if (change.previousValue === void 0) return;
192
+ const col = sortRef.current.column;
193
+ const gsv = getSortValueRef.current;
194
+ if (gsv(change.previousValue, col) === gsv(change.value, col)) {
195
+ return;
196
+ }
197
+ const rowKey = partialSyncRowKey(change.value.id);
198
+ const inDense = denseRowsRef.current.some(
199
+ (r) => partialSyncRowKey(r.id) === rowKey
200
+ );
201
+ if (!inDense) return;
202
+ if (invalidateSeekTimerRef.current !== null) {
203
+ clearTimeout(invalidateSeekTimerRef.current);
204
+ }
205
+ invalidateSeekTimerRef.current = setTimeout(() => {
206
+ invalidateSeekTimerRef.current = null;
207
+ seekToViewportRef.current(
208
+ viewportInfoRef.current.firstVisibleIndex,
209
+ {
210
+ force: true
211
+ }
212
+ );
213
+ }, 80);
214
+ }
215
+ }),
216
+ [partialClientId, collectionId, bumpIndexMap]
217
+ );
218
+ const mutationBridgeRef = useRef(mutationBridge);
219
+ mutationBridgeRef.current = mutationBridge;
220
+ const mergeTransportSendRef = useRef(mergeTransportSend);
221
+ mergeTransportSendRef.current = mergeTransportSend;
222
+ useLayoutEffect(() => {
223
+ const disconnect = connectPartialSync(bridge, {
224
+ url: wsUrl,
225
+ transport: wsTransport,
226
+ setTransportSend: (send) => {
227
+ bridge.setSend((message) => send(message));
228
+ mergeTransportSendRef.current?.(send);
229
+ },
230
+ serializeJson: (value) => serializeJsonRef.current(value),
231
+ deserializeJson: (raw) => deserializeJsonRef.current(raw),
232
+ mutationBridge: mutationBridgeRef.current
233
+ });
234
+ return () => {
235
+ disconnect();
236
+ };
237
+ }, [bridge, wsTransport, wsUrl]);
238
+ const confirmedKeysRevision = useSyncExternalStore(
239
+ (onStoreChange) => bridge.subscribeConfirmedKeysRevision(onStoreChange),
240
+ () => bridge.serverConfirmedKeysRevision,
241
+ () => 0
242
+ );
243
+ const indexRows = useMemo(() => {
244
+ const start = windowStartIndex;
245
+ const out = [];
246
+ for (let i = 0; ; i += 1) {
247
+ const id = globalIndexMapRef.current.get(start + i);
248
+ if (id === void 0) break;
249
+ const row = getPartialSyncRowByMapId(collection, id);
250
+ if (row === void 0) continue;
251
+ out.push(row);
252
+ }
253
+ if (cacheDisplayMode === "confirmed") {
254
+ return out.filter(
255
+ (row) => bridge.serverConfirmedKeys.has(partialSyncRowKey(row.id))
256
+ );
257
+ }
258
+ return out;
259
+ }, [
260
+ bridge,
261
+ cacheDisplayMode,
262
+ collection,
263
+ collectionVersion,
264
+ confirmedKeysRevision,
265
+ indexMapVersion,
266
+ windowStartIndex
267
+ ]);
268
+ const rows = indexRows;
269
+ useLayoutEffect(() => {
270
+ denseRowsRef.current = rows;
271
+ }, [rows]);
272
+ const getRowSlot = useCallback(
273
+ (globalIndex) => {
274
+ const id = globalIndexMapRef.current.get(globalIndex);
275
+ const row = id !== void 0 ? getPartialSyncRowByMapId(collection, id) : void 0;
276
+ if (row !== void 0) {
277
+ const ws = windowStartIndex;
278
+ const denseEnd = ws + rows.length;
279
+ const inDense = globalIndex >= ws && globalIndex < denseEnd;
280
+ return {
281
+ row,
282
+ slot: inDense ? "ready" : "ready_global"
283
+ };
284
+ }
285
+ if (id !== void 0) {
286
+ return { row: void 0, slot: "stale_map" };
287
+ }
288
+ if (rangeRequestInFlight && pendingServerRange !== null && globalIndex >= pendingServerRange.start && globalIndex < pendingServerRange.endExclusive) {
289
+ return { row: void 0, slot: "server" };
290
+ }
291
+ return { row: void 0, slot: "none" };
292
+ },
293
+ [
294
+ collection,
295
+ collectionVersion,
296
+ indexMapVersion,
297
+ windowStartIndex,
298
+ rows.length,
299
+ rangeRequestInFlight,
300
+ pendingServerRange
301
+ ]
302
+ );
303
+ const recordIdsAtOffset = useCallback(
304
+ (offset, fetchedRows) => {
305
+ for (let i = 0; i < fetchedRows.length; i += 1) {
306
+ globalIndexMapRef.current.set(
307
+ offset + i,
308
+ partialSyncRowKey(fetchedRows[i].id)
309
+ );
310
+ }
311
+ bumpIndexMap();
312
+ },
313
+ [bumpIndexMap]
314
+ );
315
+ const fetchNext = useCallback(async () => {
316
+ if (rangeRequestInFlight || !hasMore) return;
317
+ const gen = fetchGenRef.current;
318
+ const fetchStart = windowStartRef.current + denseRowsRef.current.length;
319
+ setPendingServerRange({
320
+ start: fetchStart,
321
+ endExclusive: fetchStart + pageLimit
322
+ });
323
+ setRangeRequestInFlight(true);
324
+ try {
325
+ const result = await bridge.requestRangeQuery({
326
+ kind: "index",
327
+ mode: "cursor",
328
+ sort: sortRef.current,
329
+ limit: pageLimit,
330
+ afterCursor: nextCursor
331
+ });
332
+ if (gen !== fetchGenRef.current) return;
333
+ if (result.invalidateWindow && result.rows.length === 0) {
334
+ const again = await bridge.requestRangeQuery({
335
+ kind: "index",
336
+ mode: "cursor",
337
+ sort: sortRef.current,
338
+ limit: pageLimit,
339
+ afterCursor: nextCursor
340
+ });
341
+ if (gen !== fetchGenRef.current) return;
342
+ recordIdsAtOffset(
343
+ windowStartRef.current + denseRowsRef.current.length,
344
+ again.rows
345
+ );
346
+ setTotalCount(again.totalCount);
347
+ setNextCursor(again.lastCursor);
348
+ setHasMore(again.rows.length === pageLimit);
349
+ return;
350
+ }
351
+ if (result.upToDate) {
352
+ setTotalCount(result.totalCount);
353
+ return;
354
+ }
355
+ recordIdsAtOffset(
356
+ windowStartRef.current + denseRowsRef.current.length,
357
+ result.rows
358
+ );
359
+ setTotalCount(result.totalCount);
360
+ setNextCursor(result.lastCursor);
361
+ setHasMore(result.rows.length === pageLimit);
362
+ } catch (error) {
363
+ if (error !== null && typeof error === "object" && "name" in error && error.name === "AbortError") {
364
+ return;
365
+ }
366
+ throw error;
367
+ } finally {
368
+ if (gen === fetchGenRef.current) {
369
+ setPendingServerRange(null);
370
+ setRangeRequestInFlight(false);
371
+ }
372
+ }
373
+ }, [
374
+ bridge,
375
+ hasMore,
376
+ rangeRequestInFlight,
377
+ nextCursor,
378
+ pageLimit,
379
+ recordIdsAtOffset
380
+ ]);
381
+ const seekToViewport = useCallback(
382
+ (firstVisibleIndex, options) => {
383
+ const offset = Math.max(0, firstVisibleIndex);
384
+ const loadedEndExclusive = windowStartRef.current + denseRowsRef.current.length;
385
+ const lastForDense = options?.scrollSettled === true && typeof options.lastVisibleIndex === "number" ? options.lastVisibleIndex : firstVisibleIndex;
386
+ const inDenseWindow = denseRowsRef.current.length > 0 && firstVisibleIndex >= windowStartRef.current && lastForDense < loadedEndExclusive;
387
+ if (options?.scrollSettled === true && inDenseWindow && !options?.force) {
388
+ return;
389
+ }
390
+ const now = Date.now();
391
+ if (!options?.force && now < seekCooldownUntilRef.current) {
392
+ return;
393
+ }
394
+ if (!options?.scrollSettled) {
395
+ const ws = windowStartRef.current;
396
+ const f = firstVisibleIndex;
397
+ const firstInsideDense = denseRowsRef.current.length > 0 && f >= ws && f < loadedEndExclusive;
398
+ if (firstInsideDense && !options?.force) return;
399
+ }
400
+ seekCooldownUntilRef.current = now + seekCooldownMs;
401
+ setLastSeekMeta({
402
+ offset,
403
+ reason: options?.scrollSettled === true ? "scrollSettled" : "scroll"
404
+ });
405
+ fetchGenRef.current += 1;
406
+ const gen = fetchGenRef.current;
407
+ bridge.abortRangeRequests();
408
+ const want = Math.min(
409
+ pageLimit,
410
+ Math.max(0, totalCountRef.current - offset)
411
+ );
412
+ if (totalCountRef.current > 0 && want === 0) {
413
+ setWindowStartIndex(offset);
414
+ windowStartRef.current = offset;
415
+ setNextCursor(null);
416
+ setHasMore(false);
417
+ setPendingServerRange(null);
418
+ setRangeRequestInFlight(false);
419
+ return;
420
+ }
421
+ const ids = tryIdsForIndexWindow(
422
+ globalIndexMapRef.current,
423
+ offset,
424
+ want,
425
+ totalCountRef.current
426
+ );
427
+ if (ids !== null) {
428
+ setWindowStartIndex(offset);
429
+ const lastId = ids[ids.length - 1];
430
+ const gsv = getSortValueRef.current;
431
+ const lastRow = lastId !== void 0 ? getPartialSyncRowByMapId(collection, lastId) : void 0;
432
+ setNextCursor(
433
+ lastRow !== void 0 ? gsv(lastRow, sortRef.current.column) : null
434
+ );
435
+ setHasMore(offset + ids.length < totalCountRef.current);
436
+ setPendingServerRange(null);
437
+ bumpIndexMap();
438
+ windowStartRef.current = offset;
439
+ return;
440
+ }
441
+ let fingerprint;
442
+ if (totalCountRef.current > 0 && want > 0) {
443
+ const fp = computeFingerprintForIndexWindow(
444
+ collection,
445
+ globalIndexMapRef.current,
446
+ offset,
447
+ want,
448
+ (row) => getVersionMsRef.current(row)
449
+ );
450
+ if (fp !== void 0) {
451
+ fingerprint = fp;
452
+ }
453
+ }
454
+ setWindowStartIndex(offset);
455
+ windowStartRef.current = offset;
456
+ setNextCursor(null);
457
+ setHasMore(true);
458
+ setPendingServerRange({ start: offset, endExclusive: offset + want });
459
+ setRangeRequestInFlight(true);
460
+ void (async () => {
461
+ try {
462
+ let result = await bridge.requestRangeQuery(
463
+ {
464
+ kind: "index",
465
+ mode: "offset",
466
+ sort: sortRef.current,
467
+ limit: pageLimit,
468
+ offset
469
+ },
470
+ fingerprint
471
+ );
472
+ if (gen !== fetchGenRef.current) return;
473
+ if (result.upToDate) {
474
+ setTotalCount(result.totalCount);
475
+ setHasMore(offset + pageLimit < result.totalCount);
476
+ const gsv = getSortValueRef.current;
477
+ const lastId = globalIndexMapRef.current.get(offset + want - 1) ?? globalIndexMapRef.current.get(offset + pageLimit - 1);
478
+ const lastRow = lastId !== void 0 ? getPartialSyncRowByMapId(collection, lastId) : void 0;
479
+ setNextCursor(
480
+ lastRow !== void 0 ? gsv(lastRow, sortRef.current.column) : null
481
+ );
482
+ return;
483
+ }
484
+ if (result.invalidateWindow && result.rows.length === 0) {
485
+ result = await bridge.requestRangeQuery({
486
+ kind: "index",
487
+ mode: "offset",
488
+ sort: sortRef.current,
489
+ limit: pageLimit,
490
+ offset
491
+ });
492
+ if (gen !== fetchGenRef.current) return;
493
+ }
494
+ recordIdsAtOffset(offset, result.rows);
495
+ setTotalCount(result.totalCount);
496
+ setNextCursor(result.lastCursor);
497
+ setHasMore(result.rows.length === pageLimit);
498
+ } catch (error) {
499
+ if (error !== null && typeof error === "object" && "name" in error && error.name === "AbortError") {
500
+ return;
501
+ }
502
+ throw error;
503
+ } finally {
504
+ if (gen === fetchGenRef.current) {
505
+ setPendingServerRange(null);
506
+ setRangeRequestInFlight(false);
507
+ }
508
+ }
509
+ })();
510
+ },
511
+ [
512
+ bridge,
513
+ bumpIndexMap,
514
+ collection,
515
+ pageLimit,
516
+ recordIdsAtOffset,
517
+ seekCooldownMs
518
+ ]
519
+ );
520
+ seekToViewportRef.current = seekToViewport;
521
+ useEffect(() => {
522
+ return () => {
523
+ if (invalidateSeekTimerRef.current !== null) {
524
+ clearTimeout(invalidateSeekTimerRef.current);
525
+ }
526
+ };
527
+ }, []);
528
+ useEffect(() => {
529
+ const sub = collection.subscribeChanges(() => {
530
+ let removedStaleMapEntry = false;
531
+ if (globalIndexMapRef.current.size > 0) {
532
+ for (const [idx, id] of globalIndexMapRef.current) {
533
+ if (getPartialSyncRowByMapId(collection, id) === void 0) {
534
+ globalIndexMapRef.current.delete(idx);
535
+ removedStaleMapEntry = true;
536
+ }
537
+ }
538
+ }
539
+ if (removedStaleMapEntry) {
540
+ bumpIndexMap();
541
+ }
542
+ setCollectionVersion((v) => v + 1);
543
+ const sortNow = sortRef.current;
544
+ const sortPos = getSortPositionsRef.current;
545
+ const gsv = getSortValueRef.current;
546
+ cacheManagerRef.current.resyncSortPositionsForTrackedRows(
547
+ (key) => getPartialSyncRowByMapId(collection, key),
548
+ (row) => sortPos !== void 0 ? sortPos(row) : {
549
+ [sortNow.column]: gsv(row, sortNow.column)
550
+ }
551
+ );
552
+ });
553
+ return () => {
554
+ sub.unsubscribe();
555
+ };
556
+ }, [bumpIndexMap, collection]);
557
+ const seekAfterScrollSettled = useCallback(
558
+ (firstVisibleIndex, lastVisibleIndex) => {
559
+ seekToViewport(firstVisibleIndex, {
560
+ scrollSettled: true,
561
+ lastVisibleIndex
562
+ });
563
+ },
564
+ [seekToViewport]
565
+ );
566
+ useEffect(() => {
567
+ fetchGenRef.current += 1;
568
+ bridge.abortRangeRequests();
569
+ bridge.clearTrackedRowIds();
570
+ setWindowStartIndex(0);
571
+ setTotalCount(0);
572
+ setNextCursor(null);
573
+ setHasMore(true);
574
+ setLastSeekMeta(null);
575
+ setRangeRequestInFlight(false);
576
+ setPendingServerRange(null);
577
+ globalIndexMapRef.current.clear();
578
+ bumpIndexMap();
579
+ void syncUtilsRef.current.truncate();
580
+ cacheManager.clear();
581
+ }, [
582
+ bridge,
583
+ bumpIndexMap,
584
+ cacheManager,
585
+ sort.column,
586
+ sort.direction,
587
+ partialWindowResetKey ?? ""
588
+ ]);
589
+ useEffect(() => {
590
+ if (rows.length === 0 && !rangeRequestInFlight && hasMore) {
591
+ void fetchNext();
592
+ }
593
+ }, [fetchNext, hasMore, rangeRequestInFlight, rows.length]);
594
+ return {
595
+ bridge,
596
+ cacheManager,
597
+ rows,
598
+ windowStartIndex,
599
+ totalCount,
600
+ rangeRequestInFlight,
601
+ hasMore,
602
+ fetchNext,
603
+ seekToViewport,
604
+ seekAfterScrollSettled,
605
+ bridgeState,
606
+ viewportInfo,
607
+ setViewportInfo,
608
+ lastSeekMeta,
609
+ getRowSlot
610
+ };
611
+ }
612
+
613
+ export { usePartialSyncWindow };
614
+ //# sourceMappingURL=chunk-O3KBDCEI.js.map
615
+ //# sourceMappingURL=chunk-O3KBDCEI.js.map