@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/src/search.ts
CHANGED
|
@@ -38,7 +38,9 @@ import { copySerialization } from "./borsh.js";
|
|
|
38
38
|
import { MAX_BATCH_SIZE } from "./constants.js";
|
|
39
39
|
import type { DocumentEvents, DocumentsChange } from "./events.js";
|
|
40
40
|
import type { QueryPredictor } from "./most-common-query-predictor.js";
|
|
41
|
-
import MostCommonQueryPredictor
|
|
41
|
+
import MostCommonQueryPredictor, {
|
|
42
|
+
idAgnosticQueryKey,
|
|
43
|
+
} from "./most-common-query-predictor.js";
|
|
42
44
|
import { type Operation, isPutOperation } from "./operation.js";
|
|
43
45
|
import { Prefetch } from "./prefetch.js";
|
|
44
46
|
import type { ExtractArgs } from "./program.js";
|
|
@@ -46,7 +48,9 @@ import { ResumableIterators } from "./resumable-iterator.js";
|
|
|
46
48
|
|
|
47
49
|
const WARNING_WHEN_ITERATING_FOR_MORE_THAN = 1e5;
|
|
48
50
|
|
|
49
|
-
const logger = loggerFn({
|
|
51
|
+
const logger: ReturnType<typeof loggerFn> = loggerFn({
|
|
52
|
+
module: "document-index",
|
|
53
|
+
});
|
|
50
54
|
|
|
51
55
|
type BufferedResult<T, I extends Record<string, any>> = {
|
|
52
56
|
value: T;
|
|
@@ -95,11 +99,16 @@ export type UpdateCallbacks<
|
|
|
95
99
|
* Unified update options for iterate()/search()/get() and hooks.
|
|
96
100
|
* If you pass `true`, defaults to `{ merge: "sorted" }`.
|
|
97
101
|
*/
|
|
102
|
+
export type UpdateModeShortcut = "local" | "remote" | "all";
|
|
103
|
+
|
|
98
104
|
export type UpdateOptions<T, I, Resolve extends boolean | undefined> =
|
|
99
105
|
| boolean
|
|
106
|
+
| UpdateModeShortcut
|
|
100
107
|
| ({
|
|
101
108
|
/** Live update behavior. Only sorted merging is supported; optional filter can mutate/ignore events. */
|
|
102
109
|
merge?: UpdateMergeStrategy<T, I, Resolve>;
|
|
110
|
+
/** Request push-style notifications backed by the prefetch channel. */
|
|
111
|
+
push?: boolean;
|
|
103
112
|
} & UpdateCallbacks<T, I, Resolve>);
|
|
104
113
|
|
|
105
114
|
export type JoiningTargets = {
|
|
@@ -221,7 +230,7 @@ export type ResultsIterator<T> = {
|
|
|
221
230
|
next: (number: number) => Promise<T[]>;
|
|
222
231
|
done: () => boolean;
|
|
223
232
|
all: () => Promise<T[]>;
|
|
224
|
-
pending: () => number | undefined
|
|
233
|
+
pending: () => MaybePromise<number | undefined>;
|
|
225
234
|
first: () => Promise<T | undefined>;
|
|
226
235
|
[Symbol.asyncIterator]: () => AsyncIterator<T>;
|
|
227
236
|
};
|
|
@@ -255,11 +264,21 @@ type ExtractResolveFromOptions<O> =
|
|
|
255
264
|
: true; // if R isn't QueryLike at all, default to true
|
|
256
265
|
|
|
257
266
|
const coerceQuery = <Resolve extends boolean | undefined>(
|
|
258
|
-
query:
|
|
267
|
+
query:
|
|
268
|
+
| types.SearchRequest
|
|
269
|
+
| types.SearchRequestIndexed
|
|
270
|
+
| types.IterationRequest
|
|
271
|
+
| QueryLike,
|
|
259
272
|
options?: QueryOptions<any, any, any, Resolve>,
|
|
260
|
-
|
|
261
|
-
|
|
273
|
+
compatibility?: number,
|
|
274
|
+
):
|
|
275
|
+
| types.SearchRequest
|
|
276
|
+
| types.SearchRequestIndexed
|
|
277
|
+
| types.IterationRequest => {
|
|
278
|
+
const replicate =
|
|
262
279
|
typeof options?.remote !== "boolean" ? options?.remote?.replicate : false;
|
|
280
|
+
const shouldResolve = options?.resolve !== false;
|
|
281
|
+
const useLegacyRequests = compatibility != null && compatibility <= 9;
|
|
263
282
|
|
|
264
283
|
if (
|
|
265
284
|
query instanceof types.SearchRequestIndexed &&
|
|
@@ -269,29 +288,66 @@ const coerceQuery = <Resolve extends boolean | undefined>(
|
|
|
269
288
|
query.replicate = true;
|
|
270
289
|
return query;
|
|
271
290
|
}
|
|
272
|
-
|
|
291
|
+
|
|
292
|
+
if (
|
|
293
|
+
query instanceof types.SearchRequest ||
|
|
294
|
+
query instanceof types.SearchRequestIndexed
|
|
295
|
+
) {
|
|
296
|
+
return query;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (query instanceof types.IterationRequest) {
|
|
300
|
+
if (useLegacyRequests) {
|
|
301
|
+
if (query.resolve === false) {
|
|
302
|
+
return new types.SearchRequestIndexed({
|
|
303
|
+
query: query.query,
|
|
304
|
+
sort: query.sort,
|
|
305
|
+
fetch: query.fetch,
|
|
306
|
+
replicate: query.replicate ?? replicate,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return new types.SearchRequest({
|
|
310
|
+
query: query.query,
|
|
311
|
+
sort: query.sort,
|
|
312
|
+
fetch: query.fetch,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
273
315
|
return query;
|
|
274
316
|
}
|
|
275
317
|
|
|
276
318
|
const queryObject = query as QueryLike;
|
|
277
319
|
|
|
278
|
-
|
|
279
|
-
|
|
320
|
+
if (useLegacyRequests) {
|
|
321
|
+
if (shouldResolve) {
|
|
322
|
+
return new types.SearchRequest({
|
|
280
323
|
query: indexerTypes.toQuery(queryObject.query),
|
|
281
|
-
sort: indexerTypes.toSort(
|
|
282
|
-
})
|
|
283
|
-
: new types.SearchRequestIndexed({
|
|
284
|
-
query: indexerTypes.toQuery(queryObject.query),
|
|
285
|
-
sort: indexerTypes.toSort(query.sort),
|
|
286
|
-
replicate,
|
|
324
|
+
sort: indexerTypes.toSort(queryObject.sort),
|
|
287
325
|
});
|
|
326
|
+
}
|
|
327
|
+
return new types.SearchRequestIndexed({
|
|
328
|
+
query: indexerTypes.toQuery(queryObject.query),
|
|
329
|
+
sort: indexerTypes.toSort(queryObject.sort),
|
|
330
|
+
replicate,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return new types.IterationRequest({
|
|
335
|
+
query: indexerTypes.toQuery(queryObject.query),
|
|
336
|
+
sort: indexerTypes.toSort(queryObject.sort),
|
|
337
|
+
fetch: 10,
|
|
338
|
+
resolve: shouldResolve,
|
|
339
|
+
replicate: shouldResolve ? false : replicate,
|
|
340
|
+
});
|
|
288
341
|
};
|
|
289
342
|
|
|
290
343
|
const introduceEntries = async <
|
|
291
344
|
T,
|
|
292
345
|
I,
|
|
293
346
|
D,
|
|
294
|
-
R extends
|
|
347
|
+
R extends
|
|
348
|
+
| types.SearchRequest
|
|
349
|
+
| types.SearchRequestIndexed
|
|
350
|
+
| types.IterationRequest,
|
|
295
351
|
>(
|
|
296
352
|
queryRequest: R,
|
|
297
353
|
responses: { response: types.AbstractSearchResult; from?: PublicSignKey }[],
|
|
@@ -347,6 +403,37 @@ const dedup = <T>(
|
|
|
347
403
|
return dedup;
|
|
348
404
|
};
|
|
349
405
|
|
|
406
|
+
type AnyIterationRequest =
|
|
407
|
+
| types.SearchRequest
|
|
408
|
+
| types.SearchRequestIndexed
|
|
409
|
+
| types.IterationRequest;
|
|
410
|
+
|
|
411
|
+
const resolvesDocuments = (req?: AnyIterationRequest) => {
|
|
412
|
+
if (!req) {
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
if (req instanceof types.SearchRequestIndexed) {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
if (req instanceof types.IterationRequest) {
|
|
419
|
+
return req.resolve !== false;
|
|
420
|
+
}
|
|
421
|
+
return true;
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const replicatesIndex = (req?: AnyIterationRequest) => {
|
|
425
|
+
if (!req) {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
if (req instanceof types.SearchRequestIndexed) {
|
|
429
|
+
return req.replicate === true;
|
|
430
|
+
}
|
|
431
|
+
if (req instanceof types.IterationRequest) {
|
|
432
|
+
return req.replicate === true;
|
|
433
|
+
}
|
|
434
|
+
return false;
|
|
435
|
+
};
|
|
436
|
+
|
|
350
437
|
function isSubclassOf(
|
|
351
438
|
SubClass: AbstractType<any>,
|
|
352
439
|
SuperClass: AbstractType<any>,
|
|
@@ -365,11 +452,15 @@ function isSubclassOf(
|
|
|
365
452
|
}
|
|
366
453
|
|
|
367
454
|
const DEFAULT_TIMEOUT = 1e4;
|
|
455
|
+
const DISCOVER_TIMEOUT_FALLBACK = 500;
|
|
368
456
|
|
|
369
457
|
const DEFAULT_INDEX_BY = "id";
|
|
370
458
|
|
|
371
459
|
export type CanSearch = (
|
|
372
|
-
request:
|
|
460
|
+
request:
|
|
461
|
+
| types.SearchRequest
|
|
462
|
+
| types.IterationRequest
|
|
463
|
+
| types.CollectNextRequest,
|
|
373
464
|
from: PublicSignKey,
|
|
374
465
|
) => Promise<boolean> | boolean;
|
|
375
466
|
|
|
@@ -443,10 +534,15 @@ export type OpenOptions<
|
|
|
443
534
|
canRead?: CanRead<I>;
|
|
444
535
|
canSearch?: CanSearch;
|
|
445
536
|
replicate: (
|
|
446
|
-
request:
|
|
537
|
+
request:
|
|
538
|
+
| types.SearchRequest
|
|
539
|
+
| types.SearchRequestIndexed
|
|
540
|
+
| types.IterationRequest,
|
|
447
541
|
results: types.Results<
|
|
448
542
|
types.ResultTypeFromRequest<
|
|
449
|
-
|
|
543
|
+
| types.SearchRequest
|
|
544
|
+
| types.SearchRequestIndexed
|
|
545
|
+
| types.IterationRequest,
|
|
450
546
|
T,
|
|
451
547
|
I
|
|
452
548
|
>
|
|
@@ -458,7 +554,7 @@ export type OpenOptions<
|
|
|
458
554
|
resolver?: number;
|
|
459
555
|
query?: QueryCacheOptions;
|
|
460
556
|
};
|
|
461
|
-
compatibility: 6 | 7 | 8 | undefined;
|
|
557
|
+
compatibility: 6 | 7 | 8 | 9 | undefined;
|
|
462
558
|
maybeOpen: (value: T & Program) => Promise<T & Program>;
|
|
463
559
|
prefetch?: boolean | Partial<PrefetchOptions>;
|
|
464
560
|
includeIndexed?: boolean; // if true, indexed representations will always be included in the search results
|
|
@@ -518,7 +614,7 @@ export class DocumentIndex<
|
|
|
518
614
|
private _prefetch?: PrefetchOptions | undefined;
|
|
519
615
|
private includeIndexed: boolean | undefined = undefined;
|
|
520
616
|
|
|
521
|
-
compatibility: 6 | 7 | 8 | undefined;
|
|
617
|
+
compatibility: 6 | 7 | 8 | 9 | undefined;
|
|
522
618
|
|
|
523
619
|
// Transformation, indexer
|
|
524
620
|
/* fields: IndexableFields<T, I>; */
|
|
@@ -526,7 +622,10 @@ export class DocumentIndex<
|
|
|
526
622
|
private _valueEncoding: Encoding<T>;
|
|
527
623
|
|
|
528
624
|
private _sync: <V extends types.ResultValue<T> | types.ResultIndexedValue<I>>(
|
|
529
|
-
request:
|
|
625
|
+
request:
|
|
626
|
+
| types.SearchRequest
|
|
627
|
+
| types.SearchRequestIndexed
|
|
628
|
+
| types.IterationRequest,
|
|
530
629
|
results: types.Results<V>,
|
|
531
630
|
) => Promise<void>;
|
|
532
631
|
|
|
@@ -550,15 +649,20 @@ export class DocumentIndex<
|
|
|
550
649
|
keptInIndex: number;
|
|
551
650
|
timeout: ReturnType<typeof setTimeout>;
|
|
552
651
|
queue: indexerTypes.IndexedResult<WithContext<I>>[];
|
|
553
|
-
fromQuery:
|
|
652
|
+
fromQuery:
|
|
653
|
+
| types.SearchRequest
|
|
654
|
+
| types.SearchRequestIndexed
|
|
655
|
+
| types.IterationRequest;
|
|
554
656
|
}
|
|
555
657
|
>;
|
|
658
|
+
private iteratorKeepAliveTimers?: Map<string, ReturnType<typeof setTimeout>>;
|
|
556
659
|
|
|
557
660
|
constructor(properties?: {
|
|
558
661
|
query?: RPC<types.AbstractSearchRequest, types.AbstractSearchResult>;
|
|
559
662
|
}) {
|
|
560
663
|
super();
|
|
561
664
|
this._query = properties?.query || new RPC();
|
|
665
|
+
this.iteratorKeepAliveTimers = new Map();
|
|
562
666
|
}
|
|
563
667
|
|
|
564
668
|
get valueEncoding() {
|
|
@@ -628,10 +732,15 @@ export class DocumentIndex<
|
|
|
628
732
|
this.dbType = properties.dbType;
|
|
629
733
|
this._resultQueue = new Map();
|
|
630
734
|
this._sync = (request, results) => {
|
|
631
|
-
let rq:
|
|
735
|
+
let rq:
|
|
736
|
+
| types.SearchRequest
|
|
737
|
+
| types.SearchRequestIndexed
|
|
738
|
+
| types.IterationRequest;
|
|
632
739
|
let rs: types.Results<
|
|
633
740
|
types.ResultTypeFromRequest<
|
|
634
|
-
|
|
741
|
+
| types.SearchRequest
|
|
742
|
+
| types.SearchRequestIndexed
|
|
743
|
+
| types.IterationRequest,
|
|
635
744
|
T,
|
|
636
745
|
I
|
|
637
746
|
>
|
|
@@ -643,7 +752,9 @@ export class DocumentIndex<
|
|
|
643
752
|
rq = request;
|
|
644
753
|
rs = results as types.Results<
|
|
645
754
|
types.ResultTypeFromRequest<
|
|
646
|
-
|
|
755
|
+
| types.SearchRequest
|
|
756
|
+
| types.SearchRequestIndexed
|
|
757
|
+
| types.IterationRequest,
|
|
647
758
|
T,
|
|
648
759
|
I
|
|
649
760
|
>
|
|
@@ -775,7 +886,8 @@ export class DocumentIndex<
|
|
|
775
886
|
if (
|
|
776
887
|
this.prefetch?.predictor &&
|
|
777
888
|
(query instanceof types.SearchRequest ||
|
|
778
|
-
query instanceof types.SearchRequestIndexed
|
|
889
|
+
query instanceof types.SearchRequestIndexed ||
|
|
890
|
+
query instanceof types.IterationRequest)
|
|
779
891
|
) {
|
|
780
892
|
const { ignore } = this.prefetch.predictor.onRequest(query, {
|
|
781
893
|
from: ctx.from!,
|
|
@@ -792,6 +904,7 @@ export class DocumentIndex<
|
|
|
792
904
|
query as
|
|
793
905
|
| types.SearchRequest
|
|
794
906
|
| types.SearchRequestIndexed
|
|
907
|
+
| types.IterationRequest
|
|
795
908
|
| types.CollectNextRequest,
|
|
796
909
|
{
|
|
797
910
|
from: ctx.from!,
|
|
@@ -802,15 +915,20 @@ export class DocumentIndex<
|
|
|
802
915
|
query:
|
|
803
916
|
| types.SearchRequest
|
|
804
917
|
| types.SearchRequestIndexed
|
|
918
|
+
| types.IterationRequest
|
|
805
919
|
| types.CollectNextRequest,
|
|
806
920
|
ctx: { from: PublicSignKey },
|
|
807
921
|
) {
|
|
808
922
|
if (
|
|
809
923
|
this.canSearch &&
|
|
810
924
|
(query instanceof types.SearchRequest ||
|
|
925
|
+
query instanceof types.IterationRequest ||
|
|
811
926
|
query instanceof types.CollectNextRequest) &&
|
|
812
927
|
!(await this.canSearch(
|
|
813
|
-
query as
|
|
928
|
+
query as
|
|
929
|
+
| types.SearchRequest
|
|
930
|
+
| types.IterationRequest
|
|
931
|
+
| types.CollectNextRequest,
|
|
814
932
|
ctx.from,
|
|
815
933
|
))
|
|
816
934
|
) {
|
|
@@ -820,17 +938,23 @@ export class DocumentIndex<
|
|
|
820
938
|
if (query instanceof types.CloseIteratorRequest) {
|
|
821
939
|
this.processCloseIteratorRequest(query, ctx.from);
|
|
822
940
|
} else {
|
|
941
|
+
const fromQueued =
|
|
942
|
+
query instanceof types.CollectNextRequest
|
|
943
|
+
? this._resultQueue.get(query.idString)?.fromQuery
|
|
944
|
+
: undefined;
|
|
945
|
+
const queryResolvesDocuments =
|
|
946
|
+
query instanceof types.CollectNextRequest
|
|
947
|
+
? resolvesDocuments(fromQueued)
|
|
948
|
+
: resolvesDocuments(query as AnyIterationRequest);
|
|
949
|
+
|
|
823
950
|
const shouldIncludedIndexedResults =
|
|
824
|
-
this.includeIndexed &&
|
|
825
|
-
(query instanceof types.SearchRequest ||
|
|
826
|
-
(query instanceof types.CollectNextRequest &&
|
|
827
|
-
this._resultQueue.get(query.idString)?.fromQuery instanceof
|
|
828
|
-
types.SearchRequest)); // we do this check here because this._resultQueue might be emptied when this.processQuery is called
|
|
951
|
+
this.includeIndexed && queryResolvesDocuments;
|
|
829
952
|
|
|
830
953
|
const results = await this.processQuery(
|
|
831
954
|
query as
|
|
832
955
|
| types.SearchRequest
|
|
833
956
|
| types.SearchRequestIndexed
|
|
957
|
+
| types.IterationRequest
|
|
834
958
|
| types.CollectNextRequest,
|
|
835
959
|
ctx.from,
|
|
836
960
|
false,
|
|
@@ -1156,19 +1280,29 @@ export class DocumentIndex<
|
|
|
1156
1280
|
| types.ResultIndexedValue<WithContext<I>>
|
|
1157
1281
|
>[]
|
|
1158
1282
|
| undefined;
|
|
1283
|
+
|
|
1284
|
+
const runAndClose = async (
|
|
1285
|
+
req: types.SearchRequest | types.SearchRequestIndexed,
|
|
1286
|
+
): Promise<typeof results> => {
|
|
1287
|
+
const response = await this.queryCommence(
|
|
1288
|
+
req,
|
|
1289
|
+
coercedOptions as QueryDetailedOptions<T, I, D, boolean | undefined>,
|
|
1290
|
+
);
|
|
1291
|
+
this._resumableIterators.close({ idString: req.idString });
|
|
1292
|
+
this.cancelIteratorKeepAlive(req.idString);
|
|
1293
|
+
return response as typeof results;
|
|
1294
|
+
};
|
|
1159
1295
|
const resolve = coercedOptions?.resolve || coercedOptions?.resolve == null;
|
|
1160
1296
|
let requestClazz = resolve
|
|
1161
1297
|
? types.SearchRequest
|
|
1162
1298
|
: types.SearchRequestIndexed;
|
|
1163
1299
|
if (key instanceof Uint8Array) {
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
coercedOptions as QueryDetailedOptions<T, I, D, boolean | undefined>,
|
|
1171
|
-
);
|
|
1300
|
+
const request = new requestClazz({
|
|
1301
|
+
query: [
|
|
1302
|
+
new indexerTypes.ByteMatchQuery({ key: this.indexBy, value: key }),
|
|
1303
|
+
],
|
|
1304
|
+
});
|
|
1305
|
+
results = await runAndClose(request);
|
|
1172
1306
|
} else {
|
|
1173
1307
|
const indexableKey = indexerTypes.toIdeable(key);
|
|
1174
1308
|
|
|
@@ -1176,42 +1310,48 @@ export class DocumentIndex<
|
|
|
1176
1310
|
typeof indexableKey === "number" ||
|
|
1177
1311
|
typeof indexableKey === "bigint"
|
|
1178
1312
|
) {
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
coercedOptions as QueryDetailedOptions<T, I, D, boolean | undefined>,
|
|
1190
|
-
);
|
|
1313
|
+
const request = new requestClazz({
|
|
1314
|
+
query: [
|
|
1315
|
+
new indexerTypes.IntegerCompare({
|
|
1316
|
+
key: this.indexBy,
|
|
1317
|
+
compare: indexerTypes.Compare.Equal,
|
|
1318
|
+
value: indexableKey,
|
|
1319
|
+
}),
|
|
1320
|
+
],
|
|
1321
|
+
});
|
|
1322
|
+
results = await runAndClose(request);
|
|
1191
1323
|
} else if (typeof indexableKey === "string") {
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
coercedOptions as QueryDetailedOptions<T, I, D, boolean | undefined>,
|
|
1202
|
-
);
|
|
1324
|
+
const request = new requestClazz({
|
|
1325
|
+
query: [
|
|
1326
|
+
new indexerTypes.StringMatch({
|
|
1327
|
+
key: this.indexBy,
|
|
1328
|
+
value: indexableKey,
|
|
1329
|
+
}),
|
|
1330
|
+
],
|
|
1331
|
+
});
|
|
1332
|
+
results = await runAndClose(request);
|
|
1203
1333
|
} else if (indexableKey instanceof Uint8Array) {
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1334
|
+
const request = new requestClazz({
|
|
1335
|
+
query: [
|
|
1336
|
+
new indexerTypes.ByteMatchQuery({
|
|
1337
|
+
key: this.indexBy,
|
|
1338
|
+
value: indexableKey,
|
|
1339
|
+
}),
|
|
1340
|
+
],
|
|
1341
|
+
});
|
|
1342
|
+
results = await runAndClose(request);
|
|
1343
|
+
} else if ((indexableKey as any) instanceof ArrayBuffer) {
|
|
1344
|
+
const request = new requestClazz({
|
|
1345
|
+
query: [
|
|
1346
|
+
new indexerTypes.ByteMatchQuery({
|
|
1347
|
+
key: this.indexBy,
|
|
1348
|
+
value: new Uint8Array(indexableKey),
|
|
1349
|
+
}),
|
|
1350
|
+
],
|
|
1351
|
+
});
|
|
1352
|
+
results = await runAndClose(request);
|
|
1353
|
+
} else {
|
|
1354
|
+
throw new Error("Unsupported key type");
|
|
1215
1355
|
}
|
|
1216
1356
|
}
|
|
1217
1357
|
|
|
@@ -1317,6 +1457,7 @@ export class DocumentIndex<
|
|
|
1317
1457
|
R extends
|
|
1318
1458
|
| types.SearchRequest
|
|
1319
1459
|
| types.SearchRequestIndexed
|
|
1460
|
+
| types.IterationRequest
|
|
1320
1461
|
| types.CollectNextRequest,
|
|
1321
1462
|
>(
|
|
1322
1463
|
query: R,
|
|
@@ -1338,25 +1479,57 @@ export class DocumentIndex<
|
|
|
1338
1479
|
let indexedResult: indexerTypes.IndexedResults<WithContext<I>> | undefined =
|
|
1339
1480
|
undefined;
|
|
1340
1481
|
|
|
1341
|
-
let fromQuery:
|
|
1482
|
+
let fromQuery:
|
|
1483
|
+
| types.SearchRequest
|
|
1484
|
+
| types.SearchRequestIndexed
|
|
1485
|
+
| types.IterationRequest
|
|
1486
|
+
| undefined;
|
|
1487
|
+
let keepAliveRequest: types.IterationRequest | undefined;
|
|
1342
1488
|
if (
|
|
1343
1489
|
query instanceof types.SearchRequest ||
|
|
1344
|
-
query instanceof types.SearchRequestIndexed
|
|
1490
|
+
query instanceof types.SearchRequestIndexed ||
|
|
1491
|
+
query instanceof types.IterationRequest
|
|
1345
1492
|
) {
|
|
1346
1493
|
fromQuery = query;
|
|
1347
|
-
|
|
1494
|
+
if (
|
|
1495
|
+
!isLocal &&
|
|
1496
|
+
query instanceof types.IterationRequest &&
|
|
1497
|
+
query.keepAliveTtl != null
|
|
1498
|
+
) {
|
|
1499
|
+
keepAliveRequest = query;
|
|
1500
|
+
}
|
|
1501
|
+
indexedResult = await this._resumableIterators.iterateAndFetch(query, {
|
|
1502
|
+
keepAlive: keepAliveRequest !== undefined,
|
|
1503
|
+
});
|
|
1348
1504
|
} else if (query instanceof types.CollectNextRequest) {
|
|
1349
|
-
|
|
1505
|
+
const cachedRequest =
|
|
1350
1506
|
prevQueued?.fromQuery ||
|
|
1351
1507
|
this._resumableIterators.queues.get(query.idString)?.request;
|
|
1508
|
+
fromQuery = cachedRequest;
|
|
1509
|
+
if (
|
|
1510
|
+
!isLocal &&
|
|
1511
|
+
cachedRequest instanceof types.IterationRequest &&
|
|
1512
|
+
cachedRequest.keepAliveTtl != null
|
|
1513
|
+
) {
|
|
1514
|
+
keepAliveRequest = cachedRequest;
|
|
1515
|
+
}
|
|
1352
1516
|
indexedResult =
|
|
1353
1517
|
prevQueued?.keptInIndex === 0
|
|
1354
1518
|
? []
|
|
1355
|
-
: await this._resumableIterators.next(query
|
|
1519
|
+
: await this._resumableIterators.next(query, {
|
|
1520
|
+
keepAlive: keepAliveRequest !== undefined,
|
|
1521
|
+
});
|
|
1356
1522
|
} else {
|
|
1357
1523
|
throw new Error("Unsupported");
|
|
1358
1524
|
}
|
|
1359
1525
|
|
|
1526
|
+
if (!isLocal && keepAliveRequest) {
|
|
1527
|
+
this.scheduleIteratorKeepAlive(
|
|
1528
|
+
query.idString,
|
|
1529
|
+
keepAliveRequest.keepAliveTtl,
|
|
1530
|
+
);
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1360
1533
|
let resultSize = 0;
|
|
1361
1534
|
|
|
1362
1535
|
let toIterate = prevQueued
|
|
@@ -1381,13 +1554,15 @@ export class DocumentIndex<
|
|
|
1381
1554
|
keptInIndex: kept,
|
|
1382
1555
|
fromQuery: (fromQuery || query) as
|
|
1383
1556
|
| types.SearchRequest
|
|
1384
|
-
| types.SearchRequestIndexed
|
|
1557
|
+
| types.SearchRequestIndexed
|
|
1558
|
+
| types.IterationRequest,
|
|
1385
1559
|
};
|
|
1386
1560
|
this._resultQueue.set(query.idString, prevQueued);
|
|
1387
1561
|
}
|
|
1388
1562
|
|
|
1389
1563
|
const filteredResults: types.Result[] = [];
|
|
1390
|
-
|
|
1564
|
+
const resolveDocumentsFlag = resolvesDocuments(fromQuery);
|
|
1565
|
+
const replicateIndexFlag = replicatesIndex(fromQuery);
|
|
1391
1566
|
for (const result of toIterate) {
|
|
1392
1567
|
if (!isLocal) {
|
|
1393
1568
|
resultSize += result.value.__context.size;
|
|
@@ -1407,7 +1582,7 @@ export class DocumentIndex<
|
|
|
1407
1582
|
) {
|
|
1408
1583
|
continue;
|
|
1409
1584
|
}
|
|
1410
|
-
if (
|
|
1585
|
+
if (resolveDocumentsFlag) {
|
|
1411
1586
|
const value = await this.resolveDocument({
|
|
1412
1587
|
indexed: result.value,
|
|
1413
1588
|
head: result.value.__context.head,
|
|
@@ -1425,11 +1600,11 @@ export class DocumentIndex<
|
|
|
1425
1600
|
indexed: indexedUnwrapped,
|
|
1426
1601
|
}),
|
|
1427
1602
|
);
|
|
1428
|
-
} else
|
|
1603
|
+
} else {
|
|
1429
1604
|
const context = result.value.__context;
|
|
1430
1605
|
const head = await this._log.log.get(context.head);
|
|
1431
1606
|
// assume remote peer will start to replicate (TODO is this ideal?)
|
|
1432
|
-
if (
|
|
1607
|
+
if (replicateIndexFlag) {
|
|
1433
1608
|
this._log.addPeersToGidPeerHistory(context.gid, [from.hashcode()]);
|
|
1434
1609
|
}
|
|
1435
1610
|
|
|
@@ -1456,10 +1631,53 @@ export class DocumentIndex<
|
|
|
1456
1631
|
return results;
|
|
1457
1632
|
}
|
|
1458
1633
|
|
|
1634
|
+
private scheduleIteratorKeepAlive(idString: string, ttl?: bigint) {
|
|
1635
|
+
if (ttl == null) {
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
const ttlNumber = Number(ttl);
|
|
1639
|
+
if (!Number.isFinite(ttlNumber) || ttlNumber <= 0) {
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
// Cap max timeout to 1 day (TODO make configurable?)
|
|
1644
|
+
const delay = Math.max(1, Math.min(ttlNumber, 86400000));
|
|
1645
|
+
this.cancelIteratorKeepAlive(idString);
|
|
1646
|
+
const timers =
|
|
1647
|
+
this.iteratorKeepAliveTimers ??
|
|
1648
|
+
(this.iteratorKeepAliveTimers = new Map<
|
|
1649
|
+
string,
|
|
1650
|
+
ReturnType<typeof setTimeout>
|
|
1651
|
+
>());
|
|
1652
|
+
const timer = setTimeout(() => {
|
|
1653
|
+
timers.delete(idString);
|
|
1654
|
+
const queued = this._resultQueue.get(idString);
|
|
1655
|
+
if (queued) {
|
|
1656
|
+
clearTimeout(queued.timeout);
|
|
1657
|
+
this._resultQueue.delete(idString);
|
|
1658
|
+
}
|
|
1659
|
+
this._resumableIterators.close({ idString });
|
|
1660
|
+
}, delay);
|
|
1661
|
+
timers.set(idString, timer);
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
private cancelIteratorKeepAlive(idString: string) {
|
|
1665
|
+
const timers = this.iteratorKeepAliveTimers;
|
|
1666
|
+
if (!timers) {
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
const timer = timers.get(idString);
|
|
1670
|
+
if (timer) {
|
|
1671
|
+
clearTimeout(timer);
|
|
1672
|
+
timers.delete(idString);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1459
1676
|
private clearResultsQueue(
|
|
1460
1677
|
query:
|
|
1461
1678
|
| types.SearchRequest
|
|
1462
1679
|
| types.SearchRequestIndexed
|
|
1680
|
+
| types.IterationRequest
|
|
1463
1681
|
| types.CollectNextRequest
|
|
1464
1682
|
| types.CloseIteratorRequest,
|
|
1465
1683
|
) {
|
|
@@ -1478,6 +1696,7 @@ export class DocumentIndex<
|
|
|
1478
1696
|
for (const [key, queue] of this._resultQueue) {
|
|
1479
1697
|
clearTimeout(queue.timeout);
|
|
1480
1698
|
this._resultQueue.delete(key);
|
|
1699
|
+
this.cancelIteratorKeepAlive(key);
|
|
1481
1700
|
this._resumableIterators.close({ idString: key });
|
|
1482
1701
|
}
|
|
1483
1702
|
}
|
|
@@ -1644,6 +1863,7 @@ export class DocumentIndex<
|
|
|
1644
1863
|
logger.info("Ignoring close iterator request from different peer");
|
|
1645
1864
|
return;
|
|
1646
1865
|
}
|
|
1866
|
+
this.cancelIteratorKeepAlive(query.idString);
|
|
1647
1867
|
this.clearResultsQueue(query);
|
|
1648
1868
|
return this._resumableIterators.close(query);
|
|
1649
1869
|
}
|
|
@@ -1655,7 +1875,10 @@ export class DocumentIndex<
|
|
|
1655
1875
|
* @returns
|
|
1656
1876
|
*/
|
|
1657
1877
|
private async queryCommence<
|
|
1658
|
-
R extends
|
|
1878
|
+
R extends
|
|
1879
|
+
| types.SearchRequest
|
|
1880
|
+
| types.SearchRequestIndexed
|
|
1881
|
+
| types.IterationRequest,
|
|
1659
1882
|
RT extends types.Result = types.ResultTypeFromRequest<R, T, I>,
|
|
1660
1883
|
>(
|
|
1661
1884
|
queryRequest: R,
|
|
@@ -1899,7 +2122,11 @@ export class DocumentIndex<
|
|
|
1899
2122
|
* @returns
|
|
1900
2123
|
*/
|
|
1901
2124
|
public async search<
|
|
1902
|
-
R extends
|
|
2125
|
+
R extends
|
|
2126
|
+
| types.SearchRequest
|
|
2127
|
+
| types.SearchRequestIndexed
|
|
2128
|
+
| types.IterationRequest
|
|
2129
|
+
| QueryLike,
|
|
1903
2130
|
O extends SearchOptions<T, I, D, Resolve>,
|
|
1904
2131
|
Resolve extends boolean = ExtractResolveFromOptions<O>,
|
|
1905
2132
|
>(
|
|
@@ -1907,8 +2134,11 @@ export class DocumentIndex<
|
|
|
1907
2134
|
options?: O,
|
|
1908
2135
|
): Promise<ValueTypeFromRequest<Resolve, T, I>[]> {
|
|
1909
2136
|
// Set fetch to search size, or max value (default to max u32 (4294967295))
|
|
1910
|
-
const coercedRequest
|
|
1911
|
-
|
|
2137
|
+
const coercedRequest = coerceQuery(
|
|
2138
|
+
queryRequest,
|
|
2139
|
+
options,
|
|
2140
|
+
this.compatibility,
|
|
2141
|
+
);
|
|
1912
2142
|
coercedRequest.fetch = coercedRequest.fetch ?? 0xffffffff;
|
|
1913
2143
|
|
|
1914
2144
|
// So that the iterator is pre-fetching the right amount of entries
|
|
@@ -1987,7 +2217,7 @@ export class DocumentIndex<
|
|
|
1987
2217
|
/**
|
|
1988
2218
|
* Query and retrieve documents in a iterator
|
|
1989
2219
|
* @param queryRequest
|
|
1990
|
-
* @param
|
|
2220
|
+
* @param optionsArg
|
|
1991
2221
|
* @returns
|
|
1992
2222
|
*/
|
|
1993
2223
|
public iterate<
|
|
@@ -1996,8 +2226,9 @@ export class DocumentIndex<
|
|
|
1996
2226
|
Resolve extends boolean | undefined = ExtractResolveFromOptions<O>,
|
|
1997
2227
|
>(
|
|
1998
2228
|
queryRequest?: R,
|
|
1999
|
-
|
|
2229
|
+
optionsArg?: QueryOptions<T, I, D, Resolve>,
|
|
2000
2230
|
): ResultsIterator<ValueTypeFromRequest<Resolve, T, I>> {
|
|
2231
|
+
let options = optionsArg;
|
|
2001
2232
|
if (
|
|
2002
2233
|
queryRequest instanceof types.SearchRequest &&
|
|
2003
2234
|
options?.resolve === false
|
|
@@ -2005,14 +2236,44 @@ export class DocumentIndex<
|
|
|
2005
2236
|
throw new Error("Cannot use resolve=false with SearchRequest"); // TODO make this work
|
|
2006
2237
|
}
|
|
2007
2238
|
|
|
2008
|
-
let queryRequestCoerced
|
|
2009
|
-
|
|
2239
|
+
let queryRequestCoerced = coerceQuery(
|
|
2240
|
+
queryRequest ?? {},
|
|
2241
|
+
options,
|
|
2242
|
+
this.compatibility,
|
|
2243
|
+
);
|
|
2244
|
+
|
|
2245
|
+
const {
|
|
2246
|
+
mergePolicy,
|
|
2247
|
+
push: pushUpdates,
|
|
2248
|
+
callbacks: updateCallbacksRaw,
|
|
2249
|
+
} = normalizeUpdatesOption(options?.updates);
|
|
2250
|
+
const hasLiveUpdates = mergePolicy !== undefined;
|
|
2251
|
+
const originalRemote = options?.remote;
|
|
2252
|
+
let remoteOptions =
|
|
2253
|
+
typeof originalRemote === "boolean"
|
|
2254
|
+
? originalRemote
|
|
2255
|
+
: originalRemote
|
|
2256
|
+
? { ...originalRemote }
|
|
2257
|
+
: undefined;
|
|
2258
|
+
if (pushUpdates && remoteOptions !== false) {
|
|
2259
|
+
if (typeof remoteOptions === "object") {
|
|
2260
|
+
if (remoteOptions.replicate !== true) {
|
|
2261
|
+
remoteOptions.replicate = true;
|
|
2262
|
+
}
|
|
2263
|
+
} else if (remoteOptions === undefined || remoteOptions === true) {
|
|
2264
|
+
remoteOptions = { replicate: true };
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
if (remoteOptions !== originalRemote) {
|
|
2268
|
+
options = Object.assign({}, options, { remote: remoteOptions });
|
|
2269
|
+
}
|
|
2010
2270
|
|
|
2011
2271
|
let resolve = options?.resolve !== false;
|
|
2012
2272
|
if (
|
|
2273
|
+
!(queryRequestCoerced instanceof types.IterationRequest) &&
|
|
2013
2274
|
options?.remote &&
|
|
2014
2275
|
typeof options.remote !== "boolean" &&
|
|
2015
|
-
options.remote.replicate &&
|
|
2276
|
+
(options.remote.replicate || pushUpdates) &&
|
|
2016
2277
|
options?.resolve !== false
|
|
2017
2278
|
) {
|
|
2018
2279
|
if (
|
|
@@ -2032,7 +2293,7 @@ export class DocumentIndex<
|
|
|
2032
2293
|
let replicate =
|
|
2033
2294
|
options?.remote &&
|
|
2034
2295
|
typeof options.remote !== "boolean" &&
|
|
2035
|
-
options.remote.replicate;
|
|
2296
|
+
(options.remote.replicate || pushUpdates);
|
|
2036
2297
|
if (
|
|
2037
2298
|
replicate &&
|
|
2038
2299
|
queryRequestCoerced instanceof types.SearchRequestIndexed
|
|
@@ -2109,6 +2370,8 @@ export class DocumentIndex<
|
|
|
2109
2370
|
|
|
2110
2371
|
let warmupPromise: Promise<any> | undefined = undefined;
|
|
2111
2372
|
|
|
2373
|
+
let discoveredTargetHashes: string[] | undefined;
|
|
2374
|
+
|
|
2112
2375
|
if (typeof options?.remote === "object") {
|
|
2113
2376
|
let waitForTime: number | undefined = undefined;
|
|
2114
2377
|
if (options.remote.wait) {
|
|
@@ -2145,11 +2408,26 @@ export class DocumentIndex<
|
|
|
2145
2408
|
}
|
|
2146
2409
|
|
|
2147
2410
|
if (options.remote.reach?.discover) {
|
|
2148
|
-
|
|
2411
|
+
const discoverTimeout =
|
|
2412
|
+
waitForTime ??
|
|
2413
|
+
(options.remote.wait ? DEFAULT_TIMEOUT : DISCOVER_TIMEOUT_FALLBACK);
|
|
2414
|
+
const discoverPromise = this.waitFor(options.remote.reach.discover, {
|
|
2149
2415
|
signal: ensureController().signal,
|
|
2150
2416
|
seek: "present",
|
|
2151
|
-
timeout:
|
|
2152
|
-
})
|
|
2417
|
+
timeout: discoverTimeout,
|
|
2418
|
+
})
|
|
2419
|
+
.then((hashes) => {
|
|
2420
|
+
discoveredTargetHashes = hashes;
|
|
2421
|
+
})
|
|
2422
|
+
.catch((error) => {
|
|
2423
|
+
if (error instanceof TimeoutError || error instanceof AbortError) {
|
|
2424
|
+
discoveredTargetHashes = [];
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2427
|
+
throw error;
|
|
2428
|
+
});
|
|
2429
|
+
const prior = warmupPromise ?? Promise.resolve();
|
|
2430
|
+
warmupPromise = prior.then(() => discoverPromise);
|
|
2153
2431
|
options.remote.reach.eager = true; // include the results from the discovered peer even if it is not mature
|
|
2154
2432
|
}
|
|
2155
2433
|
|
|
@@ -2181,6 +2459,18 @@ export class DocumentIndex<
|
|
|
2181
2459
|
): Promise<boolean> => {
|
|
2182
2460
|
await warmupPromise;
|
|
2183
2461
|
let hasMore = false;
|
|
2462
|
+
const discoverTargets =
|
|
2463
|
+
typeof options?.remote === "object"
|
|
2464
|
+
? options.remote.reach?.discover
|
|
2465
|
+
: undefined;
|
|
2466
|
+
const initialRemoteTargets =
|
|
2467
|
+
discoveredTargetHashes !== undefined
|
|
2468
|
+
? discoveredTargetHashes
|
|
2469
|
+
: discoverTargets?.map((pk) => pk.hashcode().toString());
|
|
2470
|
+
const skipRemoteDueToDiscovery =
|
|
2471
|
+
typeof options?.remote === "object" &&
|
|
2472
|
+
options.remote.reach?.discover &&
|
|
2473
|
+
discoveredTargetHashes?.length === 0;
|
|
2184
2474
|
|
|
2185
2475
|
queryRequestCoerced.fetch = n;
|
|
2186
2476
|
await this.queryCommence(
|
|
@@ -2188,12 +2478,12 @@ export class DocumentIndex<
|
|
|
2188
2478
|
{
|
|
2189
2479
|
local: fetchOptions?.from != null ? false : options?.local,
|
|
2190
2480
|
remote:
|
|
2191
|
-
options?.remote !== false
|
|
2481
|
+
options?.remote !== false && !skipRemoteDueToDiscovery
|
|
2192
2482
|
? {
|
|
2193
2483
|
...(typeof options?.remote === "object"
|
|
2194
2484
|
? options.remote
|
|
2195
2485
|
: {}),
|
|
2196
|
-
from: fetchOptions?.from,
|
|
2486
|
+
from: fetchOptions?.from ?? initialRemoteTargets,
|
|
2197
2487
|
}
|
|
2198
2488
|
: false,
|
|
2199
2489
|
resolve,
|
|
@@ -2210,17 +2500,25 @@ export class DocumentIndex<
|
|
|
2210
2500
|
types.ResultTypeFromRequest<R, T, I>
|
|
2211
2501
|
>;
|
|
2212
2502
|
|
|
2503
|
+
const existingBuffer = peerBufferMap.get(from.hashcode());
|
|
2504
|
+
const buffer: BufferedResult<
|
|
2505
|
+
types.ResultTypeFromRequest<R, T, I> | I,
|
|
2506
|
+
I
|
|
2507
|
+
>[] = existingBuffer?.buffer || [];
|
|
2508
|
+
|
|
2213
2509
|
if (results.kept === 0n && results.results.length === 0) {
|
|
2510
|
+
if (keepRemoteAlive) {
|
|
2511
|
+
peerBufferMap.set(from.hashcode(), {
|
|
2512
|
+
buffer,
|
|
2513
|
+
kept: Number(response.kept),
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2214
2516
|
return;
|
|
2215
2517
|
}
|
|
2216
2518
|
|
|
2217
2519
|
if (results.kept > 0n) {
|
|
2218
2520
|
hasMore = true;
|
|
2219
2521
|
}
|
|
2220
|
-
const buffer: BufferedResult<
|
|
2221
|
-
types.ResultTypeFromRequest<R, T, I> | I,
|
|
2222
|
-
I
|
|
2223
|
-
>[] = peerBufferMap.get(from.hashcode())?.buffer || [];
|
|
2224
2522
|
|
|
2225
2523
|
for (const result of results.results) {
|
|
2226
2524
|
const indexKey = indexerTypes.toId(
|
|
@@ -2322,7 +2620,8 @@ export class DocumentIndex<
|
|
|
2322
2620
|
|
|
2323
2621
|
for (const [peer, buffer] of peerBufferMap) {
|
|
2324
2622
|
if (buffer.buffer.length < n) {
|
|
2325
|
-
|
|
2623
|
+
const hasExistingRemoteResults = buffer.kept > 0;
|
|
2624
|
+
if (!hasExistingRemoteResults && !keepRemoteAlive) {
|
|
2326
2625
|
if (peerBufferMap.get(peer)?.buffer.length === 0) {
|
|
2327
2626
|
peerBufferMap.delete(peer); // No more results
|
|
2328
2627
|
}
|
|
@@ -2332,9 +2631,16 @@ export class DocumentIndex<
|
|
|
2332
2631
|
// TODO buffer more than deleted?
|
|
2333
2632
|
// TODO batch to multiple 'to's
|
|
2334
2633
|
|
|
2634
|
+
const lacking = n - buffer.buffer.length;
|
|
2635
|
+
const amount = lacking > 0 ? lacking : keepRemoteAlive ? 1 : 0;
|
|
2636
|
+
|
|
2637
|
+
if (amount <= 0) {
|
|
2638
|
+
continue;
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2335
2641
|
const collectRequest = new types.CollectNextRequest({
|
|
2336
2642
|
id: queryRequestCoerced.id,
|
|
2337
|
-
amount
|
|
2643
|
+
amount,
|
|
2338
2644
|
});
|
|
2339
2645
|
// Fetch locally?
|
|
2340
2646
|
if (peer === this.node.identity.publicKey.hashcode()) {
|
|
@@ -2351,7 +2657,10 @@ export class DocumentIndex<
|
|
|
2351
2657
|
resultsLeft += Number(results.kept);
|
|
2352
2658
|
|
|
2353
2659
|
if (results.results.length === 0) {
|
|
2354
|
-
if (
|
|
2660
|
+
if (
|
|
2661
|
+
!keepRemoteAlive &&
|
|
2662
|
+
peerBufferMap.get(peer)?.buffer.length === 0
|
|
2663
|
+
) {
|
|
2355
2664
|
peerBufferMap.delete(peer); // No more results
|
|
2356
2665
|
}
|
|
2357
2666
|
} else {
|
|
@@ -2492,7 +2801,10 @@ export class DocumentIndex<
|
|
|
2492
2801
|
}
|
|
2493
2802
|
|
|
2494
2803
|
if (response.response.results.length === 0) {
|
|
2495
|
-
if (
|
|
2804
|
+
if (
|
|
2805
|
+
!keepRemoteAlive &&
|
|
2806
|
+
peerBufferMap.get(peer)?.buffer.length === 0
|
|
2807
|
+
) {
|
|
2496
2808
|
peerBufferMap.delete(peer); // No more results
|
|
2497
2809
|
}
|
|
2498
2810
|
} else {
|
|
@@ -2745,48 +3057,96 @@ export class DocumentIndex<
|
|
|
2745
3057
|
let fetchedFirstForRemote: Set<string> | undefined = undefined;
|
|
2746
3058
|
|
|
2747
3059
|
let updateDeferred: ReturnType<typeof pDefer> | undefined;
|
|
2748
|
-
const signalUpdate = () =>
|
|
3060
|
+
const signalUpdate = (reason?: string) => {
|
|
3061
|
+
updateDeferred?.resolve();
|
|
3062
|
+
};
|
|
2749
3063
|
const _waitForUpdate = () =>
|
|
2750
3064
|
updateDeferred ? updateDeferred.promise : Promise.resolve();
|
|
2751
3065
|
|
|
2752
3066
|
// ---------------- Live updates wiring (sorted-only with optional filter) ----------------
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
3067
|
+
function normalizeUpdatesOption(u?: UpdateOptions<T, I, Resolve>): {
|
|
3068
|
+
mergePolicy?: {
|
|
3069
|
+
merge?:
|
|
3070
|
+
| {
|
|
3071
|
+
filter?: (
|
|
3072
|
+
evt: DocumentsChange<T, I>,
|
|
3073
|
+
) => MaybePromise<DocumentsChange<T, I> | void>;
|
|
3074
|
+
}
|
|
3075
|
+
| undefined;
|
|
3076
|
+
};
|
|
3077
|
+
push: boolean;
|
|
3078
|
+
callbacks?: UpdateCallbacks<T, I, Resolve>;
|
|
3079
|
+
} {
|
|
3080
|
+
const identityFilter = (evt: DocumentsChange<T, I>) => evt;
|
|
3081
|
+
const buildMergePolicy = (
|
|
3082
|
+
merge: UpdateMergeStrategy<T, I, Resolve> | undefined,
|
|
3083
|
+
defaultEnabled: boolean,
|
|
3084
|
+
) => {
|
|
3085
|
+
const effective =
|
|
3086
|
+
merge === undefined ? (defaultEnabled ? true : undefined) : merge;
|
|
3087
|
+
if (effective === undefined || effective === false) {
|
|
3088
|
+
return undefined;
|
|
3089
|
+
}
|
|
3090
|
+
if (effective === true) {
|
|
3091
|
+
return {
|
|
3092
|
+
merge: {
|
|
3093
|
+
filter: identityFilter,
|
|
3094
|
+
},
|
|
3095
|
+
};
|
|
3096
|
+
}
|
|
2768
3097
|
return {
|
|
2769
3098
|
merge: {
|
|
2770
|
-
filter:
|
|
3099
|
+
filter: effective.filter ?? identityFilter,
|
|
2771
3100
|
},
|
|
2772
3101
|
};
|
|
3102
|
+
};
|
|
3103
|
+
|
|
3104
|
+
if (u == null || u === false) {
|
|
3105
|
+
return { push: false };
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
if (u === true) {
|
|
3109
|
+
return {
|
|
3110
|
+
mergePolicy: buildMergePolicy(true, true),
|
|
3111
|
+
push: false,
|
|
3112
|
+
};
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3115
|
+
if (typeof u === "string") {
|
|
3116
|
+
if (u === "remote") {
|
|
3117
|
+
return { push: true };
|
|
3118
|
+
}
|
|
3119
|
+
if (u === "local") {
|
|
3120
|
+
return {
|
|
3121
|
+
mergePolicy: buildMergePolicy(true, true),
|
|
3122
|
+
push: false,
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3125
|
+
if (u === "all") {
|
|
3126
|
+
return {
|
|
3127
|
+
mergePolicy: buildMergePolicy(true, true),
|
|
3128
|
+
push: true,
|
|
3129
|
+
};
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
|
|
2773
3133
|
if (typeof u === "object") {
|
|
3134
|
+
const hasMergeProp = Object.prototype.hasOwnProperty.call(u, "merge");
|
|
3135
|
+
const mergeValue = hasMergeProp ? u.merge : undefined;
|
|
2774
3136
|
return {
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
3137
|
+
mergePolicy: buildMergePolicy(
|
|
3138
|
+
mergeValue,
|
|
3139
|
+
!hasMergeProp || mergeValue === undefined,
|
|
3140
|
+
),
|
|
3141
|
+
push: Boolean(u.push),
|
|
3142
|
+
callbacks: u,
|
|
2781
3143
|
};
|
|
2782
3144
|
}
|
|
2783
|
-
return undefined;
|
|
2784
|
-
};
|
|
2785
3145
|
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
const
|
|
3146
|
+
return { push: false };
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
const updateCallbacks = updateCallbacksRaw;
|
|
2790
3150
|
let pendingResultsReason:
|
|
2791
3151
|
| Extract<ResultBatchReason, "join" | "change">
|
|
2792
3152
|
| undefined;
|
|
@@ -2819,6 +3179,174 @@ export class DocumentIndex<
|
|
|
2819
3179
|
updateDeferred = pDefer<void>();
|
|
2820
3180
|
}
|
|
2821
3181
|
|
|
3182
|
+
const keepRemoteAlive =
|
|
3183
|
+
(options?.closePolicy === "manual" || hasLiveUpdates || pushUpdates) &&
|
|
3184
|
+
remoteOptions !== false;
|
|
3185
|
+
|
|
3186
|
+
if (queryRequestCoerced instanceof types.IterationRequest) {
|
|
3187
|
+
queryRequestCoerced.resolve = resolve;
|
|
3188
|
+
queryRequestCoerced.fetch = queryRequestCoerced.fetch ?? 10;
|
|
3189
|
+
const replicateFlag = !resolve && replicate ? true : false;
|
|
3190
|
+
queryRequestCoerced.replicate = replicateFlag;
|
|
3191
|
+
const ttlSource =
|
|
3192
|
+
typeof remoteOptions === "object" &&
|
|
3193
|
+
typeof remoteOptions?.wait === "object"
|
|
3194
|
+
? (remoteOptions.wait.timeout ?? DEFAULT_TIMEOUT)
|
|
3195
|
+
: DEFAULT_TIMEOUT;
|
|
3196
|
+
queryRequestCoerced.keepAliveTtl = keepRemoteAlive
|
|
3197
|
+
? BigInt(ttlSource)
|
|
3198
|
+
: undefined;
|
|
3199
|
+
queryRequestCoerced.pushUpdates = pushUpdates ? true : undefined;
|
|
3200
|
+
queryRequestCoerced.mergeUpdates = mergePolicy?.merge ? true : undefined;
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
if (pushUpdates && this.prefetch?.accumulator) {
|
|
3204
|
+
const targetPrefetchKey = idAgnosticQueryKey(queryRequestCoerced);
|
|
3205
|
+
const mergePrefetchedResults = async (
|
|
3206
|
+
from: PublicSignKey,
|
|
3207
|
+
results: types.Results<types.ResultTypeFromRequest<R, T, I>>,
|
|
3208
|
+
) => {
|
|
3209
|
+
const peerHash = from.hashcode();
|
|
3210
|
+
const existingBuffer = peerBufferMap.get(peerHash);
|
|
3211
|
+
const buffer: BufferedResult<
|
|
3212
|
+
types.ResultTypeFromRequest<R, T, I> | I,
|
|
3213
|
+
I
|
|
3214
|
+
>[] = existingBuffer?.buffer || [];
|
|
3215
|
+
|
|
3216
|
+
if (results.kept === 0n && results.results.length === 0) {
|
|
3217
|
+
peerBufferMap.set(peerHash, {
|
|
3218
|
+
buffer,
|
|
3219
|
+
kept: Number(results.kept),
|
|
3220
|
+
});
|
|
3221
|
+
return;
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
for (const result of results.results) {
|
|
3225
|
+
const indexKey = indexerTypes.toId(
|
|
3226
|
+
this.indexByResolver(result.value),
|
|
3227
|
+
).primitive;
|
|
3228
|
+
if (result instanceof types.ResultValue) {
|
|
3229
|
+
const existingIndexed = indexedPlaceholders?.get(indexKey);
|
|
3230
|
+
if (existingIndexed) {
|
|
3231
|
+
existingIndexed.value =
|
|
3232
|
+
result.value as types.ResultTypeFromRequest<R, T, I>;
|
|
3233
|
+
existingIndexed.context = result.context;
|
|
3234
|
+
existingIndexed.from = from;
|
|
3235
|
+
existingIndexed.indexed = await this.resolveIndexed<R>(
|
|
3236
|
+
result,
|
|
3237
|
+
results.results as types.ResultTypeFromRequest<R, T, I>[],
|
|
3238
|
+
);
|
|
3239
|
+
indexedPlaceholders?.delete(indexKey);
|
|
3240
|
+
continue;
|
|
3241
|
+
}
|
|
3242
|
+
if (visited.has(indexKey)) {
|
|
3243
|
+
continue;
|
|
3244
|
+
}
|
|
3245
|
+
visited.add(indexKey);
|
|
3246
|
+
buffer.push({
|
|
3247
|
+
value: result.value as types.ResultTypeFromRequest<R, T, I>,
|
|
3248
|
+
context: result.context,
|
|
3249
|
+
from,
|
|
3250
|
+
indexed: await this.resolveIndexed<R>(
|
|
3251
|
+
result,
|
|
3252
|
+
results.results as types.ResultTypeFromRequest<R, T, I>[],
|
|
3253
|
+
),
|
|
3254
|
+
});
|
|
3255
|
+
} else {
|
|
3256
|
+
if (visited.has(indexKey) && !indexedPlaceholders?.has(indexKey)) {
|
|
3257
|
+
continue;
|
|
3258
|
+
}
|
|
3259
|
+
visited.add(indexKey);
|
|
3260
|
+
const indexed = coerceWithContext(
|
|
3261
|
+
result.indexed || result.value,
|
|
3262
|
+
result.context,
|
|
3263
|
+
);
|
|
3264
|
+
const placeholder = {
|
|
3265
|
+
value: result.value,
|
|
3266
|
+
context: result.context,
|
|
3267
|
+
from,
|
|
3268
|
+
indexed,
|
|
3269
|
+
};
|
|
3270
|
+
buffer.push(placeholder);
|
|
3271
|
+
ensureIndexedPlaceholders().set(indexKey, placeholder);
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
peerBufferMap.set(peerHash, {
|
|
3276
|
+
buffer,
|
|
3277
|
+
kept: Number(results.kept),
|
|
3278
|
+
});
|
|
3279
|
+
};
|
|
3280
|
+
|
|
3281
|
+
const consumePrefetch = async (
|
|
3282
|
+
consumable: RPCResponse<types.PredictedSearchRequest<any>>,
|
|
3283
|
+
) => {
|
|
3284
|
+
const request = consumable.response?.request;
|
|
3285
|
+
if (!request) {
|
|
3286
|
+
return;
|
|
3287
|
+
}
|
|
3288
|
+
if (idAgnosticQueryKey(request) !== targetPrefetchKey) {
|
|
3289
|
+
return;
|
|
3290
|
+
}
|
|
3291
|
+
try {
|
|
3292
|
+
const prepared = await introduceEntries(
|
|
3293
|
+
queryRequestCoerced,
|
|
3294
|
+
[
|
|
3295
|
+
{
|
|
3296
|
+
response: consumable.response.results,
|
|
3297
|
+
from: consumable.from,
|
|
3298
|
+
},
|
|
3299
|
+
],
|
|
3300
|
+
this.documentType,
|
|
3301
|
+
this.indexedType,
|
|
3302
|
+
this._sync,
|
|
3303
|
+
options as QueryDetailedOptions<T, I, D, any>,
|
|
3304
|
+
);
|
|
3305
|
+
|
|
3306
|
+
for (const response of prepared) {
|
|
3307
|
+
if (!response.from) {
|
|
3308
|
+
continue;
|
|
3309
|
+
}
|
|
3310
|
+
const payload = response.response;
|
|
3311
|
+
if (!(payload instanceof types.Results)) {
|
|
3312
|
+
continue;
|
|
3313
|
+
}
|
|
3314
|
+
await mergePrefetchedResults(
|
|
3315
|
+
response.from,
|
|
3316
|
+
payload as types.Results<types.ResultTypeFromRequest<R, T, I>>,
|
|
3317
|
+
);
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
if (!pendingResultsReason) {
|
|
3321
|
+
pendingResultsReason = "change";
|
|
3322
|
+
}
|
|
3323
|
+
signalUpdate("prefetch-add");
|
|
3324
|
+
} catch (error) {
|
|
3325
|
+
logger.warn("Failed to merge prefetched results", error);
|
|
3326
|
+
}
|
|
3327
|
+
};
|
|
3328
|
+
|
|
3329
|
+
const onPrefetchAdd = (
|
|
3330
|
+
evt: CustomEvent<{
|
|
3331
|
+
consumable: RPCResponse<types.PredictedSearchRequest<any>>;
|
|
3332
|
+
}>,
|
|
3333
|
+
) => {
|
|
3334
|
+
void consumePrefetch(evt.detail.consumable);
|
|
3335
|
+
};
|
|
3336
|
+
this.prefetch.accumulator.addEventListener(
|
|
3337
|
+
"add",
|
|
3338
|
+
onPrefetchAdd as EventListener,
|
|
3339
|
+
);
|
|
3340
|
+
const cleanupDefault = cleanup;
|
|
3341
|
+
cleanup = () => {
|
|
3342
|
+
this.prefetch?.accumulator.removeEventListener(
|
|
3343
|
+
"add",
|
|
3344
|
+
onPrefetchAdd as EventListener,
|
|
3345
|
+
);
|
|
3346
|
+
return cleanupDefault();
|
|
3347
|
+
};
|
|
3348
|
+
}
|
|
3349
|
+
|
|
2822
3350
|
let updatesCleanup: (() => void) | undefined;
|
|
2823
3351
|
if (hasLiveUpdates) {
|
|
2824
3352
|
const localHash = this.node.identity.publicKey.hashcode();
|
|
@@ -2998,6 +3526,7 @@ export class DocumentIndex<
|
|
|
2998
3526
|
changeForCallback.removed.length > 0
|
|
2999
3527
|
) {
|
|
3000
3528
|
updateCallbacks?.onChange?.(changeForCallback);
|
|
3529
|
+
signalUpdate("change");
|
|
3001
3530
|
}
|
|
3002
3531
|
}
|
|
3003
3532
|
}
|
|
@@ -3099,8 +3628,8 @@ export class DocumentIndex<
|
|
|
3099
3628
|
};
|
|
3100
3629
|
}
|
|
3101
3630
|
|
|
3102
|
-
if (
|
|
3103
|
-
|
|
3631
|
+
if (keepRemoteAlive) {
|
|
3632
|
+
const prevMaybeSetDone = maybeSetDone;
|
|
3104
3633
|
maybeSetDone = () => {
|
|
3105
3634
|
if (drain) {
|
|
3106
3635
|
prevMaybeSetDone();
|
|
@@ -3126,7 +3655,16 @@ export class DocumentIndex<
|
|
|
3126
3655
|
close,
|
|
3127
3656
|
next,
|
|
3128
3657
|
done: doneFn,
|
|
3129
|
-
pending: () => {
|
|
3658
|
+
pending: async () => {
|
|
3659
|
+
try {
|
|
3660
|
+
await fetchPromise;
|
|
3661
|
+
if (!done && keepRemoteAlive) {
|
|
3662
|
+
await fetchAtLeast(1);
|
|
3663
|
+
}
|
|
3664
|
+
} catch (error) {
|
|
3665
|
+
logger.warn("Failed to refresh iterator pending state", error);
|
|
3666
|
+
}
|
|
3667
|
+
|
|
3130
3668
|
let pendingCount = 0;
|
|
3131
3669
|
for (const buffer of peerBufferMap.values()) {
|
|
3132
3670
|
pendingCount += buffer.kept + buffer.buffer.length;
|