@rpcbase/client 0.455.0 → 0.456.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.
@@ -152,6 +152,7 @@ const RtsSsrRuntimeProvider = (t0) => {
152
152
  const useRtsSsrRuntime = () => {
153
153
  return useContext(RtsSsrRuntimeContext);
154
154
  };
155
+ const RTS_QUERY_WINDOW_MAX_COUNT = 4096;
155
156
  const memoryStore = /* @__PURE__ */ new Map();
156
157
  let reactNativeStorage = null;
157
158
  const MMKV_STORAGE_ID = "rpcbase-rts";
@@ -239,6 +240,11 @@ const UNDERSCORE_PREFIX = "$_";
239
240
  const DEFAULT_FIND_LIMIT = 4096;
240
241
  const INDEXED_DB_ADAPTER = "indexeddb";
241
242
  const REACT_NATIVE_SQLITE_ADAPTER = "react-native-sqlite";
243
+ const QUERY_WINDOW_COLLECTION = "$query-windows-v1";
244
+ const QUERY_WINDOW_DOC_TYPE = "rts-query-window";
245
+ const QUERY_WINDOW_SCHEMA_VERSION = 1;
246
+ const QUERY_WINDOW_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
247
+ const QUERY_WINDOW_WRITE_ATTEMPTS = 4;
242
248
  let storeConfig = null;
243
249
  let pouchDbPromise = null;
244
250
  let lastAppliedPrefix = null;
@@ -374,8 +380,7 @@ const getPouchDb = async () => {
374
380
  }
375
381
  return pouchDbPromise;
376
382
  };
