@peerbit/document 12.3.5-07ba572 → 12.3.5-cb91e7b

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.
@@ -753,7 +753,24 @@ let DocumentIndex = (() => {
753
753
  this.documentEvents.removeEventListener("change", this.handleDocumentChange);
754
754
  }
755
755
  this.clearAllResultQueues();
756
- await this.index?.stop?.();
756
+ await this._resumableIterators.clearAll();
757
+ if (this.iteratorKeepAliveTimers) {
758
+ for (const timer of this.iteratorKeepAliveTimers.values()) {
759
+ clearTimeout(timer);
760
+ }
761
+ this.iteratorKeepAliveTimers.clear();
762
+ }
763
+ try {
764
+ await this.index?.stop?.();
765
+ }
766
+ catch (error) {
767
+ // Be defensive during teardown: stopping an already-stopped index shouldn't
768
+ // prevent closing the program and releasing timers/iterators.
769
+ if (error instanceof indexerTypes.NotStartedError) {
770
+ return closed;
771
+ }
772
+ throw error;
773
+ }
757
774
  }
758
775
  return closed;
759
776
  }
@@ -762,13 +779,35 @@ let DocumentIndex = (() => {
762
779
  if (dropped) {
763
780
  this.documentEvents.removeEventListener("change", this.handleDocumentChange);
764
781
  this.clearAllResultQueues();
765
- await this.index?.drop?.();
766
- await this.index?.stop?.();
782
+ await this._resumableIterators.clearAll();
783
+ if (this.iteratorKeepAliveTimers) {
784
+ for (const timer of this.iteratorKeepAliveTimers.values()) {
785
+ clearTimeout(timer);
786
+ }
787
+ this.iteratorKeepAliveTimers.clear();
788
+ }
789
+ try {
790
+ await this.index?.drop?.();
791
+ }
792
+ catch (error) {
793
+ if (!(error instanceof indexerTypes.NotStartedError)) {
794
+ throw error;
795
+ }
796
+ }
797
+ try {
798
+ await this.index?.stop?.();
799
+ }
800
+ catch (error) {
801
+ if (!(error instanceof indexerTypes.NotStartedError)) {
802
+ throw error;
803
+ }
804
+ }
767
805
  }
768
806
  return dropped;
769
807
  }
770
808
  async get(key, options) {
771
809
  let deferred;
810
+ let baseRemote;
772
811
  // Normalize the id key early so listeners can use it
773
812
  let idKey = key instanceof indexerTypes.IdKey ? key : indexerTypes.toId(key);
774
813
  if (options?.waitFor) {
@@ -801,11 +840,12 @@ let DocumentIndex = (() => {
801
840
  this.documentEvents.addEventListener("change", listener);
802
841
  deferred.promise.then(cleanup);
803
842
  // Prepare remote options without mutating caller options
804
- const baseRemote = options?.remote === false
805
- ? undefined
806
- : typeof options?.remote === "object"
807
- ? { ...options.remote }
808
- : {};
843
+ baseRemote =
844
+ options?.remote === false
845
+ ? undefined
846
+ : typeof options?.remote === "object"
847
+ ? { ...options.remote }
848
+ : {};
809
849
  if (baseRemote) {
810
850
  const waitPolicy = baseRemote.wait;
811
851
  if (!waitPolicy ||
@@ -842,7 +882,10 @@ let DocumentIndex = (() => {
842
882
  });
843
883
  }
844
884
  }
845
- const result = (await this.getDetailed(idKey, options))?.[0]?.results[0];
885
+ const initialOptions = baseRemote
886
+ ? { ...options, remote: baseRemote }
887
+ : options;
888
+ const result = (await this.getDetailed(idKey, initialOptions))?.[0]?.results[0];
846
889
  // if no results, and we have remote joining options, we wait for the timout and if there are joining peers we re-query
847
890
  if (!result) {
848
891
  return deferred?.promise;
@@ -1142,6 +1185,8 @@ let DocumentIndex = (() => {
1142
1185
  fromQuery: (fromQuery || query),
1143
1186
  resolveResults: resolveFlag,
1144
1187
  };
1188
+ // Don't keep Node alive just to GC old remote iterator state.
1189
+ prevQueued.timeout.unref?.();
1145
1190
  if (fromQuery instanceof types.IterationRequest &&
1146
1191
  fromQuery.pushUpdates) {
1147
1192
  prevQueued.pushMode = fromQuery.pushUpdates;
@@ -1228,6 +1273,8 @@ let DocumentIndex = (() => {
1228
1273
  }
1229
1274
  this._resumableIterators.close({ idString });
1230
1275
  }, delay);
1276
+ // This is a best-effort cleanup timer; it should not keep Node alive.
1277
+ timer.unref?.();
1231
1278
  timers.set(idString, timer);
1232
1279
  }
1233
1280
  cancelIteratorKeepAlive(idString) {
@@ -1409,12 +1456,7 @@ let DocumentIndex = (() => {
1409
1456
  const local = typeof options?.local === "boolean" ? options?.local : true;
1410
1457
  let remote = undefined;
1411
1458
  if (typeof options?.remote === "boolean") {
1412
- if (options?.remote) {
1413
- remote = {};
1414
- }
1415
- else {
1416
- remote = undefined;
1417
- }
1459
+ remote = options.remote ? {} : undefined;
1418
1460
  }
1419
1461
  else {
1420
1462
  remote = options?.remote || {};
@@ -1425,6 +1467,13 @@ let DocumentIndex = (() => {
1425
1467
  // this will lead to bad UX as you usually want to list/expore whats going on before doing any replication work
1426
1468
  remote.priority = 2;
1427
1469
  }
1470
+ if (remote && remote.timeout == null && options?.remote) {
1471
+ const waitPolicy = typeof options.remote === "object" ? options.remote.wait : undefined;
1472
+ const waitTimeout = typeof waitPolicy === "object" ? waitPolicy.timeout : undefined;
1473
+ if (waitTimeout != null) {
1474
+ remote.timeout = waitTimeout;
1475
+ }
1476
+ }
1428
1477
  if (!local && !remote) {
1429
1478
  throw new Error("Expecting either 'options.remote' or 'options.local' to be true");
1430
1479
  }
@@ -1443,14 +1492,69 @@ let DocumentIndex = (() => {
1443
1492
  // don't wait for responses
1444
1493
  throw new Error("Unexpected");
1445
1494
  }
1446
- const replicatorGroups = options?.remote?.from
1495
+ const coverProps = remote.domain ?? { args: undefined };
1496
+ const isDefaultDomainArgs = !("range" in coverProps) &&
1497
+ (!("args" in coverProps) || coverProps.args == null);
1498
+ let replicatorGroups = options?.remote?.from
1447
1499
  ? options?.remote?.from
1448
- : await this._log.getCover(remote.domain ?? { args: undefined }, {
1500
+ : await this._log.getCover(coverProps, {
1449
1501
  roleAge: remote.minAge,
1450
1502
  eager: remote.reach?.eager,
1451
1503
  reachableOnly: !!remote.wait, // when we want to merge joining we can ignore pending to be online peers and instead consider them once they become online
1452
1504
  signal: options?.signal,
1453
1505
  });
1506
+ // Cold start: cover can be temporarily empty/self-only while replication metadata
1507
+ // converges. For remote search, it's sometimes better to at least try currently
1508
+ // connected peers, but only if we have evidence that a remote replicator exists.
1509
+ if (!options?.remote?.from && isDefaultDomainArgs) {
1510
+ const selfHash = this.node.identity.publicKey.hashcode();
1511
+ const remoteCount = replicatorGroups.filter((h) => h !== selfHash).length;
1512
+ if (remoteCount === 0) {
1513
+ const waitEnabled = Boolean(remote.wait);
1514
+ const coverIsSelfOnly = replicatorGroups.length === 1 && replicatorGroups[0] === selfHash;
1515
+ // If the cover is explicitly empty (no shards), don't override it unless
1516
+ // the caller requested waiting for joins (e.g. get(waitFor)).
1517
+ if (!waitEnabled && !coverIsSelfOnly) {
1518
+ // no-op
1519
+ }
1520
+ else {
1521
+ let hasKnownRemoteReplicator = false;
1522
+ if (!waitEnabled) {
1523
+ try {
1524
+ const replicators = await this._log.getReplicators();
1525
+ for (const hash of replicators.keys()) {
1526
+ if (hash !== selfHash) {
1527
+ hasKnownRemoteReplicator = true;
1528
+ break;
1529
+ }
1530
+ }
1531
+ }
1532
+ catch {
1533
+ // Best-effort only.
1534
+ }
1535
+ }
1536
+ if (waitEnabled || hasKnownRemoteReplicator) {
1537
+ const peerMap = this.node.services
1538
+ .pubsub?.peers;
1539
+ if (peerMap?.keys) {
1540
+ const extra = [];
1541
+ for (const hash of peerMap.keys()) {
1542
+ if (!hash || hash === selfHash)
1543
+ continue;
1544
+ extra.push(hash);
1545
+ if (extra.length >= 8)
1546
+ break;
1547
+ }
1548
+ if (extra.length > 0) {
1549
+ replicatorGroups = [
1550
+ ...new Set([...replicatorGroups, ...extra]),
1551
+ ];
1552
+ }
1553
+ }
1554
+ }
1555
+ }
1556
+ }
1557
+ }
1454
1558
  if (replicatorGroups) {
1455
1559
  const responseHandler = async (results) => {
1456
1560
  const resultInitialized = await introduceEntries(queryRequest, results, this.documentType, this.indexedType, this._sync, options);
@@ -1575,9 +1679,8 @@ let DocumentIndex = (() => {
1575
1679
  // Set fetch to search size, or max value (default to max u32 (4294967295))
1576
1680
  const coercedRequest = coerceQuery(queryRequest, options, this.compatibility);
1577
1681
  coercedRequest.fetch = coercedRequest.fetch ?? 0xffffffff;
1578
- // So that the iterator is pre-fetching the right amount of entries
1682
+ // Use an iterator so large results respect message size limits.
1579
1683
  const iterator = this.iterate(coercedRequest, options);
1580
- // So that this call will not do any remote requests
1581
1684
  const allResults = [];
1582
1685
  while (iterator.done() !== true &&
1583
1686
  coercedRequest.fetch > allResults.length) {
@@ -1838,36 +1941,42 @@ let DocumentIndex = (() => {
1838
1941
  let discoveredTargetHashes;
1839
1942
  if (typeof options?.remote === "object") {
1840
1943
  let waitForTime = undefined;
1944
+ const waitPolicy = typeof options.remote.wait === "object"
1945
+ ? options.remote.wait
1946
+ : undefined;
1947
+ const waitBehavior = waitPolicy?.behavior ?? "keep-open";
1841
1948
  if (options.remote.wait) {
1842
- let t0 = +new Date();
1843
1949
  waitForTime =
1844
1950
  typeof options.remote.wait === "boolean"
1845
1951
  ? DEFAULT_TIMEOUT
1846
1952
  : (options.remote.wait.timeout ?? DEFAULT_TIMEOUT);
1847
- let setDoneIfTimeout = false;
1848
- maybeSetDone = () => {
1849
- if (t0 + waitForTime < +new Date()) {
1850
- cleanup();
1851
- done = true;
1852
- }
1853
- else {
1854
- setDoneIfTimeout = true;
1855
- }
1856
- };
1857
- unsetDone = () => {
1858
- setDoneIfTimeout = false;
1859
- done = false;
1860
- };
1861
- let timeout = setTimeout(() => {
1862
- if (setDoneIfTimeout) {
1863
- cleanup();
1864
- done = true;
1865
- }
1866
- }, waitForTime);
1867
- cleanup = () => {
1868
- this.clearResultsQueue(queryRequestCoerced);
1869
- clearTimeout(timeout);
1870
- };
1953
+ if (waitBehavior === "keep-open") {
1954
+ let t0 = +new Date();
1955
+ let setDoneIfTimeout = false;
1956
+ maybeSetDone = () => {
1957
+ if (t0 + waitForTime < +new Date()) {
1958
+ cleanup();
1959
+ done = true;
1960
+ }
1961
+ else {
1962
+ setDoneIfTimeout = true;
1963
+ }
1964
+ };
1965
+ unsetDone = () => {
1966
+ setDoneIfTimeout = false;
1967
+ done = false;
1968
+ };
1969
+ let timeout = setTimeout(() => {
1970
+ if (setDoneIfTimeout) {
1971
+ cleanup();
1972
+ done = true;
1973
+ }
1974
+ }, waitForTime);
1975
+ cleanup = () => {
1976
+ this.clearResultsQueue(queryRequestCoerced);
1977
+ clearTimeout(timeout);
1978
+ };
1979
+ }
1871
1980
  }
1872
1981
  if (options.remote.reach?.discover) {
1873
1982
  const discoverTimeout = waitForTime ??
@@ -1891,9 +2000,6 @@ let DocumentIndex = (() => {
1891
2000
  warmupPromise = prior.then(() => discoverPromise);
1892
2001
  options.remote.reach.eager = true; // include the results from the discovered peer even if it is not mature
1893
2002
  }
1894
- const waitPolicy = typeof options.remote.wait === "object"
1895
- ? options.remote.wait
1896
- : undefined;
1897
2003
  if (waitPolicy?.behavior === "block" &&
1898
2004
  (waitPolicy.until ?? "any") === "any") {
1899
2005
  const blockPromise = this.waitForCoverReady({
@@ -2300,25 +2406,19 @@ let DocumentIndex = (() => {
2300
2406
  };
2301
2407
  let close = async () => {
2302
2408
  cleanupAndDone();
2303
- // send close to remote
2409
+ // send close to remote (only peers that actually served results / had an active buffer)
2304
2410
  const closeRequest = new types.CloseIteratorRequest({
2305
2411
  id: queryRequestCoerced.id,
2306
2412
  });
2307
- const promises = [];
2308
- for (const [peer, buffer] of peerBufferMap) {
2309
- if (buffer.kept === 0) {
2310
- peerBufferMap.delete(peer);
2311
- continue;
2312
- }
2313
- if (peer !== this.node.identity.publicKey.hashcode()) {
2314
- // Close remote
2315
- promises.push(this._query.send(closeRequest, {
2316
- ...options,
2317
- mode: new SilentDelivery({ to: [peer], redundancy: 1 }),
2318
- }));
2319
- }
2320
- }
2321
- await Promise.all(promises);
2413
+ const selfHash = this.node.identity.publicKey.hashcode();
2414
+ const remotePeers = [...peerBufferMap.entries()]
2415
+ .filter(([peer, buffer]) => peer !== selfHash && buffer.kept > 0)
2416
+ .map(([peer]) => peer);
2417
+ peerBufferMap.clear();
2418
+ await Promise.allSettled(remotePeers.map((peer) => this._query.send(closeRequest, {
2419
+ ...options,
2420
+ mode: new SilentDelivery({ to: [peer], redundancy: 1 }),
2421
+ })));
2322
2422
  };
2323
2423
  options?.signal && options.signal.addEventListener("abort", close);
2324
2424
  let doneFn = () => {
@@ -2746,10 +2846,17 @@ let DocumentIndex = (() => {
2746
2846
  return cleanupDefaultUpdates();
2747
2847
  };
2748
2848
  }
2749
- if (typeof options?.remote === "object" && options?.remote.wait) {
2849
+ const remoteConfig = options && typeof options.remote === "object" ? options.remote : undefined;
2850
+ const remoteWaitPolicy = remoteConfig && typeof remoteConfig.wait === "object"
2851
+ ? remoteConfig.wait
2852
+ : undefined;
2853
+ const remoteWaitBehavior = remoteWaitPolicy?.behavior ?? "keep-open";
2854
+ const keepRemoteWaitOpen = !!remoteConfig?.wait &&
2855
+ remoteWaitBehavior === "keep-open";
2856
+ if (keepRemoteWaitOpen) {
2750
2857
  // was used to account for missed results when a peer joins; omitted in this minimal handler
2751
2858
  updateDeferred = pDefer();
2752
- const waitForTime = typeof options.remote.wait === "object" && options.remote.wait.timeout;
2859
+ const waitForTime = remoteWaitPolicy?.timeout;
2753
2860
  const prevMaybeSetDone = maybeSetDone;
2754
2861
  maybeSetDone = () => {
2755
2862
  prevMaybeSetDone();
@@ -2764,7 +2871,7 @@ let DocumentIndex = (() => {
2764
2871
  fetchedFirstForRemote = new Set();
2765
2872
  joinListener = this.createReplicatorJoinListener({
2766
2873
  signal: ensureController().signal,
2767
- eager: options.remote.reach?.eager,
2874
+ eager: remoteConfig?.reach?.eager,
2768
2875
  onPeer: async (pk) => {
2769
2876
  if (done)
2770
2877
  return;
@@ -2823,7 +2930,7 @@ let DocumentIndex = (() => {
2823
2930
  }
2824
2931
  };
2825
2932
  }
2826
- const remoteWaitActive = typeof options?.remote === "object" && !!options.remote.wait;
2933
+ const remoteWaitActive = keepRemoteWaitOpen;
2827
2934
  const waitForUpdateAndResetDeferred = async () => {
2828
2935
  if (remoteWaitActive) {
2829
2936
  // wait until: join fetch adds results, cleanup runs, or the join-wait times out