@peerbit/document 9.4.2 → 9.4.3-fb47029
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/most-common-query-predictor.d.ts +38 -0
- package/dist/src/most-common-query-predictor.d.ts.map +1 -0
- package/dist/src/most-common-query-predictor.js +115 -0
- package/dist/src/most-common-query-predictor.js.map +1 -0
- package/dist/src/prefetch.d.ts +22 -0
- package/dist/src/prefetch.d.ts.map +1 -0
- package/dist/src/prefetch.js +47 -0
- package/dist/src/prefetch.js.map +1 -0
- package/dist/src/program.d.ts +7 -2
- package/dist/src/program.d.ts.map +1 -1
- package/dist/src/program.js +2 -1
- package/dist/src/program.js.map +1 -1
- package/dist/src/resumable-iterator.d.ts +1 -1
- package/dist/src/resumable-iterator.d.ts.map +1 -1
- package/dist/src/resumable-iterator.js +10 -2
- package/dist/src/resumable-iterator.js.map +1 -1
- package/dist/src/search.d.ts +24 -3
- package/dist/src/search.d.ts.map +1 -1
- package/dist/src/search.js +209 -46
- package/dist/src/search.js.map +1 -1
- package/package.json +75 -74
- package/src/most-common-query-predictor.ts +161 -0
- package/src/operation.ts +9 -9
- package/src/prefetch.ts +80 -0
- package/src/program.ts +9 -2
- package/src/resumable-iterator.ts +10 -2
- package/src/search.ts +343 -68
package/src/search.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
sha256Base64Sync,
|
|
9
9
|
} from "@peerbit/crypto";
|
|
10
10
|
import * as types from "@peerbit/document-interface";
|
|
11
|
+
import { CachedIndex, type QueryCacheOptions } from "@peerbit/indexer-cache";
|
|
11
12
|
import * as indexerTypes from "@peerbit/indexer-interface";
|
|
12
13
|
import { HashmapIndex } from "@peerbit/indexer-simple";
|
|
13
14
|
import { BORSH_ENCODING, type Encoding, Entry } from "@peerbit/log";
|
|
@@ -25,12 +26,15 @@ import {
|
|
|
25
26
|
type ReplicationDomain,
|
|
26
27
|
SharedLog,
|
|
27
28
|
} from "@peerbit/shared-log";
|
|
28
|
-
import { SilentDelivery } from "@peerbit/stream-interface";
|
|
29
|
+
import { DataMessage, SilentDelivery } from "@peerbit/stream-interface";
|
|
29
30
|
import { AbortError, waitFor } from "@peerbit/time";
|
|
30
31
|
import { concat, fromString } from "uint8arrays";
|
|
31
32
|
import { copySerialization } from "./borsh.js";
|
|
32
33
|
import { MAX_BATCH_SIZE } from "./constants.js";
|
|
34
|
+
import type { QueryPredictor } from "./most-common-query-predictor.js";
|
|
35
|
+
import MostCommonQueryPredictor from "./most-common-query-predictor.js";
|
|
33
36
|
import { type Operation, isPutOperation } from "./operation.js";
|
|
37
|
+
import { Prefetch } from "./prefetch.js";
|
|
34
38
|
import type { ExtractArgs } from "./program.js";
|
|
35
39
|
import { ResumableIterators } from "./resumable-iterator.js";
|
|
36
40
|
|
|
@@ -43,7 +47,7 @@ type BufferedResult<T, I extends Record<string, any>> = {
|
|
|
43
47
|
from: PublicSignKey;
|
|
44
48
|
};
|
|
45
49
|
|
|
46
|
-
export type RemoteQueryOptions<R, D> = RPCRequestAllOptions<R> & {
|
|
50
|
+
export type RemoteQueryOptions<Q, R, D> = RPCRequestAllOptions<Q, R> & {
|
|
47
51
|
replicate?: boolean;
|
|
48
52
|
minAge?: number;
|
|
49
53
|
throwOnMissing?: boolean;
|
|
@@ -58,7 +62,13 @@ export type RemoteQueryOptions<R, D> = RPCRequestAllOptions<R> & {
|
|
|
58
62
|
eager?: boolean; // whether to query newly joined peers before they have matured
|
|
59
63
|
};
|
|
60
64
|
export type QueryOptions<R, D, Resolve extends boolean | undefined> = {
|
|
61
|
-
remote?:
|
|
65
|
+
remote?:
|
|
66
|
+
| boolean
|
|
67
|
+
| RemoteQueryOptions<
|
|
68
|
+
types.AbstractSearchRequest,
|
|
69
|
+
types.AbstractSearchResult,
|
|
70
|
+
D
|
|
71
|
+
>;
|
|
62
72
|
local?: boolean;
|
|
63
73
|
resolve?: Resolve;
|
|
64
74
|
};
|
|
@@ -141,7 +151,7 @@ const introduceEntries = async <
|
|
|
141
151
|
R extends types.SearchRequest | types.SearchRequestIndexed,
|
|
142
152
|
>(
|
|
143
153
|
queryRequest: R,
|
|
144
|
-
responses:
|
|
154
|
+
responses: { response: types.AbstractSearchResult; from?: PublicSignKey }[],
|
|
145
155
|
documentType: AbstractType<T>,
|
|
146
156
|
indexedType: AbstractType<I>,
|
|
147
157
|
sync: (
|
|
@@ -259,6 +269,19 @@ const isTransformerWithFunction = <T, I>(
|
|
|
259
269
|
return (options as TransformerAsFunction<T, I>).transform != null;
|
|
260
270
|
};
|
|
261
271
|
|
|
272
|
+
export type PrefetchOptions = {
|
|
273
|
+
predictor?: QueryPredictor;
|
|
274
|
+
ttl: number;
|
|
275
|
+
accumulator: Prefetch;
|
|
276
|
+
|
|
277
|
+
/* When `true` we assume every peer supports prefetch routing,
|
|
278
|
+
* so it is safe to drop SearchRequests that the predictor marks
|
|
279
|
+
* as `ignore === true`.
|
|
280
|
+
*
|
|
281
|
+
* Default: `false` – be conservative.
|
|
282
|
+
*/
|
|
283
|
+
strict?: boolean;
|
|
284
|
+
};
|
|
262
285
|
export type OpenOptions<
|
|
263
286
|
T,
|
|
264
287
|
I,
|
|
@@ -281,9 +304,13 @@ export type OpenOptions<
|
|
|
281
304
|
) => Promise<void>;
|
|
282
305
|
indexBy?: string | string[];
|
|
283
306
|
transform?: TransformOptions<T, I>;
|
|
284
|
-
|
|
307
|
+
cache?: {
|
|
308
|
+
resolver?: number;
|
|
309
|
+
query?: QueryCacheOptions;
|
|
310
|
+
};
|
|
285
311
|
compatibility: 6 | 7 | 8 | undefined;
|
|
286
312
|
maybeOpen: (value: T & Program) => Promise<T & Program>;
|
|
313
|
+
prefetch?: boolean | Partial<PrefetchOptions>;
|
|
287
314
|
};
|
|
288
315
|
|
|
289
316
|
type IndexableClass<I> = new (
|
|
@@ -332,6 +359,7 @@ export class DocumentIndex<
|
|
|
332
359
|
private indexByResolver: (obj: any) => string | Uint8Array;
|
|
333
360
|
index: indexerTypes.Index<WithContext<I>>;
|
|
334
361
|
private _resumableIterators: ResumableIterators<WithContext<I>>;
|
|
362
|
+
private _prefetch?: PrefetchOptions | undefined;
|
|
335
363
|
|
|
336
364
|
compatibility: 6 | 7 | 8 | undefined;
|
|
337
365
|
|
|
@@ -351,6 +379,9 @@ export class DocumentIndex<
|
|
|
351
379
|
private _resolverCache?: Cache<T>;
|
|
352
380
|
private isProgramValued: boolean;
|
|
353
381
|
private _maybeOpen: (value: T & Program) => Promise<T & Program>;
|
|
382
|
+
private canSearch?: CanSearch;
|
|
383
|
+
private canRead?: CanRead<I>;
|
|
384
|
+
private _joinListener?: (e: { detail: PublicSignKey }) => Promise<void>;
|
|
354
385
|
|
|
355
386
|
private _resultQueue: Map<
|
|
356
387
|
string,
|
|
@@ -386,6 +417,21 @@ export class DocumentIndex<
|
|
|
386
417
|
}
|
|
387
418
|
async open(properties: OpenOptions<T, I, D>) {
|
|
388
419
|
this._log = properties.log;
|
|
420
|
+
let prefectOptions =
|
|
421
|
+
typeof properties.prefetch === "object"
|
|
422
|
+
? properties.prefetch
|
|
423
|
+
: properties.prefetch
|
|
424
|
+
? {}
|
|
425
|
+
: undefined;
|
|
426
|
+
this._prefetch = prefectOptions
|
|
427
|
+
? {
|
|
428
|
+
...prefectOptions,
|
|
429
|
+
predictor:
|
|
430
|
+
prefectOptions.predictor || new MostCommonQueryPredictor(3),
|
|
431
|
+
ttl: prefectOptions.ttl ?? 5e3,
|
|
432
|
+
accumulator: prefectOptions.accumulator || new Prefetch(),
|
|
433
|
+
}
|
|
434
|
+
: undefined;
|
|
389
435
|
|
|
390
436
|
this.documentType = properties.documentType;
|
|
391
437
|
this.indexedTypeIsDocumentType =
|
|
@@ -393,6 +439,8 @@ export class DocumentIndex<
|
|
|
393
439
|
properties.transform?.type === properties.documentType;
|
|
394
440
|
|
|
395
441
|
this.compatibility = properties.compatibility;
|
|
442
|
+
this.canRead = properties.canRead;
|
|
443
|
+
this.canSearch = properties.canSearch;
|
|
396
444
|
|
|
397
445
|
@variant(0)
|
|
398
446
|
class IndexedClassWithContext {
|
|
@@ -418,7 +466,30 @@ export class DocumentIndex<
|
|
|
418
466
|
this.isProgramValued = isSubclassOf(this.documentType, Program);
|
|
419
467
|
this.dbType = properties.dbType;
|
|
420
468
|
this._resultQueue = new Map();
|
|
421
|
-
this._sync = (request, results) =>
|
|
469
|
+
this._sync = (request, results) => {
|
|
470
|
+
let rq: types.SearchRequest | types.SearchRequestIndexed;
|
|
471
|
+
let rs: types.Results<
|
|
472
|
+
types.ResultTypeFromRequest<
|
|
473
|
+
types.SearchRequest | types.SearchRequestIndexed,
|
|
474
|
+
T,
|
|
475
|
+
I
|
|
476
|
+
>
|
|
477
|
+
>;
|
|
478
|
+
if (request instanceof types.PredictedSearchRequest) {
|
|
479
|
+
rq = request.request;
|
|
480
|
+
rs = request.results;
|
|
481
|
+
} else {
|
|
482
|
+
rq = request;
|
|
483
|
+
rs = results as types.Results<
|
|
484
|
+
types.ResultTypeFromRequest<
|
|
485
|
+
types.SearchRequest | types.SearchRequestIndexed,
|
|
486
|
+
T,
|
|
487
|
+
I
|
|
488
|
+
>
|
|
489
|
+
>;
|
|
490
|
+
}
|
|
491
|
+
return properties.replicate(rq, rs);
|
|
492
|
+
};
|
|
422
493
|
|
|
423
494
|
const transformOptions = properties.transform;
|
|
424
495
|
this.transformer = transformOptions
|
|
@@ -437,9 +508,9 @@ export class DocumentIndex<
|
|
|
437
508
|
this._valueEncoding = BORSH_ENCODING(this.documentType);
|
|
438
509
|
|
|
439
510
|
this._resolverCache =
|
|
440
|
-
properties.
|
|
511
|
+
properties.cache?.resolver === 0
|
|
441
512
|
? undefined
|
|
442
|
-
: new Cache({ max: properties.
|
|
513
|
+
: new Cache({ max: properties.cache?.resolver ?? 100 }); // TODO choose limit better by default (adaptive)
|
|
443
514
|
|
|
444
515
|
this.index =
|
|
445
516
|
(await (
|
|
@@ -455,6 +526,10 @@ export class DocumentIndex<
|
|
|
455
526
|
/* maxBatchSize: MAX_BATCH_SIZE */
|
|
456
527
|
})) || new HashmapIndex<WithContext<I>>();
|
|
457
528
|
|
|
529
|
+
if (properties.cache?.query) {
|
|
530
|
+
this.index = new CachedIndex(this.index, properties.cache.query);
|
|
531
|
+
}
|
|
532
|
+
|
|
458
533
|
this._resumableIterators = new ResumableIterators(this.index);
|
|
459
534
|
this._maybeOpen = properties.maybeOpen;
|
|
460
535
|
if (this.isProgramValued) {
|
|
@@ -465,49 +540,103 @@ export class DocumentIndex<
|
|
|
465
540
|
topic: sha256Base64Sync(
|
|
466
541
|
concat([this._log.log.id, fromString("/document")]),
|
|
467
542
|
),
|
|
468
|
-
responseHandler:
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
543
|
+
responseHandler: this.queryRPCResponseHandler.bind(this),
|
|
544
|
+
responseType: types.AbstractSearchResult,
|
|
545
|
+
queryType: types.AbstractSearchRequest,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
473
548
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
query instanceof types.CollectNextRequest) &&
|
|
478
|
-
!(await properties.canSearch(
|
|
479
|
-
query as types.SearchRequest | types.CollectNextRequest,
|
|
480
|
-
ctx.from,
|
|
481
|
-
))
|
|
482
|
-
) {
|
|
483
|
-
return new types.NoAccess();
|
|
484
|
-
}
|
|
549
|
+
get prefetch() {
|
|
550
|
+
return this._prefetch;
|
|
551
|
+
}
|
|
485
552
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
553
|
+
private async queryRPCResponseHandler(
|
|
554
|
+
query: types.AbstractSearchRequest,
|
|
555
|
+
ctx: { from?: PublicSignKey; message: DataMessage },
|
|
556
|
+
) {
|
|
557
|
+
if (!ctx.from) {
|
|
558
|
+
logger.info("Receieved query without from");
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (query instanceof types.PredictedSearchRequest) {
|
|
562
|
+
// put results in a waiting cache so that we eventually in the future will query a matching thing, we already have results available
|
|
563
|
+
this._prefetch?.accumulator.add(
|
|
564
|
+
{
|
|
565
|
+
message: ctx.message,
|
|
566
|
+
response: query,
|
|
567
|
+
from: ctx.from,
|
|
568
|
+
},
|
|
569
|
+
ctx.from!.hashcode(),
|
|
570
|
+
);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
500
573
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
574
|
+
if (
|
|
575
|
+
this.prefetch?.predictor &&
|
|
576
|
+
(query instanceof types.SearchRequest ||
|
|
577
|
+
query instanceof types.SearchRequestIndexed)
|
|
578
|
+
) {
|
|
579
|
+
const { ignore } = this.prefetch.predictor.onRequest(query, {
|
|
580
|
+
from: ctx.from!,
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
if (ignore) {
|
|
584
|
+
if (this.prefetch.strict) {
|
|
585
|
+
return;
|
|
506
586
|
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return this.handleSearchRequest(
|
|
591
|
+
query as
|
|
592
|
+
| types.SearchRequest
|
|
593
|
+
| types.SearchRequestIndexed
|
|
594
|
+
| types.CollectNextRequest,
|
|
595
|
+
{
|
|
596
|
+
from: ctx.from!,
|
|
507
597
|
},
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
private async handleSearchRequest(
|
|
601
|
+
query:
|
|
602
|
+
| types.SearchRequest
|
|
603
|
+
| types.SearchRequestIndexed
|
|
604
|
+
| types.CollectNextRequest,
|
|
605
|
+
ctx: { from: PublicSignKey },
|
|
606
|
+
) {
|
|
607
|
+
if (
|
|
608
|
+
this.canSearch &&
|
|
609
|
+
(query instanceof types.SearchRequest ||
|
|
610
|
+
query instanceof types.CollectNextRequest) &&
|
|
611
|
+
!(await this.canSearch(
|
|
612
|
+
query as types.SearchRequest | types.CollectNextRequest,
|
|
613
|
+
ctx.from,
|
|
614
|
+
))
|
|
615
|
+
) {
|
|
616
|
+
return new types.NoAccess();
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (query instanceof types.CloseIteratorRequest) {
|
|
620
|
+
this.processCloseIteratorRequest(query, ctx.from);
|
|
621
|
+
} else {
|
|
622
|
+
const results = await this.processQuery(
|
|
623
|
+
query as
|
|
624
|
+
| types.SearchRequest
|
|
625
|
+
| types.SearchRequestIndexed
|
|
626
|
+
| types.CollectNextRequest,
|
|
627
|
+
ctx.from,
|
|
628
|
+
false,
|
|
629
|
+
{
|
|
630
|
+
canRead: this.canRead,
|
|
631
|
+
},
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
return new types.Results({
|
|
635
|
+
// Even if results might have length 0, respond, because then we now at least there are no matching results
|
|
636
|
+
results: results.results,
|
|
637
|
+
kept: results.kept,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
511
640
|
}
|
|
512
641
|
|
|
513
642
|
async afterOpen(): Promise<void> {
|
|
@@ -531,6 +660,35 @@ export class DocumentIndex<
|
|
|
531
660
|
this._resolverProgramCache!.set(id.primitive, programValue.value as T);
|
|
532
661
|
}
|
|
533
662
|
}
|
|
663
|
+
|
|
664
|
+
if (this.prefetch?.predictor) {
|
|
665
|
+
const predictor = this.prefetch.predictor;
|
|
666
|
+
this._joinListener = async (e: { detail: PublicSignKey }) => {
|
|
667
|
+
// create an iterator and send the peer the results
|
|
668
|
+
let request = predictor.predictedQuery(e.detail);
|
|
669
|
+
|
|
670
|
+
if (!request) {
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const results = await this.handleSearchRequest(request, {
|
|
674
|
+
from: e.detail,
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
if (results instanceof types.Results) {
|
|
678
|
+
// start a resumable iterator for the peer
|
|
679
|
+
const query = new types.PredictedSearchRequest({
|
|
680
|
+
id: request.id,
|
|
681
|
+
request,
|
|
682
|
+
results,
|
|
683
|
+
});
|
|
684
|
+
await this._query.send(query, {
|
|
685
|
+
mode: new SilentDelivery({ to: [e.detail], redundancy: 1 }),
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
this._query.events.addEventListener("join", this._joinListener);
|
|
690
|
+
}
|
|
691
|
+
|
|
534
692
|
return super.afterOpen();
|
|
535
693
|
}
|
|
536
694
|
async getPending(cursorId: string): Promise<number | undefined> {
|
|
@@ -545,6 +703,7 @@ export class DocumentIndex<
|
|
|
545
703
|
async close(from?: Program): Promise<boolean> {
|
|
546
704
|
const closed = await super.close(from);
|
|
547
705
|
if (closed) {
|
|
706
|
+
this._query.events.removeEventListener("join", this._joinListener);
|
|
548
707
|
this.clearAllResultQueues();
|
|
549
708
|
await this.index.stop?.();
|
|
550
709
|
}
|
|
@@ -660,15 +819,13 @@ export class DocumentIndex<
|
|
|
660
819
|
): Promise<types.Results<RT>[] | undefined> {
|
|
661
820
|
let coercedOptions = options;
|
|
662
821
|
if (options?.remote && typeof options.remote !== "boolean") {
|
|
663
|
-
{
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
remote
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
};
|
|
671
|
-
}
|
|
822
|
+
coercedOptions = {
|
|
823
|
+
...options,
|
|
824
|
+
remote: {
|
|
825
|
+
...options.remote,
|
|
826
|
+
strategy: options.remote?.strategy ?? "fallback",
|
|
827
|
+
},
|
|
828
|
+
};
|
|
672
829
|
} else if (options?.remote === undefined) {
|
|
673
830
|
coercedOptions = {
|
|
674
831
|
...options,
|
|
@@ -990,6 +1147,10 @@ export class DocumentIndex<
|
|
|
990
1147
|
}
|
|
991
1148
|
}
|
|
992
1149
|
|
|
1150
|
+
get countIteratorsInProgress() {
|
|
1151
|
+
return this._resumableIterators.queues.size;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
993
1154
|
private clearAllResultQueues() {
|
|
994
1155
|
for (const [key, queue] of this._resultQueue) {
|
|
995
1156
|
clearTimeout(queue.timeout);
|
|
@@ -1025,8 +1186,13 @@ export class DocumentIndex<
|
|
|
1025
1186
|
options?: QueryDetailedOptions<T, D, boolean | undefined>,
|
|
1026
1187
|
): Promise<types.Results<RT>[]> {
|
|
1027
1188
|
const local = typeof options?.local === "boolean" ? options?.local : true;
|
|
1028
|
-
let remote:
|
|
1029
|
-
|
|
1189
|
+
let remote:
|
|
1190
|
+
| RemoteQueryOptions<
|
|
1191
|
+
types.AbstractSearchRequest,
|
|
1192
|
+
types.AbstractSearchResult,
|
|
1193
|
+
D
|
|
1194
|
+
>
|
|
1195
|
+
| undefined = undefined;
|
|
1030
1196
|
if (typeof options?.remote === "boolean") {
|
|
1031
1197
|
if (options?.remote) {
|
|
1032
1198
|
remote = {};
|
|
@@ -1066,6 +1232,11 @@ export class DocumentIndex<
|
|
|
1066
1232
|
|
|
1067
1233
|
let resolved: types.Results<types.ResultTypeFromRequest<R, T, I>>[] = [];
|
|
1068
1234
|
if (remote && (remote.strategy !== "fallback" || allResults.length === 0)) {
|
|
1235
|
+
if (queryRequest instanceof types.CloseIteratorRequest) {
|
|
1236
|
+
// don't wait for responses
|
|
1237
|
+
throw new Error("Unexpected");
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1069
1240
|
const replicatorGroups = await this._log.getCover(
|
|
1070
1241
|
remote.domain ?? { args: undefined },
|
|
1071
1242
|
{
|
|
@@ -1075,9 +1246,11 @@ export class DocumentIndex<
|
|
|
1075
1246
|
);
|
|
1076
1247
|
|
|
1077
1248
|
if (replicatorGroups) {
|
|
1078
|
-
const groupHashes: string[][] = replicatorGroups.map((x) => [x]);
|
|
1079
1249
|
const responseHandler = async (
|
|
1080
|
-
results:
|
|
1250
|
+
results: {
|
|
1251
|
+
response: types.AbstractSearchResult;
|
|
1252
|
+
from?: PublicSignKey;
|
|
1253
|
+
}[],
|
|
1081
1254
|
) => {
|
|
1082
1255
|
const resultInitialized = await introduceEntries(
|
|
1083
1256
|
queryRequest,
|
|
@@ -1091,19 +1264,105 @@ export class DocumentIndex<
|
|
|
1091
1264
|
resolved.push(r.response);
|
|
1092
1265
|
}
|
|
1093
1266
|
};
|
|
1267
|
+
|
|
1268
|
+
let extraPromises: Promise<void>[] | undefined = undefined;
|
|
1269
|
+
|
|
1270
|
+
const groupHashes: string[][] = replicatorGroups
|
|
1271
|
+
.filter((hash) => {
|
|
1272
|
+
if (hash === this.node.identity.publicKey.hashcode()) {
|
|
1273
|
+
return false;
|
|
1274
|
+
}
|
|
1275
|
+
const resultAlready = this._prefetch?.accumulator.consume(
|
|
1276
|
+
queryRequest,
|
|
1277
|
+
hash,
|
|
1278
|
+
);
|
|
1279
|
+
if (resultAlready) {
|
|
1280
|
+
(extraPromises || (extraPromises = [])).push(
|
|
1281
|
+
(async () => {
|
|
1282
|
+
let from = await this.node.services.pubsub.getPublicKey(hash);
|
|
1283
|
+
if (from) {
|
|
1284
|
+
return responseHandler([
|
|
1285
|
+
{
|
|
1286
|
+
response: resultAlready.response.results,
|
|
1287
|
+
from,
|
|
1288
|
+
},
|
|
1289
|
+
]);
|
|
1290
|
+
}
|
|
1291
|
+
})(),
|
|
1292
|
+
);
|
|
1293
|
+
return false;
|
|
1294
|
+
}
|
|
1295
|
+
return true;
|
|
1296
|
+
})
|
|
1297
|
+
.map((x) => [x]);
|
|
1298
|
+
|
|
1299
|
+
extraPromises && (await Promise.all(extraPromises));
|
|
1300
|
+
let tearDown: (() => void) | undefined = undefined;
|
|
1301
|
+
const search = this;
|
|
1302
|
+
|
|
1094
1303
|
try {
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
await this._query.request(queryRequest, { mode: remote!.mode });
|
|
1098
|
-
} else {
|
|
1099
|
-
await queryAll(
|
|
1304
|
+
groupHashes.length > 0 &&
|
|
1305
|
+
(await queryAll(
|
|
1100
1306
|
this._query,
|
|
1101
1307
|
groupHashes,
|
|
1102
1308
|
queryRequest,
|
|
1103
1309
|
responseHandler,
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1310
|
+
search._prefetch?.accumulator
|
|
1311
|
+
? {
|
|
1312
|
+
...remote,
|
|
1313
|
+
responseInterceptor(fn) {
|
|
1314
|
+
const listener = (evt: {
|
|
1315
|
+
detail: {
|
|
1316
|
+
consumable: RPCResponse<
|
|
1317
|
+
types.PredictedSearchRequest<any>
|
|
1318
|
+
>;
|
|
1319
|
+
};
|
|
1320
|
+
}) => {
|
|
1321
|
+
const consumable =
|
|
1322
|
+
search._prefetch?.accumulator.consume(
|
|
1323
|
+
queryRequest,
|
|
1324
|
+
evt.detail.consumable.from!.hashcode(),
|
|
1325
|
+
);
|
|
1326
|
+
|
|
1327
|
+
if (consumable) {
|
|
1328
|
+
fn({
|
|
1329
|
+
message: consumable.message,
|
|
1330
|
+
response: consumable.response.results,
|
|
1331
|
+
from: consumable.from,
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
|
|
1336
|
+
for (const groups of groupHashes) {
|
|
1337
|
+
for (const hash of groups) {
|
|
1338
|
+
const consumable =
|
|
1339
|
+
search._prefetch?.accumulator.consume(
|
|
1340
|
+
queryRequest,
|
|
1341
|
+
hash,
|
|
1342
|
+
);
|
|
1343
|
+
if (consumable) {
|
|
1344
|
+
fn({
|
|
1345
|
+
message: consumable.message,
|
|
1346
|
+
response: consumable.response.results,
|
|
1347
|
+
from: consumable.from,
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
search.prefetch?.accumulator.addEventListener(
|
|
1353
|
+
"add",
|
|
1354
|
+
listener,
|
|
1355
|
+
);
|
|
1356
|
+
tearDown = () => {
|
|
1357
|
+
search.prefetch?.accumulator.removeEventListener(
|
|
1358
|
+
"add",
|
|
1359
|
+
listener,
|
|
1360
|
+
);
|
|
1361
|
+
};
|
|
1362
|
+
},
|
|
1363
|
+
}
|
|
1364
|
+
: remote,
|
|
1365
|
+
));
|
|
1107
1366
|
} catch (error) {
|
|
1108
1367
|
if (error instanceof MissingResponsesError) {
|
|
1109
1368
|
logger.warn("Did not reciveve responses from all shard");
|
|
@@ -1113,6 +1372,8 @@ export class DocumentIndex<
|
|
|
1113
1372
|
} else {
|
|
1114
1373
|
throw error;
|
|
1115
1374
|
}
|
|
1375
|
+
} finally {
|
|
1376
|
+
tearDown && (tearDown as any)();
|
|
1116
1377
|
}
|
|
1117
1378
|
} else {
|
|
1118
1379
|
// TODO send without direction out to the world? or just assume we can insert?
|
|
@@ -1400,6 +1661,7 @@ export class DocumentIndex<
|
|
|
1400
1661
|
|
|
1401
1662
|
// TODO buffer more than deleted?
|
|
1402
1663
|
// TODO batch to multiple 'to's
|
|
1664
|
+
|
|
1403
1665
|
const collectRequest = new types.CollectNextRequest({
|
|
1404
1666
|
id: queryRequestCoerced.id,
|
|
1405
1667
|
amount: n - buffer.buffer.length,
|
|
@@ -1464,9 +1726,21 @@ export class DocumentIndex<
|
|
|
1464
1726
|
);
|
|
1465
1727
|
} else {
|
|
1466
1728
|
// Fetch remotely
|
|
1729
|
+
const idTranslation =
|
|
1730
|
+
this._prefetch?.accumulator.getTranslationMap(
|
|
1731
|
+
queryRequestCoerced,
|
|
1732
|
+
);
|
|
1733
|
+
let remoteCollectRequest: types.CollectNextRequest = collectRequest;
|
|
1734
|
+
if (idTranslation) {
|
|
1735
|
+
remoteCollectRequest = new types.CollectNextRequest({
|
|
1736
|
+
id: idTranslation.get(peer) || collectRequest.id,
|
|
1737
|
+
amount: collectRequest.amount,
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1467
1741
|
promises.push(
|
|
1468
1742
|
this._query
|
|
1469
|
-
.request(
|
|
1743
|
+
.request(remoteCollectRequest, {
|
|
1470
1744
|
...options,
|
|
1471
1745
|
signal: controller.signal,
|
|
1472
1746
|
priority: 1,
|
|
@@ -1627,6 +1901,7 @@ export class DocumentIndex<
|
|
|
1627
1901
|
const closeRequest = new types.CloseIteratorRequest({
|
|
1628
1902
|
id: queryRequestCoerced.id,
|
|
1629
1903
|
});
|
|
1904
|
+
this.prefetch?.accumulator.clear(queryRequestCoerced);
|
|
1630
1905
|
const promises: Promise<any>[] = [];
|
|
1631
1906
|
for (const [peer, buffer] of peerBufferMap) {
|
|
1632
1907
|
if (buffer.kept === 0) {
|