377
- const applyPrefix = (PouchDB) => {
378
- const prefix = getPrefix();
383
+ const applyPrefix = (PouchDB, prefix = getPrefix()) => {
379
384
  if (prefix === lastAppliedPrefix) return;
380
385
  PouchDB.prefix = prefix;
381
386
  lastAppliedPrefix = prefix;
@@ -386,9 +391,9 @@ const configureRtsPouchStore = (config) => {
386
391
  collections.clear();
387
392
  };
388
393
  const getCollection = async (modelName, options) => {
389
- const PouchDB = await getPouchDb();
390
- applyPrefix(PouchDB);
391
394
  const prefix = getPrefix();
395
+ const PouchDB = await getPouchDb();
396
+ applyPrefix(PouchDB, prefix);
392
397
  const dbName = `${options.uid}/${modelName}`;
393
398
  const dbKey = `${prefix}${dbName}`;
394
399
  const existing = collections.get(dbKey);
@@ -401,6 +406,284 @@ const getCollection = async (modelName, options) => {
401
406
  collections.set(dbKey, db);
402
407
  return db;
403
408
  };
409
+ const getQueryWindowScope = (uid) => {
410
+ if (!storeConfig) {
411
+ throw new Error("RTS PouchDB store is not configured");
412
+ }
413
+ return {
414
+ appName: storeConfig.appName ?? "",
415
+ tenantId: storeConfig.tenantId,
416
+ uid
417
+ };
418
+ };
419
+ const getQueryWindowFingerprint = (scope, modelName, queryKey) => JSON.stringify([QUERY_WINDOW_SCHEMA_VERSION, scope.appName, scope.tenantId, scope.uid, modelName, queryKey]);
420
+ const getQueryWindowDocumentId = (fingerprint) => `${QUERY_WINDOW_DOC_TYPE}:${encodeURIComponent(fingerprint)}`;
421
+ const getPouchErrorStatus = (error) => {
422
+ if (!error || typeof error !== "object") return void 0;
423
+ const status = error.status;
424
+ return typeof status === "number" && Number.isFinite(status) ? status : void 0;
425
+ };
426
+ const getPouchDocument = async (collection, id) => {
427
+ try {
428
+ return await collection.get(id);
429
+ } catch (error) {
430
+ if (getPouchErrorStatus(error) === 404) return null;
431
+ throw error;
432
+ }
433
+ };
434
+ const normalizeQueryWindowPageInfo = (value) => {
435
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
436
+ const raw = value;
437
+ if (typeof raw.hasNextPage !== "boolean" || typeof raw.hasPrevPage !== "boolean") return null;
438
+ if (raw.nextCursor !== void 0 && typeof raw.nextCursor !== "string") return null;
439
+ if (raw.prevCursor !== void 0 && typeof raw.prevCursor !== "string") return null;
440
+ const nextCursor = typeof raw.nextCursor === "string" && raw.nextCursor ? raw.nextCursor : void 0;
441
+ const prevCursor = typeof raw.prevCursor === "string" && raw.prevCursor ? raw.prevCursor : void 0;
442
+ return {
443
+ hasNextPage: raw.hasNextPage,
444
+ hasPrevPage: raw.hasPrevPage,
445
+ ...nextCursor ? {
446
+ nextCursor
447
+ } : {},
448
+ ...prevCursor ? {
449
+ prevCursor
450
+ } : {}
451
+ };
452
+ };
453
+ const isValidQueryWindowServerVersion = (value) => {
454
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
455
+ };
456
+ const isQueryWindowData = (value) => Array.isArray(value) && value.every((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
457
+ const parseQueryWindowSnapshot = (doc, expected) => {
458
+ if (doc.type !== QUERY_WINDOW_DOC_TYPE || doc.schemaVersion !== QUERY_WINDOW_SCHEMA_VERSION) return null;
459
+ if (doc.fingerprint !== expected.fingerprint) return null;
460
+ if (doc.modelName !== expected.modelName || doc.queryKey !== expected.queryKey) return null;
461
+ const scope = doc.scope;
462
+ if (!scope || typeof scope !== "object" || Array.isArray(scope)) return null;
463
+ const rawScope = scope;
464
+ if (rawScope.appName !== expected.scope.appName || rawScope.tenantId !== expected.scope.tenantId || rawScope.uid !== expected.scope.uid) {
465
+ return null;
466
+ }
467
+ if (!isQueryWindowData(doc.data)) return null;
468
+ const pageInfo = normalizeQueryWindowPageInfo(doc.pageInfo);
469
+ if (!pageInfo) return null;
470
+ const requestedCount = doc.requestedCount;
471
+ if (!Number.isSafeInteger(requestedCount) || requestedCount <= 0 || requestedCount > RTS_QUERY_WINDOW_MAX_COUNT || doc.data.length > requestedCount) {
472
+ return null;
473
+ }
474
+ const storedAt = doc.storedAt;
475
+ if (typeof storedAt !== "number" || !Number.isFinite(storedAt) || storedAt < 0) return null;
476
+ const totalCount = doc.totalCount;
477
+ if (totalCount !== void 0 && (!Number.isSafeInteger(totalCount) || totalCount < 0)) {
478
+ return null;
479
+ }
480
+ const serverVersion = doc.serverVersion;
481
+ if (serverVersion !== void 0 && !isValidQueryWindowServerVersion(serverVersion)) return null;
482
+ const serverEpoch = doc.serverEpoch;
483
+ if (serverEpoch !== void 0 && (typeof serverEpoch !== "string" || !serverEpoch)) return null;
484
+ if (serverEpoch === void 0 !== (serverVersion === void 0)) return null;
485
+ return {
486
+ data: doc.data,
487
+ pageInfo,
488
+ ...typeof totalCount === "number" ? {
489
+ totalCount
490
+ } : {},
491
+ requestedCount,
492
+ ...serverEpoch !== void 0 ? {
493
+ serverEpoch
494
+ } : {},
495
+ ...serverVersion !== void 0 ? {
496
+ serverVersion
497
+ } : {},
498
+ storedAt
499
+ };
500
+ };
501
+ const deleteQueryWindowDocument = async (collection, doc) => {
502
+ const id = typeof doc._id === "string" ? doc._id : "";
503
+ const rev = typeof doc._rev === "string" ? doc._rev : "";
504
+ if (!id || !rev) return;
505
+ await collection.put({
506
+ _id: id,
507
+ _rev: rev,
508
+ _deleted: true
509
+ }).catch(() => void 0);
510
+ };
511
+ const readQueryWindowSnapshot = async ({
512
+ modelName,
513
+ queryKey,
514
+ uid,
515
+ now = Date.now()
516
+ }) => {
517
+ const scope = getQueryWindowScope(uid);
518
+ const fingerprint = getQueryWindowFingerprint(scope, modelName, queryKey);
519
+ const id = getQueryWindowDocumentId(fingerprint);
520
+ const collection = await getCollection(QUERY_WINDOW_COLLECTION, {
521
+ uid
522
+ });
523
+ const doc = await getPouchDocument(collection, id);
524
+ if (!doc) return {
525
+ hit: false
526
+ };
527
+ const snapshot = parseQueryWindowSnapshot(doc, {
528
+ fingerprint,
529
+ scope,
530
+ modelName,
531
+ queryKey
532
+ });
533
+ if (!snapshot || now - snapshot.storedAt >= QUERY_WINDOW_TTL_MS) {
534
+ await deleteQueryWindowDocument(collection, doc);
535
+ return {
536
+ hit: false
537
+ };
538
+ }
539
+ return {
540
+ hit: true,
541
+ snapshot
542
+ };
543
+ };
544
+ const writeQueryWindowSnapshot = async ({
545
+ modelName,
546
+ queryKey,
547
+ uid,
548
+ data,
549
+ pageInfo: rawPageInfo,
550
+ totalCount,
551
+ requestedCount,
552
+ serverEpoch,
553
+ serverVersion,
554
+ storedAt: rawStoredAt
555
+ }) => {
556
+ if (!modelName.trim()) throw new Error("writeQueryWindowSnapshot: modelName must be a non-empty string");
557
+ if (!queryKey) throw new Error("writeQueryWindowSnapshot: queryKey must be a non-empty string");
558
+ if (!uid.trim()) throw new Error("writeQueryWindowSnapshot: uid must be a non-empty string");
559
+ if (!isQueryWindowData(data)) throw new Error("writeQueryWindowSnapshot: data must contain objects");
560
+ const pageInfo = normalizeQueryWindowPageInfo(rawPageInfo);
561
+ if (!pageInfo) throw new Error("writeQueryWindowSnapshot: invalid pageInfo");
562
+ if (!Number.isSafeInteger(requestedCount) || requestedCount <= 0 || requestedCount > RTS_QUERY_WINDOW_MAX_COUNT || data.length > requestedCount) {
563
+ throw new Error("writeQueryWindowSnapshot: requestedCount must cover data and be between 1 and 4096");
564
+ }
565
+ if (totalCount !== void 0 && (!Number.isSafeInteger(totalCount) || totalCount < 0)) {
566
+ throw new Error("writeQueryWindowSnapshot: totalCount must be a non-negative integer");
567
+ }
568
+ if (serverVersion !== void 0 && !isValidQueryWindowServerVersion(serverVersion)) {
569
+ throw new Error("writeQueryWindowSnapshot: invalid serverVersion");
570
+ }
571
+ if (serverEpoch !== void 0 && (typeof serverEpoch !== "string" || !serverEpoch)) {
572
+ throw new Error("writeQueryWindowSnapshot: invalid serverEpoch");
573
+ }
574
+ if (serverEpoch === void 0 !== (serverVersion === void 0)) {
575
+ throw new Error("writeQueryWindowSnapshot: serverEpoch and serverVersion must be provided together");
576
+ }
577
+ const storedAt = rawStoredAt ?? Date.now();
578
+ if (!Number.isFinite(storedAt) || storedAt < 0) {
579
+ throw new Error("writeQueryWindowSnapshot: storedAt must be a non-negative finite number");
580
+ }
581
+ const scope = getQueryWindowScope(uid);
582
+ const fingerprint = getQueryWindowFingerprint(scope, modelName, queryKey);
583
+ const id = getQueryWindowDocumentId(fingerprint);
584
+ const collection = await getCollection(QUERY_WINDOW_COLLECTION, {
585
+ uid
586
+ });
587
+ const snapshot = {
588
+ data,
589
+ pageInfo,
590
+ ...totalCount !== void 0 ? {
591
+ totalCount
592
+ } : {},
593
+ requestedCount,
594
+ ...serverEpoch !== void 0 ? {
595
+ serverEpoch
596
+ } : {},
597
+ ...serverVersion !== void 0 ? {
598
+ serverVersion
599
+ } : {},
600
+ storedAt
601
+ };
602
+ let lastConflict;
603
+ for (let attempt = 0; attempt < QUERY_WINDOW_WRITE_ATTEMPTS; attempt += 1) {
604
+ const current = await getPouchDocument(collection, id);
605
+ const currentSnapshot = current ? parseQueryWindowSnapshot(current, {
606
+ fingerprint,
607
+ scope,
608
+ modelName,
609
+ queryKey
610
+ }) : null;
611
+ if (currentSnapshot && (currentSnapshot.serverEpoch === serverEpoch && currentSnapshot.serverVersion !== void 0 && serverVersion !== void 0 && currentSnapshot.serverVersion >= serverVersion || (!serverEpoch || currentSnapshot.serverEpoch !== serverEpoch) && currentSnapshot.storedAt > storedAt)) {
612
+ return currentSnapshot;
613
+ }
614
+ const rev = typeof current?._rev === "string" ? current._rev : void 0;
615
+ const doc = {
616
+ _id: id,
617
+ ...rev ? {
618
+ _rev: rev
619
+ } : {},
620
+ type: QUERY_WINDOW_DOC_TYPE,
621
+ schemaVersion: QUERY_WINDOW_SCHEMA_VERSION,
622
+ fingerprint,
623
+ scope,
624
+ modelName,
625
+ queryKey,
626
+ data,
627
+ pageInfo,
628
+ ...totalCount !== void 0 ? {
629
+ totalCount
630
+ } : {},
631
+ requestedCount,
632
+ ...serverEpoch !== void 0 ? {
633
+ serverEpoch
634
+ } : {},
635
+ ...serverVersion !== void 0 ? {
636
+ serverVersion
637
+ } : {},
638
+ storedAt
639
+ };
640
+ try {
641
+ await collection.put(doc);
642
+ return snapshot;
643
+ } catch (error) {
644
+ if (getPouchErrorStatus(error) !== 409) throw error;
645
+ lastConflict = error;
646
+ }
647
+ }
648
+ throw lastConflict ?? new Error("writeQueryWindowSnapshot: failed to persist snapshot");
649
+ };
650
+ const invalidateQueryWindowSnapshots = async ({
651
+ uid,
652
+ modelName
653
+ }) => {
654
+ const collection = await getCollection(QUERY_WINDOW_COLLECTION, {
655
+ uid
656
+ });
657
+ const selector = {
658
+ type: QUERY_WINDOW_DOC_TYPE,
659
+ ...modelName ? {
660
+ modelName
661
+ } : {}
662
+ };
663
+ for (let batch = 0; batch < 32; batch += 1) {
664
+ const {
665
+ docs
666
+ } = await collection.find({
667
+ selector,
668
+ fields: ["_id", "_rev"],
669
+ limit: DEFAULT_FIND_LIMIT
670
+ });
671
+ const deletions = docs.map((doc) => ({
672
+ _id: typeof doc._id === "string" ? doc._id : "",
673
+ _rev: typeof doc._rev === "string" ? doc._rev : "",
674
+ _deleted: true
675
+ })).filter((doc) => doc._id && doc._rev);
676
+ if (!deletions.length) return;
677
+ const results = await collection.bulkDocs(deletions);
678
+ const hasConflict = Array.isArray(results) && results.some((result) => {
679
+ if (!result || typeof result !== "object") return false;
680
+ const record = result;
681
+ return record.error === true || record.status === 409;
682
+ });
683
+ if (hasConflict) continue;
684
+ if (docs.length < DEFAULT_FIND_LIMIT) return;
685
+ }
686
+ };
404
687
  const replaceQueryKeys = (value, replaceKey) => {
405
688
  if (typeof value !== "object" || value === null) {
406
689
  return value;
@@ -1288,13 +1571,16 @@ const serializeRtsQueryValue = (value) => {
1288
1571
  }) ?? "";
1289
1572
  };
1290
1573
  const computeRtsQueryKey = (query, options) => {
1291
- const key = options.key ?? "";
1292
- const projection = options.projection ? serializeRtsQueryValue(options.projection) : "";
1293
- const sort = options.sort ? serializeRtsQueryValue(options.sort) : "";
1294
- const limit = typeof options.limit === "number" ? String(options.limit) : "";
1295
- const populate = options.populate ? serializeRtsQueryValue(options.populate) : "";
1296
- const pagination = options.pagination ? serializeRtsQueryValue(options.pagination) : "";
1297
- return `${key}${serializeRtsQueryValue(query)}${projection}${sort}${limit}${populate}${pagination}`;
1574
+ return serializeRtsQueryValue({
1575
+ version: 2,
1576
+ key: options.key ?? null,
1577
+ query,
1578
+ projection: options.projection ?? null,
1579
+ sort: options.sort ?? null,
1580
+ limit: typeof options.limit === "number" ? options.limit : null,
1581
+ populate: options.populate ?? null,
1582
+ pagination: options.pagination ?? null
1583
+ });
1298
1584
  };
1299
1585
  const hasSnapshotError = (snapshot) => {
1300
1586
  return snapshot?.error !== null && snapshot?.error !== void 0;
@@ -1306,11 +1592,15 @@ const SERVER_RECONNECT_DELAY_MAX_MS = 15e3;
1306
1592
  const RUN_NETWORK_QUERY_TIMEOUT_ERROR = "runNetworkQuery: request timed out";
1307
1593
  const RUN_NETWORK_COUNT_TIMEOUT_ERROR = "runNetworkCount: request timed out";
1308
1594
  let socket = null;
1595
+ let socketReadyForMessages = false;
1309
1596
  let connectPromise = null;
1597
+ let pendingConnectionAttempt = null;
1310
1598
  let explicitDisconnect = false;
1311
1599
  let currentTenantId = null;
1312
1600
  let currentUid = null;
1313
1601
  let connectOptions = {};
1602
+ let connectionGeneration = 0;
1603
+ let connectionEpoch = null;
1314
1604
  const localTxnBuf = [];
1315
1605
  const queryCallbacks = /* @__PURE__ */ new Map();
1316
1606
  const countCallbacks = /* @__PURE__ */ new Map();
@@ -1318,9 +1608,11 @@ const subscriptions = /* @__PURE__ */ new Map();
1318
1608
  const countSubscriptions = /* @__PURE__ */ new Map();
1319
1609
  const messageCallbacks = /* @__PURE__ */ new Map();
1320
1610
  const rtsMessageCallbacks = /* @__PURE__ */ new Map();
1611
+ const pendingWindowRequests = /* @__PURE__ */ new Map();
1321
1612
  let reconnectTimer = null;
1322
1613
  let reconnectAttempts = 0;
1323
1614
  let hasEstablishedConnection = false;
1615
+ let forceInitialQueryOnNextConnection = false;
1324
1616
  let pendingServerReconnectJitter = false;
1325
1617
  let syncPromise = null;
1326
1618
  let syncKey = null;
@@ -1422,10 +1714,33 @@ const buildSocketUrl = (_tenantId, _uid, options) => {
1422
1714
  return base.toString();
1423
1715
  };
1424
1716
  const sendToServer = (message) => {
1425
- if (!socket) return;
1717
+ if (!socket || !socketReadyForMessages) return;
1426
1718
  if (socket.readyState !== WebSocket.OPEN) return;
1427
1719
  socket.send(JSON.stringify(message));
1428
1720
  };
1721
+ const isSocketReady = () => Boolean(socket && socketReadyForMessages && socket.readyState === WebSocket.OPEN);
1722
+ const normalizeRequestedCount = (value) => {
1723
+ if (!Number.isSafeInteger(value)) return void 0;
1724
+ if (value < 1 || value > RTS_QUERY_WINDOW_MAX_COUNT) return void 0;
1725
+ return value;
1726
+ };
1727
+ const getSubscriptionServerOptions = (subscription) => {
1728
+ if (!subscription.options.pagination || !subscription.requestedCount) {
1729
+ return subscription.options;
1730
+ }
1731
+ const {
1732
+ cursor: _cursor,
1733
+ direction: _direction,
1734
+ ...pagination
1735
+ } = subscription.options.pagination;
1736
+ return {
1737
+ ...subscription.options,
1738
+ pagination: {
1739
+ ...pagination,
1740
+ limit: subscription.requestedCount
1741
+ }
1742
+ };
1743
+ };
1429
1744
  const resubscribeAll = ({
1430
1745
  forceInitialQuery
1431
1746
  }) => {
@@ -1436,7 +1751,7 @@ const resubscribeAll = ({
1436
1751
  modelName: sub.modelName,
1437
1752
  queryKey: sub.queryKey,
1438
1753
  query: sub.query,
1439
- options: sub.options,
1754
+ options: getSubscriptionServerOptions(sub),
1440
1755
  runInitialQuery
1441
1756
  });
1442
1757
  }
@@ -1457,6 +1772,12 @@ const clearReconnectTimer = () => {
1457
1772
  clearRuntimeTimeout(reconnectTimer);
1458
1773
  reconnectTimer = null;
1459
1774
  };
1775
+ const rejectPendingConnectionAttempt = () => {
1776
+ const attempt = pendingConnectionAttempt;
1777
+ if (!attempt) return;
1778
+ pendingConnectionAttempt = null;
1779
+ attempt.reject(new Error("RTS WebSocket connection attempt superseded"));
1780
+ };
1460
1781
  const scheduleReconnect = () => {
1461
1782
  clearReconnectTimer();
1462
1783
  if (explicitDisconnect) {
@@ -1509,9 +1830,68 @@ const normalizeTotalCount$1 = (value) => {
1509
1830
  if (!Number.isFinite(value) || value < 0) return void 0;
1510
1831
  return Math.floor(value);
1511
1832
  };
1833
+ const normalizeQueryWindow = (value) => {
1834
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
1835
+ const raw = value;
1836
+ const requestedCount = normalizeRequestedCount(raw.requestedCount);
1837
+ if (!requestedCount) return void 0;
1838
+ const version = Number.isSafeInteger(raw.version) && raw.version >= 0 ? raw.version : void 0;
1839
+ return {
1840
+ requestedCount,
1841
+ ...version !== void 0 ? {
1842
+ version
1843
+ } : {}
1844
+ };
1845
+ };
1846
+ const settlePendingWindowRequests = (cbKey, requestedCount, success) => {
1847
+ const pending = pendingWindowRequests.get(cbKey);
1848
+ if (!pending?.size) return;
1849
+ for (const request of Array.from(pending)) {
1850
+ if (request.requestedCount !== requestedCount) continue;
1851
+ clearRuntimeTimeout(request.timeoutId);
1852
+ pending.delete(request);
1853
+ if (!success) {
1854
+ const subscription = subscriptions.get(cbKey);
1855
+ if (subscription?.requestedCount === request.requestedCount) {
1856
+ const snapshotRequestedCount = subscription.lastSnapshot && !hasSnapshotError(subscription.lastSnapshot) ? subscription.lastSnapshot.context.window?.requestedCount : void 0;
1857
+ const rollbackRequestedCount = snapshotRequestedCount ?? request.previousRequestedCount;
1858
+ subscription.windowIntentVersion += 1;
1859
+ subscription.requestedCount = rollbackRequestedCount;
1860
+ sendToServer({
1861
+ type: "set-query-window",
1862
+ modelName: subscription.modelName,
1863
+ queryKey: subscription.queryKey,
1864
+ requestedCount: rollbackRequestedCount
1865
+ });
1866
+ }
1867
+ }
1868
+ request.resolve(success);
1869
+ }
1870
+ if (!pending.size) pendingWindowRequests.delete(cbKey);
1871
+ };
1872
+ const failPendingWindowRequests = (cbKey) => {
1873
+ const pending = pendingWindowRequests.get(cbKey);
1874
+ if (!pending?.size) return;
1875
+ for (const request of Array.from(pending)) {
1876
+ settlePendingWindowRequests(cbKey, request.requestedCount, false);
1877
+ }
1878
+ };
1879
+ const cancelPendingWindowRequests = (cbKey) => {
1880
+ const entries = cbKey ? [[cbKey, pendingWindowRequests.get(cbKey)]] : Array.from(pendingWindowRequests.entries());
1881
+ for (const [key, pending] of entries) {
1882
+ if (!pending) continue;
1883
+ for (const request of pending) {
1884
+ clearRuntimeTimeout(request.timeoutId);
1885
+ request.resolve(false);
1886
+ }
1887
+ pendingWindowRequests.delete(key);
1888
+ }
1889
+ };
1512
1890
  const updateQuerySubscriptionSnapshot = (subscription, snapshot) => {
1513
1891
  if (snapshot.context.source === "cache" && subscription.lastSnapshot?.context.source === "network") {
1514
- return false;
1892
+ const cachedCount = snapshot.context.window?.requestedCount;
1893
+ const networkCount = subscription.lastSnapshot.context.window?.requestedCount;
1894
+ if (!cachedCount || !networkCount || cachedCount <= networkCount) return false;
1515
1895
  }
1516
1896
  subscription.lastSnapshot = snapshot;
1517
1897
  return true;
@@ -1548,12 +1928,21 @@ const dispatchCountSnapshotToOneshotCallbacks = (cbKey, snapshot) => {
1548
1928
  }
1549
1929
  };
1550
1930
  const requestRegisteredQueryRefresh = (subscription) => {
1931
+ if (subscription.options.pagination && subscription.requestedCount) {
1932
+ sendToServer({
1933
+ type: "set-query-window",
1934
+ modelName: subscription.modelName,
1935
+ queryKey: subscription.queryKey,
1936
+ requestedCount: subscription.requestedCount
1937
+ });
1938
+ return;
1939
+ }
1551
1940
  sendToServer({
1552
1941
  type: "run-query",
1553
1942
  modelName: subscription.modelName,
1554
1943
  queryKey: subscription.queryKey,
1555
1944
  query: subscription.query,
1556
- options: subscription.options
1945
+ options: getSubscriptionServerOptions(subscription)
1557
1946
  });
1558
1947
  };
1559
1948
  const requestRegisteredCountRefresh = (subscription) => {
@@ -1566,7 +1955,61 @@ const requestRegisteredCountRefresh = (subscription) => {
1566
1955
  });
1567
1956
  };
1568
1957
  const requestSubscriptionLocalQuery = (cbKey, subscription) => {
1569
- if (!currentUid || subscription.options.pagination) return;
1958
+ if (!currentUid) return false;
1959
+ const uid = currentUid;
1960
+ const generation = connectionGeneration;
1961
+ if (subscription.options.pagination) {
1962
+ const requestedCountAtStart = subscription.requestedCount;
1963
+ const windowIntentVersionAtStart = subscription.windowIntentVersion;
1964
+ const localQueryPromise = readQueryWindowSnapshot({
1965
+ uid,
1966
+ modelName: subscription.modelName,
1967
+ queryKey: subscription.queryKey
1968
+ }).then((result) => {
1969
+ if (!result.hit || generation !== connectionGeneration) return;
1970
+ const currentSubscription = subscriptions.get(cbKey);
1971
+ if (!currentSubscription) return;
1972
+ const requestedCount = normalizeRequestedCount(result.snapshot.requestedCount);
1973
+ if (!requestedCount) return;
1974
+ if (currentSubscription.windowIntentVersion !== windowIntentVersionAtStart) return;
1975
+ if (currentSubscription.requestedCount !== requestedCountAtStart) return;
1976
+ const snapshot = {
1977
+ error: null,
1978
+ data: result.snapshot.data,
1979
+ context: {
1980
+ source: "cache",
1981
+ pageInfo: result.snapshot.pageInfo,
1982
+ ...result.snapshot.totalCount !== void 0 ? {
1983
+ totalCount: result.snapshot.totalCount
1984
+ } : {},
1985
+ window: {
1986
+ requestedCount
1987
+ }
1988
+ }
1989
+ };
1990
+ if (!updateQuerySubscriptionSnapshot(currentSubscription, snapshot)) return;
1991
+ currentSubscription.requestedCount = requestedCount;
1992
+ currentSubscription.windowIntentVersion += 1;
1993
+ if (requestedCount !== requestedCountAtStart) {
1994
+ sendToServer({
1995
+ type: "set-query-window",
1996
+ modelName: currentSubscription.modelName,
1997
+ queryKey: currentSubscription.queryKey,
1998
+ requestedCount
1999
+ });
2000
+ }
2001
+ dispatchQuerySnapshotToSubscription(currentSubscription, snapshot);
2002
+ }).catch(() => {
2003
+ });
2004
+ subscription.localQueryPromise = localQueryPromise;
2005
+ void localQueryPromise.finally(() => {
2006
+ const currentSubscription = subscriptions.get(cbKey);
2007
+ if (currentSubscription?.localQueryPromise === localQueryPromise) {
2008
+ delete currentSubscription.localQueryPromise;
2009
+ }
2010
+ });
2011
+ return true;
2012
+ }
1570
2013
  const populateCache = subscription.populateCache;
1571
2014
  const hasPopulate = Boolean(populateCache);
1572
2015
  if (hasPopulate && populateCache) {
@@ -1574,7 +2017,7 @@ const requestSubscriptionLocalQuery = (cbKey, subscription) => {
1574
2017
  modelName: subscription.modelName,
1575
2018
  query: subscription.query,
1576
2019
  options: {
1577
- uid: currentUid,
2020
+ uid,
1578
2021
  projection: populateCache.rootProjection,
1579
2022
  sort: subscription.options.sort,
1580
2023
  limit: subscription.options.limit,
@@ -1586,6 +2029,7 @@ const requestSubscriptionLocalQuery = (cbKey, subscription) => {
1586
2029
  context
1587
2030
  }) => {
1588
2031
  if (!hit) return;
2032
+ if (generation !== connectionGeneration) return;
1589
2033
  const currentSubscription = subscriptions.get(cbKey);
1590
2034
  if (!currentSubscription) return;
1591
2035
  const snapshot = {
@@ -1597,13 +2041,13 @@ const requestSubscriptionLocalQuery = (cbKey, subscription) => {
1597
2041
  dispatchQuerySnapshotToSubscription(currentSubscription, snapshot);
1598
2042
  }).catch(() => {
1599
2043
  });
1600
- return;
2044
+ return true;
1601
2045
  }
1602
2046
  void runQuery({
1603
2047
  modelName: subscription.modelName,
1604
2048
  query: subscription.query,
1605
2049
  options: {
1606
- uid: currentUid,
2050
+ uid,
1607
2051
  projection: subscription.options.projection,
1608
2052
  sort: subscription.options.sort,
1609
2053
  limit: subscription.options.limit
@@ -1612,6 +2056,7 @@ const requestSubscriptionLocalQuery = (cbKey, subscription) => {
1612
2056
  data,
1613
2057
  context
1614
2058
  }) => {
2059
+ if (generation !== connectionGeneration) return;
1615
2060
  const currentSubscription = subscriptions.get(cbKey);
1616
2061
  if (!currentSubscription) return;
1617
2062
  const snapshot = {
@@ -1623,6 +2068,7 @@ const requestSubscriptionLocalQuery = (cbKey, subscription) => {
1623
2068
  dispatchQuerySnapshotToSubscription(currentSubscription, snapshot);
1624
2069
  }).catch(() => {
1625
2070
  });
2071
+ return true;
1626
2072
  };
1627
2073
  const handleQueryPayload = (payload) => {
1628
2074
  const {
@@ -1639,9 +2085,17 @@ const handleQueryPayload = (payload) => {
1639
2085
  if (!hasSubscriptionCallbacks && !hasOneshotCallbacks) return;
1640
2086
  const pageInfo = normalizePageInfo$1(payload.pageInfo);
1641
2087
  const totalCount = normalizeTotalCount$1(payload.totalCount);
2088
+ const queryWindow = normalizeQueryWindow(payload.window);
1642
2089
  const populateCache = subscription?.populateCache;
1643
2090
  const hasPopulate = Boolean(populateCache);
1644
2091
  const hasPagination = Boolean(subscription?.options?.pagination || pageInfo || totalCount !== void 0);
2092
+ if (subscription?.options.pagination && queryWindow) {
2093
+ if (queryWindow.requestedCount !== subscription.requestedCount) return;
2094
+ if (queryWindow.version !== void 0 && queryWindow.version <= subscription.lastNetworkVersion) return;
2095
+ if (queryWindow.version !== void 0) {
2096
+ subscription.lastNetworkVersion = queryWindow.version;
2097
+ }
2098
+ }
1645
2099
  const isLocal = !!(txnId && localTxnBuf.includes(txnId));
1646
2100
  const context = {
1647
2101
  source: "network",
@@ -1652,6 +2106,9 @@ const handleQueryPayload = (payload) => {
1652
2106
  } : {},
1653
2107
  ...totalCount !== void 0 ? {
1654
2108
  totalCount
2109
+ } : {},
2110
+ ...queryWindow ? {
2111
+ window: queryWindow
1655
2112
  } : {}
1656
2113
  };
1657
2114
  const snapshot = {
@@ -1664,13 +2121,50 @@ const handleQueryPayload = (payload) => {
1664
2121
  dispatchQuerySnapshotToSubscription(subscription, snapshot);
1665
2122
  }
1666
2123
  dispatchQuerySnapshotToOneshotCallbacks(cbKey, snapshot);
2124
+ if (queryWindow) {
2125
+ settlePendingWindowRequests(cbKey, queryWindow.requestedCount, false);
2126
+ } else if (subscription?.options.pagination) {
2127
+ failPendingWindowRequests(cbKey);
2128
+ }
1667
2129
  return;
1668
2130
  }
1669
2131
  if (subscription && updateQuerySubscriptionSnapshot(subscription, snapshot)) {
1670
2132
  dispatchQuerySnapshotToSubscription(subscription, snapshot);
1671
2133
  }
1672
2134
  dispatchQuerySnapshotToOneshotCallbacks(cbKey, snapshot);
2135
+ if (queryWindow) {
2136
+ settlePendingWindowRequests(cbKey, queryWindow.requestedCount, true);
2137
+ }
1673
2138
  if (!currentUid) return;
2139
+ if (subscription?.options.pagination && queryWindow && pageInfo && Array.isArray(data) && connectionEpoch) {
2140
+ const uid = currentUid;
2141
+ const generation = connectionGeneration;
2142
+ const serverEpoch = connectionEpoch;
2143
+ const localQueryPromise = subscription.localQueryPromise;
2144
+ void Promise.resolve().then(async () => {
2145
+ await localQueryPromise;
2146
+ if (generation !== connectionGeneration || serverEpoch !== connectionEpoch) return;
2147
+ const currentSubscription = subscriptions.get(cbKey);
2148
+ if (!currentSubscription || currentSubscription.requestedCount !== queryWindow.requestedCount) return;
2149
+ await writeQueryWindowSnapshot({
2150
+ uid,
2151
+ modelName,
2152
+ queryKey,
2153
+ data,
2154
+ pageInfo,
2155
+ ...totalCount !== void 0 ? {
2156
+ totalCount
2157
+ } : {},
2158
+ requestedCount: queryWindow.requestedCount,
2159
+ ...queryWindow.version !== void 0 ? {
2160
+ serverEpoch,
2161
+ serverVersion: queryWindow.version
2162
+ } : {}
2163
+ });
2164
+ }).catch(() => {
2165
+ });
2166
+ return;
2167
+ }
1674
2168
  const docs = Array.isArray(data) ? data.filter(isDocWithId) : [];
1675
2169
  if (hasPagination) return;
1676
2170
  if (!docs.length) return;
@@ -1789,7 +2283,7 @@ const writeStoredSeq = (key, value) => {
1789
2283
  return;
1790
2284
  }
1791
2285
  };
1792
- const applyChangeBatch = async (changes, uid) => {
2286
+ const applyChangeBatch = async (changes, uid, isCurrent) => {
1793
2287
  const resetModels = /* @__PURE__ */ new Set();
1794
2288
  const deletesByModel = /* @__PURE__ */ new Map();
1795
2289
  for (const change of changes) {
@@ -1808,24 +2302,35 @@ const applyChangeBatch = async (changes, uid) => {
1808
2302
  }
1809
2303
  }
1810
2304
  for (const modelName of resetModels) {
2305
+ if (!isCurrent()) return false;
1811
2306
  await destroyCollection(modelName, uid).catch(() => {
1812
2307
  });
1813
2308
  }
1814
2309
  for (const [modelName, ids] of deletesByModel.entries()) {
1815
2310
  if (resetModels.has(modelName)) continue;
2311
+ if (!isCurrent()) return false;
1816
2312
  await deleteDocs(modelName, ids, uid).catch(() => {
1817
2313
  });
1818
2314
  }
2315
+ if (resetModels.size || deletesByModel.size) {
2316
+ if (!isCurrent()) return false;
2317
+ await invalidateQueryWindowSnapshots({
2318
+ uid
2319
+ }).catch(() => {
2320
+ });
2321
+ }
2322
+ return true;
1819
2323
  };
1820
- const syncRtsChanges = async (tenantId, uid, options = {}) => {
2324
+ const syncRtsChangesWithResult = async (tenantId, uid, options = {}, isCurrent = () => true) => {
1821
2325
  ensureSyncRuntime();
1822
- if (!tenantId || !uid) return;
2326
+ if (!tenantId || !uid) return false;
1823
2327
  const storageKey = getSyncStorageKey({
1824
2328
  tenantId,
1825
2329
  uid,
1826
2330
  appName: options.appName
1827
2331
  });
1828
2332
  let sinceSeq = readStoredSeq(storageKey);
2333
+ let cacheInvalidated = false;
1829
2334
  const syncUrl = buildSyncChangesUrl(tenantId, {
1830
2335
  url: options.url
1831
2336
  });
@@ -1841,21 +2346,23 @@ const syncRtsChanges = async (tenantId, uid, options = {}) => {
1841
2346
  limit: 2e3
1842
2347
  })
1843
2348
  });
1844
- if (!response.ok) return;
2349
+ if (!isCurrent()) return false;
2350
+ if (!response.ok) return cacheInvalidated;
1845
2351
  const payload = await response.json().catch(() => null);
1846
- if (!payload || typeof payload !== "object") return;
2352
+ if (!payload || typeof payload !== "object") return cacheInvalidated;
1847
2353
  const payloadObj = payload;
1848
2354
  const ok = payloadObj.ok;
1849
- if (ok !== true) return;
2355
+ if (ok !== true) return cacheInvalidated;
1850
2356
  const latestSeq = Number(payloadObj.latestSeq ?? 0);
1851
2357
  const needsFullResync = Boolean(payloadObj.needsFullResync);
1852
2358
  if (needsFullResync) {
2359
+ if (!isCurrent()) return false;
1853
2360
  resetRtsPouchStore({
1854
2361
  tenantId,
1855
2362
  appName: options.appName
1856
2363
  });
1857
2364
  writeStoredSeq(storageKey, latestSeq);
1858
- return;
2365
+ return true;
1859
2366
  }
1860
2367
  const changesRaw = payloadObj.changes;
1861
2368
  const changes = Array.isArray(changesRaw) ? changesRaw : [];
@@ -1877,31 +2384,37 @@ const syncRtsChanges = async (tenantId, uid, options = {}) => {
1877
2384
  }).filter((c2) => c2 !== null).filter((c2) => Number.isFinite(c2.seq) && c2.seq > 0 && c2.modelName && (c2.op === "reset_model" || !!c2.docId));
1878
2385
  if (!normalized.length) {
1879
2386
  writeStoredSeq(storageKey, latestSeq);
1880
- return;
2387
+ return cacheInvalidated;
1881
2388
  }
1882
- await applyChangeBatch(normalized, uid);
2389
+ const applied = await applyChangeBatch(normalized, uid, isCurrent);
2390
+ if (!applied) return false;
2391
+ cacheInvalidated = true;
1883
2392
  const lastSeq = normalized.reduce((max, c2) => c2.seq > max ? c2.seq : max, sinceSeq);
1884
2393
  sinceSeq = lastSeq;
1885
2394
  writeStoredSeq(storageKey, sinceSeq);
1886
2395
  if (latestSeq > 0 && sinceSeq >= latestSeq) {
1887
- return;
2396
+ return cacheInvalidated;
1888
2397
  }
1889
2398
  }
2399
+ return cacheInvalidated;
2400
+ };
2401
+ const syncRtsChanges = async (tenantId, uid, options = {}) => {
2402
+ await syncRtsChangesWithResult(tenantId, uid, options);
1890
2403
  };
1891
2404
  const ensureSynced = (tenantId, uid, options) => {
1892
- if (options.syncChanges === false) return;
2405
+ if (options.syncChanges === false) return Promise.resolve(false);
1893
2406
  const key = `${options.appName ?? ""}:${tenantId}:${uid}`;
1894
- if (syncPromise && syncKey === key) return;
2407
+ if (syncPromise && syncKey === key) return syncPromise;
1895
2408
  syncKey = key;
1896
- syncPromise = syncRtsChanges(tenantId, uid, {
2409
+ syncPromise = syncRtsChangesWithResult(tenantId, uid, {
1897
2410
  appName: options.appName,
1898
2411
  url: options.url
1899
- }).catch(() => {
1900
- }).finally(() => {
2412
+ }, () => currentTenantId === tenantId && currentUid === uid && (connectOptions.appName ?? "") === (options.appName ?? "")).catch(() => false).finally(() => {
1901
2413
  if (syncKey === key) {
1902
2414
  syncPromise = null;
1903
2415
  }
1904
2416
  });
2417
+ return syncPromise;
1905
2418
  };
1906
2419
  const connectInternal = (tenantId, uid, options, {
1907
2420
  resetReconnectAttempts
@@ -1918,37 +2431,84 @@ const connectInternal = (tenantId, uid, options, {
1918
2431
  appName: options.appName
1919
2432
  });
1920
2433
  }
1921
- ensureSynced(tenantId, uid, options);
1922
2434
  if (socket && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING)) {
1923
2435
  return connectPromise ?? Promise.resolve();
1924
2436
  }
2437
+ const synchronization = ensureSynced(tenantId, uid, options);
1925
2438
  explicitDisconnect = false;
1926
2439
  clearReconnectTimer();
2440
+ socketReadyForMessages = false;
1927
2441
  const url = buildSocketUrl(tenantId, uid, options);
1928
2442
  connectPromise = new Promise((resolve, reject) => {
1929
2443
  if (resetReconnectAttempts) reconnectAttempts = 0;
1930
2444
  let opened = false;
1931
2445
  let settled = false;
2446
+ let subscriptionsStarted = false;
2447
+ let cacheInvalidatedBeforeSubscriptions = false;
2448
+ const generation = ++connectionGeneration;
2449
+ const epoch = `${Date.now().toString(36)}.${generation.toString(36)}.${Math.random().toString(36).slice(2, 10)}`;
2450
+ connectionEpoch = epoch;
2451
+ pendingConnectionAttempt = {
2452
+ generation,
2453
+ reject: (error) => {
2454
+ if (settled) return;
2455
+ settled = true;
2456
+ reject(error);
2457
+ }
2458
+ };
1932
2459
  setConnectionStatus("connecting");
1933
- socket = new WebSocket(url);
1934
- socket.addEventListener("open", () => {
2460
+ const nextSocket = new WebSocket(url);
2461
+ socket = nextSocket;
2462
+ if (options.syncChanges !== false) {
2463
+ void synchronization.then((cacheInvalidated) => {
2464
+ if (!cacheInvalidated || generation !== connectionGeneration) return;
2465
+ if (!subscriptionsStarted) {
2466
+ cacheInvalidatedBeforeSubscriptions = true;
2467
+ return;
2468
+ }
2469
+ if (nextSocket.readyState !== WebSocket.OPEN) return;
2470
+ for (const subscription of subscriptions.values()) {
2471
+ if (!subscription.options.pagination) continue;
2472
+ requestRegisteredQueryRefresh(subscription);
2473
+ }
2474
+ });
2475
+ }
2476
+ nextSocket.addEventListener("open", () => {
2477
+ if (generation !== connectionGeneration) return;
1935
2478
  opened = true;
1936
2479
  settled = true;
2480
+ if (pendingConnectionAttempt?.generation === generation) pendingConnectionAttempt = null;
1937
2481
  reconnectAttempts = 0;
1938
2482
  pendingServerReconnectJitter = false;
2483
+ socketReadyForMessages = true;
1939
2484
  setConnectionStatus("connected");
1940
- const forceInitialQuery = hasEstablishedConnection;
2485
+ for (const subscription of subscriptions.values()) {
2486
+ subscription.lastNetworkVersion = 0;
2487
+ if (subscription.runInitialLocalQuery && !subscription.hasRequestedInitialLocalQuery) {
2488
+ const cbKey = `${subscription.modelName}.${subscription.queryKey}`;
2489
+ subscription.hasRequestedInitialLocalQuery = requestSubscriptionLocalQuery(cbKey, subscription);
2490
+ }
2491
+ }
2492
+ const forceInitialQuery = hasEstablishedConnection || forceInitialQueryOnNextConnection || cacheInvalidatedBeforeSubscriptions;
1941
2493
  resubscribeAll({
1942
2494
  forceInitialQuery
1943
2495
  });
2496
+ forceInitialQueryOnNextConnection = false;
2497
+ subscriptionsStarted = true;
1944
2498
  hasEstablishedConnection = true;
1945
2499
  resolve();
1946
2500
  });
1947
- socket.addEventListener("message", handleMessage);
1948
- socket.addEventListener("close", (event) => {
1949
- if (!opened && !settled) {
2501
+ nextSocket.addEventListener("message", (event) => {
2502
+ if (generation !== connectionGeneration) return;
2503
+ handleMessage(event);
2504
+ });
2505
+ nextSocket.addEventListener("close", (event) => {
2506
+ if (generation !== connectionGeneration) return;
2507
+ socketReadyForMessages = false;
2508
+ if (!settled) {
1950
2509
  settled = true;
1951
- const error = new Error(`RTS WebSocket closed before opening (code=${event.code})`);
2510
+ if (pendingConnectionAttempt?.generation === generation) pendingConnectionAttempt = null;
2511
+ const error = new Error(`RTS WebSocket closed before becoming ready (code=${event.code})`);
1952
2512
  setConnectionStatus("error", error);
1953
2513
  reject(error);
1954
2514
  }
@@ -1962,9 +2522,12 @@ const connectInternal = (tenantId, uid, options, {
1962
2522
  connectPromise = null;
1963
2523
  scheduleReconnect();
1964
2524
  });
1965
- socket.addEventListener("error", (err) => {
2525
+ nextSocket.addEventListener("error", (err) => {
2526
+ if (generation !== connectionGeneration) return;
1966
2527
  if (settled) return;
1967
2528
  settled = true;
2529
+ if (pendingConnectionAttempt?.generation === generation) pendingConnectionAttempt = null;
2530
+ socketReadyForMessages = false;
1968
2531
  const error = err instanceof Error ? err : new Error("RTS WebSocket error");
1969
2532
  setConnectionStatus("error", error);
1970
2533
  reject(error);
@@ -1973,14 +2536,19 @@ const connectInternal = (tenantId, uid, options, {
1973
2536
  return connectPromise;
1974
2537
  };
1975
2538
  const connect = (tenantId, uid, options = {}) => {
2539
+ const identityChanged = currentTenantId !== null && (currentTenantId !== tenantId || currentUid !== uid || (connectOptions.appName ?? "") !== (options.appName ?? ""));
2540
+ if (identityChanged) return reconnect(tenantId, uid, options);
1976
2541
  return connectInternal(tenantId, uid, options, {
1977
2542
  resetReconnectAttempts: true
1978
2543
  });
1979
2544
  };
1980
2545
  const disconnect = () => {
1981
2546
  explicitDisconnect = true;
2547
+ connectionGeneration += 1;
2548
+ rejectPendingConnectionAttempt();
1982
2549
  clearReconnectTimer();
1983
2550
  hasEstablishedConnection = false;
2551
+ forceInitialQueryOnNextConnection = false;
1984
2552
  pendingServerReconnectJitter = false;
1985
2553
  if (socket) {
1986
2554
  try {
@@ -1989,11 +2557,54 @@ const disconnect = () => {
1989
2557
  }
1990
2558
  }
1991
2559
  socket = null;
2560
+ socketReadyForMessages = false;
1992
2561
  connectPromise = null;
2562
+ connectionEpoch = null;
2563
+ cancelPendingWindowRequests();
1993
2564
  setConnectionStatus("idle");
1994
2565
  };
1995
2566
  const reconnect = (tenantId, uid, options = {}) => {
2567
+ const hasPreviousIdentity = currentTenantId !== null && currentUid !== null;
2568
+ const identityChanged = hasPreviousIdentity && (currentTenantId !== tenantId || currentUid !== uid || (connectOptions.appName ?? "") !== (options.appName ?? ""));
2569
+ if (identityChanged) {
2570
+ cancelPendingWindowRequests();
2571
+ for (const subscription of subscriptions.values()) {
2572
+ subscription.hasRequestedInitialLocalQuery = false;
2573
+ subscription.lastSnapshot = void 0;
2574
+ subscription.lastNetworkVersion = 0;
2575
+ subscription.windowIntentVersion += 1;
2576
+ subscription.requestedCount = subscription.initialWindowSize;
2577
+ delete subscription.localQueryPromise;
2578
+ dispatchQuerySnapshotToSubscription(subscription, {
2579
+ error: null,
2580
+ data: void 0,
2581
+ context: {
2582
+ source: "cache",
2583
+ reset: true,
2584
+ ...subscription.initialWindowSize ? {
2585
+ window: {
2586
+ requestedCount: subscription.initialWindowSize
2587
+ }
2588
+ } : {}
2589
+ }
2590
+ });
2591
+ }
2592
+ for (const subscription of countSubscriptions.values()) {
2593
+ subscription.lastSnapshot = void 0;
2594
+ dispatchCountSnapshotToSubscription(subscription, {
2595
+ error: null,
2596
+ count: void 0,
2597
+ context: {
2598
+ source: "cache",
2599
+ reset: true
2600
+ }
2601
+ });
2602
+ }
2603
+ }
2604
+ forceInitialQueryOnNextConnection = hasEstablishedConnection || identityChanged;
1996
2605
  explicitDisconnect = true;
2606
+ connectionGeneration += 1;
2607
+ rejectPendingConnectionAttempt();
1997
2608
  clearReconnectTimer();
1998
2609
  pendingServerReconnectJitter = false;
1999
2610
  if (socket) {
@@ -2003,8 +2614,12 @@ const reconnect = (tenantId, uid, options = {}) => {
2003
2614
  }
2004
2615
  }
2005
2616
  socket = null;
2617
+ socketReadyForMessages = false;
2006
2618
  connectPromise = null;
2007
- return connect(tenantId, uid, options);
2619
+ connectionEpoch = null;
2620
+ return connectInternal(tenantId, uid, options, {
2621
+ resetReconnectAttempts: true
2622
+ });
2008
2623
  };
2009
2624
  const getConnectionStatus = () => {
2010
2625
  return connectionStatus;
@@ -2041,7 +2656,7 @@ const registerQuery = (modelName, query, optionsOrCallback, callbackMaybe, behav
2041
2656
  projection: options.projection,
2042
2657
  populate: options.populate
2043
2658
  }, "registerQuery");
2044
- const hasPagination = Boolean(options.pagination);
2659
+ const initialWindowSize = options.pagination ? normalizeRequestedCount(options.pagination.limit) : void 0;
2045
2660
  const existingSubscription = subscriptions.get(cbKey);
2046
2661
  const subscription = existingSubscription ?? {
2047
2662
  modelName,
@@ -2050,7 +2665,14 @@ const registerQuery = (modelName, query, optionsOrCallback, callbackMaybe, behav
2050
2665
  queryKey,
2051
2666
  callbacks: /* @__PURE__ */ new Set(),
2052
2667
  runInitialNetworkQuery,
2668
+ runInitialLocalQuery,
2053
2669
  hasRequestedInitialLocalQuery: false,
2670
+ lastNetworkVersion: 0,
2671
+ windowIntentVersion: 0,
2672
+ ...initialWindowSize ? {
2673
+ initialWindowSize,
2674
+ requestedCount: initialWindowSize
2675
+ } : {},
2054
2676
  ...populateCache ? {
2055
2677
  populateCache
2056
2678
  } : {}
@@ -2061,6 +2683,9 @@ const registerQuery = (modelName, query, optionsOrCallback, callbackMaybe, behav
2061
2683
  if (runInitialNetworkQuery) {
2062
2684
  subscription.runInitialNetworkQuery = true;
2063
2685
  }
2686
+ if (runInitialLocalQuery) {
2687
+ subscription.runInitialLocalQuery = true;
2688
+ }
2064
2689
  subscriptions.set(cbKey, subscription);
2065
2690
  let seeded = false;
2066
2691
  if (behavior?.seedSnapshot && updateQuerySubscriptionSnapshot(subscription, behavior.seedSnapshot)) {
@@ -2071,22 +2696,20 @@ const registerQuery = (modelName, query, optionsOrCallback, callbackMaybe, behav
2071
2696
  callback(subscription.lastSnapshot.error, subscription.lastSnapshot.data, subscription.lastSnapshot.context);
2072
2697
  }
2073
2698
  if (!hadCallbacks) {
2074
- if (runInitialLocalQuery && !hasPagination) {
2075
- subscription.hasRequestedInitialLocalQuery = true;
2076
- requestSubscriptionLocalQuery(cbKey, subscription);
2699
+ if (runInitialLocalQuery) {
2700
+ subscription.hasRequestedInitialLocalQuery = requestSubscriptionLocalQuery(cbKey, subscription);
2077
2701
  }
2078
2702
  sendToServer({
2079
2703
  type: "register-query",
2080
2704
  modelName,
2081
2705
  queryKey,
2082
2706
  query,
2083
- options,
2707
+ options: getSubscriptionServerOptions(subscription),
2084
2708
  runInitialQuery: runInitialNetworkQuery
2085
2709
  });
2086
2710
  } else {
2087
- if (runInitialLocalQuery && !subscription.hasRequestedInitialLocalQuery && !hasPagination) {
2088
- subscription.hasRequestedInitialLocalQuery = true;
2089
- requestSubscriptionLocalQuery(cbKey, subscription);
2711
+ if (runInitialLocalQuery && !subscription.hasRequestedInitialLocalQuery) {
2712
+ subscription.hasRequestedInitialLocalQuery = requestSubscriptionLocalQuery(cbKey, subscription);
2090
2713
  }
2091
2714
  if (forceRefreshOnMount || runInitialNetworkQuery && (!hadInitialNetworkQuery || hasSnapshotError(subscription.lastSnapshot))) {
2092
2715
  requestRegisteredQueryRefresh(subscription);
@@ -2097,6 +2720,7 @@ const registerQuery = (modelName, query, optionsOrCallback, callbackMaybe, behav
2097
2720
  currentSubscription?.callbacks.delete(callback);
2098
2721
  if (currentSubscription && currentSubscription.callbacks.size === 0) {
2099
2722
  subscriptions.delete(cbKey);
2723
+ cancelPendingWindowRequests(cbKey);
2100
2724
  sendToServer({
2101
2725
  type: "remove-query",
2102
2726
  modelName,
@@ -2105,6 +2729,63 @@ const registerQuery = (modelName, query, optionsOrCallback, callbackMaybe, behav
2105
2729
  }
2106
2730
  };
2107
2731
  };
2732
+ const setQueryWindowSize = async ({
2733
+ modelName,
2734
+ query,
2735
+ options,
2736
+ requestedCount,
2737
+ timeoutMs = 1e4
2738
+ }) => {
2739
+ const normalizedRequestedCount = normalizeRequestedCount(requestedCount);
2740
+ if (!normalizedRequestedCount || !options.pagination) return false;
2741
+ const queryKey = computeRtsQueryKey(query, options);
2742
+ const cbKey = `${modelName}.${queryKey}`;
2743
+ const subscription = subscriptions.get(cbKey);
2744
+ if (!subscription?.options.pagination) return false;
2745
+ const currentNetworkWindow = subscription.lastSnapshot?.context.source === "network" ? subscription.lastSnapshot.context.window : void 0;
2746
+ if (subscription.requestedCount === normalizedRequestedCount && currentNetworkWindow?.requestedCount === normalizedRequestedCount) {
2747
+ return true;
2748
+ }
2749
+ const previousRequestedCount = subscription.requestedCount ?? subscription.initialWindowSize ?? normalizedRequestedCount;
2750
+ subscription.windowIntentVersion += 1;
2751
+ subscription.requestedCount = normalizedRequestedCount;
2752
+ let request;
2753
+ const response = new Promise((resolve) => {
2754
+ const timeoutId = setRuntimeTimeout(() => {
2755
+ settlePendingWindowRequests(cbKey, request.requestedCount, false);
2756
+ }, timeoutMs);
2757
+ request = {
2758
+ requestedCount: normalizedRequestedCount,
2759
+ previousRequestedCount,
2760
+ resolve,
2761
+ timeoutId
2762
+ };
2763
+ const pending = pendingWindowRequests.get(cbKey) ?? /* @__PURE__ */ new Set();
2764
+ pending.add(request);
2765
+ pendingWindowRequests.set(cbKey, pending);
2766
+ });
2767
+ try {
2768
+ if (!isSocketReady()) {
2769
+ if (!currentTenantId || !currentUid) {
2770
+ settlePendingWindowRequests(cbKey, normalizedRequestedCount, false);
2771
+ return false;
2772
+ }
2773
+ await connectInternal(currentTenantId, currentUid, connectOptions, {
2774
+ resetReconnectAttempts: false
2775
+ });
2776
+ }
2777
+ sendToServer({
2778
+ type: "set-query-window",
2779
+ modelName,
2780
+ queryKey,
2781
+ requestedCount: normalizedRequestedCount
2782
+ });
2783
+ } catch {
2784
+ settlePendingWindowRequests(cbKey, normalizedRequestedCount, false);
2785
+ return false;
2786
+ }
2787
+ return await response;
2788
+ };
2108
2789
  const registerCount = (modelName, query, optionsOrCallback, callbackMaybe, behavior) => {
2109
2790
  let options;
2110
2791
  let callback;
@@ -2188,7 +2869,7 @@ const runNetworkQuery = async ({
2188
2869
  }, "runNetworkQuery");
2189
2870
  const hasTimeout = typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0;
2190
2871
  const timeoutStartedAt = hasTimeout ? Date.now() : 0;
2191
- if (!socket || socket.readyState !== WebSocket.OPEN) {
2872
+ if (!isSocketReady()) {
2192
2873
  if (currentTenantId && currentUid) {
2193
2874
  try {
2194
2875
  const connectAttempt = connectInternal(currentTenantId, currentUid, connectOptions, {
@@ -2197,7 +2878,7 @@ const runNetworkQuery = async ({
2197
2878
  if (hasTimeout) {
2198
2879
  await new Promise((resolve, reject) => {
2199
2880
  const timeoutId = setRuntimeTimeout(() => {
2200
- reject(new Error(RUN_NETWORK_COUNT_TIMEOUT_ERROR));
2881
+ reject(new Error(RUN_NETWORK_QUERY_TIMEOUT_ERROR));
2201
2882
  }, timeoutMs);
2202
2883
  connectAttempt.then(() => {
2203
2884
  clearRuntimeTimeout(timeoutId);
@@ -2214,7 +2895,7 @@ const runNetworkQuery = async ({
2214
2895
  }
2215
2896
  }
2216
2897
  }
2217
- if (!socket || socket.readyState !== WebSocket.OPEN) {
2898
+ if (!isSocketReady()) {
2218
2899
  if (hasTimeout && Date.now() - timeoutStartedAt >= timeoutMs) {
2219
2900
  throw new Error(RUN_NETWORK_QUERY_TIMEOUT_ERROR);
2220
2901
  }
@@ -2224,7 +2905,7 @@ const runNetworkQuery = async ({
2224
2905
  if (remainingTimeoutMs !== null && remainingTimeoutMs <= 0) {
2225
2906
  throw new Error(RUN_NETWORK_QUERY_TIMEOUT_ERROR);
2226
2907
  }
2227
- const resolvedOptions = options.key ? options : {
2908
+ const resolvedOptions = {
2228
2909
  ...options,
2229
2910
  key: makeRunQueryKey()
2230
2911
  };
@@ -2285,7 +2966,7 @@ const runNetworkCount = async ({
2285
2966
  }
2286
2967
  const hasTimeout = typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0;
2287
2968
  const timeoutStartedAt = hasTimeout ? Date.now() : 0;
2288
- if (!socket || socket.readyState !== WebSocket.OPEN) {
2969
+ if (!isSocketReady()) {
2289
2970
  if (currentTenantId && currentUid) {
2290
2971
  try {
2291
2972
  const connectAttempt = connectInternal(currentTenantId, currentUid, connectOptions, {
@@ -2294,7 +2975,7 @@ const runNetworkCount = async ({
2294
2975
  if (hasTimeout) {
2295
2976
  await new Promise((resolve, reject) => {
2296
2977
  const timeoutId = setRuntimeTimeout(() => {
2297
- reject(new Error(RUN_NETWORK_QUERY_TIMEOUT_ERROR));
2978
+ reject(new Error(RUN_NETWORK_COUNT_TIMEOUT_ERROR));
2298
2979
  }, timeoutMs);
2299
2980
  connectAttempt.then(() => {
2300
2981
  clearRuntimeTimeout(timeoutId);
@@ -2311,7 +2992,7 @@ const runNetworkCount = async ({
2311
2992
  }
2312
2993
  }
2313
2994
  }
2314
- if (!socket || socket.readyState !== WebSocket.OPEN) {
2995
+ if (!isSocketReady()) {
2315
2996
  if (hasTimeout && Date.now() - timeoutStartedAt >= timeoutMs) {
2316
2997
  throw new Error(RUN_NETWORK_COUNT_TIMEOUT_ERROR);
2317
2998
  }
@@ -2321,7 +3002,7 @@ const runNetworkCount = async ({
2321
3002
  if (remainingTimeoutMs !== null && remainingTimeoutMs <= 0) {
2322
3003
  throw new Error(RUN_NETWORK_COUNT_TIMEOUT_ERROR);
2323
3004
  }
2324
- const resolvedOptions = options.key ? options : {
3005
+ const resolvedOptions = {
2325
3006
  ...options,
2326
3007
  key: makeRunQueryKey()
2327
3008
  };
@@ -2432,28 +3113,9 @@ const normalizeTotalCount = (value) => {
2432
3113
  if (!Number.isFinite(value) || value < 0) return void 0;
2433
3114
  return Math.floor(value);
2434
3115
  };
2435
- const getDocId = (doc) => {
2436
- if (!doc || typeof doc !== "object") return "";
2437
- const id = doc._id;
2438
- return typeof id === "string" ? id.trim() : "";
2439
- };
2440
- const dedupeById = (docs) => {
2441
- const seen = /* @__PURE__ */ new Set();
2442
- const merged = [];
2443
- for (const doc of docs) {
2444
- const id = getDocId(doc);
2445
- if (id && seen.has(id)) continue;
2446
- if (id) seen.add(id);
2447
- merged.push(doc);
2448
- }
2449
- return merged;
2450
- };
2451
- const flattenLoadedPages = (previousPages, headPage, nextPages) => {
2452
- const merged = [];
2453
- for (const page of previousPages) merged.push(...page.nodes);
2454
- if (headPage) merged.push(...headPage.nodes);
2455
- for (const page of nextPages) merged.push(...page.nodes);
2456
- return dedupeById(merged);
3116
+ const normalizeWindowSize = (value) => {
3117
+ if (typeof value !== "number" || !Number.isFinite(value)) return 1;
3118
+ return Math.max(1, Math.min(RTS_QUERY_WINDOW_MAX_COUNT, Math.floor(value)));
2457
3119
  };
2458
3120
  const assertIncludeOnlyProjection = (projection) => {
2459
3121
  if (!projection) return;
@@ -2484,6 +3146,13 @@ const useQuery = (modelName, query = {}, options = {}) => {
2484
3146
  populate: options.populate
2485
3147
  }, "useQuery");
2486
3148
  const isPaginated = Boolean(options.pagination);
3149
+ if (options.pagination && (!Number.isInteger(options.pagination.limit) || options.pagination.limit < 1 || options.pagination.limit > RTS_QUERY_WINDOW_MAX_COUNT)) {
3150
+ throw new Error("useQuery: pagination limit must be an integer between 1 and 4096");
3151
+ }
3152
+ if (options.pagination?.cursor || options.pagination?.direction) {
3153
+ throw new Error("useQuery: cursor and direction are not supported by virtual query windows");
3154
+ }
3155
+ const initialWindowSize = isPaginated ? normalizeWindowSize(options.pagination?.limit) : void 0;
2487
3156
  const queryKey = computeRtsQueryKey(query, {
2488
3157
  key,
2489
3158
  projection: options.projection,
@@ -2513,7 +3182,7 @@ const useQuery = (modelName, query = {}, options = {}) => {
2513
3182
  const seedTotalCountRaw = useMemo(() => enabled && ssrEnabled ? ssrRuntime ? ssrRuntime.getQueryTotalCount(modelName, queryKey) : peekHydratedRtsQueryTotalCount(modelName, queryKey) : void 0, [enabled, ssrEnabled, ssrRuntime, modelName, queryKey]);
2514
3183
  const hasSeedData = Array.isArray(seedDataRaw);
2515
3184
  const seedData = hasSeedData ? seedDataRaw : void 0;
2516
- const seedPageInfo = normalizePageInfo(seedPageInfoRaw);
3185
+ const seedPageInfo = useMemo(() => normalizePageInfo(seedPageInfoRaw), [seedPageInfoRaw]);
2517
3186
  const seedTotalCount = normalizeTotalCount(seedTotalCountRaw);
2518
3187
  const seedJson = (() => {
2519
3188
  if (!hasSeedData) return "";
@@ -2533,38 +3202,21 @@ const useQuery = (modelName, query = {}, options = {}) => {
2533
3202
  })();
2534
3203
  const seedTotalCountStr = seedTotalCount !== void 0 ? String(seedTotalCount) : "";
2535
3204
  const [data, setData] = useState(() => isPaginated ? void 0 : seedData);
2536
- const [headPage, setHeadPage] = useState(() => isPaginated && seedData ? {
2537
- nodes: seedData,
2538
- ...seedPageInfo ? {
2539
- pageInfo: seedPageInfo
2540
- } : {}
2541
- } : null);
3205
+ const [paginatedData, setPaginatedData] = useState(() => isPaginated ? seedData : void 0);
3206
+ const [pageInfo, setPageInfo] = useState(() => isPaginated ? seedPageInfo : void 0);
2542
3207
  const [totalCount, setTotalCount] = useState(() => isPaginated ? seedTotalCount : void 0);
2543
- const [previousPages, setPreviousPages] = useState([]);
2544
- const [nextPages, setNextPages] = useState([]);
2545
3208
  const [source, setSource] = useState(() => hasSeedData ? "cache" : void 0);
2546
3209
  const [error, setError] = useState(void 0);
2547
3210
  const [loading, setLoading] = useState(enabled && !hasSeedData);
2548
- const [pagingDirection, setPagingDirection] = useState(null);
2549
3211
  const hasFirstReply = useRef(false);
2550
3212
  const hasNetworkReply = useRef(false);
2551
3213
  const lastDataJsonRef = useRef("");
2552
- const previousPagesRef = useRef([]);
2553
- const nextPagesRef = useRef([]);
2554
- const headPageRef = useRef(null);
2555
- const pagingDirectionRef = useRef(null);
3214
+ const pageInfoRef = useRef(seedPageInfo);
3215
+ const requestedCountRef = useRef(initialWindowSize ?? 1);
3216
+ const pagingRef = useRef(false);
2556
3217
  useEffect(() => {
2557
- previousPagesRef.current = previousPages;
2558
- }, [previousPages]);
2559
- useEffect(() => {
2560
- nextPagesRef.current = nextPages;
2561
- }, [nextPages]);
2562
- useEffect(() => {
2563
- headPageRef.current = headPage;
2564
- }, [headPage]);
2565
- useEffect(() => {
2566
- pagingDirectionRef.current = pagingDirection;
2567
- }, [pagingDirection]);
3218
+ pageInfoRef.current = pageInfo;
3219
+ }, [pageInfo]);
2568
3220
  useEffect(() => {
2569
3221
  if (!ssrRuntime && enabled && ssrEnabled && hasSeedData) {
2570
3222
  consumeHydratedRtsQueryData(modelName, queryKey);
@@ -2572,13 +3224,14 @@ const useQuery = (modelName, query = {}, options = {}) => {
2572
3224
  hasFirstReply.current = hasSeedData;
2573
3225
  hasNetworkReply.current = false;
2574
3226
  lastDataJsonRef.current = seedJson;
3227
+ requestedCountRef.current = initialWindowSize ?? 1;
3228
+ pagingRef.current = false;
2575
3229
  setError(void 0);
2576
- setPreviousPages([]);
2577
- setNextPages([]);
2578
3230
  if (!enabled) {
2579
3231
  setLoading(false);
2580
3232
  setData(void 0);
2581
- setHeadPage(null);
3233
+ setPaginatedData(void 0);
3234
+ setPageInfo(void 0);
2582
3235
  setTotalCount(void 0);
2583
3236
  setSource(void 0);
2584
3237
  return;
@@ -2588,30 +3241,28 @@ const useQuery = (modelName, query = {}, options = {}) => {
2588
3241
  setLoading(false);
2589
3242
  setSource("cache");
2590
3243
  if (isPaginated) {
2591
- setHeadPage({
2592
- nodes: nextSeedData,
2593
- ...seedPageInfo ? {
2594
- pageInfo: seedPageInfo
2595
- } : {}
2596
- });
3244
+ setPaginatedData(nextSeedData);
3245
+ setPageInfo(seedPageInfo);
2597
3246
  setTotalCount(seedTotalCount);
2598
3247
  setData(void 0);
2599
3248
  } else {
2600
3249
  setData(nextSeedData);
2601
3250
  setTotalCount(void 0);
2602
- setHeadPage(null);
3251
+ setPaginatedData(void 0);
3252
+ setPageInfo(void 0);
2603
3253
  }
2604
3254
  return;
2605
3255
  }
2606
3256
  setData(void 0);
2607
- setHeadPage(null);
3257
+ setPaginatedData(void 0);
3258
+ setPageInfo(void 0);
2608
3259
  setTotalCount(void 0);
2609
3260
  setLoading(true);
2610
- }, [enabled, ssrEnabled, ssrRuntime, modelName, queryKey, hasSeedData, seedJson, seedPageInfoJson, seedTotalCountStr, isPaginated]);
3261
+ }, [enabled, ssrEnabled, ssrRuntime, modelName, queryKey, hasSeedData, seedData, seedJson, seedPageInfo, seedPageInfoJson, seedTotalCount, seedTotalCountStr, isPaginated, initialWindowSize]);
2611
3262
  useEffect(() => {
2612
3263
  if (!enabled) return;
2613
- const runInitialNetworkQuery = refreshOnMount || !hasSeedData;
2614
- const runInitialLocalQuery = !hasSeedData && !isPaginated;
3264
+ const runInitialNetworkQuery = isPaginated || refreshOnMount || !hasSeedData;
3265
+ const runInitialLocalQuery = !hasSeedData;
2615
3266
  const unsubscribe = registerQuery(modelName, query, {
2616
3267
  key,
2617
3268
  projection: options.projection,
@@ -2620,7 +3271,23 @@ const useQuery = (modelName, query = {}, options = {}) => {
2620
3271
  populate: options.populate,
2621
3272
  pagination: options.pagination
2622
3273
  }, (err, result, context) => {
2623
- if (context.source === "cache" && hasNetworkReply.current) return;
3274
+ if (context.source === "cache" && context.reset) {
3275
+ hasFirstReply.current = false;
3276
+ hasNetworkReply.current = false;
3277
+ lastDataJsonRef.current = "";
3278
+ pageInfoRef.current = void 0;
3279
+ requestedCountRef.current = initialWindowSize ?? 1;
3280
+ pagingRef.current = false;
3281
+ setData(void 0);
3282
+ setPaginatedData(void 0);
3283
+ setPageInfo(void 0);
3284
+ setTotalCount(void 0);
3285
+ setSource(void 0);
3286
+ setError(void 0);
3287
+ setLoading(true);
3288
+ return;
3289
+ }
3290
+ if (context.source === "cache" && hasNetworkReply.current && (!isPaginated || !context.window || context.window.requestedCount <= requestedCountRef.current)) return;
2624
3291
  if (context.source === "network") {
2625
3292
  hasNetworkReply.current = true;
2626
3293
  }
@@ -2636,10 +3303,15 @@ const useQuery = (modelName, query = {}, options = {}) => {
2636
3303
  hasFirstReply.current = true;
2637
3304
  const nextPageInfo = context.pageInfo;
2638
3305
  const nextTotalCount = context.totalCount;
3306
+ const nextRequestedCount = context.window?.requestedCount;
3307
+ if (isPaginated && nextRequestedCount !== void 0) {
3308
+ requestedCountRef.current = nextRequestedCount;
3309
+ }
2639
3310
  const payloadForHash = isPaginated ? {
2640
3311
  result,
2641
3312
  pageInfo: nextPageInfo,
2642
- totalCount: nextTotalCount
3313
+ totalCount: nextTotalCount,
3314
+ requestedCount: nextRequestedCount
2643
3315
  } : result;
2644
3316
  let nextJson = "";
2645
3317
  try {
@@ -2655,13 +3327,9 @@ const useQuery = (modelName, query = {}, options = {}) => {
2655
3327
  setSource(context.source);
2656
3328
  setError(void 0);
2657
3329
  if (isPaginated) {
2658
- setHeadPage({
2659
- nodes: result,
2660
- ...nextPageInfo ? {
2661
- pageInfo: nextPageInfo
2662
- } : {}
2663
- });
2664
- setTotalCount((current) => nextTotalCount === void 0 ? current : nextTotalCount);
3330
+ setPaginatedData(result);
3331
+ setPageInfo(nextPageInfo);
3332
+ setTotalCount(nextTotalCount);
2665
3333
  return;
2666
3334
  }
2667
3335
  setData(result);
@@ -2681,6 +3349,11 @@ const useQuery = (modelName, query = {}, options = {}) => {
2681
3349
  } : {},
2682
3350
  ...seedTotalCount !== void 0 ? {
2683
3351
  totalCount: seedTotalCount
3352
+ } : {},
3353
+ ...initialWindowSize ? {
3354
+ window: {
3355
+ requestedCount: initialWindowSize
3356
+ }
2684
3357
  } : {}
2685
3358
  }
2686
3359
  }
@@ -2689,45 +3362,18 @@ const useQuery = (modelName, query = {}, options = {}) => {
2689
3362
  return () => {
2690
3363
  unsubscribe?.();
2691
3364
  };
2692
- }, [enabled, modelName, queryKey, queryJson, projectionJson, sortJson, limitStr, populateJson, paginationJson, hasSeedData, refreshOnMount, isPaginated]);
2693
- const effectivePageInfo = useMemo(() => {
2694
- if (!isPaginated) return void 0;
2695
- const firstPageInfo = previousPages.length > 0 ? previousPages[0]?.pageInfo : headPage?.pageInfo;
2696
- const lastPageInfo = nextPages.length > 0 ? nextPages[nextPages.length - 1]?.pageInfo : headPage?.pageInfo;
2697
- if (!firstPageInfo && !lastPageInfo) return void 0;
2698
- const hasPrevPage = Boolean(firstPageInfo?.hasPrevPage);
2699
- const hasNextPage = Boolean(lastPageInfo?.hasNextPage);
2700
- const prevCursor = firstPageInfo?.prevCursor;
2701
- const nextCursor = lastPageInfo?.nextCursor;
2702
- return {
2703
- hasPrevPage,
2704
- hasNextPage,
2705
- ...prevCursor ? {
2706
- prevCursor
2707
- } : {},
2708
- ...nextCursor ? {
2709
- nextCursor
2710
- } : {}
2711
- };
2712
- }, [headPage, isPaginated, nextPages, previousPages]);
2713
- const mergedPaginatedData = useMemo(() => {
2714
- if (!isPaginated) return void 0;
2715
- if (!headPage && previousPages.length === 0 && nextPages.length === 0) return void 0;
2716
- return flattenLoadedPages(previousPages, headPage, nextPages);
2717
- }, [headPage, isPaginated, nextPages, previousPages]);
3365
+ }, [enabled, modelName, queryKey, queryJson, projectionJson, sortJson, limitStr, populateJson, paginationJson, hasSeedData, refreshOnMount, isPaginated, initialWindowSize]);
2718
3366
  const fetchNext = async () => {
2719
3367
  if (!enabled || !isPaginated || !options.pagination) return false;
2720
- if (pagingDirectionRef.current) return false;
2721
- const currentHead = headPageRef.current;
2722
- const currentNextPages = nextPagesRef.current;
2723
- const cursor = currentNextPages.length > 0 ? currentNextPages[currentNextPages.length - 1]?.pageInfo?.nextCursor : currentHead?.pageInfo?.nextCursor;
2724
- const hasNextPage_0 = currentNextPages.length > 0 ? Boolean(currentNextPages[currentNextPages.length - 1]?.pageInfo?.hasNextPage) : Boolean(currentHead?.pageInfo?.hasNextPage);
2725
- if (!cursor || !hasNextPage_0) return false;
2726
- setPagingDirection("next");
3368
+ if (pagingRef.current || !pageInfoRef.current?.hasNextPage) return false;
3369
+ const pageSize = initialWindowSize ?? normalizeWindowSize(options.pagination.limit);
3370
+ const nextRequestedCount_0 = Math.min(RTS_QUERY_WINDOW_MAX_COUNT, requestedCountRef.current + pageSize);
3371
+ if (nextRequestedCount_0 === requestedCountRef.current) return false;
3372
+ pagingRef.current = true;
2727
3373
  setLoading(true);
2728
3374
  setError(void 0);
2729
3375
  try {
2730
- const response = await runNetworkQuery({
3376
+ return await setQueryWindowSize({
2731
3377
  modelName,
2732
3378
  query,
2733
3379
  options: {
@@ -2736,91 +3382,47 @@ const useQuery = (modelName, query = {}, options = {}) => {
2736
3382
  sort: options.sort,
2737
3383
  limit: options.limit,
2738
3384
  populate: options.populate,
2739
- pagination: {
2740
- ...options.pagination,
2741
- direction: "next",
2742
- cursor
2743
- }
2744
- }
3385
+ pagination: options.pagination
3386
+ },
3387
+ requestedCount: nextRequestedCount_0
2745
3388
  });
2746
- if (!Array.isArray(response.data)) return false;
2747
- const page = {
2748
- nodes: response.data,
2749
- ...response.context.source === "network" && response.context.pageInfo ? {
2750
- pageInfo: response.context.pageInfo
2751
- } : {}
2752
- };
2753
- setSource("network");
2754
- if (response.context.source === "network" && response.context.totalCount !== void 0) {
2755
- setTotalCount(response.context.totalCount);
2756
- }
2757
- setNextPages((current_0) => [...current_0, page]);
2758
- return true;
2759
3389
  } catch (err_0) {
2760
3390
  setError(err_0);
2761
3391
  return false;
2762
3392
  } finally {
2763
- setPagingDirection(null);
3393
+ pagingRef.current = false;
2764
3394
  setLoading(false);
2765
3395
  }
2766
3396
  };
2767
3397
  const fetchPrevious = async () => {
2768
- if (!enabled || !isPaginated || !options.pagination) return false;
2769
- if (pagingDirectionRef.current) return false;
2770
- const currentHead_0 = headPageRef.current;
2771
- const currentPreviousPages = previousPagesRef.current;
2772
- const cursor_0 = currentPreviousPages.length > 0 ? currentPreviousPages[0]?.pageInfo?.prevCursor : currentHead_0?.pageInfo?.prevCursor;
2773
- const hasPrevPage_0 = currentPreviousPages.length > 0 ? Boolean(currentPreviousPages[0]?.pageInfo?.hasPrevPage) : Boolean(currentHead_0?.pageInfo?.hasPrevPage);
2774
- if (!cursor_0 || !hasPrevPage_0) return false;
2775
- setPagingDirection("prev");
3398
+ return false;
3399
+ };
3400
+ const resetPagination = () => {
3401
+ if (!enabled || !isPaginated || !options.pagination || !initialWindowSize) return;
3402
+ if (pagingRef.current || requestedCountRef.current === initialWindowSize) return;
3403
+ pagingRef.current = true;
2776
3404
  setLoading(true);
2777
3405
  setError(void 0);
2778
- try {
2779
- const response_0 = await runNetworkQuery({
2780
- modelName,
2781
- query,
2782
- options: {
2783
- key,
2784
- projection: options.projection,
2785
- sort: options.sort,
2786
- limit: options.limit,
2787
- populate: options.populate,
2788
- pagination: {
2789
- ...options.pagination,
2790
- direction: "prev",
2791
- cursor: cursor_0
2792
- }
2793
- }
2794
- });
2795
- if (!Array.isArray(response_0.data)) return false;
2796
- const page_0 = {
2797
- nodes: response_0.data,
2798
- ...response_0.context.source === "network" && response_0.context.pageInfo ? {
2799
- pageInfo: response_0.context.pageInfo
2800
- } : {}
2801
- };
2802
- setSource("network");
2803
- if (response_0.context.source === "network" && response_0.context.totalCount !== void 0) {
2804
- setTotalCount(response_0.context.totalCount);
2805
- }
2806
- setPreviousPages((current_1) => [page_0, ...current_1]);
2807
- return true;
2808
- } catch (err_1) {
2809
- setError(err_1);
2810
- return false;
2811
- } finally {
2812
- setPagingDirection(null);
3406
+ void setQueryWindowSize({
3407
+ modelName,
3408
+ query,
3409
+ options: {
3410
+ key,
3411
+ projection: options.projection,
3412
+ sort: options.sort,
3413
+ limit: options.limit,
3414
+ populate: options.populate,
3415
+ pagination: options.pagination
3416
+ },
3417
+ requestedCount: initialWindowSize
3418
+ }).finally(() => {
3419
+ pagingRef.current = false;
2813
3420
  setLoading(false);
2814
- }
2815
- };
2816
- const resetPagination = () => {
2817
- if (!isPaginated) return;
2818
- setPreviousPages([]);
2819
- setNextPages([]);
3421
+ });
2820
3422
  };
2821
- return useMemo(() => ({
2822
- data: isPaginated ? mergedPaginatedData : data,
2823
- pageInfo: effectivePageInfo,
3423
+ return {
3424
+ data: isPaginated ? paginatedData : data,
3425
+ pageInfo: isPaginated ? pageInfo : void 0,
2824
3426
  totalCount: isPaginated ? totalCount : void 0,
2825
3427
  source,
2826
3428
  error,
@@ -2828,7 +3430,7 @@ const useQuery = (modelName, query = {}, options = {}) => {
2828
3430
  fetchNext,
2829
3431
  fetchPrevious,
2830
3432
  resetPagination
2831
- }), [data, effectivePageInfo, error, fetchNext, fetchPrevious, isPaginated, loading, mergedPaginatedData, source, totalCount]);
3433
+ };
2832
3434
  };
2833
3435
  export {
2834
3436
  registerQuery as A,
@@ -2869,4 +3471,4 @@ export {
2869
3471
  onMessage as y,
2870
3472
  onRtsMessage as z
2871
3473
  };
2872
- //# sourceMappingURL=useQuery-BOjtIBwv.js.map
3474
+ //# sourceMappingURL=useQuery-DZqYIJog.js.map