@peerbit/document 9.13.10 → 10.0.0
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/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 +491 -69
- package/dist/src/search.js.map +1 -1
- package/package.json +2 -2
- package/src/program.ts +1 -1
- package/src/resumable-iterator.ts +48 -23
- package/src/search.ts +725 -139
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";
|
|
@@ -95,11 +97,16 @@ export type UpdateCallbacks<
|
|
|
95
97
|
* Unified update options for iterate()/search()/get() and hooks.
|
|
96
98
|
* If you pass `true`, defaults to `{ merge: "sorted" }`.
|
|
97
99
|
*/
|
|
100
|
+
export type UpdateModeShortcut = "local" | "remote" | "all";
|
|
101
|
+
|
|
98
102
|
export type UpdateOptions<T, I, Resolve extends boolean | undefined> =
|
|
99
103
|
| boolean
|
|
104
|
+
| UpdateModeShortcut
|
|
100
105
|
| ({
|
|
101
106
|
/** Live update behavior. Only sorted merging is supported; optional filter can mutate/ignore events. */
|
|
102
107
|
merge?: UpdateMergeStrategy<T, I, Resolve>;
|
|
108
|
+
/** Request push-style notifications backed by the prefetch channel. */
|
|
109
|
+
push?: boolean;
|
|
103
110
|
} & UpdateCallbacks<T, I, Resolve>);
|
|
104
111
|
|
|
105
112
|
export type JoiningTargets = {
|
|
@@ -221,7 +228,7 @@ export type ResultsIterator<T> = {
|
|
|
221
228
|
next: (number: number) => Promise<T[]>;
|
|
222
229
|
done: () => boolean;
|
|
223
230
|
all: () => Promise<T[]>;
|
|
224
|
-
pending: () => number | undefined
|
|
231
|
+
pending: () => MaybePromise<number | undefined>;
|
|
225
232
|
first: () => Promise<T | undefined>;
|
|
226
233
|
[Symbol.asyncIterator]: () => AsyncIterator<T>;
|
|
227
234
|
};
|
|
@@ -255,11 +262,21 @@ type ExtractResolveFromOptions<O> =
|
|
|
255
262
|
: true; // if R isn't QueryLike at all, default to true
|
|
256
263
|
|
|
257
264
|
const coerceQuery = <Resolve extends boolean | undefined>(
|
|
258
|
-
query:
|
|
265
|
+
query:
|
|
266
|
+
| types.SearchRequest
|
|
267
|
+
| types.SearchRequestIndexed
|
|
268
|
+
| types.IterationRequest
|
|
269
|
+
| QueryLike,
|
|
259
270
|
options?: QueryOptions<any, any, any, Resolve>,
|
|
260
|
-
|
|
261
|
-
|
|
271
|
+
compatibility?: number,
|
|
272
|
+
):
|
|
273
|
+
| types.SearchRequest
|
|
274
|
+
| types.SearchRequestIndexed
|
|
275
|
+
| types.IterationRequest => {
|
|
276
|
+
const replicate =
|
|
262
277
|
typeof options?.remote !== "boolean" ? options?.remote?.replicate : false;
|
|
278
|
+
const shouldResolve = options?.resolve !== false;
|
|
279
|
+
const useLegacyRequests = compatibility != null && compatibility <= 9;
|
|
263
280
|
|
|
264
281
|
if (
|
|
265
282
|
query instanceof types.SearchRequestIndexed &&
|
|
@@ -269,29 +286,66 @@ const coerceQuery = <Resolve extends boolean | undefined>(
|
|
|
269
286
|
query.replicate = true;
|
|
270
287
|
return query;
|
|
271
288
|
}
|
|
272
|
-
|
|
289
|
+
|
|
290
|
+
if (
|
|
291
|
+
query instanceof types.SearchRequest ||
|
|
292
|
+
query instanceof types.SearchRequestIndexed
|
|
293
|
+
) {
|
|
294
|
+
return query;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (query instanceof types.IterationRequest) {
|
|
298
|
+
if (useLegacyRequests) {
|
|
299
|
+
if (query.resolve === false) {
|
|
300
|
+
return new types.SearchRequestIndexed({
|
|
301
|
+
query: query.query,
|
|
302
|
+
sort: query.sort,
|
|
303
|
+
fetch: query.fetch,
|
|
304
|
+
replicate: query.replicate ?? replicate,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return new types.SearchRequest({
|
|
308
|
+
query: query.query,
|
|
309
|
+
sort: query.sort,
|
|
310
|
+
fetch: query.fetch,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
273
313
|
return query;
|
|
274
314
|
}
|
|
275
315
|
|
|
276
316
|
const queryObject = query as QueryLike;
|
|
277
317
|
|
|
278
|
-
|
|
279
|
-
|
|
318
|
+
if (useLegacyRequests) {
|
|
319
|
+
if (shouldResolve) {
|
|
320
|
+
return new types.SearchRequest({
|
|
280
321
|
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,
|
|
322
|
+
sort: indexerTypes.toSort(queryObject.sort),
|
|
287
323
|
});
|
|
324
|
+
}
|
|
325
|
+
return new types.SearchRequestIndexed({
|
|
326
|
+
query: indexerTypes.toQuery(queryObject.query),
|
|
327
|
+
sort: indexerTypes.toSort(queryObject.sort),
|
|
328
|
+
replicate,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return new types.IterationRequest({
|
|
333
|
+
query: indexerTypes.toQuery(queryObject.query),
|
|
334
|
+
sort: indexerTypes.toSort(queryObject.sort),
|
|
335
|
+
fetch: 10,
|
|
336
|
+
resolve: shouldResolve,
|
|
337
|
+
replicate: shouldResolve ? false : replicate,
|
|
338
|
+
});
|
|
288
339
|
};
|
|
289
340
|
|
|
290
341
|
const introduceEntries = async <
|
|
291
342
|
T,
|
|
292
343
|
I,
|
|
293
344
|
D,
|
|
294
|
-
R extends
|
|
345
|
+
R extends
|
|
346
|
+
| types.SearchRequest
|
|
347
|
+
| types.SearchRequestIndexed
|
|
348
|
+
| types.IterationRequest,
|
|
295
349
|
>(
|
|
296
350
|
queryRequest: R,
|
|
297
351
|
responses: { response: types.AbstractSearchResult; from?: PublicSignKey }[],
|
|
@@ -347,6 +401,37 @@ const dedup = <T>(
|
|
|
347
401
|
return dedup;
|
|
348
402
|
};
|
|
349
403
|
|
|
404
|
+
type AnyIterationRequest =
|
|
405
|
+
| types.SearchRequest
|
|
406
|
+
| types.SearchRequestIndexed
|
|
407
|
+
| types.IterationRequest;
|
|
408
|
+
|
|
409
|
+
const resolvesDocuments = (req?: AnyIterationRequest) => {
|
|
410
|
+
if (!req) {
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
413
|
+
if (req instanceof types.SearchRequestIndexed) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
if (req instanceof types.IterationRequest) {
|
|
417
|
+
return req.resolve !== false;
|
|
418
|
+
}
|
|
419
|
+
return true;
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const replicatesIndex = (req?: AnyIterationRequest) => {
|
|
423
|
+
if (!req) {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
if (req instanceof types.SearchRequestIndexed) {
|
|
427
|
+
return req.replicate === true;
|
|
428
|
+
}
|
|
429
|
+
if (req instanceof types.IterationRequest) {
|
|
430
|
+
return req.replicate === true;
|
|
431
|
+
}
|
|
432
|
+
return false;
|
|
433
|
+
};
|
|
434
|
+
|
|
350
435
|
function isSubclassOf(
|
|
351
436
|
SubClass: AbstractType<any>,
|
|
352
437
|
SuperClass: AbstractType<any>,
|
|
@@ -365,11 +450,15 @@ function isSubclassOf(
|
|
|
365
450
|
}
|
|
366
451
|
|
|
367
452
|
const DEFAULT_TIMEOUT = 1e4;
|
|
453
|
+
const DISCOVER_TIMEOUT_FALLBACK = 500;
|
|
368
454
|
|
|
369
455
|
const DEFAULT_INDEX_BY = "id";
|
|
370
456
|
|
|
371
457
|
export type CanSearch = (
|
|
372
|
-
request:
|
|
458
|
+
request:
|
|
459
|
+
| types.SearchRequest
|
|
460
|
+
| types.IterationRequest
|
|
461
|
+
| types.CollectNextRequest,
|
|
373
462
|
from: PublicSignKey,
|
|
374
463
|
) => Promise<boolean> | boolean;
|
|
375
464
|
|
|
@@ -443,10 +532,15 @@ export type OpenOptions<
|
|
|
443
532
|
canRead?: CanRead<I>;
|
|
444
533
|
canSearch?: CanSearch;
|
|
445
534
|
replicate: (
|
|
446
|
-
request:
|
|
535
|
+
request:
|
|
536
|
+
| types.SearchRequest
|
|
537
|
+
| types.SearchRequestIndexed
|
|
538
|
+
| types.IterationRequest,
|
|
447
539
|
results: types.Results<
|
|
448
540
|
types.ResultTypeFromRequest<
|
|
449
|
-
|
|
541
|
+
| types.SearchRequest
|
|
542
|
+
| types.SearchRequestIndexed
|
|
543
|
+
| types.IterationRequest,
|
|
450
544
|
T,
|
|
451
545
|
I
|
|
452
546
|
>
|
|
@@ -458,7 +552,7 @@ export type OpenOptions<
|
|
|
458
552
|
resolver?: number;
|
|
459
553
|
query?: QueryCacheOptions;
|
|
460
554
|
};
|
|
461
|
-
compatibility: 6 | 7 | 8 | undefined;
|
|
555
|
+
compatibility: 6 | 7 | 8 | 9 | undefined;
|
|
462
556
|
maybeOpen: (value: T & Program) => Promise<T & Program>;
|
|
463
557
|
prefetch?: boolean | Partial<PrefetchOptions>;
|
|
464
558
|
includeIndexed?: boolean; // if true, indexed representations will always be included in the search results
|
|
@@ -518,7 +612,7 @@ export class DocumentIndex<
|
|
|
518
612
|
private _prefetch?: PrefetchOptions | undefined;
|
|
519
613
|
private includeIndexed: boolean | undefined = undefined;
|
|
520
614
|
|
|
521
|
-
compatibility: 6 | 7 | 8 | undefined;
|
|
615
|
+
compatibility: 6 | 7 | 8 | 9 | undefined;
|
|
522
616
|
|
|
523
617
|
// Transformation, indexer
|
|
524
618
|
/* fields: IndexableFields<T, I>; */
|
|
@@ -526,7 +620,10 @@ export class DocumentIndex<
|
|
|
526
620
|
private _valueEncoding: Encoding<T>;
|
|
527
621
|
|
|
528
622
|
private _sync: <V extends types.ResultValue<T> | types.ResultIndexedValue<I>>(
|
|
529
|
-
request:
|
|
623
|
+
request:
|
|
624
|
+
| types.SearchRequest
|
|
625
|
+
| types.SearchRequestIndexed
|
|
626
|
+
| types.IterationRequest,
|
|
530
627
|
results: types.Results<V>,
|
|
531
628
|
) => Promise<void>;
|
|
532
629
|
|
|
@@ -550,15 +647,20 @@ export class DocumentIndex<
|
|
|
550
647
|
keptInIndex: number;
|
|
551
648
|
timeout: ReturnType<typeof setTimeout>;
|
|
552
649
|
queue: indexerTypes.IndexedResult<WithContext<I>>[];
|
|
553
|
-
fromQuery:
|
|
650
|
+
fromQuery:
|
|
651
|
+
| types.SearchRequest
|
|
652
|
+
| types.SearchRequestIndexed
|
|
653
|
+
| types.IterationRequest;
|
|
554
654
|
}
|
|
555
655
|
>;
|
|
656
|
+
private iteratorKeepAliveTimers?: Map<string, ReturnType<typeof setTimeout>>;
|
|
556
657
|
|
|
557
658
|
constructor(properties?: {
|
|
558
659
|
query?: RPC<types.AbstractSearchRequest, types.AbstractSearchResult>;
|
|
559
660
|
}) {
|
|
560
661
|
super();
|
|
561
662
|
this._query = properties?.query || new RPC();
|
|
663
|
+
this.iteratorKeepAliveTimers = new Map();
|
|
562
664
|
}
|
|
563
665
|
|
|
564
666
|
get valueEncoding() {
|
|
@@ -628,10 +730,15 @@ export class DocumentIndex<
|
|
|
628
730
|
this.dbType = properties.dbType;
|
|
629
731
|
this._resultQueue = new Map();
|
|
630
732
|
this._sync = (request, results) => {
|
|
631
|
-
let rq:
|
|
733
|
+
let rq:
|
|
734
|
+
| types.SearchRequest
|
|
735
|
+
| types.SearchRequestIndexed
|
|
736
|
+
| types.IterationRequest;
|
|
632
737
|
let rs: types.Results<
|
|
633
738
|
types.ResultTypeFromRequest<
|
|
634
|
-
|
|
739
|
+
| types.SearchRequest
|
|
740
|
+
| types.SearchRequestIndexed
|
|
741
|
+
| types.IterationRequest,
|
|
635
742
|
T,
|
|
636
743
|
I
|
|
637
744
|
>
|
|
@@ -643,7 +750,9 @@ export class DocumentIndex<
|
|
|
643
750
|
rq = request;
|
|
644
751
|
rs = results as types.Results<
|
|
645
752
|
types.ResultTypeFromRequest<
|
|
646
|
-
|
|
753
|
+
| types.SearchRequest
|
|
754
|
+
| types.SearchRequestIndexed
|
|
755
|
+
| types.IterationRequest,
|
|
647
756
|
T,
|
|
648
757
|
I
|
|
649
758
|
>
|
|
@@ -775,7 +884,8 @@ export class DocumentIndex<
|
|
|
775
884
|
if (
|
|
776
885
|
this.prefetch?.predictor &&
|
|
777
886
|
(query instanceof types.SearchRequest ||
|
|
778
|
-
query instanceof types.SearchRequestIndexed
|
|
887
|
+
query instanceof types.SearchRequestIndexed ||
|
|
888
|
+
query instanceof types.IterationRequest)
|
|
779
889
|
) {
|
|
780
890
|
const { ignore } = this.prefetch.predictor.onRequest(query, {
|
|
781
891
|
from: ctx.from!,
|
|
@@ -792,6 +902,7 @@ export class DocumentIndex<
|
|
|
792
902
|
query as
|
|
793
903
|
| types.SearchRequest
|
|
794
904
|
| types.SearchRequestIndexed
|
|
905
|
+
| types.IterationRequest
|
|
795
906
|
| types.CollectNextRequest,
|
|
796
907
|
{
|
|
797
908
|
from: ctx.from!,
|
|
@@ -802,15 +913,20 @@ export class DocumentIndex<
|
|
|
802
913
|
query:
|
|
803
914
|
| types.SearchRequest
|
|
804
915
|
| types.SearchRequestIndexed
|
|
916
|
+
| types.IterationRequest
|
|
805
917
|
| types.CollectNextRequest,
|
|
806
918
|
ctx: { from: PublicSignKey },
|
|
807
919
|
) {
|
|
808
920
|
if (
|
|
809
921
|
this.canSearch &&
|
|
810
922
|
(query instanceof types.SearchRequest ||
|
|
923
|
+
query instanceof types.IterationRequest ||
|
|
811
924
|
query instanceof types.CollectNextRequest) &&
|
|
812
925
|
!(await this.canSearch(
|
|
813
|
-
query as
|
|
926
|
+
query as
|
|
927
|
+
| types.SearchRequest
|
|
928
|
+
| types.IterationRequest
|
|
929
|
+
| types.CollectNextRequest,
|
|
814
930
|
ctx.from,
|
|
815
931
|
))
|
|
816
932
|
) {
|
|
@@ -820,17 +936,23 @@ export class DocumentIndex<
|
|
|
820
936
|
if (query instanceof types.CloseIteratorRequest) {
|
|
821
937
|
this.processCloseIteratorRequest(query, ctx.from);
|
|
822
938
|
} else {
|
|
939
|
+
const fromQueued =
|
|
940
|
+
query instanceof types.CollectNextRequest
|
|
941
|
+
? this._resultQueue.get(query.idString)?.fromQuery
|
|
942
|
+
: undefined;
|
|
943
|
+
const queryResolvesDocuments =
|
|
944
|
+
query instanceof types.CollectNextRequest
|
|
945
|
+
? resolvesDocuments(fromQueued)
|
|
946
|
+
: resolvesDocuments(query as AnyIterationRequest);
|
|
947
|
+
|
|
823
948
|
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
|
|
949
|
+
this.includeIndexed && queryResolvesDocuments;
|
|
829
950
|
|
|
830
951
|
const results = await this.processQuery(
|
|
831
952
|
query as
|
|
832
953
|
| types.SearchRequest
|
|
833
954
|
| types.SearchRequestIndexed
|
|
955
|
+
| types.IterationRequest
|
|
834
956
|
| types.CollectNextRequest,
|
|
835
957
|
ctx.from,
|
|
836
958
|
false,
|
|
@@ -1156,19 +1278,29 @@ export class DocumentIndex<
|
|
|
1156
1278
|
| types.ResultIndexedValue<WithContext<I>>
|
|
1157
1279
|
>[]
|
|
1158
1280
|
| undefined;
|
|
1281
|
+
|
|
1282
|
+
const runAndClose = async (
|
|
1283
|
+
req: types.SearchRequest | types.SearchRequestIndexed,
|
|
1284
|
+
): Promise<typeof results> => {
|
|
1285
|
+
const response = await this.queryCommence(
|
|
1286
|
+
req,
|
|
1287
|
+
coercedOptions as QueryDetailedOptions<T, I, D, boolean | undefined>,
|
|
1288
|
+
);
|
|
1289
|
+
this._resumableIterators.close({ idString: req.idString });
|
|
1290
|
+
this.cancelIteratorKeepAlive(req.idString);
|
|
1291
|
+
return response as typeof results;
|
|
1292
|
+
};
|
|
1159
1293
|
const resolve = coercedOptions?.resolve || coercedOptions?.resolve == null;
|
|
1160
1294
|
let requestClazz = resolve
|
|
1161
1295
|
? types.SearchRequest
|
|
1162
1296
|
: types.SearchRequestIndexed;
|
|
1163
1297
|
if (key instanceof Uint8Array) {
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
coercedOptions as QueryDetailedOptions<T, I, D, boolean | undefined>,
|
|
1171
|
-
);
|
|
1298
|
+
const request = new requestClazz({
|
|
1299
|
+
query: [
|
|
1300
|
+
new indexerTypes.ByteMatchQuery({ key: this.indexBy, value: key }),
|
|
1301
|
+
],
|
|
1302
|
+
});
|
|
1303
|
+
results = await runAndClose(request);
|
|
1172
1304
|
} else {
|
|
1173
1305
|
const indexableKey = indexerTypes.toIdeable(key);
|
|
1174
1306
|
|
|
@@ -1176,42 +1308,48 @@ export class DocumentIndex<
|
|
|
1176
1308
|
typeof indexableKey === "number" ||
|
|
1177
1309
|
typeof indexableKey === "bigint"
|
|
1178
1310
|
) {
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
coercedOptions as QueryDetailedOptions<T, I, D, boolean | undefined>,
|
|
1190
|
-
);
|
|
1311
|
+
const request = new requestClazz({
|
|
1312
|
+
query: [
|
|
1313
|
+
new indexerTypes.IntegerCompare({
|
|
1314
|
+
key: this.indexBy,
|
|
1315
|
+
compare: indexerTypes.Compare.Equal,
|
|
1316
|
+
value: indexableKey,
|
|
1317
|
+
}),
|
|
1318
|
+
],
|
|
1319
|
+
});
|
|
1320
|
+
results = await runAndClose(request);
|
|
1191
1321
|
} else if (typeof indexableKey === "string") {
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
coercedOptions as QueryDetailedOptions<T, I, D, boolean | undefined>,
|
|
1202
|
-
);
|
|
1322
|
+
const request = new requestClazz({
|
|
1323
|
+
query: [
|
|
1324
|
+
new indexerTypes.StringMatch({
|
|
1325
|
+
key: this.indexBy,
|
|
1326
|
+
value: indexableKey,
|
|
1327
|
+
}),
|
|
1328
|
+
],
|
|
1329
|
+
});
|
|
1330
|
+
results = await runAndClose(request);
|
|
1203
1331
|
} else if (indexableKey instanceof Uint8Array) {
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1332
|
+
const request = new requestClazz({
|
|
1333
|
+
query: [
|
|
1334
|
+
new indexerTypes.ByteMatchQuery({
|
|
1335
|
+
key: this.indexBy,
|
|
1336
|
+
value: indexableKey,
|
|
1337
|
+
}),
|
|
1338
|
+
],
|
|
1339
|
+
});
|
|
1340
|
+
results = await runAndClose(request);
|
|
1341
|
+
} else if ((indexableKey as any) instanceof ArrayBuffer) {
|
|
1342
|
+
const request = new requestClazz({
|
|
1343
|
+
query: [
|
|
1344
|
+
new indexerTypes.ByteMatchQuery({
|
|
1345
|
+
key: this.indexBy,
|
|
1346
|
+
value: new Uint8Array(indexableKey),
|
|
1347
|
+
}),
|
|
1348
|
+
],
|
|
1349
|
+
});
|
|
1350
|
+
results = await runAndClose(request);
|
|
1351
|
+
} else {
|
|
1352
|
+
throw new Error("Unsupported key type");
|
|
1215
1353
|
}
|
|
1216
1354
|
}
|
|
1217
1355
|
|
|
@@ -1317,6 +1455,7 @@ export class DocumentIndex<
|
|
|
1317
1455
|
R extends
|
|
1318
1456
|
| types.SearchRequest
|
|
1319
1457
|
| types.SearchRequestIndexed
|
|
1458
|
+
| types.IterationRequest
|
|
1320
1459
|
| types.CollectNextRequest,
|
|
1321
1460
|
>(
|
|
1322
1461
|
query: R,
|
|
@@ -1338,25 +1477,57 @@ export class DocumentIndex<
|
|
|
1338
1477
|
let indexedResult: indexerTypes.IndexedResults<WithContext<I>> | undefined =
|
|
1339
1478
|
undefined;
|
|
1340
1479
|
|
|
1341
|
-
let fromQuery:
|
|
1480
|
+
let fromQuery:
|
|
1481
|
+
| types.SearchRequest
|
|
1482
|
+
| types.SearchRequestIndexed
|
|
1483
|
+
| types.IterationRequest
|
|
1484
|
+
| undefined;
|
|
1485
|
+
let keepAliveRequest: types.IterationRequest | undefined;
|
|
1342
1486
|
if (
|
|
1343
1487
|
query instanceof types.SearchRequest ||
|
|
1344
|
-
query instanceof types.SearchRequestIndexed
|
|
1488
|
+
query instanceof types.SearchRequestIndexed ||
|
|
1489
|
+
query instanceof types.IterationRequest
|
|
1345
1490
|
) {
|
|
1346
1491
|
fromQuery = query;
|
|
1347
|
-
|
|
1492
|
+
if (
|
|
1493
|
+
!isLocal &&
|
|
1494
|
+
query instanceof types.IterationRequest &&
|
|
1495
|
+
query.keepAliveTtl != null
|
|
1496
|
+
) {
|
|
1497
|
+
keepAliveRequest = query;
|
|
1498
|
+
}
|
|
1499
|
+
indexedResult = await this._resumableIterators.iterateAndFetch(query, {
|
|
1500
|
+
keepAlive: keepAliveRequest !== undefined,
|
|
1501
|
+
});
|
|
1348
1502
|
} else if (query instanceof types.CollectNextRequest) {
|
|
1349
|
-
|
|
1503
|
+
const cachedRequest =
|
|
1350
1504
|
prevQueued?.fromQuery ||
|
|
1351
1505
|
this._resumableIterators.queues.get(query.idString)?.request;
|
|
1506
|
+
fromQuery = cachedRequest;
|
|
1507
|
+
if (
|
|
1508
|
+
!isLocal &&
|
|
1509
|
+
cachedRequest instanceof types.IterationRequest &&
|
|
1510
|
+
cachedRequest.keepAliveTtl != null
|
|
1511
|
+
) {
|
|
1512
|
+
keepAliveRequest = cachedRequest;
|
|
1513
|
+
}
|
|
1352
1514
|
indexedResult =
|
|
1353
1515
|
prevQueued?.keptInIndex === 0
|
|
1354
1516
|
? []
|
|
1355
|
-
: await this._resumableIterators.next(query
|
|
1517
|
+
: await this._resumableIterators.next(query, {
|
|
1518
|
+
keepAlive: keepAliveRequest !== undefined,
|
|
1519
|
+
});
|
|
1356
1520
|
} else {
|
|
1357
1521
|
throw new Error("Unsupported");
|
|
1358
1522
|
}
|
|
1359
1523
|
|
|
1524
|
+
if (!isLocal && keepAliveRequest) {
|
|
1525
|
+
this.scheduleIteratorKeepAlive(
|
|
1526
|
+
query.idString,
|
|
1527
|
+
keepAliveRequest.keepAliveTtl,
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1360
1531
|
let resultSize = 0;
|
|
1361
1532
|
|
|
1362
1533
|
let toIterate = prevQueued
|
|
@@ -1381,13 +1552,15 @@ export class DocumentIndex<
|
|
|
1381
1552
|
keptInIndex: kept,
|
|
1382
1553
|
fromQuery: (fromQuery || query) as
|
|
1383
1554
|
| types.SearchRequest
|
|
1384
|
-
| types.SearchRequestIndexed
|
|
1555
|
+
| types.SearchRequestIndexed
|
|
1556
|
+
| types.IterationRequest,
|
|
1385
1557
|
};
|
|
1386
1558
|
this._resultQueue.set(query.idString, prevQueued);
|
|
1387
1559
|
}
|
|
1388
1560
|
|
|
1389
1561
|
const filteredResults: types.Result[] = [];
|
|
1390
|
-
|
|
1562
|
+
const resolveDocumentsFlag = resolvesDocuments(fromQuery);
|
|
1563
|
+
const replicateIndexFlag = replicatesIndex(fromQuery);
|
|
1391
1564
|
for (const result of toIterate) {
|
|
1392
1565
|
if (!isLocal) {
|
|
1393
1566
|
resultSize += result.value.__context.size;
|
|
@@ -1407,7 +1580,7 @@ export class DocumentIndex<
|
|
|
1407
1580
|
) {
|
|
1408
1581
|
continue;
|
|
1409
1582
|
}
|
|
1410
|
-
if (
|
|
1583
|
+
if (resolveDocumentsFlag) {
|
|
1411
1584
|
const value = await this.resolveDocument({
|
|
1412
1585
|
indexed: result.value,
|
|
1413
1586
|
head: result.value.__context.head,
|
|
@@ -1425,11 +1598,11 @@ export class DocumentIndex<
|
|
|
1425
1598
|
indexed: indexedUnwrapped,
|
|
1426
1599
|
}),
|
|
1427
1600
|
);
|
|
1428
|
-
} else
|
|
1601
|
+
} else {
|
|
1429
1602
|
const context = result.value.__context;
|
|
1430
1603
|
const head = await this._log.log.get(context.head);
|
|
1431
1604
|
// assume remote peer will start to replicate (TODO is this ideal?)
|
|
1432
|
-
if (
|
|
1605
|
+
if (replicateIndexFlag) {
|
|
1433
1606
|
this._log.addPeersToGidPeerHistory(context.gid, [from.hashcode()]);
|
|
1434
1607
|
}
|
|
1435
1608
|
|
|
@@ -1448,6 +1621,13 @@ export class DocumentIndex<
|
|
|
1448
1621
|
results: filteredResults,
|
|
1449
1622
|
kept: BigInt(kept + (prevQueued?.queue.length || 0)),
|
|
1450
1623
|
});
|
|
1624
|
+
/* console.debug("[DocumentIndex] processQuery", {
|
|
1625
|
+
id: query.idString,
|
|
1626
|
+
isLocal,
|
|
1627
|
+
batchLength: filteredResults.length,
|
|
1628
|
+
kept: results.kept,
|
|
1629
|
+
source: from.hashcode(),
|
|
1630
|
+
}); */
|
|
1451
1631
|
|
|
1452
1632
|
if (!isLocal && results.kept === 0n) {
|
|
1453
1633
|
this.clearResultsQueue(query);
|
|
@@ -1456,10 +1636,53 @@ export class DocumentIndex<
|
|
|
1456
1636
|
return results;
|
|
1457
1637
|
}
|
|
1458
1638
|
|
|
1639
|
+
private scheduleIteratorKeepAlive(idString: string, ttl?: bigint) {
|
|
1640
|
+
if (ttl == null) {
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
const ttlNumber = Number(ttl);
|
|
1644
|
+
if (!Number.isFinite(ttlNumber) || ttlNumber <= 0) {
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
// Cap max timeout to 1 day (TODO make configurable?)
|
|
1649
|
+
const delay = Math.max(1, Math.min(ttlNumber, 86400000));
|
|
1650
|
+
this.cancelIteratorKeepAlive(idString);
|
|
1651
|
+
const timers =
|
|
1652
|
+
this.iteratorKeepAliveTimers ??
|
|
1653
|
+
(this.iteratorKeepAliveTimers = new Map<
|
|
1654
|
+
string,
|
|
1655
|
+
ReturnType<typeof setTimeout>
|
|
1656
|
+
>());
|
|
1657
|
+
const timer = setTimeout(() => {
|
|
1658
|
+
timers.delete(idString);
|
|
1659
|
+
const queued = this._resultQueue.get(idString);
|
|
1660
|
+
if (queued) {
|
|
1661
|
+
clearTimeout(queued.timeout);
|
|
1662
|
+
this._resultQueue.delete(idString);
|
|
1663
|
+
}
|
|
1664
|
+
this._resumableIterators.close({ idString });
|
|
1665
|
+
}, delay);
|
|
1666
|
+
timers.set(idString, timer);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
private cancelIteratorKeepAlive(idString: string) {
|
|
1670
|
+
const timers = this.iteratorKeepAliveTimers;
|
|
1671
|
+
if (!timers) {
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
const timer = timers.get(idString);
|
|
1675
|
+
if (timer) {
|
|
1676
|
+
clearTimeout(timer);
|
|
1677
|
+
timers.delete(idString);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1459
1681
|
private clearResultsQueue(
|
|
1460
1682
|
query:
|
|
1461
1683
|
| types.SearchRequest
|
|
1462
1684
|
| types.SearchRequestIndexed
|
|
1685
|
+
| types.IterationRequest
|
|
1463
1686
|
| types.CollectNextRequest
|
|
1464
1687
|
| types.CloseIteratorRequest,
|
|
1465
1688
|
) {
|
|
@@ -1478,6 +1701,7 @@ export class DocumentIndex<
|
|
|
1478
1701
|
for (const [key, queue] of this._resultQueue) {
|
|
1479
1702
|
clearTimeout(queue.timeout);
|
|
1480
1703
|
this._resultQueue.delete(key);
|
|
1704
|
+
this.cancelIteratorKeepAlive(key);
|
|
1481
1705
|
this._resumableIterators.close({ idString: key });
|
|
1482
1706
|
}
|
|
1483
1707
|
}
|
|
@@ -1644,6 +1868,7 @@ export class DocumentIndex<
|
|
|
1644
1868
|
logger.info("Ignoring close iterator request from different peer");
|
|
1645
1869
|
return;
|
|
1646
1870
|
}
|
|
1871
|
+
this.cancelIteratorKeepAlive(query.idString);
|
|
1647
1872
|
this.clearResultsQueue(query);
|
|
1648
1873
|
return this._resumableIterators.close(query);
|
|
1649
1874
|
}
|
|
@@ -1655,7 +1880,10 @@ export class DocumentIndex<
|
|
|
1655
1880
|
* @returns
|
|
1656
1881
|
*/
|
|
1657
1882
|
private async queryCommence<
|
|
1658
|
-
R extends
|
|
1883
|
+
R extends
|
|
1884
|
+
| types.SearchRequest
|
|
1885
|
+
| types.SearchRequestIndexed
|
|
1886
|
+
| types.IterationRequest,
|
|
1659
1887
|
RT extends types.Result = types.ResultTypeFromRequest<R, T, I>,
|
|
1660
1888
|
>(
|
|
1661
1889
|
queryRequest: R,
|
|
@@ -1899,7 +2127,11 @@ export class DocumentIndex<
|
|
|
1899
2127
|
* @returns
|
|
1900
2128
|
*/
|
|
1901
2129
|
public async search<
|
|
1902
|
-
R extends
|
|
2130
|
+
R extends
|
|
2131
|
+
| types.SearchRequest
|
|
2132
|
+
| types.SearchRequestIndexed
|
|
2133
|
+
| types.IterationRequest
|
|
2134
|
+
| QueryLike,
|
|
1903
2135
|
O extends SearchOptions<T, I, D, Resolve>,
|
|
1904
2136
|
Resolve extends boolean = ExtractResolveFromOptions<O>,
|
|
1905
2137
|
>(
|
|
@@ -1907,8 +2139,11 @@ export class DocumentIndex<
|
|
|
1907
2139
|
options?: O,
|
|
1908
2140
|
): Promise<ValueTypeFromRequest<Resolve, T, I>[]> {
|
|
1909
2141
|
// Set fetch to search size, or max value (default to max u32 (4294967295))
|
|
1910
|
-
const coercedRequest
|
|
1911
|
-
|
|
2142
|
+
const coercedRequest = coerceQuery(
|
|
2143
|
+
queryRequest,
|
|
2144
|
+
options,
|
|
2145
|
+
this.compatibility,
|
|
2146
|
+
);
|
|
1912
2147
|
coercedRequest.fetch = coercedRequest.fetch ?? 0xffffffff;
|
|
1913
2148
|
|
|
1914
2149
|
// So that the iterator is pre-fetching the right amount of entries
|
|
@@ -1987,7 +2222,7 @@ export class DocumentIndex<
|
|
|
1987
2222
|
/**
|
|
1988
2223
|
* Query and retrieve documents in a iterator
|
|
1989
2224
|
* @param queryRequest
|
|
1990
|
-
* @param
|
|
2225
|
+
* @param optionsArg
|
|
1991
2226
|
* @returns
|
|
1992
2227
|
*/
|
|
1993
2228
|
public iterate<
|
|
@@ -1996,8 +2231,9 @@ export class DocumentIndex<
|
|
|
1996
2231
|
Resolve extends boolean | undefined = ExtractResolveFromOptions<O>,
|
|
1997
2232
|
>(
|
|
1998
2233
|
queryRequest?: R,
|
|
1999
|
-
|
|
2234
|
+
optionsArg?: QueryOptions<T, I, D, Resolve>,
|
|
2000
2235
|
): ResultsIterator<ValueTypeFromRequest<Resolve, T, I>> {
|
|
2236
|
+
let options = optionsArg;
|
|
2001
2237
|
if (
|
|
2002
2238
|
queryRequest instanceof types.SearchRequest &&
|
|
2003
2239
|
options?.resolve === false
|
|
@@ -2005,14 +2241,44 @@ export class DocumentIndex<
|
|
|
2005
2241
|
throw new Error("Cannot use resolve=false with SearchRequest"); // TODO make this work
|
|
2006
2242
|
}
|
|
2007
2243
|
|
|
2008
|
-
let queryRequestCoerced
|
|
2009
|
-
|
|
2244
|
+
let queryRequestCoerced = coerceQuery(
|
|
2245
|
+
queryRequest ?? {},
|
|
2246
|
+
options,
|
|
2247
|
+
this.compatibility,
|
|
2248
|
+
);
|
|
2249
|
+
|
|
2250
|
+
const {
|
|
2251
|
+
mergePolicy,
|
|
2252
|
+
push: pushUpdates,
|
|
2253
|
+
callbacks: updateCallbacksRaw,
|
|
2254
|
+
} = normalizeUpdatesOption(options?.updates);
|
|
2255
|
+
const hasLiveUpdates = mergePolicy !== undefined;
|
|
2256
|
+
const originalRemote = options?.remote;
|
|
2257
|
+
let remoteOptions =
|
|
2258
|
+
typeof originalRemote === "boolean"
|
|
2259
|
+
? originalRemote
|
|
2260
|
+
: originalRemote
|
|
2261
|
+
? { ...originalRemote }
|
|
2262
|
+
: undefined;
|
|
2263
|
+
if (pushUpdates && remoteOptions !== false) {
|
|
2264
|
+
if (typeof remoteOptions === "object") {
|
|
2265
|
+
if (remoteOptions.replicate !== true) {
|
|
2266
|
+
remoteOptions.replicate = true;
|
|
2267
|
+
}
|
|
2268
|
+
} else if (remoteOptions === undefined || remoteOptions === true) {
|
|
2269
|
+
remoteOptions = { replicate: true };
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
if (remoteOptions !== originalRemote) {
|
|
2273
|
+
options = Object.assign({}, options, { remote: remoteOptions });
|
|
2274
|
+
}
|
|
2010
2275
|
|
|
2011
2276
|
let resolve = options?.resolve !== false;
|
|
2012
2277
|
if (
|
|
2278
|
+
!(queryRequestCoerced instanceof types.IterationRequest) &&
|
|
2013
2279
|
options?.remote &&
|
|
2014
2280
|
typeof options.remote !== "boolean" &&
|
|
2015
|
-
options.remote.replicate &&
|
|
2281
|
+
(options.remote.replicate || pushUpdates) &&
|
|
2016
2282
|
options?.resolve !== false
|
|
2017
2283
|
) {
|
|
2018
2284
|
if (
|
|
@@ -2032,7 +2298,7 @@ export class DocumentIndex<
|
|
|
2032
2298
|
let replicate =
|
|
2033
2299
|
options?.remote &&
|
|
2034
2300
|
typeof options.remote !== "boolean" &&
|
|
2035
|
-
options.remote.replicate;
|
|
2301
|
+
(options.remote.replicate || pushUpdates);
|
|
2036
2302
|
if (
|
|
2037
2303
|
replicate &&
|
|
2038
2304
|
queryRequestCoerced instanceof types.SearchRequestIndexed
|
|
@@ -2109,6 +2375,8 @@ export class DocumentIndex<
|
|
|
2109
2375
|
|
|
2110
2376
|
let warmupPromise: Promise<any> | undefined = undefined;
|
|
2111
2377
|
|
|
2378
|
+
let discoveredTargetHashes: string[] | undefined;
|
|
2379
|
+
|
|
2112
2380
|
if (typeof options?.remote === "object") {
|
|
2113
2381
|
let waitForTime: number | undefined = undefined;
|
|
2114
2382
|
if (options.remote.wait) {
|
|
@@ -2145,11 +2413,26 @@ export class DocumentIndex<
|
|
|
2145
2413
|
}
|
|
2146
2414
|
|
|
2147
2415
|
if (options.remote.reach?.discover) {
|
|
2148
|
-
|
|
2416
|
+
const discoverTimeout =
|
|
2417
|
+
waitForTime ??
|
|
2418
|
+
(options.remote.wait ? DEFAULT_TIMEOUT : DISCOVER_TIMEOUT_FALLBACK);
|
|
2419
|
+
const discoverPromise = this.waitFor(options.remote.reach.discover, {
|
|
2149
2420
|
signal: ensureController().signal,
|
|
2150
2421
|
seek: "present",
|
|
2151
|
-
timeout:
|
|
2152
|
-
})
|
|
2422
|
+
timeout: discoverTimeout,
|
|
2423
|
+
})
|
|
2424
|
+
.then((hashes) => {
|
|
2425
|
+
discoveredTargetHashes = hashes;
|
|
2426
|
+
})
|
|
2427
|
+
.catch((error) => {
|
|
2428
|
+
if (error instanceof TimeoutError || error instanceof AbortError) {
|
|
2429
|
+
discoveredTargetHashes = [];
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
throw error;
|
|
2433
|
+
});
|
|
2434
|
+
const prior = warmupPromise ?? Promise.resolve();
|
|
2435
|
+
warmupPromise = prior.then(() => discoverPromise);
|
|
2153
2436
|
options.remote.reach.eager = true; // include the results from the discovered peer even if it is not mature
|
|
2154
2437
|
}
|
|
2155
2438
|
|
|
@@ -2181,6 +2464,18 @@ export class DocumentIndex<
|
|
|
2181
2464
|
): Promise<boolean> => {
|
|
2182
2465
|
await warmupPromise;
|
|
2183
2466
|
let hasMore = false;
|
|
2467
|
+
const discoverTargets =
|
|
2468
|
+
typeof options?.remote === "object"
|
|
2469
|
+
? options.remote.reach?.discover
|
|
2470
|
+
: undefined;
|
|
2471
|
+
const initialRemoteTargets =
|
|
2472
|
+
discoveredTargetHashes !== undefined
|
|
2473
|
+
? discoveredTargetHashes
|
|
2474
|
+
: discoverTargets?.map((pk) => pk.hashcode().toString());
|
|
2475
|
+
const skipRemoteDueToDiscovery =
|
|
2476
|
+
typeof options?.remote === "object" &&
|
|
2477
|
+
options.remote.reach?.discover &&
|
|
2478
|
+
discoveredTargetHashes?.length === 0;
|
|
2184
2479
|
|
|
2185
2480
|
queryRequestCoerced.fetch = n;
|
|
2186
2481
|
await this.queryCommence(
|
|
@@ -2188,12 +2483,12 @@ export class DocumentIndex<
|
|
|
2188
2483
|
{
|
|
2189
2484
|
local: fetchOptions?.from != null ? false : options?.local,
|
|
2190
2485
|
remote:
|
|
2191
|
-
options?.remote !== false
|
|
2486
|
+
options?.remote !== false && !skipRemoteDueToDiscovery
|
|
2192
2487
|
? {
|
|
2193
2488
|
...(typeof options?.remote === "object"
|
|
2194
2489
|
? options.remote
|
|
2195
2490
|
: {}),
|
|
2196
|
-
from: fetchOptions?.from,
|
|
2491
|
+
from: fetchOptions?.from ?? initialRemoteTargets,
|
|
2197
2492
|
}
|
|
2198
2493
|
: false,
|
|
2199
2494
|
resolve,
|
|
@@ -2210,17 +2505,25 @@ export class DocumentIndex<
|
|
|
2210
2505
|
types.ResultTypeFromRequest<R, T, I>
|
|
2211
2506
|
>;
|
|
2212
2507
|
|
|
2508
|
+
const existingBuffer = peerBufferMap.get(from.hashcode());
|
|
2509
|
+
const buffer: BufferedResult<
|
|
2510
|
+
types.ResultTypeFromRequest<R, T, I> | I,
|
|
2511
|
+
I
|
|
2512
|
+
>[] = existingBuffer?.buffer || [];
|
|
2513
|
+
|
|
2213
2514
|
if (results.kept === 0n && results.results.length === 0) {
|
|
2515
|
+
if (keepRemoteAlive) {
|
|
2516
|
+
peerBufferMap.set(from.hashcode(), {
|
|
2517
|
+
buffer,
|
|
2518
|
+
kept: Number(response.kept),
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
2214
2521
|
return;
|
|
2215
2522
|
}
|
|
2216
2523
|
|
|
2217
2524
|
if (results.kept > 0n) {
|
|
2218
2525
|
hasMore = true;
|
|
2219
2526
|
}
|
|
2220
|
-
const buffer: BufferedResult<
|
|
2221
|
-
types.ResultTypeFromRequest<R, T, I> | I,
|
|
2222
|
-
I
|
|
2223
|
-
>[] = peerBufferMap.get(from.hashcode())?.buffer || [];
|
|
2224
2527
|
|
|
2225
2528
|
for (const result of results.results) {
|
|
2226
2529
|
const indexKey = indexerTypes.toId(
|
|
@@ -2290,6 +2593,15 @@ export class DocumentIndex<
|
|
|
2290
2593
|
},
|
|
2291
2594
|
fetchOptions?.fetchedFirstForRemote,
|
|
2292
2595
|
);
|
|
2596
|
+
/* console.debug(
|
|
2597
|
+
"[DocumentIndex] fetchFirst",
|
|
2598
|
+
{
|
|
2599
|
+
id: queryRequestCoerced.idString,
|
|
2600
|
+
requestedFrom: fetchOptions?.from,
|
|
2601
|
+
initialRemoteTargets,
|
|
2602
|
+
keepRemoteAlive,
|
|
2603
|
+
},
|
|
2604
|
+
); */
|
|
2293
2605
|
|
|
2294
2606
|
if (!hasMore) {
|
|
2295
2607
|
maybeSetDone();
|
|
@@ -2322,7 +2634,8 @@ export class DocumentIndex<
|
|
|
2322
2634
|
|
|
2323
2635
|
for (const [peer, buffer] of peerBufferMap) {
|
|
2324
2636
|
if (buffer.buffer.length < n) {
|
|
2325
|
-
|
|
2637
|
+
const hasExistingRemoteResults = buffer.kept > 0;
|
|
2638
|
+
if (!hasExistingRemoteResults && !keepRemoteAlive) {
|
|
2326
2639
|
if (peerBufferMap.get(peer)?.buffer.length === 0) {
|
|
2327
2640
|
peerBufferMap.delete(peer); // No more results
|
|
2328
2641
|
}
|
|
@@ -2332,9 +2645,22 @@ export class DocumentIndex<
|
|
|
2332
2645
|
// TODO buffer more than deleted?
|
|
2333
2646
|
// TODO batch to multiple 'to's
|
|
2334
2647
|
|
|
2648
|
+
const lacking = n - buffer.buffer.length;
|
|
2649
|
+
const amount = lacking > 0 ? lacking : keepRemoteAlive ? 1 : 0;
|
|
2650
|
+
/* console.debug("[DocumentIndex] fetchAtLeast loop", {
|
|
2651
|
+
peer,
|
|
2652
|
+
bufferLength: buffer.buffer.length,
|
|
2653
|
+
bufferKept: buffer.kept,
|
|
2654
|
+
amount,
|
|
2655
|
+
keepRemoteAlive,
|
|
2656
|
+
}); */
|
|
2657
|
+
if (amount <= 0) {
|
|
2658
|
+
continue;
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2335
2661
|
const collectRequest = new types.CollectNextRequest({
|
|
2336
2662
|
id: queryRequestCoerced.id,
|
|
2337
|
-
amount
|
|
2663
|
+
amount,
|
|
2338
2664
|
});
|
|
2339
2665
|
// Fetch locally?
|
|
2340
2666
|
if (peer === this.node.identity.publicKey.hashcode()) {
|
|
@@ -2351,7 +2677,10 @@ export class DocumentIndex<
|
|
|
2351
2677
|
resultsLeft += Number(results.kept);
|
|
2352
2678
|
|
|
2353
2679
|
if (results.results.length === 0) {
|
|
2354
|
-
if (
|
|
2680
|
+
if (
|
|
2681
|
+
!keepRemoteAlive &&
|
|
2682
|
+
peerBufferMap.get(peer)?.buffer.length === 0
|
|
2683
|
+
) {
|
|
2355
2684
|
peerBufferMap.delete(peer); // No more results
|
|
2356
2685
|
}
|
|
2357
2686
|
} else {
|
|
@@ -2492,7 +2821,10 @@ export class DocumentIndex<
|
|
|
2492
2821
|
}
|
|
2493
2822
|
|
|
2494
2823
|
if (response.response.results.length === 0) {
|
|
2495
|
-
if (
|
|
2824
|
+
if (
|
|
2825
|
+
!keepRemoteAlive &&
|
|
2826
|
+
peerBufferMap.get(peer)?.buffer.length === 0
|
|
2827
|
+
) {
|
|
2496
2828
|
peerBufferMap.delete(peer); // No more results
|
|
2497
2829
|
}
|
|
2498
2830
|
} else {
|
|
@@ -2745,48 +3077,102 @@ export class DocumentIndex<
|
|
|
2745
3077
|
let fetchedFirstForRemote: Set<string> | undefined = undefined;
|
|
2746
3078
|
|
|
2747
3079
|
let updateDeferred: ReturnType<typeof pDefer> | undefined;
|
|
2748
|
-
const signalUpdate = () =>
|
|
3080
|
+
const signalUpdate = (reason?: string) => {
|
|
3081
|
+
if (reason) {
|
|
3082
|
+
/* console.debug("[DocumentIndex] signalUpdate", {
|
|
3083
|
+
id: queryRequestCoerced.idString,
|
|
3084
|
+
reason,
|
|
3085
|
+
}); */
|
|
3086
|
+
}
|
|
3087
|
+
updateDeferred?.resolve();
|
|
3088
|
+
};
|
|
2749
3089
|
const _waitForUpdate = () =>
|
|
2750
3090
|
updateDeferred ? updateDeferred.promise : Promise.resolve();
|
|
2751
3091
|
|
|
2752
3092
|
// ---------------- Live updates wiring (sorted-only with optional filter) ----------------
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
3093
|
+
function normalizeUpdatesOption(u?: UpdateOptions<T, I, Resolve>): {
|
|
3094
|
+
mergePolicy?: {
|
|
3095
|
+
merge?:
|
|
3096
|
+
| {
|
|
3097
|
+
filter?: (
|
|
3098
|
+
evt: DocumentsChange<T, I>,
|
|
3099
|
+
) => MaybePromise<DocumentsChange<T, I> | void>;
|
|
3100
|
+
}
|
|
3101
|
+
| undefined;
|
|
3102
|
+
};
|
|
3103
|
+
push: boolean;
|
|
3104
|
+
callbacks?: UpdateCallbacks<T, I, Resolve>;
|
|
3105
|
+
} {
|
|
3106
|
+
const identityFilter = (evt: DocumentsChange<T, I>) => evt;
|
|
3107
|
+
const buildMergePolicy = (
|
|
3108
|
+
merge: UpdateMergeStrategy<T, I, Resolve> | undefined,
|
|
3109
|
+
defaultEnabled: boolean,
|
|
3110
|
+
) => {
|
|
3111
|
+
const effective =
|
|
3112
|
+
merge === undefined ? (defaultEnabled ? true : undefined) : merge;
|
|
3113
|
+
if (effective === undefined || effective === false) {
|
|
3114
|
+
return undefined;
|
|
3115
|
+
}
|
|
3116
|
+
if (effective === true) {
|
|
3117
|
+
return {
|
|
3118
|
+
merge: {
|
|
3119
|
+
filter: identityFilter,
|
|
3120
|
+
},
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
2768
3123
|
return {
|
|
2769
3124
|
merge: {
|
|
2770
|
-
filter:
|
|
3125
|
+
filter: effective.filter ?? identityFilter,
|
|
2771
3126
|
},
|
|
2772
3127
|
};
|
|
3128
|
+
};
|
|
3129
|
+
|
|
3130
|
+
if (u == null || u === false) {
|
|
3131
|
+
return { push: false };
|
|
3132
|
+
}
|
|
3133
|
+
|
|
3134
|
+
if (u === true) {
|
|
3135
|
+
return {
|
|
3136
|
+
mergePolicy: buildMergePolicy(true, true),
|
|
3137
|
+
push: false,
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
if (typeof u === "string") {
|
|
3142
|
+
if (u === "remote") {
|
|
3143
|
+
return { push: true };
|
|
3144
|
+
}
|
|
3145
|
+
if (u === "local") {
|
|
3146
|
+
return {
|
|
3147
|
+
mergePolicy: buildMergePolicy(true, true),
|
|
3148
|
+
push: false,
|
|
3149
|
+
};
|
|
3150
|
+
}
|
|
3151
|
+
if (u === "all") {
|
|
3152
|
+
return {
|
|
3153
|
+
mergePolicy: buildMergePolicy(true, true),
|
|
3154
|
+
push: true,
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
|
|
2773
3159
|
if (typeof u === "object") {
|
|
3160
|
+
const hasMergeProp = Object.prototype.hasOwnProperty.call(u, "merge");
|
|
3161
|
+
const mergeValue = hasMergeProp ? u.merge : undefined;
|
|
2774
3162
|
return {
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
3163
|
+
mergePolicy: buildMergePolicy(
|
|
3164
|
+
mergeValue,
|
|
3165
|
+
!hasMergeProp || mergeValue === undefined,
|
|
3166
|
+
),
|
|
3167
|
+
push: Boolean(u.push),
|
|
3168
|
+
callbacks: u,
|
|
2781
3169
|
};
|
|
2782
3170
|
}
|
|
2783
|
-
return undefined;
|
|
2784
|
-
};
|
|
2785
3171
|
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
const
|
|
3172
|
+
return { push: false };
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
const updateCallbacks = updateCallbacksRaw;
|
|
2790
3176
|
let pendingResultsReason:
|
|
2791
3177
|
| Extract<ResultBatchReason, "join" | "change">
|
|
2792
3178
|
| undefined;
|
|
@@ -2819,6 +3205,180 @@ export class DocumentIndex<
|
|
|
2819
3205
|
updateDeferred = pDefer<void>();
|
|
2820
3206
|
}
|
|
2821
3207
|
|
|
3208
|
+
const keepRemoteAlive =
|
|
3209
|
+
(options?.closePolicy === "manual" || hasLiveUpdates || pushUpdates) &&
|
|
3210
|
+
remoteOptions !== false;
|
|
3211
|
+
|
|
3212
|
+
if (queryRequestCoerced instanceof types.IterationRequest) {
|
|
3213
|
+
queryRequestCoerced.resolve = resolve;
|
|
3214
|
+
queryRequestCoerced.fetch = queryRequestCoerced.fetch ?? 10;
|
|
3215
|
+
const replicateFlag = !resolve && replicate ? true : false;
|
|
3216
|
+
queryRequestCoerced.replicate = replicateFlag;
|
|
3217
|
+
const ttlSource =
|
|
3218
|
+
typeof remoteOptions === "object" &&
|
|
3219
|
+
typeof remoteOptions?.wait === "object"
|
|
3220
|
+
? (remoteOptions.wait.timeout ?? DEFAULT_TIMEOUT)
|
|
3221
|
+
: DEFAULT_TIMEOUT;
|
|
3222
|
+
queryRequestCoerced.keepAliveTtl = keepRemoteAlive
|
|
3223
|
+
? BigInt(ttlSource)
|
|
3224
|
+
: undefined;
|
|
3225
|
+
queryRequestCoerced.pushUpdates = pushUpdates ? true : undefined;
|
|
3226
|
+
queryRequestCoerced.mergeUpdates = mergePolicy?.merge ? true : undefined;
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
if (pushUpdates && this.prefetch?.accumulator) {
|
|
3230
|
+
const targetPrefetchKey = idAgnosticQueryKey(queryRequestCoerced);
|
|
3231
|
+
const mergePrefetchedResults = async (
|
|
3232
|
+
from: PublicSignKey,
|
|
3233
|
+
results: types.Results<types.ResultTypeFromRequest<R, T, I>>,
|
|
3234
|
+
) => {
|
|
3235
|
+
const peerHash = from.hashcode();
|
|
3236
|
+
const existingBuffer = peerBufferMap.get(peerHash);
|
|
3237
|
+
const buffer: BufferedResult<
|
|
3238
|
+
types.ResultTypeFromRequest<R, T, I> | I,
|
|
3239
|
+
I
|
|
3240
|
+
>[] = existingBuffer?.buffer || [];
|
|
3241
|
+
|
|
3242
|
+
if (results.kept === 0n && results.results.length === 0) {
|
|
3243
|
+
peerBufferMap.set(peerHash, {
|
|
3244
|
+
buffer,
|
|
3245
|
+
kept: Number(results.kept),
|
|
3246
|
+
});
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
for (const result of results.results) {
|
|
3251
|
+
const indexKey = indexerTypes.toId(
|
|
3252
|
+
this.indexByResolver(result.value),
|
|
3253
|
+
).primitive;
|
|
3254
|
+
if (result instanceof types.ResultValue) {
|
|
3255
|
+
const existingIndexed = indexedPlaceholders?.get(indexKey);
|
|
3256
|
+
if (existingIndexed) {
|
|
3257
|
+
existingIndexed.value =
|
|
3258
|
+
result.value as types.ResultTypeFromRequest<R, T, I>;
|
|
3259
|
+
existingIndexed.context = result.context;
|
|
3260
|
+
existingIndexed.from = from;
|
|
3261
|
+
existingIndexed.indexed = await this.resolveIndexed<R>(
|
|
3262
|
+
result,
|
|
3263
|
+
results.results as types.ResultTypeFromRequest<R, T, I>[],
|
|
3264
|
+
);
|
|
3265
|
+
indexedPlaceholders?.delete(indexKey);
|
|
3266
|
+
continue;
|
|
3267
|
+
}
|
|
3268
|
+
if (visited.has(indexKey)) {
|
|
3269
|
+
continue;
|
|
3270
|
+
}
|
|
3271
|
+
visited.add(indexKey);
|
|
3272
|
+
buffer.push({
|
|
3273
|
+
value: result.value as types.ResultTypeFromRequest<R, T, I>,
|
|
3274
|
+
context: result.context,
|
|
3275
|
+
from,
|
|
3276
|
+
indexed: await this.resolveIndexed<R>(
|
|
3277
|
+
result,
|
|
3278
|
+
results.results as types.ResultTypeFromRequest<R, T, I>[],
|
|
3279
|
+
),
|
|
3280
|
+
});
|
|
3281
|
+
} else {
|
|
3282
|
+
if (visited.has(indexKey) && !indexedPlaceholders?.has(indexKey)) {
|
|
3283
|
+
continue;
|
|
3284
|
+
}
|
|
3285
|
+
visited.add(indexKey);
|
|
3286
|
+
const indexed = coerceWithContext(
|
|
3287
|
+
result.indexed || result.value,
|
|
3288
|
+
result.context,
|
|
3289
|
+
);
|
|
3290
|
+
const placeholder = {
|
|
3291
|
+
value: result.value,
|
|
3292
|
+
context: result.context,
|
|
3293
|
+
from,
|
|
3294
|
+
indexed,
|
|
3295
|
+
};
|
|
3296
|
+
buffer.push(placeholder);
|
|
3297
|
+
ensureIndexedPlaceholders().set(indexKey, placeholder);
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
peerBufferMap.set(peerHash, {
|
|
3302
|
+
buffer,
|
|
3303
|
+
kept: Number(results.kept),
|
|
3304
|
+
});
|
|
3305
|
+
};
|
|
3306
|
+
|
|
3307
|
+
const consumePrefetch = async (
|
|
3308
|
+
consumable: RPCResponse<types.PredictedSearchRequest<any>>,
|
|
3309
|
+
) => {
|
|
3310
|
+
const request = consumable.response?.request;
|
|
3311
|
+
if (!request) {
|
|
3312
|
+
return;
|
|
3313
|
+
}
|
|
3314
|
+
if (idAgnosticQueryKey(request) !== targetPrefetchKey) {
|
|
3315
|
+
return;
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
/* console.debug("[DocumentIndex] prefetch match", {
|
|
3319
|
+
iterator: queryRequestCoerced.idString,
|
|
3320
|
+
source: consumable.from?.hashcode(),
|
|
3321
|
+
});
|
|
3322
|
+
*/
|
|
3323
|
+
try {
|
|
3324
|
+
const prepared = await introduceEntries(
|
|
3325
|
+
queryRequestCoerced,
|
|
3326
|
+
[
|
|
3327
|
+
{
|
|
3328
|
+
response: consumable.response.results,
|
|
3329
|
+
from: consumable.from,
|
|
3330
|
+
},
|
|
3331
|
+
],
|
|
3332
|
+
this.documentType,
|
|
3333
|
+
this.indexedType,
|
|
3334
|
+
this._sync,
|
|
3335
|
+
options as QueryDetailedOptions<T, I, D, any>,
|
|
3336
|
+
);
|
|
3337
|
+
|
|
3338
|
+
for (const response of prepared) {
|
|
3339
|
+
if (!response.from) {
|
|
3340
|
+
continue;
|
|
3341
|
+
}
|
|
3342
|
+
const payload = response.response;
|
|
3343
|
+
if (!(payload instanceof types.Results)) {
|
|
3344
|
+
continue;
|
|
3345
|
+
}
|
|
3346
|
+
await mergePrefetchedResults(
|
|
3347
|
+
response.from,
|
|
3348
|
+
payload as types.Results<types.ResultTypeFromRequest<R, T, I>>,
|
|
3349
|
+
);
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
if (!pendingResultsReason) {
|
|
3353
|
+
pendingResultsReason = "change";
|
|
3354
|
+
}
|
|
3355
|
+
signalUpdate("prefetch-add");
|
|
3356
|
+
} catch (error) {
|
|
3357
|
+
logger.warn("Failed to merge prefetched results", error);
|
|
3358
|
+
}
|
|
3359
|
+
};
|
|
3360
|
+
|
|
3361
|
+
const onPrefetchAdd = (
|
|
3362
|
+
evt: CustomEvent<{
|
|
3363
|
+
consumable: RPCResponse<types.PredictedSearchRequest<any>>;
|
|
3364
|
+
}>,
|
|
3365
|
+
) => {
|
|
3366
|
+
void consumePrefetch(evt.detail.consumable);
|
|
3367
|
+
};
|
|
3368
|
+
this.prefetch.accumulator.addEventListener(
|
|
3369
|
+
"add",
|
|
3370
|
+
onPrefetchAdd as EventListener,
|
|
3371
|
+
);
|
|
3372
|
+
const cleanupDefault = cleanup;
|
|
3373
|
+
cleanup = () => {
|
|
3374
|
+
this.prefetch?.accumulator.removeEventListener(
|
|
3375
|
+
"add",
|
|
3376
|
+
onPrefetchAdd as EventListener,
|
|
3377
|
+
);
|
|
3378
|
+
return cleanupDefault();
|
|
3379
|
+
};
|
|
3380
|
+
}
|
|
3381
|
+
|
|
2822
3382
|
let updatesCleanup: (() => void) | undefined;
|
|
2823
3383
|
if (hasLiveUpdates) {
|
|
2824
3384
|
const localHash = this.node.identity.publicKey.hashcode();
|
|
@@ -2864,6 +3424,11 @@ export class DocumentIndex<
|
|
|
2864
3424
|
};
|
|
2865
3425
|
|
|
2866
3426
|
const onChange = async (evt: CustomEvent<DocumentsChange<T, I>>) => {
|
|
3427
|
+
/* console.debug("[DocumentIndex] onChange event", {
|
|
3428
|
+
id: queryRequestCoerced.idString,
|
|
3429
|
+
added: evt.detail.added?.length,
|
|
3430
|
+
removed: evt.detail.removed?.length,
|
|
3431
|
+
}); */
|
|
2867
3432
|
// Optional filter to mutate/suppress change events
|
|
2868
3433
|
let filtered: DocumentsChange<T, I> | void = evt.detail;
|
|
2869
3434
|
if (mergePolicy?.merge?.filter) {
|
|
@@ -2981,6 +3546,12 @@ export class DocumentIndex<
|
|
|
2981
3546
|
indexed: indexedCandidate,
|
|
2982
3547
|
};
|
|
2983
3548
|
buf.buffer.push(placeholder);
|
|
3549
|
+
/* console.debug("[DocumentIndex] buffered change", {
|
|
3550
|
+
id: queryRequestCoerced.idString,
|
|
3551
|
+
placeholderId: (valueForBuffer as any)?.id,
|
|
3552
|
+
peer: localHash,
|
|
3553
|
+
bufferSize: buf.buffer.length,
|
|
3554
|
+
}); */
|
|
2984
3555
|
if (!resolve) {
|
|
2985
3556
|
ensureIndexedPlaceholders().set(id, placeholder);
|
|
2986
3557
|
}
|
|
@@ -2997,7 +3568,13 @@ export class DocumentIndex<
|
|
|
2997
3568
|
changeForCallback.added.length > 0 ||
|
|
2998
3569
|
changeForCallback.removed.length > 0
|
|
2999
3570
|
) {
|
|
3571
|
+
/* console.debug("[DocumentIndex] changeForCallback", {
|
|
3572
|
+
id: queryRequestCoerced.idString,
|
|
3573
|
+
added: changeForCallback.added.map((x) => (x as any)?.id),
|
|
3574
|
+
removed: changeForCallback.removed.map((x) => (x as any)?.id),
|
|
3575
|
+
}); */
|
|
3000
3576
|
updateCallbacks?.onChange?.(changeForCallback);
|
|
3577
|
+
signalUpdate("change");
|
|
3001
3578
|
}
|
|
3002
3579
|
}
|
|
3003
3580
|
}
|
|
@@ -3099,8 +3676,8 @@ export class DocumentIndex<
|
|
|
3099
3676
|
};
|
|
3100
3677
|
}
|
|
3101
3678
|
|
|
3102
|
-
if (
|
|
3103
|
-
|
|
3679
|
+
if (keepRemoteAlive) {
|
|
3680
|
+
const prevMaybeSetDone = maybeSetDone;
|
|
3104
3681
|
maybeSetDone = () => {
|
|
3105
3682
|
if (drain) {
|
|
3106
3683
|
prevMaybeSetDone();
|
|
@@ -3126,7 +3703,16 @@ export class DocumentIndex<
|
|
|
3126
3703
|
close,
|
|
3127
3704
|
next,
|
|
3128
3705
|
done: doneFn,
|
|
3129
|
-
pending: () => {
|
|
3706
|
+
pending: async () => {
|
|
3707
|
+
try {
|
|
3708
|
+
await fetchPromise;
|
|
3709
|
+
if (!done && keepRemoteAlive) {
|
|
3710
|
+
await fetchAtLeast(1);
|
|
3711
|
+
}
|
|
3712
|
+
} catch (error) {
|
|
3713
|
+
logger.warn("Failed to refresh iterator pending state", error);
|
|
3714
|
+
}
|
|
3715
|
+
|
|
3130
3716
|
let pendingCount = 0;
|
|
3131
3717
|
for (const buffer of peerBufferMap.values()) {
|
|
3132
3718
|
pendingCount += buffer.kept + buffer.buffer.length;
|