@peerbit/document 13.1.6 → 13.1.8

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.
@@ -48,7 +48,7 @@ import { SharedLog, } from "@peerbit/shared-log";
48
48
  import { DataMessage, FOREGROUND_READ_MESSAGE_PRIORITY, SilentDelivery, } from "@peerbit/stream-interface";
49
49
  import { AbortError, TimeoutError, waitFor } from "@peerbit/time";
50
50
  import pDefer, {} from "p-defer";
51
- import { concat, fromString } from "uint8arrays";
51
+ import { concat, equals, fromString } from "uint8arrays";
52
52
  import { copySerialization } from "./borsh.js";
53
53
  import { MAX_BATCH_SIZE } from "./constants.js";
54
54
  import MostCommonQueryPredictor, { idAgnosticQueryKey, } from "./most-common-query-predictor.js";
@@ -377,6 +377,7 @@ function isSubclassOf(SubClass, SuperClass) {
377
377
  const DEFAULT_TIMEOUT = 1e4;
378
378
  const DEFAULT_KEEP_REMOTE_ITERATOR_TIMEOUT = 3e5;
379
379
  const DISCOVER_TIMEOUT_FALLBACK = 500;
380
+ const CLOSE_ITERATOR_REQUEST_TIMEOUT = 5e3;
380
381
  const DEFAULT_INDEX_BY = "id";
381
382
  export const INDEX_CONTEXT_SHAPE = {
382
383
  __context: {
@@ -2794,6 +2795,7 @@ let DocumentIndex = (() => {
2794
2795
  };
2795
2796
  let extraPromises = undefined;
2796
2797
  const seenRemoteHashes = new Set();
2798
+ const selectedRemoteHashes = [];
2797
2799
  const groupHashes = replicatorGroups
2798
2800
  .filter((hash) => {
2799
2801
  if (hash === this.node.identity.publicKey.hashcode()) {
@@ -2808,6 +2810,7 @@ let DocumentIndex = (() => {
2808
2810
  return false;
2809
2811
  }
2810
2812
  fetchFirstForRemote?.add(hash);
2813
+ selectedRemoteHashes.push(hash);
2811
2814
  const resultAlready = this._prefetch?.accumulator.consume(queryRequest, hash);
2812
2815
  if (resultAlready) {
2813
2816
  (extraPromises || (extraPromises = [])).push((async () => {
@@ -2826,6 +2829,7 @@ let DocumentIndex = (() => {
2826
2829
  return true;
2827
2830
  })
2828
2831
  .map((x) => [x]);
2832
+ options?.onRemoteTargets?.(selectedRemoteHashes);
2829
2833
  extraPromises && (await Promise.all(extraPromises));
2830
2834
  let tearDown = undefined;
2831
2835
  const search = this;
@@ -2930,16 +2934,20 @@ let DocumentIndex = (() => {
2930
2934
  // Use an iterator so large results respect message size limits.
2931
2935
  const iterator = this.iterate(coercedRequest, searchOptions);
2932
2936
  const allResults = [];
2933
- while (iterator.done() !== true &&
2934
- coercedRequest.fetch > allResults.length) {
2935
- // We might need to pull .next multiple time due to data message size limitations
2936
- for (const result of await iterator.next(coercedRequest.fetch - allResults.length)) {
2937
- allResults.push(result);
2937
+ try {
2938
+ while (iterator.done() !== true &&
2939
+ coercedRequest.fetch > allResults.length) {
2940
+ // We might need to pull .next multiple time due to data message size limitations
2941
+ for (const result of await iterator.next(coercedRequest.fetch - allResults.length)) {
2942
+ allResults.push(result);
2943
+ }
2938
2944
  }
2945
+ // Deduplicate and return values directly
2946
+ return dedup(allResults, this.indexByResolver);
2947
+ }
2948
+ finally {
2949
+ await iterator.close();
2939
2950
  }
2940
- await iterator.close();
2941
- // Deduplicate and return values directly
2942
- return dedup(allResults, this.indexByResolver);
2943
2951
  }
2944
2952
  resolveIndexed(result, results) {
2945
2953
  if (isResultIndexedValue(result)) {
@@ -3135,6 +3143,48 @@ let DocumentIndex = (() => {
3135
3143
  return tracked;
3136
3144
  };
3137
3145
  const peerBufferMap = new Map();
3146
+ const remoteIteratorPeersToClose = new Set();
3147
+ const staleRemoteIteratorIdsToClose = new Map();
3148
+ const retiredRemoteIteratorPeers = new Set();
3149
+ const pendingMissingResponseRetryPeers = new Set();
3150
+ const missingResponseRetryAttempts = new Map();
3151
+ const maxMissingResponseRetryAttempts = 2;
3152
+ const retireRemoteIteratorPeer = (peer) => {
3153
+ remoteIteratorPeersToClose.add(peer);
3154
+ retiredRemoteIteratorPeers.add(peer);
3155
+ const peerBuffer = peerBufferMap.get(peer);
3156
+ if (!peerBuffer || peerBuffer.buffer.length === 0) {
3157
+ peerBufferMap.delete(peer);
3158
+ }
3159
+ else {
3160
+ // Keep already-received values available to the caller, but do not
3161
+ // issue another CollectNextRequest until a fresh iteration succeeds.
3162
+ peerBuffer.kept = 0;
3163
+ }
3164
+ };
3165
+ const recordMissingResponseGroups = (missingGroups) => {
3166
+ const selfHash = this.node.identity.publicKey.hashcode();
3167
+ for (const group of missingGroups) {
3168
+ for (const hash of group) {
3169
+ if (hash && hash !== selfHash) {
3170
+ retireRemoteIteratorPeer(hash);
3171
+ }
3172
+ }
3173
+ if (!retryMissingResponseGroups) {
3174
+ continue;
3175
+ }
3176
+ const target = group.find((hash) => {
3177
+ if (!hash || hash === selfHash)
3178
+ return false;
3179
+ const attempts = missingResponseRetryAttempts.get(hash) ?? 0;
3180
+ return attempts < maxMissingResponseRetryAttempts;
3181
+ });
3182
+ if (!target)
3183
+ continue;
3184
+ pendingMissingResponseRetryPeers.add(target);
3185
+ missingResponseRetryAttempts.set(target, (missingResponseRetryAttempts.get(target) ?? 0) + 1);
3186
+ }
3187
+ };
3138
3188
  const visited = new Set();
3139
3189
  let indexedPlaceholders;
3140
3190
  const ensureIndexedPlaceholders = () => {
@@ -3314,6 +3364,15 @@ let DocumentIndex = (() => {
3314
3364
  }
3315
3365
  }
3316
3366
  const fetchFirst = async (n, fetchOptions) => {
3367
+ const remoteRequestOptions = typeof options?.remote === "object" ? options.remote : undefined;
3368
+ const fetchSignals = [
3369
+ options?.signal,
3370
+ remoteRequestOptions?.signal,
3371
+ ensureController().signal,
3372
+ ].filter((signal) => signal != null);
3373
+ const fetchSignal = fetchSignals.length === 1
3374
+ ? fetchSignals[0]
3375
+ : AbortSignal.any(fetchSignals);
3317
3376
  await warmupPromise;
3318
3377
  let hasMore = false;
3319
3378
  let missingResponses = false;
@@ -3326,43 +3385,56 @@ let DocumentIndex = (() => {
3326
3385
  const skipRemoteDueToDiscovery = typeof options?.remote === "object" &&
3327
3386
  options.remote.reach?.discover &&
3328
3387
  discoveredTargetHashes?.length === 0;
3388
+ const queryRemote = options?.remote !== false && !skipRemoteDueToDiscovery;
3389
+ const remoteFrom = fetchOptions?.from ??
3390
+ initialRemoteTargets ??
3391
+ remoteRequestOptions?.from;
3392
+ if (queryRemote) {
3393
+ const selfHash = this.node.identity.publicKey.hashcode();
3394
+ for (const peer of remoteFrom ?? []) {
3395
+ if (peer !== selfHash) {
3396
+ remoteIteratorPeersToClose.add(peer);
3397
+ }
3398
+ }
3399
+ }
3329
3400
  queryRequestCoerced.fetch = n;
3330
3401
  await this.queryCommence(queryRequestCoerced, {
3331
3402
  local: fetchOptions?.from != null ? false : options?.local,
3332
- remote: options?.remote !== false && !skipRemoteDueToDiscovery
3403
+ remote: queryRemote
3333
3404
  ? {
3334
- ...(typeof options?.remote === "object"
3335
- ? options.remote
3336
- : {}),
3337
- from: fetchOptions?.from ??
3338
- initialRemoteTargets ??
3339
- (typeof options?.remote === "object"
3340
- ? options.remote.from
3341
- : undefined),
3405
+ ...remoteRequestOptions,
3406
+ from: remoteFrom,
3407
+ signal: fetchSignal,
3342
3408
  }
3343
3409
  : false,
3344
3410
  resolve,
3345
- signal: options?.signal,
3411
+ signal: fetchSignal,
3346
3412
  onResponse: async (response, from) => {
3347
3413
  if (!from) {
3348
3414
  logger.error("Missing response from");
3349
3415
  return;
3350
3416
  }
3417
+ const fromHash = from.hashcode();
3418
+ remoteIteratorPeersToClose.add(fromHash);
3419
+ retiredRemoteIteratorPeers.delete(fromHash);
3351
3420
  if (response instanceof types.NoAccess) {
3352
3421
  logger.error("Dont have access");
3353
3422
  return;
3354
3423
  }
3355
3424
  else if (isResults(response)) {
3356
3425
  const results = response;
3357
- const existingBuffer = peerBufferMap.get(from.hashcode());
3426
+ const existingBuffer = peerBufferMap.get(fromHash);
3358
3427
  const buffer = existingBuffer?.buffer || [];
3359
3428
  if (results.kept === 0n && results.results.length === 0) {
3360
3429
  if (keepRemoteAlive) {
3361
- peerBufferMap.set(from.hashcode(), {
3430
+ peerBufferMap.set(fromHash, {
3362
3431
  buffer,
3363
3432
  kept: 0,
3364
3433
  });
3365
3434
  }
3435
+ else {
3436
+ remoteIteratorPeersToClose.delete(fromHash);
3437
+ }
3366
3438
  return;
3367
3439
  }
3368
3440
  const reqFetch = queryRequestCoerced.fetch ?? 0;
@@ -3371,6 +3443,9 @@ let DocumentIndex = (() => {
3371
3443
  if (effectiveKept > 0) {
3372
3444
  hasMore = true;
3373
3445
  }
3446
+ else if (!keepRemoteAlive) {
3447
+ remoteIteratorPeersToClose.delete(fromHash);
3448
+ }
3374
3449
  for (const result of results.results) {
3375
3450
  const indexKey = indexerTypes.toId(this.indexByResolver(result.value)).primitive;
3376
3451
  if (isResultValue(result)) {
@@ -3415,7 +3490,7 @@ let DocumentIndex = (() => {
3415
3490
  ensureIndexedPlaceholders().set(indexKey, placeholder);
3416
3491
  }
3417
3492
  }
3418
- peerBufferMap.set(from.hashcode(), {
3493
+ peerBufferMap.set(fromHash, {
3419
3494
  buffer,
3420
3495
  kept: effectiveKept,
3421
3496
  });
@@ -3426,29 +3501,21 @@ let DocumentIndex = (() => {
3426
3501
  },
3427
3502
  onMissingResponses: (error) => {
3428
3503
  missingResponses = true;
3429
- if (!retryMissingResponseGroups) {
3430
- return;
3431
- }
3432
3504
  const missingGroups = error.missingGroups;
3433
3505
  if (!missingGroups?.length) {
3434
3506
  return;
3435
3507
  }
3436
- const selfHash = this.node.identity.publicKey.hashcode();
3437
- for (const group of missingGroups) {
3438
- const target = group.find((hash) => {
3439
- if (!hash || hash === selfHash)
3440
- return false;
3441
- const attempts = missingResponseRetryAttempts.get(hash) ?? 0;
3442
- return attempts < maxMissingResponseRetryAttempts;
3443
- });
3444
- if (!target)
3445
- continue;
3446
- pendingMissingResponseRetryPeers.add(target);
3447
- missingResponseRetryAttempts.set(target, (missingResponseRetryAttempts.get(target) ?? 0) + 1);
3508
+ recordMissingResponseGroups(missingGroups);
3509
+ },
3510
+ onRemoteTargets: (targets) => {
3511
+ for (const peer of targets) {
3512
+ remoteIteratorPeersToClose.add(peer);
3448
3513
  }
3449
3514
  },
3450
3515
  }, fetchOptions?.fetchedFirstForRemote);
3451
- if (missingResponses && retryMissingResponseGroups) {
3516
+ if (missingResponses &&
3517
+ retryMissingResponseGroups &&
3518
+ pendingMissingResponseRetryPeers.size > 0) {
3452
3519
  hasMore = true;
3453
3520
  unsetDone();
3454
3521
  }
@@ -3473,6 +3540,18 @@ let DocumentIndex = (() => {
3473
3540
  if (pendingMissingResponseRetryPeers.size > 0) {
3474
3541
  const retryTargets = [...pendingMissingResponseRetryPeers];
3475
3542
  pendingMissingResponseRetryPeers.clear();
3543
+ const idTranslation = this._prefetch?.accumulator.getTranslationMap(queryRequestCoerced);
3544
+ for (const peer of retryTargets) {
3545
+ const staleRemoteIteratorId = idTranslation?.get(peer);
3546
+ if (staleRemoteIteratorId) {
3547
+ const staleIds = staleRemoteIteratorIdsToClose.get(peer) ?? [];
3548
+ if (!staleIds.some((id) => equals(id, staleRemoteIteratorId))) {
3549
+ staleIds.push(staleRemoteIteratorId);
3550
+ staleRemoteIteratorIdsToClose.set(peer, staleIds);
3551
+ }
3552
+ idTranslation.delete(peer);
3553
+ }
3554
+ }
3476
3555
  return setFetchPromise(fetchFirst(n, {
3477
3556
  from: retryTargets,
3478
3557
  // retries for missing groups should not be suppressed by first-fetch dedupe
@@ -3482,6 +3561,12 @@ let DocumentIndex = (() => {
3482
3561
  const promises = [];
3483
3562
  let resultsLeft = 0;
3484
3563
  for (const [peer, buffer] of peerBufferMap) {
3564
+ if (retiredRemoteIteratorPeers.has(peer)) {
3565
+ if (buffer.buffer.length === 0) {
3566
+ peerBufferMap.delete(peer);
3567
+ }
3568
+ continue;
3569
+ }
3485
3570
  if (buffer.buffer.length < n) {
3486
3571
  const hasExistingRemoteResults = buffer.kept > 0;
3487
3572
  if (!hasExistingRemoteResults && !keepRemoteAlive) {
@@ -3576,6 +3661,7 @@ let DocumentIndex = (() => {
3576
3661
  }
3577
3662
  else {
3578
3663
  // Fetch remotely
3664
+ remoteIteratorPeersToClose.add(peer);
3579
3665
  const idTranslation = this._prefetch?.accumulator.getTranslationMap(queryRequestCoerced);
3580
3666
  let remoteCollectRequest = collectRequest;
3581
3667
  if (idTranslation) {
@@ -3584,97 +3670,118 @@ let DocumentIndex = (() => {
3584
3670
  amount: collectRequest.amount,
3585
3671
  });
3586
3672
  }
3673
+ const remoteRequestOptions = typeof options?.remote === "object" ? options.remote : undefined;
3674
+ const collectSignals = [
3675
+ options?.signal,
3676
+ remoteRequestOptions?.signal,
3677
+ ensureController().signal,
3678
+ ].filter((signal) => signal != null);
3679
+ const collectSignal = collectSignals.length === 1
3680
+ ? collectSignals[0]
3681
+ : AbortSignal.any(collectSignals);
3587
3682
  promises.push(this._query
3588
3683
  .request(remoteCollectRequest, {
3589
3684
  ...options,
3590
- signal: options?.signal
3591
- ? AbortSignal.any([
3592
- options.signal,
3593
- ensureController().signal,
3594
- ])
3595
- : ensureController().signal,
3685
+ ...remoteRequestOptions,
3686
+ signal: collectSignal,
3596
3687
  priority: getRemoteQueryPriority(options?.remote),
3597
3688
  mode: new SilentDelivery({ to: [peer], redundancy: 1 }),
3598
3689
  })
3599
- .then((response) => introduceEntries(queryRequestCoerced, response, this.documentType, this.indexedType, this._sync, options)
3600
- .then(async (responses) => {
3601
- return Promise.all(responses.map(async (response, i) => {
3602
- resultsLeft += Number(response.response.kept);
3603
- const from = responses[i].from;
3604
- if (!from) {
3605
- logger.error("Missing from for sorted query");
3606
- return;
3690
+ .then((response) => {
3691
+ if (!response.some((result) => result.from?.hashcode() === peer)) {
3692
+ const missingGroups = [[peer]];
3693
+ if (remoteRequestOptions?.throwOnMissing) {
3694
+ retireRemoteIteratorPeer(peer);
3695
+ throw new MissingResponsesError("Did not receive responses from all shards: " +
3696
+ JSON.stringify(missingGroups), missingGroups);
3607
3697
  }
3608
- if (response.response.results.length === 0) {
3698
+ recordMissingResponseGroups(missingGroups);
3699
+ return;
3700
+ }
3701
+ return introduceEntries(queryRequestCoerced, response, this.documentType, this.indexedType, this._sync, options)
3702
+ .then(async (responses) => {
3703
+ return Promise.all(responses.map(async (response, i) => {
3704
+ resultsLeft += Number(response.response.kept);
3705
+ const from = responses[i].from;
3706
+ if (!from) {
3707
+ logger.error("Missing from for sorted query");
3708
+ return;
3709
+ }
3609
3710
  if (!keepRemoteAlive &&
3610
- peerBufferMap.get(peer)?.buffer.length === 0) {
3611
- peerBufferMap.delete(peer); // No more results
3711
+ response.response.kept === 0n) {
3712
+ remoteIteratorPeersToClose.delete(peer);
3612
3713
  }
3613
- }
3614
- else {
3615
- const peerBuffer = peerBufferMap.get(peer);
3616
- if (!peerBuffer) {
3617
- return;
3714
+ if (response.response.results.length === 0) {
3715
+ if (!keepRemoteAlive &&
3716
+ peerBufferMap.get(peer)?.buffer.length === 0) {
3717
+ peerBufferMap.delete(peer); // No more results
3718
+ }
3618
3719
  }
3619
- peerBuffer.kept = Number(response.response.kept);
3620
- for (const result of response.response.results) {
3621
- const indexKey = indexerTypes.toId(this.indexByResolver(result.value)).primitive;
3622
- if (isResultValue(result)) {
3623
- const existingIndexed = indexedPlaceholders?.get(indexKey);
3624
- if (existingIndexed) {
3625
- existingIndexed.value =
3626
- result.value;
3627
- existingIndexed.context = result.context;
3628
- existingIndexed.from = from;
3629
- existingIndexed.indexed =
3630
- await this.resolveIndexed(result, response.response
3631
- .results);
3632
- indexedPlaceholders?.delete(indexKey);
3633
- continue;
3634
- }
3635
- if (visited.has(indexKey) &&
3636
- !evictStaleBuffered(indexKey, result.context)) {
3637
- continue;
3638
- }
3639
- visited.add(indexKey);
3640
- const indexed = await this.resolveIndexed(result, response.response
3641
- .results);
3642
- peerBuffer.buffer.push({
3643
- value: result.value,
3644
- context: result.context,
3645
- from: from,
3646
- indexed,
3647
- });
3720
+ else {
3721
+ const peerBuffer = peerBufferMap.get(peer);
3722
+ if (!peerBuffer) {
3723
+ return;
3648
3724
  }
3649
- else {
3650
- const indexedResult = result;
3651
- if (visited.has(indexKey) &&
3652
- !indexedPlaceholders?.has(indexKey) &&
3653
- !evictStaleBuffered(indexKey, indexedResult.context)) {
3654
- continue;
3725
+ peerBuffer.kept = Number(response.response.kept);
3726
+ for (const result of response.response.results) {
3727
+ const indexKey = indexerTypes.toId(this.indexByResolver(result.value)).primitive;
3728
+ if (isResultValue(result)) {
3729
+ const existingIndexed = indexedPlaceholders?.get(indexKey);
3730
+ if (existingIndexed) {
3731
+ existingIndexed.value =
3732
+ result.value;
3733
+ existingIndexed.context = result.context;
3734
+ existingIndexed.from = from;
3735
+ existingIndexed.indexed =
3736
+ await this.resolveIndexed(result, response.response
3737
+ .results);
3738
+ indexedPlaceholders?.delete(indexKey);
3739
+ continue;
3740
+ }
3741
+ if (visited.has(indexKey) &&
3742
+ !evictStaleBuffered(indexKey, result.context)) {
3743
+ continue;
3744
+ }
3745
+ visited.add(indexKey);
3746
+ const indexed = await this.resolveIndexed(result, response.response
3747
+ .results);
3748
+ peerBuffer.buffer.push({
3749
+ value: result.value,
3750
+ context: result.context,
3751
+ from: from,
3752
+ indexed,
3753
+ });
3754
+ }
3755
+ else {
3756
+ const indexedResult = result;
3757
+ if (visited.has(indexKey) &&
3758
+ !indexedPlaceholders?.has(indexKey) &&
3759
+ !evictStaleBuffered(indexKey, indexedResult.context)) {
3760
+ continue;
3761
+ }
3762
+ visited.add(indexKey);
3763
+ const indexed = coerceWithContext(indexedResult.value, indexedResult.context);
3764
+ const placeholder = {
3765
+ value: indexedResult.value,
3766
+ context: indexedResult.context,
3767
+ from: from,
3768
+ indexed,
3769
+ };
3770
+ peerBuffer.buffer.push(placeholder);
3771
+ ensureIndexedPlaceholders().set(indexKey, placeholder);
3655
3772
  }
3656
- visited.add(indexKey);
3657
- const indexed = coerceWithContext(indexedResult.value, indexedResult.context);
3658
- const placeholder = {
3659
- value: indexedResult.value,
3660
- context: indexedResult.context,
3661
- from: from,
3662
- indexed,
3663
- };
3664
- peerBuffer.buffer.push(placeholder);
3665
- ensureIndexedPlaceholders().set(indexKey, placeholder);
3666
3773
  }
3667
3774
  }
3668
- }
3669
- }));
3670
- })
3671
- .catch((e) => {
3672
- logger.error("Failed to collect sorted results from: " +
3673
- peer +
3674
- ". " +
3675
- e?.message);
3676
- peerBufferMap.delete(peer);
3677
- })));
3775
+ }));
3776
+ })
3777
+ .catch((e) => {
3778
+ logger.error("Failed to collect sorted results from: " +
3779
+ peer +
3780
+ ". " +
3781
+ e?.message);
3782
+ peerBufferMap.delete(peer);
3783
+ });
3784
+ }));
3678
3785
  }
3679
3786
  }
3680
3787
  else {
@@ -3697,7 +3804,7 @@ let DocumentIndex = (() => {
3697
3804
  }
3698
3805
  }
3699
3806
  }
3700
- return resultsLeft === 0; // 0 results left to fetch and 0 pending results
3807
+ return (resultsLeft === 0 && pendingMissingResponseRetryPeers.size === 0); // 0 results left to fetch and 0 pending results
3701
3808
  }));
3702
3809
  };
3703
3810
  const next = async (n) => {
@@ -3779,35 +3886,83 @@ let DocumentIndex = (() => {
3779
3886
  this.processCloseIteratorRequest(queryRequestCoerced, this.node.identity.publicKey);
3780
3887
  done = true;
3781
3888
  };
3782
- let close = async () => {
3889
+ const outerSignal = options?.signal;
3890
+ let outerAbortListener;
3891
+ let closeStarted = false;
3892
+ let closePromise;
3893
+ const performClose = async () => {
3894
+ const idTranslation = this._prefetch?.accumulator.getTranslationMap(queryRequestCoerced);
3895
+ const remoteIteratorIds = idTranslation
3896
+ ? new Map(idTranslation)
3897
+ : undefined;
3783
3898
  cleanupAndDone();
3784
3899
  // Keep-open iterators can still have active remote state even when
3785
3900
  // their pending count has already drained to zero.
3786
- const closeRequest = new types.CloseIteratorRequest({
3787
- id: queryRequestCoerced.id,
3788
- });
3789
3901
  const selfHash = this.node.identity.publicKey.hashcode();
3790
- const remotePeers = keepRemoteAlive
3902
+ const activeRemotePeers = new Set(keepRemoteAlive
3791
3903
  ? [...peerBufferMap.keys()].filter((peer) => peer !== selfHash)
3792
3904
  : [...peerBufferMap.entries()]
3793
3905
  .filter(([peer, buffer]) => peer !== selfHash && buffer.kept > 0)
3794
- .map(([peer]) => peer);
3906
+ .map(([peer]) => peer));
3907
+ for (const peer of remoteIteratorPeersToClose) {
3908
+ if (peer !== selfHash) {
3909
+ activeRemotePeers.add(peer);
3910
+ }
3911
+ }
3912
+ const staleRemoteIteratorIds = new Map([...staleRemoteIteratorIdsToClose].map(([peer, ids]) => [
3913
+ peer,
3914
+ [...ids],
3915
+ ]));
3916
+ const remotePeers = new Set([
3917
+ ...activeRemotePeers,
3918
+ ...staleRemoteIteratorIds.keys(),
3919
+ ]);
3795
3920
  peerBufferMap.clear();
3796
- await Promise.allSettled(remotePeers.map((peer) => this._query.send(closeRequest, {
3797
- ...options,
3798
- priority: getRemoteQueryPriority(options?.remote),
3799
- mode: new SilentDelivery({ to: [peer], redundancy: 1 }),
3800
- })));
3921
+ retiredRemoteIteratorPeers.clear();
3922
+ remoteIteratorPeersToClose.clear();
3923
+ staleRemoteIteratorIdsToClose.clear();
3924
+ if (remotePeers.size === 0) {
3925
+ return;
3926
+ }
3927
+ const remoteCloseOptions = typeof options?.remote === "object" ? options.remote : undefined;
3928
+ const closeSignal = AbortSignal.timeout(CLOSE_ITERATOR_REQUEST_TIMEOUT);
3929
+ await Promise.allSettled([...remotePeers].flatMap((peer) => {
3930
+ const ids = [];
3931
+ if (activeRemotePeers.has(peer)) {
3932
+ ids.push(remoteIteratorIds?.get(peer) ?? queryRequestCoerced.id);
3933
+ }
3934
+ for (const staleRemoteIteratorId of staleRemoteIteratorIds.get(peer) ?? []) {
3935
+ if (!ids.some((id) => equals(id, staleRemoteIteratorId))) {
3936
+ ids.push(staleRemoteIteratorId);
3937
+ }
3938
+ }
3939
+ return ids.map((id) => {
3940
+ const closeRequest = new types.CloseIteratorRequest({ id });
3941
+ return this._query.send(closeRequest, {
3942
+ ...remoteCloseOptions,
3943
+ signal: closeSignal,
3944
+ priority: getRemoteQueryPriority(options?.remote),
3945
+ mode: new SilentDelivery({ to: [peer], redundancy: 1 }),
3946
+ });
3947
+ });
3948
+ }));
3949
+ };
3950
+ const close = () => {
3951
+ if (closeStarted)
3952
+ return closePromise;
3953
+ closeStarted = true;
3954
+ if (outerAbortListener) {
3955
+ outerSignal?.removeEventListener("abort", outerAbortListener);
3956
+ outerAbortListener = undefined;
3957
+ }
3958
+ closePromise = performClose();
3959
+ return closePromise;
3801
3960
  };
3802
- options?.signal && options.signal.addEventListener("abort", close);
3803
3961
  let doneFn = () => {
3804
3962
  return done;
3805
3963
  };
3806
3964
  let joinListener;
3807
3965
  let fetchedFirstForRemote = undefined;
3808
- const pendingMissingResponseRetryPeers = new Set();
3809
- const missingResponseRetryAttempts = new Map();
3810
- const maxMissingResponseRetryAttempts = 2;
3811
3966
  let joinFetchesInFlight = 0;
3812
3967
  let updateDeferred;
3813
3968
  const updateWaiters = new Set();
@@ -4460,6 +4615,15 @@ let DocumentIndex = (() => {
4460
4615
  }
4461
4616
  }
4462
4617
  };
4618
+ if (outerSignal) {
4619
+ outerAbortListener = () => {
4620
+ void close();
4621
+ };
4622
+ outerSignal.addEventListener("abort", outerAbortListener, { once: true });
4623
+ if (outerSignal.aborted) {
4624
+ void close();
4625
+ }
4626
+ }
4463
4627
  return {
4464
4628
  close,
4465
4629
  next,
@@ -4529,49 +4693,61 @@ let DocumentIndex = (() => {
4529
4693
  const drainBatchSize = replicate ? 1000 : 100;
4530
4694
  let result = [];
4531
4695
  let c = 0;
4532
- while (doneFn() !== true) {
4533
- let batch = await next(drainBatchSize);
4534
- c += batch.length;
4535
- if (c > WARNING_WHEN_ITERATING_FOR_MORE_THAN) {
4536
- warn("Iterating for more than " +
4537
- WARNING_WHEN_ITERATING_FOR_MORE_THAN +
4538
- " results");
4539
- }
4540
- if (batch.length > 0) {
4541
- result.push(...batch);
4542
- continue;
4696
+ try {
4697
+ while (doneFn() !== true) {
4698
+ let batch = await next(drainBatchSize);
4699
+ c += batch.length;
4700
+ if (c > WARNING_WHEN_ITERATING_FOR_MORE_THAN) {
4701
+ warn("Iterating for more than " +
4702
+ WARNING_WHEN_ITERATING_FOR_MORE_THAN +
4703
+ " results");
4704
+ }
4705
+ if (batch.length > 0) {
4706
+ result.push(...batch);
4707
+ continue;
4708
+ }
4709
+ await waitForUpdateAndResetDeferred();
4543
4710
  }
4544
- await waitForUpdateAndResetDeferred();
4711
+ return result;
4712
+ }
4713
+ finally {
4714
+ await close();
4545
4715
  }
4546
- cleanupAndDone();
4547
- return result;
4548
4716
  },
4549
4717
  first: async () => {
4550
- if (doneFn()) {
4551
- return undefined;
4718
+ try {
4719
+ if (doneFn()) {
4720
+ return undefined;
4721
+ }
4722
+ let batch = await next(1);
4723
+ return batch[0];
4724
+ }
4725
+ finally {
4726
+ await close();
4552
4727
  }
4553
- let batch = await next(1);
4554
- cleanupAndDone();
4555
- return batch[0];
4556
4728
  },
4557
4729
  [Symbol.asyncIterator]: async function* () {
4558
4730
  drain = true;
4559
4731
  const drainBatchSize = replicate ? 1000 : 100;
4560
4732
  let c = 0;
4561
- while (doneFn() !== true) {
4562
- const batch = await next(drainBatchSize);
4563
- c += batch.length;
4564
- if (c > WARNING_WHEN_ITERATING_FOR_MORE_THAN) {
4565
- warn("Iterating for more than " +
4566
- WARNING_WHEN_ITERATING_FOR_MORE_THAN +
4567
- " results");
4568
- }
4569
- for (const entry of batch) {
4570
- yield entry;
4733
+ try {
4734
+ while (doneFn() !== true) {
4735
+ const batch = await next(drainBatchSize);
4736
+ c += batch.length;
4737
+ if (c > WARNING_WHEN_ITERATING_FOR_MORE_THAN) {
4738
+ warn("Iterating for more than " +
4739
+ WARNING_WHEN_ITERATING_FOR_MORE_THAN +
4740
+ " results");
4741
+ }
4742
+ for (const entry of batch) {
4743
+ yield entry;
4744
+ }
4745
+ await waitForUpdateAndResetDeferred();
4571
4746
  }
4572
- await waitForUpdateAndResetDeferred();
4573
4747
  }
4574
- cleanupAndDone();
4748
+ finally {
4749
+ await close();
4750
+ }
4575
4751
  },
4576
4752
  };
4577
4753
  }