@peerbit/document 12.3.4 → 12.3.5-000e3f1
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/benchmark/iterate-replicate-2.js +0 -13
- package/dist/benchmark/iterate-replicate-2.js.map +1 -1
- package/dist/benchmark/iterate-replicate.js +0 -13
- package/dist/benchmark/iterate-replicate.js.map +1 -1
- package/dist/benchmark/replication-network.d.ts +2 -0
- package/dist/benchmark/replication-network.d.ts.map +1 -0
- package/dist/benchmark/replication-network.js +872 -0
- package/dist/benchmark/replication-network.js.map +1 -0
- package/dist/benchmark/replication.js +0 -19
- package/dist/benchmark/replication.js.map +1 -1
- package/dist/src/program.d.ts.map +1 -1
- package/dist/src/program.js +1 -0
- package/dist/src/program.js.map +1 -1
- package/dist/src/resumable-iterator.d.ts +1 -0
- package/dist/src/resumable-iterator.d.ts.map +1 -1
- package/dist/src/resumable-iterator.js +29 -0
- package/dist/src/resumable-iterator.js.map +1 -1
- package/dist/src/search.d.ts.map +1 -1
- package/dist/src/search.js +215 -67
- package/dist/src/search.js.map +1 -1
- package/package.json +20 -20
- package/src/program.ts +1 -0
- package/src/resumable-iterator.ts +33 -0
- package/src/search.ts +319 -154
package/src/search.ts
CHANGED
|
@@ -289,6 +289,7 @@ type QueryDetailedOptions<
|
|
|
289
289
|
response: types.AbstractSearchResult,
|
|
290
290
|
from: PublicSignKey,
|
|
291
291
|
) => void | Promise<void>;
|
|
292
|
+
onMissingResponses?: (error: MissingResponsesError) => void | Promise<void>;
|
|
292
293
|
remote?: {
|
|
293
294
|
from?: string[]; // if specified, only query these peers
|
|
294
295
|
};
|
|
@@ -1338,7 +1339,23 @@ export class DocumentIndex<
|
|
|
1338
1339
|
);
|
|
1339
1340
|
}
|
|
1340
1341
|
this.clearAllResultQueues();
|
|
1341
|
-
await this.
|
|
1342
|
+
await this._resumableIterators.clearAll();
|
|
1343
|
+
if (this.iteratorKeepAliveTimers) {
|
|
1344
|
+
for (const timer of this.iteratorKeepAliveTimers.values()) {
|
|
1345
|
+
clearTimeout(timer);
|
|
1346
|
+
}
|
|
1347
|
+
this.iteratorKeepAliveTimers.clear();
|
|
1348
|
+
}
|
|
1349
|
+
try {
|
|
1350
|
+
await this.index?.stop?.();
|
|
1351
|
+
} catch (error) {
|
|
1352
|
+
// Be defensive during teardown: stopping an already-stopped index shouldn't
|
|
1353
|
+
// prevent closing the program and releasing timers/iterators.
|
|
1354
|
+
if (error instanceof indexerTypes.NotStartedError) {
|
|
1355
|
+
return closed;
|
|
1356
|
+
}
|
|
1357
|
+
throw error;
|
|
1358
|
+
}
|
|
1342
1359
|
}
|
|
1343
1360
|
return closed;
|
|
1344
1361
|
}
|
|
@@ -1351,8 +1368,27 @@ export class DocumentIndex<
|
|
|
1351
1368
|
this.handleDocumentChange,
|
|
1352
1369
|
);
|
|
1353
1370
|
this.clearAllResultQueues();
|
|
1354
|
-
await this.
|
|
1355
|
-
|
|
1371
|
+
await this._resumableIterators.clearAll();
|
|
1372
|
+
if (this.iteratorKeepAliveTimers) {
|
|
1373
|
+
for (const timer of this.iteratorKeepAliveTimers.values()) {
|
|
1374
|
+
clearTimeout(timer);
|
|
1375
|
+
}
|
|
1376
|
+
this.iteratorKeepAliveTimers.clear();
|
|
1377
|
+
}
|
|
1378
|
+
try {
|
|
1379
|
+
await this.index?.drop?.();
|
|
1380
|
+
} catch (error) {
|
|
1381
|
+
if (!(error instanceof indexerTypes.NotStartedError)) {
|
|
1382
|
+
throw error;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
try {
|
|
1386
|
+
await this.index?.stop?.();
|
|
1387
|
+
} catch (error) {
|
|
1388
|
+
if (!(error instanceof indexerTypes.NotStartedError)) {
|
|
1389
|
+
throw error;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1356
1392
|
}
|
|
1357
1393
|
return dropped;
|
|
1358
1394
|
}
|
|
@@ -1367,17 +1403,24 @@ export class DocumentIndex<
|
|
|
1367
1403
|
options?: Options,
|
|
1368
1404
|
): Promise<WithContext<I>>;
|
|
1369
1405
|
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1406
|
+
public async get<
|
|
1407
|
+
Options extends GetOptions<T, I, D, Resolve>,
|
|
1408
|
+
Resolve extends boolean | undefined = ExtractResolveFromOptions<Options>,
|
|
1409
|
+
>(key: indexerTypes.Ideable | indexerTypes.IdKey, options?: Options) {
|
|
1410
|
+
let deferred:
|
|
1411
|
+
| DeferredPromise<WithIndexedContext<T, I> | WithContext<I>>
|
|
1412
|
+
| undefined;
|
|
1413
|
+
let baseRemote:
|
|
1414
|
+
| RemoteQueryOptions<
|
|
1415
|
+
types.AbstractSearchRequest,
|
|
1416
|
+
types.AbstractSearchResult,
|
|
1417
|
+
D
|
|
1418
|
+
>
|
|
1419
|
+
| undefined;
|
|
1420
|
+
|
|
1421
|
+
// Normalize the id key early so listeners can use it
|
|
1422
|
+
let idKey =
|
|
1423
|
+
key instanceof indexerTypes.IdKey ? key : indexerTypes.toId(key);
|
|
1381
1424
|
|
|
1382
1425
|
if (options?.waitFor) {
|
|
1383
1426
|
// add change listener before query because we might get a concurrent change that matches the query,
|
|
@@ -1410,16 +1453,16 @@ export class DocumentIndex<
|
|
|
1410
1453
|
|
|
1411
1454
|
let timeout = setTimeout(resolveUndefined, options.waitFor);
|
|
1412
1455
|
this.events.addEventListener("close", resolveUndefined);
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1456
|
+
this.documentEvents.addEventListener("change", listener);
|
|
1457
|
+
deferred.promise.then(cleanup);
|
|
1458
|
+
|
|
1459
|
+
// Prepare remote options without mutating caller options
|
|
1460
|
+
baseRemote =
|
|
1461
|
+
options?.remote === false
|
|
1462
|
+
? undefined
|
|
1463
|
+
: typeof options?.remote === "object"
|
|
1464
|
+
? { ...options.remote }
|
|
1465
|
+
: {};
|
|
1423
1466
|
if (baseRemote) {
|
|
1424
1467
|
const waitPolicy = baseRemote.wait;
|
|
1425
1468
|
if (
|
|
@@ -1455,16 +1498,20 @@ export class DocumentIndex<
|
|
|
1455
1498
|
deferred!.resolve(first.value as any);
|
|
1456
1499
|
}
|
|
1457
1500
|
},
|
|
1458
|
-
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1459
1503
|
}
|
|
1460
|
-
}
|
|
1461
1504
|
|
|
1462
|
-
|
|
1505
|
+
const initialOptions = baseRemote
|
|
1506
|
+
? ({ ...(options as any), remote: baseRemote } as Options)
|
|
1507
|
+
: options;
|
|
1508
|
+
const result =
|
|
1509
|
+
(await this.getDetailed(idKey, initialOptions))?.[0]?.results[0];
|
|
1463
1510
|
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1511
|
+
// if no results, and we have remote joining options, we wait for the timout and if there are joining peers we re-query
|
|
1512
|
+
if (!result) {
|
|
1513
|
+
return deferred?.promise;
|
|
1514
|
+
} else if (deferred) {
|
|
1468
1515
|
deferred.resolve(undefined);
|
|
1469
1516
|
}
|
|
1470
1517
|
return result?.value;
|
|
@@ -1855,24 +1902,26 @@ export class DocumentIndex<
|
|
|
1855
1902
|
const resolveFlag = resolvesDocuments(
|
|
1856
1903
|
(fromQuery || query) as AnyIterationRequest,
|
|
1857
1904
|
);
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1905
|
+
prevQueued = {
|
|
1906
|
+
from,
|
|
1907
|
+
queue: [],
|
|
1908
|
+
timeout: setTimeout(() => {
|
|
1909
|
+
this._resultQueue.delete(query.idString);
|
|
1910
|
+
}, 6e4),
|
|
1911
|
+
keptInIndex: kept,
|
|
1912
|
+
fromQuery: (fromQuery || query) as
|
|
1913
|
+
| types.SearchRequest
|
|
1914
|
+
| types.SearchRequestIndexed
|
|
1915
|
+
| types.IterationRequest,
|
|
1916
|
+
resolveResults: resolveFlag,
|
|
1917
|
+
};
|
|
1918
|
+
// Don't keep Node alive just to GC old remote iterator state.
|
|
1919
|
+
prevQueued.timeout.unref?.();
|
|
1920
|
+
if (
|
|
1921
|
+
fromQuery instanceof types.IterationRequest &&
|
|
1922
|
+
fromQuery.pushUpdates
|
|
1923
|
+
) {
|
|
1924
|
+
prevQueued.pushMode = fromQuery.pushUpdates;
|
|
1876
1925
|
}
|
|
1877
1926
|
this._resultQueue.set(query.idString, prevQueued);
|
|
1878
1927
|
}
|
|
@@ -1970,17 +2019,19 @@ export class DocumentIndex<
|
|
|
1970
2019
|
string,
|
|
1971
2020
|
ReturnType<typeof setTimeout>
|
|
1972
2021
|
>());
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
2022
|
+
const timer = setTimeout(() => {
|
|
2023
|
+
timers.delete(idString);
|
|
2024
|
+
const queued = this._resultQueue.get(idString);
|
|
2025
|
+
if (queued) {
|
|
2026
|
+
clearTimeout(queued.timeout);
|
|
2027
|
+
this._resultQueue.delete(idString);
|
|
2028
|
+
}
|
|
2029
|
+
this._resumableIterators.close({ idString });
|
|
2030
|
+
}, delay);
|
|
2031
|
+
// This is a best-effort cleanup timer; it should not keep Node alive.
|
|
2032
|
+
timer.unref?.();
|
|
2033
|
+
timers.set(idString, timer);
|
|
2034
|
+
}
|
|
1984
2035
|
|
|
1985
2036
|
private cancelIteratorKeepAlive(idString: string) {
|
|
1986
2037
|
const timers = this.iteratorKeepAliveTimers;
|
|
@@ -2213,30 +2264,35 @@ export class DocumentIndex<
|
|
|
2213
2264
|
queryRequest: R,
|
|
2214
2265
|
options?: QueryDetailedOptions<T, I, D, boolean | undefined>,
|
|
2215
2266
|
fetchFirstForRemote?: Set<string>,
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
remote = {};
|
|
2267
|
+
): Promise<types.Results<RT>[]> {
|
|
2268
|
+
const local = typeof options?.local === "boolean" ? options?.local : true;
|
|
2269
|
+
let remote:
|
|
2270
|
+
| RemoteQueryOptions<
|
|
2271
|
+
types.AbstractSearchRequest,
|
|
2272
|
+
types.AbstractSearchResult,
|
|
2273
|
+
D
|
|
2274
|
+
>
|
|
2275
|
+
| undefined = undefined;
|
|
2276
|
+
if (typeof options?.remote === "boolean") {
|
|
2277
|
+
remote = options.remote ? {} : undefined;
|
|
2228
2278
|
} else {
|
|
2229
|
-
remote =
|
|
2279
|
+
remote = options?.remote || {};
|
|
2230
2280
|
}
|
|
2231
|
-
} else {
|
|
2232
|
-
remote = options?.remote || {};
|
|
2233
|
-
}
|
|
2234
2281
|
if (remote && remote.priority == null) {
|
|
2235
2282
|
// give queries higher priority than other "normal" data activities
|
|
2236
2283
|
// without this, we might have a scenario that a peer joina network with large amount of data to be synced, but can not query anything before that is done
|
|
2237
2284
|
// this will lead to bad UX as you usually want to list/expore whats going on before doing any replication work
|
|
2238
2285
|
remote.priority = 2;
|
|
2239
2286
|
}
|
|
2287
|
+
if (remote && remote.timeout == null && options?.remote) {
|
|
2288
|
+
const waitPolicy =
|
|
2289
|
+
typeof options.remote === "object" ? options.remote.wait : undefined;
|
|
2290
|
+
const waitTimeout =
|
|
2291
|
+
typeof waitPolicy === "object" ? waitPolicy.timeout : undefined;
|
|
2292
|
+
if (waitTimeout != null) {
|
|
2293
|
+
remote.timeout = waitTimeout;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2240
2296
|
|
|
2241
2297
|
if (!local && !remote) {
|
|
2242
2298
|
throw new Error(
|
|
@@ -2266,19 +2322,76 @@ export class DocumentIndex<
|
|
|
2266
2322
|
throw new Error("Unexpected");
|
|
2267
2323
|
}
|
|
2268
2324
|
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2325
|
+
const coverProps = remote.domain ?? { args: undefined };
|
|
2326
|
+
const isDefaultDomainArgs =
|
|
2327
|
+
!("range" in coverProps) &&
|
|
2328
|
+
(!("args" in coverProps) || (coverProps as any).args == null);
|
|
2329
|
+
|
|
2330
|
+
let replicatorGroups = options?.remote?.from
|
|
2331
|
+
? options?.remote?.from
|
|
2332
|
+
: await this._log.getCover(coverProps, {
|
|
2333
|
+
roleAge: remote.minAge,
|
|
2334
|
+
eager: remote.reach?.eager,
|
|
2335
|
+
reachableOnly: !!remote.wait, // when we want to merge joining we can ignore pending to be online peers and instead consider them once they become online
|
|
2336
|
+
signal: options?.signal,
|
|
2337
|
+
});
|
|
2338
|
+
|
|
2339
|
+
// Cold start: cover can be temporarily empty/self-only while replication metadata
|
|
2340
|
+
// converges. For remote search, it's sometimes better to at least try currently
|
|
2341
|
+
// connected peers, but only if we have evidence that a remote replicator exists.
|
|
2342
|
+
if (!options?.remote?.from && isDefaultDomainArgs) {
|
|
2343
|
+
const selfHash = this.node.identity.publicKey.hashcode();
|
|
2344
|
+
const remoteCount = replicatorGroups.filter((h) => h !== selfHash).length;
|
|
2345
|
+
if (remoteCount === 0) {
|
|
2346
|
+
const waitEnabled = Boolean(remote.wait);
|
|
2347
|
+
const coverIsSelfOnly =
|
|
2348
|
+
replicatorGroups.length === 1 && replicatorGroups[0] === selfHash;
|
|
2349
|
+
|
|
2350
|
+
// If the cover is explicitly empty (no shards), don't override it unless
|
|
2351
|
+
// the caller requested waiting for joins (e.g. get(waitFor)).
|
|
2352
|
+
if (!waitEnabled && !coverIsSelfOnly) {
|
|
2353
|
+
// no-op
|
|
2354
|
+
} else {
|
|
2355
|
+
let hasKnownRemoteReplicator = false;
|
|
2356
|
+
if (!waitEnabled) {
|
|
2357
|
+
try {
|
|
2358
|
+
const replicators = await this._log.getReplicators();
|
|
2359
|
+
for (const hash of replicators.keys()) {
|
|
2360
|
+
if (hash !== selfHash) {
|
|
2361
|
+
hasKnownRemoteReplicator = true;
|
|
2362
|
+
break;
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
} catch {
|
|
2366
|
+
// Best-effort only.
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
if (waitEnabled || hasKnownRemoteReplicator) {
|
|
2371
|
+
const peerMap: Map<string, unknown> | undefined = (this.node.services
|
|
2372
|
+
.pubsub as any)?.peers;
|
|
2373
|
+
if (peerMap?.keys) {
|
|
2374
|
+
const extra: string[] = [];
|
|
2375
|
+
for (const hash of peerMap.keys()) {
|
|
2376
|
+
if (!hash || hash === selfHash) continue;
|
|
2377
|
+
extra.push(hash);
|
|
2378
|
+
if (extra.length >= 8) break;
|
|
2379
|
+
}
|
|
2380
|
+
if (extra.length > 0) {
|
|
2381
|
+
replicatorGroups = [
|
|
2382
|
+
...new Set([...replicatorGroups, ...extra]),
|
|
2383
|
+
];
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2277
2390
|
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2391
|
+
if (replicatorGroups) {
|
|
2392
|
+
const responseHandler = async (
|
|
2393
|
+
results: {
|
|
2394
|
+
response: types.AbstractSearchResult;
|
|
2282
2395
|
from?: PublicSignKey;
|
|
2283
2396
|
}[],
|
|
2284
2397
|
) => {
|
|
@@ -2403,6 +2516,9 @@ export class DocumentIndex<
|
|
|
2403
2516
|
} catch (error) {
|
|
2404
2517
|
if (error instanceof MissingResponsesError) {
|
|
2405
2518
|
warn("Did not reciveve responses from all shard");
|
|
2519
|
+
if (options?.onMissingResponses) {
|
|
2520
|
+
await options.onMissingResponses(error);
|
|
2521
|
+
}
|
|
2406
2522
|
if (remote?.throwOnMissing) {
|
|
2407
2523
|
throw error;
|
|
2408
2524
|
}
|
|
@@ -2464,18 +2580,17 @@ export class DocumentIndex<
|
|
|
2464
2580
|
options?: O,
|
|
2465
2581
|
): Promise<ValueTypeFromRequest<Resolve, T, I>[]> {
|
|
2466
2582
|
// Set fetch to search size, or max value (default to max u32 (4294967295))
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2583
|
+
const coercedRequest = coerceQuery(
|
|
2584
|
+
queryRequest,
|
|
2585
|
+
options,
|
|
2586
|
+
this.compatibility,
|
|
2587
|
+
);
|
|
2588
|
+
coercedRequest.fetch = coercedRequest.fetch ?? 0xffffffff;
|
|
2473
2589
|
|
|
2474
|
-
|
|
2475
|
-
|
|
2590
|
+
// Use an iterator so large results respect message size limits.
|
|
2591
|
+
const iterator = this.iterate<Resolve>(coercedRequest, options);
|
|
2476
2592
|
|
|
2477
|
-
|
|
2478
|
-
const allResults: ValueTypeFromRequest<Resolve, T, I>[] = [];
|
|
2593
|
+
const allResults: ValueTypeFromRequest<Resolve, T, I>[] = [];
|
|
2479
2594
|
|
|
2480
2595
|
while (
|
|
2481
2596
|
iterator.done() !== true &&
|
|
@@ -2876,37 +2991,43 @@ export class DocumentIndex<
|
|
|
2876
2991
|
|
|
2877
2992
|
if (typeof options?.remote === "object") {
|
|
2878
2993
|
let waitForTime: number | undefined = undefined;
|
|
2994
|
+
const waitPolicy =
|
|
2995
|
+
typeof options.remote.wait === "object"
|
|
2996
|
+
? options.remote.wait
|
|
2997
|
+
: undefined;
|
|
2998
|
+
const waitBehavior: WaitBehavior = waitPolicy?.behavior ?? "keep-open";
|
|
2879
2999
|
if (options.remote.wait) {
|
|
2880
|
-
let t0 = +new Date();
|
|
2881
|
-
|
|
2882
3000
|
waitForTime =
|
|
2883
3001
|
typeof options.remote.wait === "boolean"
|
|
2884
3002
|
? DEFAULT_TIMEOUT
|
|
2885
3003
|
: (options.remote.wait.timeout ?? DEFAULT_TIMEOUT);
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
3004
|
+
if (waitBehavior === "keep-open") {
|
|
3005
|
+
let t0 = +new Date();
|
|
3006
|
+
let setDoneIfTimeout = false;
|
|
3007
|
+
maybeSetDone = () => {
|
|
3008
|
+
if (t0 + waitForTime! < +new Date()) {
|
|
3009
|
+
cleanup();
|
|
3010
|
+
done = true;
|
|
3011
|
+
} else {
|
|
3012
|
+
setDoneIfTimeout = true;
|
|
3013
|
+
}
|
|
3014
|
+
};
|
|
3015
|
+
unsetDone = () => {
|
|
3016
|
+
setDoneIfTimeout = false;
|
|
3017
|
+
done = false;
|
|
3018
|
+
};
|
|
3019
|
+
let timeout = setTimeout(() => {
|
|
3020
|
+
if (setDoneIfTimeout) {
|
|
3021
|
+
cleanup();
|
|
3022
|
+
done = true;
|
|
3023
|
+
}
|
|
3024
|
+
}, waitForTime);
|
|
2905
3025
|
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
3026
|
+
cleanup = () => {
|
|
3027
|
+
this.clearResultsQueue(queryRequestCoerced);
|
|
3028
|
+
clearTimeout(timeout);
|
|
3029
|
+
};
|
|
3030
|
+
}
|
|
2910
3031
|
}
|
|
2911
3032
|
|
|
2912
3033
|
if (options.remote.reach?.discover) {
|
|
@@ -2933,10 +3054,6 @@ export class DocumentIndex<
|
|
|
2933
3054
|
options.remote.reach.eager = true; // include the results from the discovered peer even if it is not mature
|
|
2934
3055
|
}
|
|
2935
3056
|
|
|
2936
|
-
const waitPolicy =
|
|
2937
|
-
typeof options.remote.wait === "object"
|
|
2938
|
-
? options.remote.wait
|
|
2939
|
-
: undefined;
|
|
2940
3057
|
if (
|
|
2941
3058
|
waitPolicy?.behavior === "block" &&
|
|
2942
3059
|
(waitPolicy.until ?? "any") === "any"
|
|
@@ -2961,6 +3078,7 @@ export class DocumentIndex<
|
|
|
2961
3078
|
): Promise<boolean> => {
|
|
2962
3079
|
await warmupPromise;
|
|
2963
3080
|
let hasMore = false;
|
|
3081
|
+
let missingResponses = false;
|
|
2964
3082
|
const discoverTargets =
|
|
2965
3083
|
typeof options?.remote === "object"
|
|
2966
3084
|
? options.remote.reach?.discover
|
|
@@ -3096,10 +3214,38 @@ export class DocumentIndex<
|
|
|
3096
3214
|
);
|
|
3097
3215
|
}
|
|
3098
3216
|
},
|
|
3217
|
+
onMissingResponses: (error) => {
|
|
3218
|
+
missingResponses = true;
|
|
3219
|
+
const missingGroups = (error as MissingResponsesError & {
|
|
3220
|
+
missingGroups?: string[][];
|
|
3221
|
+
}).missingGroups;
|
|
3222
|
+
if (!missingGroups?.length) {
|
|
3223
|
+
return;
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
const selfHash = this.node.identity.publicKey.hashcode();
|
|
3227
|
+
for (const group of missingGroups) {
|
|
3228
|
+
const target = group.find((hash) => {
|
|
3229
|
+
if (!hash || hash === selfHash) return false;
|
|
3230
|
+
const attempts = missingResponseRetryAttempts.get(hash) ?? 0;
|
|
3231
|
+
return attempts < maxMissingResponseRetryAttempts;
|
|
3232
|
+
});
|
|
3233
|
+
if (!target) continue;
|
|
3234
|
+
pendingMissingResponseRetryPeers.add(target);
|
|
3235
|
+
missingResponseRetryAttempts.set(
|
|
3236
|
+
target,
|
|
3237
|
+
(missingResponseRetryAttempts.get(target) ?? 0) + 1,
|
|
3238
|
+
);
|
|
3239
|
+
}
|
|
3240
|
+
},
|
|
3099
3241
|
},
|
|
3100
3242
|
fetchOptions?.fetchedFirstForRemote,
|
|
3101
3243
|
);
|
|
3102
3244
|
|
|
3245
|
+
if (missingResponses) {
|
|
3246
|
+
hasMore = true;
|
|
3247
|
+
unsetDone();
|
|
3248
|
+
}
|
|
3103
3249
|
if (!hasMore) {
|
|
3104
3250
|
maybeSetDone();
|
|
3105
3251
|
}
|
|
@@ -3126,6 +3272,17 @@ export class DocumentIndex<
|
|
|
3126
3272
|
return fetchPromise;
|
|
3127
3273
|
}
|
|
3128
3274
|
|
|
3275
|
+
if (pendingMissingResponseRetryPeers.size > 0) {
|
|
3276
|
+
const retryTargets = [...pendingMissingResponseRetryPeers];
|
|
3277
|
+
pendingMissingResponseRetryPeers.clear();
|
|
3278
|
+
fetchPromise = fetchFirst(n, {
|
|
3279
|
+
from: retryTargets,
|
|
3280
|
+
// retries for missing groups should not be suppressed by first-fetch dedupe
|
|
3281
|
+
fetchedFirstForRemote: undefined,
|
|
3282
|
+
});
|
|
3283
|
+
return fetchPromise;
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3129
3286
|
const promises: Promise<any>[] = [];
|
|
3130
3287
|
let resultsLeft = 0;
|
|
3131
3288
|
|
|
@@ -3533,32 +3690,27 @@ export class DocumentIndex<
|
|
|
3533
3690
|
done = true;
|
|
3534
3691
|
};
|
|
3535
3692
|
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
// send close to remote
|
|
3540
|
-
const closeRequest = new types.CloseIteratorRequest({
|
|
3541
|
-
id: queryRequestCoerced.id,
|
|
3542
|
-
});
|
|
3543
|
-
const promises: Promise<any>[] = [];
|
|
3693
|
+
let close = async () => {
|
|
3694
|
+
cleanupAndDone();
|
|
3544
3695
|
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3696
|
+
// send close to remote (only peers that actually served results / had an active buffer)
|
|
3697
|
+
const closeRequest = new types.CloseIteratorRequest({
|
|
3698
|
+
id: queryRequestCoerced.id,
|
|
3699
|
+
});
|
|
3700
|
+
const selfHash = this.node.identity.publicKey.hashcode();
|
|
3701
|
+
const remotePeers = [...peerBufferMap.entries()]
|
|
3702
|
+
.filter(([peer, buffer]) => peer !== selfHash && buffer.kept > 0)
|
|
3703
|
+
.map(([peer]) => peer);
|
|
3704
|
+
peerBufferMap.clear();
|
|
3705
|
+
await Promise.allSettled(
|
|
3706
|
+
remotePeers.map((peer) =>
|
|
3553
3707
|
this._query.send(closeRequest, {
|
|
3554
3708
|
...options,
|
|
3555
3709
|
mode: new SilentDelivery({ to: [peer], redundancy: 1 }),
|
|
3556
3710
|
}),
|
|
3557
|
-
)
|
|
3558
|
-
|
|
3559
|
-
}
|
|
3560
|
-
await Promise.all(promises);
|
|
3561
|
-
};
|
|
3711
|
+
),
|
|
3712
|
+
);
|
|
3713
|
+
};
|
|
3562
3714
|
options?.signal && options.signal.addEventListener("abort", close);
|
|
3563
3715
|
|
|
3564
3716
|
let doneFn = () => {
|
|
@@ -3568,6 +3720,9 @@ export class DocumentIndex<
|
|
|
3568
3720
|
let joinListener: (() => void) | undefined;
|
|
3569
3721
|
|
|
3570
3722
|
let fetchedFirstForRemote: Set<string> | undefined = undefined;
|
|
3723
|
+
const pendingMissingResponseRetryPeers = new Set<string>();
|
|
3724
|
+
const missingResponseRetryAttempts = new Map<string, number>();
|
|
3725
|
+
const maxMissingResponseRetryAttempts = 2;
|
|
3571
3726
|
|
|
3572
3727
|
let updateDeferred: ReturnType<typeof pDefer> | undefined;
|
|
3573
3728
|
const onLateResultsQueue =
|
|
@@ -4144,13 +4299,24 @@ export class DocumentIndex<
|
|
|
4144
4299
|
};
|
|
4145
4300
|
}
|
|
4146
4301
|
|
|
4147
|
-
|
|
4302
|
+
const remoteConfig =
|
|
4303
|
+
options && typeof options.remote === "object" ? options.remote : undefined;
|
|
4304
|
+
const remoteWaitPolicy =
|
|
4305
|
+
remoteConfig && typeof remoteConfig.wait === "object"
|
|
4306
|
+
? remoteConfig.wait
|
|
4307
|
+
: undefined;
|
|
4308
|
+
const remoteWaitBehavior: WaitBehavior =
|
|
4309
|
+
remoteWaitPolicy?.behavior ?? "keep-open";
|
|
4310
|
+
const keepRemoteWaitOpen =
|
|
4311
|
+
!!remoteConfig?.wait &&
|
|
4312
|
+
remoteWaitBehavior === "keep-open";
|
|
4313
|
+
|
|
4314
|
+
if (keepRemoteWaitOpen) {
|
|
4148
4315
|
// was used to account for missed results when a peer joins; omitted in this minimal handler
|
|
4149
4316
|
|
|
4150
4317
|
updateDeferred = pDefer<void>();
|
|
4151
4318
|
|
|
4152
|
-
const waitForTime =
|
|
4153
|
-
typeof options.remote.wait === "object" && options.remote.wait.timeout;
|
|
4319
|
+
const waitForTime = remoteWaitPolicy?.timeout;
|
|
4154
4320
|
|
|
4155
4321
|
const prevMaybeSetDone = maybeSetDone;
|
|
4156
4322
|
maybeSetDone = () => {
|
|
@@ -4167,7 +4333,7 @@ export class DocumentIndex<
|
|
|
4167
4333
|
fetchedFirstForRemote = new Set<string>();
|
|
4168
4334
|
joinListener = this.createReplicatorJoinListener({
|
|
4169
4335
|
signal: ensureController().signal,
|
|
4170
|
-
eager:
|
|
4336
|
+
eager: remoteConfig?.reach?.eager,
|
|
4171
4337
|
onPeer: async (pk) => {
|
|
4172
4338
|
if (done) return;
|
|
4173
4339
|
const hash = pk.hashcode();
|
|
@@ -4240,8 +4406,7 @@ export class DocumentIndex<
|
|
|
4240
4406
|
}
|
|
4241
4407
|
};
|
|
4242
4408
|
}
|
|
4243
|
-
const remoteWaitActive =
|
|
4244
|
-
typeof options?.remote === "object" && !!options.remote.wait;
|
|
4409
|
+
const remoteWaitActive = keepRemoteWaitOpen;
|
|
4245
4410
|
|
|
4246
4411
|
const waitForUpdateAndResetDeferred = async () => {
|
|
4247
4412
|
if (remoteWaitActive) {
|