@peerbit/document 13.0.21 → 13.0.23
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.
- package/dist/src/search.d.ts.map +1 -1
- package/dist/src/search.js +206 -49
- package/dist/src/search.js.map +1 -1
- package/package.json +30 -21
- package/src/search.ts +250 -53
package/src/search.ts
CHANGED
|
@@ -2300,8 +2300,7 @@ export class DocumentIndex<
|
|
|
2300
2300
|
onPeer: (pk: PublicSignKey) => Promise<void> | void;
|
|
2301
2301
|
}): () => void {
|
|
2302
2302
|
const active = new Set<string>();
|
|
2303
|
-
const
|
|
2304
|
-
const pk = e.detail;
|
|
2303
|
+
const handlePeer = async (pk: PublicSignKey) => {
|
|
2305
2304
|
const hash = pk.hashcode();
|
|
2306
2305
|
if (hash === this.node.identity.publicKey.hashcode()) return;
|
|
2307
2306
|
if (params.signal?.aborted) return;
|
|
@@ -2323,8 +2322,35 @@ export class DocumentIndex<
|
|
|
2323
2322
|
}
|
|
2324
2323
|
};
|
|
2325
2324
|
|
|
2326
|
-
|
|
2327
|
-
|
|
2325
|
+
const onQueryJoin = (e: { detail: PublicSignKey }) => {
|
|
2326
|
+
void handlePeer(e.detail);
|
|
2327
|
+
};
|
|
2328
|
+
const onReplicatorEvent = (e: {
|
|
2329
|
+
detail: { publicKey: PublicSignKey };
|
|
2330
|
+
}) => {
|
|
2331
|
+
void handlePeer(e.detail.publicKey);
|
|
2332
|
+
};
|
|
2333
|
+
|
|
2334
|
+
this._query.events.addEventListener("join", onQueryJoin);
|
|
2335
|
+
this._log?.events?.addEventListener("replicator:join", onReplicatorEvent);
|
|
2336
|
+
this._log?.events?.addEventListener("replicator:mature", onReplicatorEvent);
|
|
2337
|
+
this._log?.events?.addEventListener("replication:change", onReplicatorEvent);
|
|
2338
|
+
|
|
2339
|
+
return () => {
|
|
2340
|
+
this._query.events.removeEventListener("join", onQueryJoin);
|
|
2341
|
+
this._log?.events?.removeEventListener(
|
|
2342
|
+
"replicator:join",
|
|
2343
|
+
onReplicatorEvent,
|
|
2344
|
+
);
|
|
2345
|
+
this._log?.events?.removeEventListener(
|
|
2346
|
+
"replicator:mature",
|
|
2347
|
+
onReplicatorEvent,
|
|
2348
|
+
);
|
|
2349
|
+
this._log?.events?.removeEventListener(
|
|
2350
|
+
"replication:change",
|
|
2351
|
+
onReplicatorEvent,
|
|
2352
|
+
);
|
|
2353
|
+
};
|
|
2328
2354
|
}
|
|
2329
2355
|
|
|
2330
2356
|
processCloseIteratorRequest(
|
|
@@ -2992,6 +3018,18 @@ export class DocumentIndex<
|
|
|
2992
3018
|
});
|
|
2993
3019
|
|
|
2994
3020
|
let fetchPromise: Promise<any> | undefined = undefined;
|
|
3021
|
+
let fetchesInFlight = 0;
|
|
3022
|
+
const trackFetch = <T>(promise: Promise<T>): Promise<T> => {
|
|
3023
|
+
fetchesInFlight++;
|
|
3024
|
+
return promise.finally(() => {
|
|
3025
|
+
fetchesInFlight--;
|
|
3026
|
+
});
|
|
3027
|
+
};
|
|
3028
|
+
const setFetchPromise = <T>(promise: Promise<T>): Promise<T> => {
|
|
3029
|
+
const tracked = trackFetch(promise);
|
|
3030
|
+
fetchPromise = tracked;
|
|
3031
|
+
return tracked;
|
|
3032
|
+
};
|
|
2995
3033
|
const peerBufferMap: Map<
|
|
2996
3034
|
string,
|
|
2997
3035
|
{
|
|
@@ -3378,19 +3416,19 @@ export class DocumentIndex<
|
|
|
3378
3416
|
|
|
3379
3417
|
if (!first) {
|
|
3380
3418
|
first = true;
|
|
3381
|
-
|
|
3382
|
-
return fetchPromise;
|
|
3419
|
+
return setFetchPromise(fetchFirst(n));
|
|
3383
3420
|
}
|
|
3384
3421
|
|
|
3385
3422
|
if (pendingMissingResponseRetryPeers.size > 0) {
|
|
3386
3423
|
const retryTargets = [...pendingMissingResponseRetryPeers];
|
|
3387
3424
|
pendingMissingResponseRetryPeers.clear();
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3425
|
+
return setFetchPromise(
|
|
3426
|
+
fetchFirst(n, {
|
|
3427
|
+
from: retryTargets,
|
|
3428
|
+
// retries for missing groups should not be suppressed by first-fetch dedupe
|
|
3429
|
+
fetchedFirstForRemote: undefined,
|
|
3430
|
+
}),
|
|
3431
|
+
);
|
|
3394
3432
|
}
|
|
3395
3433
|
|
|
3396
3434
|
const promises: Promise<any>[] = [];
|
|
@@ -3696,9 +3734,11 @@ export class DocumentIndex<
|
|
|
3696
3734
|
resultsLeft += peerBufferMap.get(peer)?.kept || 0;
|
|
3697
3735
|
}
|
|
3698
3736
|
}
|
|
3699
|
-
return (
|
|
3700
|
-
|
|
3701
|
-
|
|
3737
|
+
return setFetchPromise(
|
|
3738
|
+
Promise.all(promises).then(() => {
|
|
3739
|
+
return resultsLeft === 0; // 0 results left to fetch and 0 pending results
|
|
3740
|
+
}),
|
|
3741
|
+
);
|
|
3702
3742
|
};
|
|
3703
3743
|
|
|
3704
3744
|
const next = async (n: number) => {
|
|
@@ -3853,8 +3893,10 @@ export class DocumentIndex<
|
|
|
3853
3893
|
const pendingMissingResponseRetryPeers = new Set<string>();
|
|
3854
3894
|
const missingResponseRetryAttempts = new Map<string, number>();
|
|
3855
3895
|
const maxMissingResponseRetryAttempts = 2;
|
|
3896
|
+
let joinFetchesInFlight = 0;
|
|
3856
3897
|
|
|
3857
3898
|
let updateDeferred: ReturnType<typeof pDefer> | undefined;
|
|
3899
|
+
const updateWaiters = new Set<ReturnType<typeof pDefer<void>>>();
|
|
3858
3900
|
const onLateResultsQueue =
|
|
3859
3901
|
options?.outOfOrder?.mode === "queue" &&
|
|
3860
3902
|
typeof options?.outOfOrder?.handle === "function"
|
|
@@ -3970,9 +4012,20 @@ export class DocumentIndex<
|
|
|
3970
4012
|
runNotify(reason);
|
|
3971
4013
|
}
|
|
3972
4014
|
updateDeferred?.resolve();
|
|
4015
|
+
for (const waiter of updateWaiters) {
|
|
4016
|
+
waiter.resolve();
|
|
4017
|
+
}
|
|
4018
|
+
updateWaiters.clear();
|
|
3973
4019
|
};
|
|
3974
4020
|
const _waitForUpdate = () =>
|
|
3975
4021
|
updateDeferred ? updateDeferred.promise : Promise.resolve();
|
|
4022
|
+
const waitForAnyUpdate = () => {
|
|
4023
|
+
const waiter = pDefer<void>();
|
|
4024
|
+
updateWaiters.add(waiter);
|
|
4025
|
+
return waiter.promise.finally(() => {
|
|
4026
|
+
updateWaiters.delete(waiter);
|
|
4027
|
+
});
|
|
4028
|
+
};
|
|
3976
4029
|
|
|
3977
4030
|
// ---------------- Live updates wiring (sorted-only with optional filter) ----------------
|
|
3978
4031
|
const updateCallbacks = updateCallbacksRaw;
|
|
@@ -4435,11 +4488,143 @@ export class DocumentIndex<
|
|
|
4435
4488
|
const keepRemoteWaitOpen =
|
|
4436
4489
|
!!remoteConfig?.wait &&
|
|
4437
4490
|
remoteWaitBehavior === "keep-open";
|
|
4491
|
+
let fetchLateJoinPeers = async (
|
|
4492
|
+
_candidateHashes?: Iterable<string>,
|
|
4493
|
+
_candidateKeys?: Map<string, PublicSignKey>,
|
|
4494
|
+
) => false;
|
|
4438
4495
|
|
|
4439
4496
|
if (keepRemoteWaitOpen) {
|
|
4440
4497
|
// was used to account for missed results when a peer joins; omitted in this minimal handler
|
|
4441
4498
|
|
|
4442
4499
|
updateDeferred = pDefer<void>();
|
|
4500
|
+
const lateJoinFetchesInFlight = new Set<string>();
|
|
4501
|
+
|
|
4502
|
+
fetchLateJoinPeers = async (
|
|
4503
|
+
candidateHashes?: Iterable<string>,
|
|
4504
|
+
candidateKeys?: Map<string, PublicSignKey>,
|
|
4505
|
+
) => {
|
|
4506
|
+
if (totalFetchedCounter === 0) {
|
|
4507
|
+
return false;
|
|
4508
|
+
}
|
|
4509
|
+
|
|
4510
|
+
if (done) {
|
|
4511
|
+
unsetDone();
|
|
4512
|
+
}
|
|
4513
|
+
|
|
4514
|
+
const selfHash = this.node.identity.publicKey.hashcode();
|
|
4515
|
+
const knownCandidateKeys = candidateKeys
|
|
4516
|
+
? new Map(candidateKeys)
|
|
4517
|
+
: new Map<string, PublicSignKey>();
|
|
4518
|
+
const hashes = candidateHashes
|
|
4519
|
+
? [...candidateHashes]
|
|
4520
|
+
: [...(await this._log.getReplicators()).keys()];
|
|
4521
|
+
let missing = hashes.filter((hash) => {
|
|
4522
|
+
if (hash === selfHash) return false;
|
|
4523
|
+
if (peerBufferMap.has(hash)) return false;
|
|
4524
|
+
if (fetchedFirstForRemote!.has(hash)) return false;
|
|
4525
|
+
if (lateJoinFetchesInFlight.has(hash)) return false;
|
|
4526
|
+
return true;
|
|
4527
|
+
});
|
|
4528
|
+
if (missing.length === 0 && !candidateHashes) {
|
|
4529
|
+
const connectedPeers = (this.node.services.pubsub as any)?.peers as
|
|
4530
|
+
| Map<string, unknown>
|
|
4531
|
+
| undefined;
|
|
4532
|
+
if (connectedPeers?.size) {
|
|
4533
|
+
const connectedCandidates = [...connectedPeers.keys()].filter(
|
|
4534
|
+
(hash) =>
|
|
4535
|
+
hash !== selfHash &&
|
|
4536
|
+
!hashes.includes(hash) &&
|
|
4537
|
+
!peerBufferMap.has(hash) &&
|
|
4538
|
+
!fetchedFirstForRemote!.has(hash) &&
|
|
4539
|
+
!lateJoinFetchesInFlight.has(hash),
|
|
4540
|
+
);
|
|
4541
|
+
if (connectedCandidates.length > 0) {
|
|
4542
|
+
const discovered = await Promise.all(
|
|
4543
|
+
connectedCandidates.slice(0, 8).map(async (hash) => {
|
|
4544
|
+
const pk = await this.node.services.pubsub.getPublicKey(hash);
|
|
4545
|
+
if (!pk) {
|
|
4546
|
+
return undefined;
|
|
4547
|
+
}
|
|
4548
|
+
try {
|
|
4549
|
+
await this._log.waitForReplicator(pk, {
|
|
4550
|
+
signal: ensureController().signal,
|
|
4551
|
+
eager: true,
|
|
4552
|
+
timeout: 250,
|
|
4553
|
+
});
|
|
4554
|
+
knownCandidateKeys.set(hash, pk);
|
|
4555
|
+
return hash;
|
|
4556
|
+
} catch {
|
|
4557
|
+
return undefined;
|
|
4558
|
+
}
|
|
4559
|
+
}),
|
|
4560
|
+
);
|
|
4561
|
+
missing = discovered.filter((hash): hash is string => !!hash);
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4564
|
+
}
|
|
4565
|
+
if (missing.length === 0) {
|
|
4566
|
+
return false;
|
|
4567
|
+
}
|
|
4568
|
+
|
|
4569
|
+
missing.forEach((hash) => lateJoinFetchesInFlight.add(hash));
|
|
4570
|
+
joinFetchesInFlight += missing.length;
|
|
4571
|
+
|
|
4572
|
+
try {
|
|
4573
|
+
const unresolved = missing.filter((hash) => {
|
|
4574
|
+
if (peerBufferMap.has(hash)) return false;
|
|
4575
|
+
if (fetchedFirstForRemote!.has(hash)) return false;
|
|
4576
|
+
return true;
|
|
4577
|
+
});
|
|
4578
|
+
|
|
4579
|
+
if (unresolved.length === 0) {
|
|
4580
|
+
return false;
|
|
4581
|
+
}
|
|
4582
|
+
|
|
4583
|
+
const lateJoinFetchPromise = trackFetch(
|
|
4584
|
+
fetchFirst(totalFetchedCounter, {
|
|
4585
|
+
from: unresolved,
|
|
4586
|
+
fetchedFirstForRemote,
|
|
4587
|
+
}),
|
|
4588
|
+
);
|
|
4589
|
+
await lateJoinFetchPromise;
|
|
4590
|
+
for (const hash of unresolved) {
|
|
4591
|
+
if (!peerBufferMap.has(hash)) {
|
|
4592
|
+
fetchedFirstForRemote?.delete(hash);
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
|
|
4596
|
+
if (onLateResultsQueue || onLateResultsDrop) {
|
|
4597
|
+
for (const hash of unresolved) {
|
|
4598
|
+
const pending = peerBufferMap.get(hash)?.buffer;
|
|
4599
|
+
if (!pending || pending.length === 0) {
|
|
4600
|
+
continue;
|
|
4601
|
+
}
|
|
4602
|
+
|
|
4603
|
+
const peer = knownCandidateKeys.get(hash);
|
|
4604
|
+
if (lastDeliveredIndexed) {
|
|
4605
|
+
const delivered = lastDeliveredIndexed;
|
|
4606
|
+
const lateItems = pending.filter(
|
|
4607
|
+
(item) => compareIndexed(item.indexed, delivered) < 0,
|
|
4608
|
+
);
|
|
4609
|
+
if (lateItems.length > 0) {
|
|
4610
|
+
notifyLateResults?.(lateItems.length, peer, lateItems);
|
|
4611
|
+
}
|
|
4612
|
+
} else {
|
|
4613
|
+
notifyLateResults?.(pending.length, peer, pending);
|
|
4614
|
+
}
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
|
|
4618
|
+
if (!pendingBatchReason) {
|
|
4619
|
+
pendingBatchReason = "join";
|
|
4620
|
+
}
|
|
4621
|
+
signalUpdate("join");
|
|
4622
|
+
return true;
|
|
4623
|
+
} finally {
|
|
4624
|
+
missing.forEach((hash) => lateJoinFetchesInFlight.delete(hash));
|
|
4625
|
+
joinFetchesInFlight -= missing.length;
|
|
4626
|
+
}
|
|
4627
|
+
};
|
|
4443
4628
|
|
|
4444
4629
|
const waitForTime = remoteWaitPolicy?.timeout;
|
|
4445
4630
|
|
|
@@ -4462,36 +4647,7 @@ export class DocumentIndex<
|
|
|
4462
4647
|
onPeer: async (pk) => {
|
|
4463
4648
|
if (done) return;
|
|
4464
4649
|
const hash = pk.hashcode();
|
|
4465
|
-
await
|
|
4466
|
-
if (peerBufferMap.has(hash)) return;
|
|
4467
|
-
if (fetchedFirstForRemote!.has(hash)) return;
|
|
4468
|
-
if (totalFetchedCounter > 0) {
|
|
4469
|
-
fetchPromise = fetchFirst(totalFetchedCounter, {
|
|
4470
|
-
from: [hash],
|
|
4471
|
-
fetchedFirstForRemote,
|
|
4472
|
-
});
|
|
4473
|
-
await fetchPromise;
|
|
4474
|
-
if (onLateResultsQueue || onLateResultsDrop) {
|
|
4475
|
-
const pending = peerBufferMap.get(hash)?.buffer;
|
|
4476
|
-
if (pending && pending.length > 0) {
|
|
4477
|
-
if (lastDeliveredIndexed) {
|
|
4478
|
-
const delivered = lastDeliveredIndexed;
|
|
4479
|
-
const lateItems = pending.filter(
|
|
4480
|
-
(item) => compareIndexed(item.indexed, delivered) < 0,
|
|
4481
|
-
);
|
|
4482
|
-
if (lateItems.length > 0) {
|
|
4483
|
-
notifyLateResults?.(lateItems.length, pk, lateItems);
|
|
4484
|
-
}
|
|
4485
|
-
} else {
|
|
4486
|
-
notifyLateResults?.(pending.length, pk, pending);
|
|
4487
|
-
}
|
|
4488
|
-
}
|
|
4489
|
-
}
|
|
4490
|
-
}
|
|
4491
|
-
if (!pendingBatchReason) {
|
|
4492
|
-
pendingBatchReason = "join";
|
|
4493
|
-
}
|
|
4494
|
-
signalUpdate("join");
|
|
4650
|
+
await fetchLateJoinPeers([hash], new Map([[hash, pk]]));
|
|
4495
4651
|
},
|
|
4496
4652
|
});
|
|
4497
4653
|
const cleanupDefault = cleanup;
|
|
@@ -4531,27 +4687,68 @@ export class DocumentIndex<
|
|
|
4531
4687
|
next,
|
|
4532
4688
|
done: doneFn,
|
|
4533
4689
|
pending: async () => {
|
|
4690
|
+
const countPending = () => {
|
|
4691
|
+
let total = 0;
|
|
4692
|
+
for (const buffer of peerBufferMap.values()) {
|
|
4693
|
+
total += buffer.kept + buffer.buffer.length;
|
|
4694
|
+
}
|
|
4695
|
+
return total;
|
|
4696
|
+
};
|
|
4697
|
+
|
|
4534
4698
|
try {
|
|
4699
|
+
let pendingTotal = countPending();
|
|
4700
|
+
if (remoteWaitActive && first) {
|
|
4701
|
+
if (pendingTotal === 0) {
|
|
4702
|
+
await fetchLateJoinPeers();
|
|
4703
|
+
pendingTotal = countPending();
|
|
4704
|
+
}
|
|
4705
|
+
|
|
4706
|
+
const shouldPrimePending =
|
|
4707
|
+
!done &&
|
|
4708
|
+
keepRemoteAlive &&
|
|
4709
|
+
(!pushUpdates || !first) &&
|
|
4710
|
+
pendingTotal === 0 &&
|
|
4711
|
+
joinFetchesInFlight === 0;
|
|
4712
|
+
if (shouldPrimePending && fetchesInFlight === 0) {
|
|
4713
|
+
const primePending = fetchAtLeast(1).catch((error) => {
|
|
4714
|
+
warn("Failed to prime keep-open iterator pending state", error);
|
|
4715
|
+
});
|
|
4716
|
+
if (remoteWaitActive) {
|
|
4717
|
+
await Promise.race([primePending, waitForAnyUpdate()]);
|
|
4718
|
+
} else {
|
|
4719
|
+
await primePending;
|
|
4720
|
+
}
|
|
4721
|
+
}
|
|
4722
|
+
return countPending();
|
|
4723
|
+
}
|
|
4724
|
+
|
|
4535
4725
|
await fetchPromise;
|
|
4726
|
+
pendingTotal = countPending();
|
|
4536
4727
|
// In push-update mode, remotes will stream new results proactively.
|
|
4537
4728
|
// After the iterator has been primed (`first === true`), calling
|
|
4538
4729
|
// `fetchAtLeast(1)` from `pending()` can double-count by pulling from
|
|
4539
4730
|
// the remote iterator while we also have pushed results buffered locally.
|
|
4540
4731
|
//
|
|
4541
|
-
//
|
|
4542
|
-
//
|
|
4543
|
-
|
|
4732
|
+
// In keep-open remote-wait mode, we also avoid starting another remote
|
|
4733
|
+
// collect while a late-join fetch is already in flight or when we already
|
|
4734
|
+
// have buffered results to report. This keeps `pending()` observational
|
|
4735
|
+
// enough to avoid starving late joins behind unrelated long-poll collects,
|
|
4736
|
+
// while preserving the existing "pull one more" behavior when there is
|
|
4737
|
+
// nothing buffered yet.
|
|
4738
|
+
const shouldPrimePending =
|
|
4739
|
+
!done &&
|
|
4740
|
+
keepRemoteAlive &&
|
|
4741
|
+
(!pushUpdates || !first) &&
|
|
4742
|
+
pendingTotal === 0 &&
|
|
4743
|
+
!(remoteWaitActive && first && joinFetchesInFlight > 0);
|
|
4744
|
+
if (shouldPrimePending) {
|
|
4544
4745
|
await fetchAtLeast(1);
|
|
4545
4746
|
}
|
|
4546
4747
|
} catch (error) {
|
|
4547
4748
|
warn("Failed to refresh iterator pending state", error);
|
|
4548
4749
|
}
|
|
4549
4750
|
|
|
4550
|
-
|
|
4551
|
-
for (const buffer of peerBufferMap.values()) {
|
|
4552
|
-
total += buffer.kept + buffer.buffer.length;
|
|
4553
|
-
}
|
|
4554
|
-
return total;
|
|
4751
|
+
return countPending();
|
|
4555
4752
|
},
|
|
4556
4753
|
all: async () => {
|
|
4557
4754
|
drain = true;
|