@peerbit/document 9.13.10 → 10.0.0-954957e
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/domain.d.ts.map +1 -1
- package/dist/src/domain.js +3 -1
- package/dist/src/domain.js.map +1 -1
- package/dist/src/program.js +1 -1
- package/dist/src/program.js.map +1 -1
- package/dist/src/resumable-iterator.d.ts +11 -8
- package/dist/src/resumable-iterator.d.ts.map +1 -1
- package/dist/src/resumable-iterator.js +33 -17
- package/dist/src/resumable-iterator.js.map +1 -1
- package/dist/src/search.d.ts +13 -7
- package/dist/src/search.d.ts.map +1 -1
- package/dist/src/search.js +444 -70
- package/dist/src/search.js.map +1 -1
- package/package.json +89 -75
- package/src/domain.ts +3 -1
- package/src/program.ts +2 -2
- package/src/resumable-iterator.ts +48 -23
- package/src/search.ts +678 -140
package/dist/src/search.js
CHANGED
|
@@ -25,34 +25,67 @@ import pDefer, {} from "p-defer";
|
|
|
25
25
|
import { concat, fromString } from "uint8arrays";
|
|
26
26
|
import { copySerialization } from "./borsh.js";
|
|
27
27
|
import { MAX_BATCH_SIZE } from "./constants.js";
|
|
28
|
-
import MostCommonQueryPredictor from "./most-common-query-predictor.js";
|
|
28
|
+
import MostCommonQueryPredictor, { idAgnosticQueryKey, } from "./most-common-query-predictor.js";
|
|
29
29
|
import { isPutOperation } from "./operation.js";
|
|
30
30
|
import { Prefetch } from "./prefetch.js";
|
|
31
31
|
import { ResumableIterators } from "./resumable-iterator.js";
|
|
32
32
|
const WARNING_WHEN_ITERATING_FOR_MORE_THAN = 1e5;
|
|
33
|
-
const logger = loggerFn({
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
const logger = loggerFn({
|
|
34
|
+
module: "document-index",
|
|
35
|
+
});
|
|
36
|
+
const coerceQuery = (query, options, compatibility) => {
|
|
37
|
+
const replicate = typeof options?.remote !== "boolean" ? options?.remote?.replicate : false;
|
|
38
|
+
const shouldResolve = options?.resolve !== false;
|
|
39
|
+
const useLegacyRequests = compatibility != null && compatibility <= 9;
|
|
36
40
|
if (query instanceof types.SearchRequestIndexed &&
|
|
37
41
|
query.replicate === false &&
|
|
38
42
|
replicate) {
|
|
39
43
|
query.replicate = true;
|
|
40
44
|
return query;
|
|
41
45
|
}
|
|
42
|
-
if (query instanceof types.SearchRequest
|
|
46
|
+
if (query instanceof types.SearchRequest ||
|
|
47
|
+
query instanceof types.SearchRequestIndexed) {
|
|
48
|
+
return query;
|
|
49
|
+
}
|
|
50
|
+
if (query instanceof types.IterationRequest) {
|
|
51
|
+
if (useLegacyRequests) {
|
|
52
|
+
if (query.resolve === false) {
|
|
53
|
+
return new types.SearchRequestIndexed({
|
|
54
|
+
query: query.query,
|
|
55
|
+
sort: query.sort,
|
|
56
|
+
fetch: query.fetch,
|
|
57
|
+
replicate: query.replicate ?? replicate,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return new types.SearchRequest({
|
|
61
|
+
query: query.query,
|
|
62
|
+
sort: query.sort,
|
|
63
|
+
fetch: query.fetch,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
43
66
|
return query;
|
|
44
67
|
}
|
|
45
68
|
const queryObject = query;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
if (useLegacyRequests) {
|
|
70
|
+
if (shouldResolve) {
|
|
71
|
+
return new types.SearchRequest({
|
|
72
|
+
query: indexerTypes.toQuery(queryObject.query),
|
|
73
|
+
sort: indexerTypes.toSort(queryObject.sort),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return new types.SearchRequestIndexed({
|
|
52
77
|
query: indexerTypes.toQuery(queryObject.query),
|
|
53
|
-
sort: indexerTypes.toSort(
|
|
78
|
+
sort: indexerTypes.toSort(queryObject.sort),
|
|
54
79
|
replicate,
|
|
55
80
|
});
|
|
81
|
+
}
|
|
82
|
+
return new types.IterationRequest({
|
|
83
|
+
query: indexerTypes.toQuery(queryObject.query),
|
|
84
|
+
sort: indexerTypes.toSort(queryObject.sort),
|
|
85
|
+
fetch: 10,
|
|
86
|
+
resolve: shouldResolve,
|
|
87
|
+
replicate: shouldResolve ? false : replicate,
|
|
88
|
+
});
|
|
56
89
|
};
|
|
57
90
|
const introduceEntries = async (queryRequest, responses, documentType, indexedType, sync, options) => {
|
|
58
91
|
const results = [];
|
|
@@ -94,6 +127,30 @@ const dedup = (allResult, dedupBy) => {
|
|
|
94
127
|
}
|
|
95
128
|
return dedup;
|
|
96
129
|
};
|
|
130
|
+
const resolvesDocuments = (req) => {
|
|
131
|
+
if (!req) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
if (req instanceof types.SearchRequestIndexed) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
if (req instanceof types.IterationRequest) {
|
|
138
|
+
return req.resolve !== false;
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
};
|
|
142
|
+
const replicatesIndex = (req) => {
|
|
143
|
+
if (!req) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
if (req instanceof types.SearchRequestIndexed) {
|
|
147
|
+
return req.replicate === true;
|
|
148
|
+
}
|
|
149
|
+
if (req instanceof types.IterationRequest) {
|
|
150
|
+
return req.replicate === true;
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
};
|
|
97
154
|
function isSubclassOf(SubClass, SuperClass) {
|
|
98
155
|
// Start with the immediate parent of SubClass
|
|
99
156
|
let proto = Object.getPrototypeOf(SubClass);
|
|
@@ -106,6 +163,7 @@ function isSubclassOf(SubClass, SuperClass) {
|
|
|
106
163
|
return false;
|
|
107
164
|
}
|
|
108
165
|
const DEFAULT_TIMEOUT = 1e4;
|
|
166
|
+
const DISCOVER_TIMEOUT_FALLBACK = 500;
|
|
109
167
|
const DEFAULT_INDEX_BY = "id";
|
|
110
168
|
const isTransformerWithFunction = (options) => {
|
|
111
169
|
return options.transform != null;
|
|
@@ -154,9 +212,11 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
154
212
|
documentEvents;
|
|
155
213
|
_joinListener;
|
|
156
214
|
_resultQueue;
|
|
215
|
+
iteratorKeepAliveTimers;
|
|
157
216
|
constructor(properties) {
|
|
158
217
|
super();
|
|
159
218
|
this._query = properties?.query || new RPC();
|
|
219
|
+
this.iteratorKeepAliveTimers = new Map();
|
|
160
220
|
}
|
|
161
221
|
get valueEncoding() {
|
|
162
222
|
return this._valueEncoding;
|
|
@@ -317,7 +377,8 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
317
377
|
}
|
|
318
378
|
if (this.prefetch?.predictor &&
|
|
319
379
|
(query instanceof types.SearchRequest ||
|
|
320
|
-
query instanceof types.SearchRequestIndexed
|
|
380
|
+
query instanceof types.SearchRequestIndexed ||
|
|
381
|
+
query instanceof types.IterationRequest)) {
|
|
321
382
|
const { ignore } = this.prefetch.predictor.onRequest(query, {
|
|
322
383
|
from: ctx.from,
|
|
323
384
|
});
|
|
@@ -334,6 +395,7 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
334
395
|
async handleSearchRequest(query, ctx) {
|
|
335
396
|
if (this.canSearch &&
|
|
336
397
|
(query instanceof types.SearchRequest ||
|
|
398
|
+
query instanceof types.IterationRequest ||
|
|
337
399
|
query instanceof types.CollectNextRequest) &&
|
|
338
400
|
!(await this.canSearch(query, ctx.from))) {
|
|
339
401
|
return new types.NoAccess();
|
|
@@ -342,11 +404,13 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
342
404
|
this.processCloseIteratorRequest(query, ctx.from);
|
|
343
405
|
}
|
|
344
406
|
else {
|
|
345
|
-
const
|
|
346
|
-
(query
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
407
|
+
const fromQueued = query instanceof types.CollectNextRequest
|
|
408
|
+
? this._resultQueue.get(query.idString)?.fromQuery
|
|
409
|
+
: undefined;
|
|
410
|
+
const queryResolvesDocuments = query instanceof types.CollectNextRequest
|
|
411
|
+
? resolvesDocuments(fromQueued)
|
|
412
|
+
: resolvesDocuments(query);
|
|
413
|
+
const shouldIncludedIndexedResults = this.includeIndexed && queryResolvesDocuments;
|
|
350
414
|
const results = await this.processQuery(query, ctx.from, false, {
|
|
351
415
|
canRead: this.canRead,
|
|
352
416
|
});
|
|
@@ -585,22 +649,29 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
585
649
|
};
|
|
586
650
|
}
|
|
587
651
|
let results;
|
|
652
|
+
const runAndClose = async (req) => {
|
|
653
|
+
const response = await this.queryCommence(req, coercedOptions);
|
|
654
|
+
this._resumableIterators.close({ idString: req.idString });
|
|
655
|
+
this.cancelIteratorKeepAlive(req.idString);
|
|
656
|
+
return response;
|
|
657
|
+
};
|
|
588
658
|
const resolve = coercedOptions?.resolve || coercedOptions?.resolve == null;
|
|
589
659
|
let requestClazz = resolve
|
|
590
660
|
? types.SearchRequest
|
|
591
661
|
: types.SearchRequestIndexed;
|
|
592
662
|
if (key instanceof Uint8Array) {
|
|
593
|
-
|
|
663
|
+
const request = new requestClazz({
|
|
594
664
|
query: [
|
|
595
665
|
new indexerTypes.ByteMatchQuery({ key: this.indexBy, value: key }),
|
|
596
666
|
],
|
|
597
|
-
})
|
|
667
|
+
});
|
|
668
|
+
results = await runAndClose(request);
|
|
598
669
|
}
|
|
599
670
|
else {
|
|
600
671
|
const indexableKey = indexerTypes.toIdeable(key);
|
|
601
672
|
if (typeof indexableKey === "number" ||
|
|
602
673
|
typeof indexableKey === "bigint") {
|
|
603
|
-
|
|
674
|
+
const request = new requestClazz({
|
|
604
675
|
query: [
|
|
605
676
|
new indexerTypes.IntegerCompare({
|
|
606
677
|
key: this.indexBy,
|
|
@@ -608,27 +679,44 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
608
679
|
value: indexableKey,
|
|
609
680
|
}),
|
|
610
681
|
],
|
|
611
|
-
})
|
|
682
|
+
});
|
|
683
|
+
results = await runAndClose(request);
|
|
612
684
|
}
|
|
613
685
|
else if (typeof indexableKey === "string") {
|
|
614
|
-
|
|
686
|
+
const request = new requestClazz({
|
|
615
687
|
query: [
|
|
616
688
|
new indexerTypes.StringMatch({
|
|
617
689
|
key: this.indexBy,
|
|
618
690
|
value: indexableKey,
|
|
619
691
|
}),
|
|
620
692
|
],
|
|
621
|
-
})
|
|
693
|
+
});
|
|
694
|
+
results = await runAndClose(request);
|
|
622
695
|
}
|
|
623
696
|
else if (indexableKey instanceof Uint8Array) {
|
|
624
|
-
|
|
697
|
+
const request = new requestClazz({
|
|
625
698
|
query: [
|
|
626
699
|
new indexerTypes.ByteMatchQuery({
|
|
627
700
|
key: this.indexBy,
|
|
628
701
|
value: indexableKey,
|
|
629
702
|
}),
|
|
630
703
|
],
|
|
631
|
-
})
|
|
704
|
+
});
|
|
705
|
+
results = await runAndClose(request);
|
|
706
|
+
}
|
|
707
|
+
else if (indexableKey instanceof ArrayBuffer) {
|
|
708
|
+
const request = new requestClazz({
|
|
709
|
+
query: [
|
|
710
|
+
new indexerTypes.ByteMatchQuery({
|
|
711
|
+
key: this.indexBy,
|
|
712
|
+
value: new Uint8Array(indexableKey),
|
|
713
|
+
}),
|
|
714
|
+
],
|
|
715
|
+
});
|
|
716
|
+
results = await runAndClose(request);
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
throw new Error("Unsupported key type");
|
|
632
720
|
}
|
|
633
721
|
}
|
|
634
722
|
// if we are to resolve the document we need to go through all results and replace the results with the resolved values
|
|
@@ -711,23 +799,42 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
711
799
|
}
|
|
712
800
|
let indexedResult = undefined;
|
|
713
801
|
let fromQuery;
|
|
802
|
+
let keepAliveRequest;
|
|
714
803
|
if (query instanceof types.SearchRequest ||
|
|
715
|
-
query instanceof types.SearchRequestIndexed
|
|
804
|
+
query instanceof types.SearchRequestIndexed ||
|
|
805
|
+
query instanceof types.IterationRequest) {
|
|
716
806
|
fromQuery = query;
|
|
717
|
-
|
|
807
|
+
if (!isLocal &&
|
|
808
|
+
query instanceof types.IterationRequest &&
|
|
809
|
+
query.keepAliveTtl != null) {
|
|
810
|
+
keepAliveRequest = query;
|
|
811
|
+
}
|
|
812
|
+
indexedResult = await this._resumableIterators.iterateAndFetch(query, {
|
|
813
|
+
keepAlive: keepAliveRequest !== undefined,
|
|
814
|
+
});
|
|
718
815
|
}
|
|
719
816
|
else if (query instanceof types.CollectNextRequest) {
|
|
720
|
-
fromQuery
|
|
721
|
-
|
|
722
|
-
|
|
817
|
+
const cachedRequest = prevQueued?.fromQuery ||
|
|
818
|
+
this._resumableIterators.queues.get(query.idString)?.request;
|
|
819
|
+
fromQuery = cachedRequest;
|
|
820
|
+
if (!isLocal &&
|
|
821
|
+
cachedRequest instanceof types.IterationRequest &&
|
|
822
|
+
cachedRequest.keepAliveTtl != null) {
|
|
823
|
+
keepAliveRequest = cachedRequest;
|
|
824
|
+
}
|
|
723
825
|
indexedResult =
|
|
724
826
|
prevQueued?.keptInIndex === 0
|
|
725
827
|
? []
|
|
726
|
-
: await this._resumableIterators.next(query
|
|
828
|
+
: await this._resumableIterators.next(query, {
|
|
829
|
+
keepAlive: keepAliveRequest !== undefined,
|
|
830
|
+
});
|
|
727
831
|
}
|
|
728
832
|
else {
|
|
729
833
|
throw new Error("Unsupported");
|
|
730
834
|
}
|
|
835
|
+
if (!isLocal && keepAliveRequest) {
|
|
836
|
+
this.scheduleIteratorKeepAlive(query.idString, keepAliveRequest.keepAliveTtl);
|
|
837
|
+
}
|
|
731
838
|
let resultSize = 0;
|
|
732
839
|
let toIterate = prevQueued
|
|
733
840
|
? [...prevQueued.queue, ...indexedResult]
|
|
@@ -751,6 +858,8 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
751
858
|
this._resultQueue.set(query.idString, prevQueued);
|
|
752
859
|
}
|
|
753
860
|
const filteredResults = [];
|
|
861
|
+
const resolveDocumentsFlag = resolvesDocuments(fromQuery);
|
|
862
|
+
const replicateIndexFlag = replicatesIndex(fromQuery);
|
|
754
863
|
for (const result of toIterate) {
|
|
755
864
|
if (!isLocal) {
|
|
756
865
|
resultSize += result.value.__context.size;
|
|
@@ -764,7 +873,7 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
764
873
|
!(await options.canRead(indexedUnwrapped, from))) {
|
|
765
874
|
continue;
|
|
766
875
|
}
|
|
767
|
-
if (
|
|
876
|
+
if (resolveDocumentsFlag) {
|
|
768
877
|
const value = await this.resolveDocument({
|
|
769
878
|
indexed: result.value,
|
|
770
879
|
head: result.value.__context.head,
|
|
@@ -779,11 +888,11 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
779
888
|
indexed: indexedUnwrapped,
|
|
780
889
|
}));
|
|
781
890
|
}
|
|
782
|
-
else
|
|
891
|
+
else {
|
|
783
892
|
const context = result.value.__context;
|
|
784
893
|
const head = await this._log.log.get(context.head);
|
|
785
894
|
// assume remote peer will start to replicate (TODO is this ideal?)
|
|
786
|
-
if (
|
|
895
|
+
if (replicateIndexFlag) {
|
|
787
896
|
this._log.addPeersToGidPeerHistory(context.gid, [from.hashcode()]);
|
|
788
897
|
}
|
|
789
898
|
filteredResults.push(new types.ResultIndexedValue({
|
|
@@ -803,6 +912,41 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
803
912
|
}
|
|
804
913
|
return results;
|
|
805
914
|
}
|
|
915
|
+
scheduleIteratorKeepAlive(idString, ttl) {
|
|
916
|
+
if (ttl == null) {
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
const ttlNumber = Number(ttl);
|
|
920
|
+
if (!Number.isFinite(ttlNumber) || ttlNumber <= 0) {
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
// Cap max timeout to 1 day (TODO make configurable?)
|
|
924
|
+
const delay = Math.max(1, Math.min(ttlNumber, 86400000));
|
|
925
|
+
this.cancelIteratorKeepAlive(idString);
|
|
926
|
+
const timers = this.iteratorKeepAliveTimers ??
|
|
927
|
+
(this.iteratorKeepAliveTimers = new Map());
|
|
928
|
+
const timer = setTimeout(() => {
|
|
929
|
+
timers.delete(idString);
|
|
930
|
+
const queued = this._resultQueue.get(idString);
|
|
931
|
+
if (queued) {
|
|
932
|
+
clearTimeout(queued.timeout);
|
|
933
|
+
this._resultQueue.delete(idString);
|
|
934
|
+
}
|
|
935
|
+
this._resumableIterators.close({ idString });
|
|
936
|
+
}, delay);
|
|
937
|
+
timers.set(idString, timer);
|
|
938
|
+
}
|
|
939
|
+
cancelIteratorKeepAlive(idString) {
|
|
940
|
+
const timers = this.iteratorKeepAliveTimers;
|
|
941
|
+
if (!timers) {
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
const timer = timers.get(idString);
|
|
945
|
+
if (timer) {
|
|
946
|
+
clearTimeout(timer);
|
|
947
|
+
timers.delete(idString);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
806
950
|
clearResultsQueue(query) {
|
|
807
951
|
const queue = this._resultQueue.get(query.idString);
|
|
808
952
|
if (queue) {
|
|
@@ -817,6 +961,7 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
817
961
|
for (const [key, queue] of this._resultQueue) {
|
|
818
962
|
clearTimeout(queue.timeout);
|
|
819
963
|
this._resultQueue.delete(key);
|
|
964
|
+
this.cancelIteratorKeepAlive(key);
|
|
820
965
|
this._resumableIterators.close({ idString: key });
|
|
821
966
|
}
|
|
822
967
|
}
|
|
@@ -950,6 +1095,7 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
950
1095
|
logger.info("Ignoring close iterator request from different peer");
|
|
951
1096
|
return;
|
|
952
1097
|
}
|
|
1098
|
+
this.cancelIteratorKeepAlive(query.idString);
|
|
953
1099
|
this.clearResultsQueue(query);
|
|
954
1100
|
return this._resumableIterators.close(query);
|
|
955
1101
|
}
|
|
@@ -1126,7 +1272,7 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
1126
1272
|
*/
|
|
1127
1273
|
async search(queryRequest, options) {
|
|
1128
1274
|
// Set fetch to search size, or max value (default to max u32 (4294967295))
|
|
1129
|
-
const coercedRequest = coerceQuery(queryRequest, options);
|
|
1275
|
+
const coercedRequest = coerceQuery(queryRequest, options, this.compatibility);
|
|
1130
1276
|
coercedRequest.fetch = coercedRequest.fetch ?? 0xffffffff;
|
|
1131
1277
|
// So that the iterator is pre-fetching the right amount of entries
|
|
1132
1278
|
const iterator = this.iterate(coercedRequest, options);
|
|
@@ -1168,19 +1314,42 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
1168
1314
|
/**
|
|
1169
1315
|
* Query and retrieve documents in a iterator
|
|
1170
1316
|
* @param queryRequest
|
|
1171
|
-
* @param
|
|
1317
|
+
* @param optionsArg
|
|
1172
1318
|
* @returns
|
|
1173
1319
|
*/
|
|
1174
|
-
iterate(queryRequest,
|
|
1320
|
+
iterate(queryRequest, optionsArg) {
|
|
1321
|
+
let options = optionsArg;
|
|
1175
1322
|
if (queryRequest instanceof types.SearchRequest &&
|
|
1176
1323
|
options?.resolve === false) {
|
|
1177
1324
|
throw new Error("Cannot use resolve=false with SearchRequest"); // TODO make this work
|
|
1178
1325
|
}
|
|
1179
|
-
let queryRequestCoerced = coerceQuery(queryRequest ?? {}, options);
|
|
1326
|
+
let queryRequestCoerced = coerceQuery(queryRequest ?? {}, options, this.compatibility);
|
|
1327
|
+
const { mergePolicy, push: pushUpdates, callbacks: updateCallbacksRaw, } = normalizeUpdatesOption(options?.updates);
|
|
1328
|
+
const hasLiveUpdates = mergePolicy !== undefined;
|
|
1329
|
+
const originalRemote = options?.remote;
|
|
1330
|
+
let remoteOptions = typeof originalRemote === "boolean"
|
|
1331
|
+
? originalRemote
|
|
1332
|
+
: originalRemote
|
|
1333
|
+
? { ...originalRemote }
|
|
1334
|
+
: undefined;
|
|
1335
|
+
if (pushUpdates && remoteOptions !== false) {
|
|
1336
|
+
if (typeof remoteOptions === "object") {
|
|
1337
|
+
if (remoteOptions.replicate !== true) {
|
|
1338
|
+
remoteOptions.replicate = true;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
else if (remoteOptions === undefined || remoteOptions === true) {
|
|
1342
|
+
remoteOptions = { replicate: true };
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
if (remoteOptions !== originalRemote) {
|
|
1346
|
+
options = Object.assign({}, options, { remote: remoteOptions });
|
|
1347
|
+
}
|
|
1180
1348
|
let resolve = options?.resolve !== false;
|
|
1181
|
-
if (
|
|
1349
|
+
if (!(queryRequestCoerced instanceof types.IterationRequest) &&
|
|
1350
|
+
options?.remote &&
|
|
1182
1351
|
typeof options.remote !== "boolean" &&
|
|
1183
|
-
options.remote.replicate &&
|
|
1352
|
+
(options.remote.replicate || pushUpdates) &&
|
|
1184
1353
|
options?.resolve !== false) {
|
|
1185
1354
|
if ((queryRequest instanceof types.SearchRequestIndexed === false &&
|
|
1186
1355
|
this.compatibility == null) ||
|
|
@@ -1195,7 +1364,7 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
1195
1364
|
}
|
|
1196
1365
|
let replicate = options?.remote &&
|
|
1197
1366
|
typeof options.remote !== "boolean" &&
|
|
1198
|
-
options.remote.replicate;
|
|
1367
|
+
(options.remote.replicate || pushUpdates);
|
|
1199
1368
|
if (replicate &&
|
|
1200
1369
|
queryRequestCoerced instanceof types.SearchRequestIndexed) {
|
|
1201
1370
|
queryRequestCoerced.replicate = true;
|
|
@@ -1237,6 +1406,7 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
1237
1406
|
this.clearResultsQueue(queryRequestCoerced);
|
|
1238
1407
|
};
|
|
1239
1408
|
let warmupPromise = undefined;
|
|
1409
|
+
let discoveredTargetHashes;
|
|
1240
1410
|
if (typeof options?.remote === "object") {
|
|
1241
1411
|
let waitForTime = undefined;
|
|
1242
1412
|
if (options.remote.wait) {
|
|
@@ -1271,11 +1441,25 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
1271
1441
|
};
|
|
1272
1442
|
}
|
|
1273
1443
|
if (options.remote.reach?.discover) {
|
|
1274
|
-
|
|
1444
|
+
const discoverTimeout = waitForTime ??
|
|
1445
|
+
(options.remote.wait ? DEFAULT_TIMEOUT : DISCOVER_TIMEOUT_FALLBACK);
|
|
1446
|
+
const discoverPromise = this.waitFor(options.remote.reach.discover, {
|
|
1275
1447
|
signal: ensureController().signal,
|
|
1276
1448
|
seek: "present",
|
|
1277
|
-
timeout:
|
|
1449
|
+
timeout: discoverTimeout,
|
|
1450
|
+
})
|
|
1451
|
+
.then((hashes) => {
|
|
1452
|
+
discoveredTargetHashes = hashes;
|
|
1453
|
+
})
|
|
1454
|
+
.catch((error) => {
|
|
1455
|
+
if (error instanceof TimeoutError || error instanceof AbortError) {
|
|
1456
|
+
discoveredTargetHashes = [];
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
throw error;
|
|
1278
1460
|
});
|
|
1461
|
+
const prior = warmupPromise ?? Promise.resolve();
|
|
1462
|
+
warmupPromise = prior.then(() => discoverPromise);
|
|
1279
1463
|
options.remote.reach.eager = true; // include the results from the discovered peer even if it is not mature
|
|
1280
1464
|
}
|
|
1281
1465
|
const waitPolicy = typeof options.remote.wait === "object"
|
|
@@ -1299,15 +1483,24 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
1299
1483
|
const fetchFirst = async (n, fetchOptions) => {
|
|
1300
1484
|
await warmupPromise;
|
|
1301
1485
|
let hasMore = false;
|
|
1486
|
+
const discoverTargets = typeof options?.remote === "object"
|
|
1487
|
+
? options.remote.reach?.discover
|
|
1488
|
+
: undefined;
|
|
1489
|
+
const initialRemoteTargets = discoveredTargetHashes !== undefined
|
|
1490
|
+
? discoveredTargetHashes
|
|
1491
|
+
: discoverTargets?.map((pk) => pk.hashcode().toString());
|
|
1492
|
+
const skipRemoteDueToDiscovery = typeof options?.remote === "object" &&
|
|
1493
|
+
options.remote.reach?.discover &&
|
|
1494
|
+
discoveredTargetHashes?.length === 0;
|
|
1302
1495
|
queryRequestCoerced.fetch = n;
|
|
1303
1496
|
await this.queryCommence(queryRequestCoerced, {
|
|
1304
1497
|
local: fetchOptions?.from != null ? false : options?.local,
|
|
1305
|
-
remote: options?.remote !== false
|
|
1498
|
+
remote: options?.remote !== false && !skipRemoteDueToDiscovery
|
|
1306
1499
|
? {
|
|
1307
1500
|
...(typeof options?.remote === "object"
|
|
1308
1501
|
? options.remote
|
|
1309
1502
|
: {}),
|
|
1310
|
-
from: fetchOptions?.from,
|
|
1503
|
+
from: fetchOptions?.from ?? initialRemoteTargets,
|
|
1311
1504
|
}
|
|
1312
1505
|
: false,
|
|
1313
1506
|
resolve,
|
|
@@ -1322,13 +1515,20 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
1322
1515
|
}
|
|
1323
1516
|
else if (response instanceof types.Results) {
|
|
1324
1517
|
const results = response;
|
|
1518
|
+
const existingBuffer = peerBufferMap.get(from.hashcode());
|
|
1519
|
+
const buffer = existingBuffer?.buffer || [];
|
|
1325
1520
|
if (results.kept === 0n && results.results.length === 0) {
|
|
1521
|
+
if (keepRemoteAlive) {
|
|
1522
|
+
peerBufferMap.set(from.hashcode(), {
|
|
1523
|
+
buffer,
|
|
1524
|
+
kept: Number(response.kept),
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1326
1527
|
return;
|
|
1327
1528
|
}
|
|
1328
1529
|
if (results.kept > 0n) {
|
|
1329
1530
|
hasMore = true;
|
|
1330
1531
|
}
|
|
1331
|
-
const buffer = peerBufferMap.get(from.hashcode())?.buffer || [];
|
|
1332
1532
|
for (const result of results.results) {
|
|
1333
1533
|
const indexKey = indexerTypes.toId(this.indexByResolver(result.value)).primitive;
|
|
1334
1534
|
if (result instanceof types.ResultValue) {
|
|
@@ -1403,7 +1603,8 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
1403
1603
|
let resultsLeft = 0;
|
|
1404
1604
|
for (const [peer, buffer] of peerBufferMap) {
|
|
1405
1605
|
if (buffer.buffer.length < n) {
|
|
1406
|
-
|
|
1606
|
+
const hasExistingRemoteResults = buffer.kept > 0;
|
|
1607
|
+
if (!hasExistingRemoteResults && !keepRemoteAlive) {
|
|
1407
1608
|
if (peerBufferMap.get(peer)?.buffer.length === 0) {
|
|
1408
1609
|
peerBufferMap.delete(peer); // No more results
|
|
1409
1610
|
}
|
|
@@ -1411,9 +1612,14 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
1411
1612
|
}
|
|
1412
1613
|
// TODO buffer more than deleted?
|
|
1413
1614
|
// TODO batch to multiple 'to's
|
|
1615
|
+
const lacking = n - buffer.buffer.length;
|
|
1616
|
+
const amount = lacking > 0 ? lacking : keepRemoteAlive ? 1 : 0;
|
|
1617
|
+
if (amount <= 0) {
|
|
1618
|
+
continue;
|
|
1619
|
+
}
|
|
1414
1620
|
const collectRequest = new types.CollectNextRequest({
|
|
1415
1621
|
id: queryRequestCoerced.id,
|
|
1416
|
-
amount
|
|
1622
|
+
amount,
|
|
1417
1623
|
});
|
|
1418
1624
|
// Fetch locally?
|
|
1419
1625
|
if (peer === this.node.identity.publicKey.hashcode()) {
|
|
@@ -1424,7 +1630,8 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
1424
1630
|
.then(async (results) => {
|
|
1425
1631
|
resultsLeft += Number(results.kept);
|
|
1426
1632
|
if (results.results.length === 0) {
|
|
1427
|
-
if (
|
|
1633
|
+
if (!keepRemoteAlive &&
|
|
1634
|
+
peerBufferMap.get(peer)?.buffer.length === 0) {
|
|
1428
1635
|
peerBufferMap.delete(peer); // No more results
|
|
1429
1636
|
}
|
|
1430
1637
|
}
|
|
@@ -1516,7 +1723,8 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
1516
1723
|
return;
|
|
1517
1724
|
}
|
|
1518
1725
|
if (response.response.results.length === 0) {
|
|
1519
|
-
if (
|
|
1726
|
+
if (!keepRemoteAlive &&
|
|
1727
|
+
peerBufferMap.get(peer)?.buffer.length === 0) {
|
|
1520
1728
|
peerBufferMap.delete(peer); // No more results
|
|
1521
1729
|
}
|
|
1522
1730
|
}
|
|
@@ -1684,32 +1892,69 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
1684
1892
|
let joinListener;
|
|
1685
1893
|
let fetchedFirstForRemote = undefined;
|
|
1686
1894
|
let updateDeferred;
|
|
1687
|
-
const signalUpdate = () =>
|
|
1895
|
+
const signalUpdate = (reason) => {
|
|
1896
|
+
updateDeferred?.resolve();
|
|
1897
|
+
};
|
|
1688
1898
|
const _waitForUpdate = () => updateDeferred ? updateDeferred.promise : Promise.resolve();
|
|
1689
1899
|
// ---------------- Live updates wiring (sorted-only with optional filter) ----------------
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1900
|
+
function normalizeUpdatesOption(u) {
|
|
1901
|
+
const identityFilter = (evt) => evt;
|
|
1902
|
+
const buildMergePolicy = (merge, defaultEnabled) => {
|
|
1903
|
+
const effective = merge === undefined ? (defaultEnabled ? true : undefined) : merge;
|
|
1904
|
+
if (effective === undefined || effective === false) {
|
|
1905
|
+
return undefined;
|
|
1906
|
+
}
|
|
1907
|
+
if (effective === true) {
|
|
1908
|
+
return {
|
|
1909
|
+
merge: {
|
|
1910
|
+
filter: identityFilter,
|
|
1911
|
+
},
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1694
1914
|
return {
|
|
1695
1915
|
merge: {
|
|
1696
|
-
filter:
|
|
1916
|
+
filter: effective.filter ?? identityFilter,
|
|
1697
1917
|
},
|
|
1698
1918
|
};
|
|
1919
|
+
};
|
|
1920
|
+
if (u == null || u === false) {
|
|
1921
|
+
return { push: false };
|
|
1922
|
+
}
|
|
1923
|
+
if (u === true) {
|
|
1924
|
+
return {
|
|
1925
|
+
mergePolicy: buildMergePolicy(true, true),
|
|
1926
|
+
push: false,
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
if (typeof u === "string") {
|
|
1930
|
+
if (u === "remote") {
|
|
1931
|
+
return { push: true };
|
|
1932
|
+
}
|
|
1933
|
+
if (u === "local") {
|
|
1934
|
+
return {
|
|
1935
|
+
mergePolicy: buildMergePolicy(true, true),
|
|
1936
|
+
push: false,
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
if (u === "all") {
|
|
1940
|
+
return {
|
|
1941
|
+
mergePolicy: buildMergePolicy(true, true),
|
|
1942
|
+
push: true,
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1699
1946
|
if (typeof u === "object") {
|
|
1947
|
+
const hasMergeProp = Object.prototype.hasOwnProperty.call(u, "merge");
|
|
1948
|
+
const mergeValue = hasMergeProp ? u.merge : undefined;
|
|
1700
1949
|
return {
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
}
|
|
1705
|
-
: {},
|
|
1950
|
+
mergePolicy: buildMergePolicy(mergeValue, !hasMergeProp || mergeValue === undefined),
|
|
1951
|
+
push: Boolean(u.push),
|
|
1952
|
+
callbacks: u,
|
|
1706
1953
|
};
|
|
1707
1954
|
}
|
|
1708
|
-
return
|
|
1709
|
-
}
|
|
1710
|
-
const updateCallbacks =
|
|
1711
|
-
const mergePolicy = normalizeUpdatesOption(options?.updates);
|
|
1712
|
-
const hasLiveUpdates = mergePolicy !== undefined;
|
|
1955
|
+
return { push: false };
|
|
1956
|
+
}
|
|
1957
|
+
const updateCallbacks = updateCallbacksRaw;
|
|
1713
1958
|
let pendingResultsReason;
|
|
1714
1959
|
let hasDeliveredResults = false;
|
|
1715
1960
|
const emitOnResults = async (batch, defaultReason) => {
|
|
@@ -1735,6 +1980,125 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
1735
1980
|
if (hasLiveUpdates && !updateDeferred) {
|
|
1736
1981
|
updateDeferred = pDefer();
|
|
1737
1982
|
}
|
|
1983
|
+
const keepRemoteAlive = (options?.closePolicy === "manual" || hasLiveUpdates || pushUpdates) &&
|
|
1984
|
+
remoteOptions !== false;
|
|
1985
|
+
if (queryRequestCoerced instanceof types.IterationRequest) {
|
|
1986
|
+
queryRequestCoerced.resolve = resolve;
|
|
1987
|
+
queryRequestCoerced.fetch = queryRequestCoerced.fetch ?? 10;
|
|
1988
|
+
const replicateFlag = !resolve && replicate ? true : false;
|
|
1989
|
+
queryRequestCoerced.replicate = replicateFlag;
|
|
1990
|
+
const ttlSource = typeof remoteOptions === "object" &&
|
|
1991
|
+
typeof remoteOptions?.wait === "object"
|
|
1992
|
+
? (remoteOptions.wait.timeout ?? DEFAULT_TIMEOUT)
|
|
1993
|
+
: DEFAULT_TIMEOUT;
|
|
1994
|
+
queryRequestCoerced.keepAliveTtl = keepRemoteAlive
|
|
1995
|
+
? BigInt(ttlSource)
|
|
1996
|
+
: undefined;
|
|
1997
|
+
queryRequestCoerced.pushUpdates = pushUpdates ? true : undefined;
|
|
1998
|
+
queryRequestCoerced.mergeUpdates = mergePolicy?.merge ? true : undefined;
|
|
1999
|
+
}
|
|
2000
|
+
if (pushUpdates && this.prefetch?.accumulator) {
|
|
2001
|
+
const targetPrefetchKey = idAgnosticQueryKey(queryRequestCoerced);
|
|
2002
|
+
const mergePrefetchedResults = async (from, results) => {
|
|
2003
|
+
const peerHash = from.hashcode();
|
|
2004
|
+
const existingBuffer = peerBufferMap.get(peerHash);
|
|
2005
|
+
const buffer = existingBuffer?.buffer || [];
|
|
2006
|
+
if (results.kept === 0n && results.results.length === 0) {
|
|
2007
|
+
peerBufferMap.set(peerHash, {
|
|
2008
|
+
buffer,
|
|
2009
|
+
kept: Number(results.kept),
|
|
2010
|
+
});
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
for (const result of results.results) {
|
|
2014
|
+
const indexKey = indexerTypes.toId(this.indexByResolver(result.value)).primitive;
|
|
2015
|
+
if (result instanceof types.ResultValue) {
|
|
2016
|
+
const existingIndexed = indexedPlaceholders?.get(indexKey);
|
|
2017
|
+
if (existingIndexed) {
|
|
2018
|
+
existingIndexed.value =
|
|
2019
|
+
result.value;
|
|
2020
|
+
existingIndexed.context = result.context;
|
|
2021
|
+
existingIndexed.from = from;
|
|
2022
|
+
existingIndexed.indexed = await this.resolveIndexed(result, results.results);
|
|
2023
|
+
indexedPlaceholders?.delete(indexKey);
|
|
2024
|
+
continue;
|
|
2025
|
+
}
|
|
2026
|
+
if (visited.has(indexKey)) {
|
|
2027
|
+
continue;
|
|
2028
|
+
}
|
|
2029
|
+
visited.add(indexKey);
|
|
2030
|
+
buffer.push({
|
|
2031
|
+
value: result.value,
|
|
2032
|
+
context: result.context,
|
|
2033
|
+
from,
|
|
2034
|
+
indexed: await this.resolveIndexed(result, results.results),
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
else {
|
|
2038
|
+
if (visited.has(indexKey) && !indexedPlaceholders?.has(indexKey)) {
|
|
2039
|
+
continue;
|
|
2040
|
+
}
|
|
2041
|
+
visited.add(indexKey);
|
|
2042
|
+
const indexed = coerceWithContext(result.indexed || result.value, result.context);
|
|
2043
|
+
const placeholder = {
|
|
2044
|
+
value: result.value,
|
|
2045
|
+
context: result.context,
|
|
2046
|
+
from,
|
|
2047
|
+
indexed,
|
|
2048
|
+
};
|
|
2049
|
+
buffer.push(placeholder);
|
|
2050
|
+
ensureIndexedPlaceholders().set(indexKey, placeholder);
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
peerBufferMap.set(peerHash, {
|
|
2054
|
+
buffer,
|
|
2055
|
+
kept: Number(results.kept),
|
|
2056
|
+
});
|
|
2057
|
+
};
|
|
2058
|
+
const consumePrefetch = async (consumable) => {
|
|
2059
|
+
const request = consumable.response?.request;
|
|
2060
|
+
if (!request) {
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
if (idAgnosticQueryKey(request) !== targetPrefetchKey) {
|
|
2064
|
+
return;
|
|
2065
|
+
}
|
|
2066
|
+
try {
|
|
2067
|
+
const prepared = await introduceEntries(queryRequestCoerced, [
|
|
2068
|
+
{
|
|
2069
|
+
response: consumable.response.results,
|
|
2070
|
+
from: consumable.from,
|
|
2071
|
+
},
|
|
2072
|
+
], this.documentType, this.indexedType, this._sync, options);
|
|
2073
|
+
for (const response of prepared) {
|
|
2074
|
+
if (!response.from) {
|
|
2075
|
+
continue;
|
|
2076
|
+
}
|
|
2077
|
+
const payload = response.response;
|
|
2078
|
+
if (!(payload instanceof types.Results)) {
|
|
2079
|
+
continue;
|
|
2080
|
+
}
|
|
2081
|
+
await mergePrefetchedResults(response.from, payload);
|
|
2082
|
+
}
|
|
2083
|
+
if (!pendingResultsReason) {
|
|
2084
|
+
pendingResultsReason = "change";
|
|
2085
|
+
}
|
|
2086
|
+
signalUpdate("prefetch-add");
|
|
2087
|
+
}
|
|
2088
|
+
catch (error) {
|
|
2089
|
+
logger.warn("Failed to merge prefetched results", error);
|
|
2090
|
+
}
|
|
2091
|
+
};
|
|
2092
|
+
const onPrefetchAdd = (evt) => {
|
|
2093
|
+
void consumePrefetch(evt.detail.consumable);
|
|
2094
|
+
};
|
|
2095
|
+
this.prefetch.accumulator.addEventListener("add", onPrefetchAdd);
|
|
2096
|
+
const cleanupDefault = cleanup;
|
|
2097
|
+
cleanup = () => {
|
|
2098
|
+
this.prefetch?.accumulator.removeEventListener("add", onPrefetchAdd);
|
|
2099
|
+
return cleanupDefault();
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
1738
2102
|
let updatesCleanup;
|
|
1739
2103
|
if (hasLiveUpdates) {
|
|
1740
2104
|
const localHash = this.node.identity.publicKey.hashcode();
|
|
@@ -1876,6 +2240,7 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
1876
2240
|
if (changeForCallback.added.length > 0 ||
|
|
1877
2241
|
changeForCallback.removed.length > 0) {
|
|
1878
2242
|
updateCallbacks?.onChange?.(changeForCallback);
|
|
2243
|
+
signalUpdate("change");
|
|
1879
2244
|
}
|
|
1880
2245
|
}
|
|
1881
2246
|
}
|
|
@@ -1962,8 +2327,8 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
1962
2327
|
return cleanupDefault();
|
|
1963
2328
|
};
|
|
1964
2329
|
}
|
|
1965
|
-
if (
|
|
1966
|
-
|
|
2330
|
+
if (keepRemoteAlive) {
|
|
2331
|
+
const prevMaybeSetDone = maybeSetDone;
|
|
1967
2332
|
maybeSetDone = () => {
|
|
1968
2333
|
if (drain) {
|
|
1969
2334
|
prevMaybeSetDone();
|
|
@@ -1985,7 +2350,16 @@ let DocumentIndex = class DocumentIndex extends Program {
|
|
|
1985
2350
|
close,
|
|
1986
2351
|
next,
|
|
1987
2352
|
done: doneFn,
|
|
1988
|
-
pending: () => {
|
|
2353
|
+
pending: async () => {
|
|
2354
|
+
try {
|
|
2355
|
+
await fetchPromise;
|
|
2356
|
+
if (!done && keepRemoteAlive) {
|
|
2357
|
+
await fetchAtLeast(1);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
catch (error) {
|
|
2361
|
+
logger.warn("Failed to refresh iterator pending state", error);
|
|
2362
|
+
}
|
|
1989
2363
|
let pendingCount = 0;
|
|
1990
2364
|
for (const buffer of peerBufferMap.values()) {
|
|
1991
2365
|
pendingCount += buffer.kept + buffer.buffer.length;
|