@peerbit/document 12.3.5-07ba572 → 12.3.5-cb91e7b

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/src/search.ts CHANGED
@@ -1338,7 +1338,23 @@ export class DocumentIndex<
1338
1338
  );
1339
1339
  }
1340
1340
  this.clearAllResultQueues();
1341
- await this.index?.stop?.();
1341
+ await this._resumableIterators.clearAll();
1342
+ if (this.iteratorKeepAliveTimers) {
1343
+ for (const timer of this.iteratorKeepAliveTimers.values()) {
1344
+ clearTimeout(timer);
1345
+ }
1346
+ this.iteratorKeepAliveTimers.clear();
1347
+ }
1348
+ try {
1349
+ await this.index?.stop?.();
1350
+ } catch (error) {
1351
+ // Be defensive during teardown: stopping an already-stopped index shouldn't
1352
+ // prevent closing the program and releasing timers/iterators.
1353
+ if (error instanceof indexerTypes.NotStartedError) {
1354
+ return closed;
1355
+ }
1356
+ throw error;
1357
+ }
1342
1358
  }
1343
1359
  return closed;
1344
1360
  }
@@ -1351,8 +1367,27 @@ export class DocumentIndex<
1351
1367
  this.handleDocumentChange,
1352
1368
  );
1353
1369
  this.clearAllResultQueues();
1354
- await this.index?.drop?.();
1355
- await this.index?.stop?.();
1370
+ await this._resumableIterators.clearAll();
1371
+ if (this.iteratorKeepAliveTimers) {
1372
+ for (const timer of this.iteratorKeepAliveTimers.values()) {
1373
+ clearTimeout(timer);
1374
+ }
1375
+ this.iteratorKeepAliveTimers.clear();
1376
+ }
1377
+ try {
1378
+ await this.index?.drop?.();
1379
+ } catch (error) {
1380
+ if (!(error instanceof indexerTypes.NotStartedError)) {
1381
+ throw error;
1382
+ }
1383
+ }
1384
+ try {
1385
+ await this.index?.stop?.();
1386
+ } catch (error) {
1387
+ if (!(error instanceof indexerTypes.NotStartedError)) {
1388
+ throw error;
1389
+ }
1390
+ }
1356
1391
  }
1357
1392
  return dropped;
1358
1393
  }
@@ -1367,17 +1402,24 @@ export class DocumentIndex<
1367
1402
  options?: Options,
1368
1403
  ): Promise<WithContext<I>>;
1369
1404
 
1370
- public async get<
1371
- Options extends GetOptions<T, I, D, Resolve>,
1372
- Resolve extends boolean | undefined = ExtractResolveFromOptions<Options>,
1373
- >(key: indexerTypes.Ideable | indexerTypes.IdKey, options?: Options) {
1374
- let deferred:
1375
- | DeferredPromise<WithIndexedContext<T, I> | WithContext<I>>
1376
- | undefined;
1377
-
1378
- // Normalize the id key early so listeners can use it
1379
- let idKey =
1380
- key instanceof indexerTypes.IdKey ? key : indexerTypes.toId(key);
1405
+ public async get<
1406
+ Options extends GetOptions<T, I, D, Resolve>,
1407
+ Resolve extends boolean | undefined = ExtractResolveFromOptions<Options>,
1408
+ >(key: indexerTypes.Ideable | indexerTypes.IdKey, options?: Options) {
1409
+ let deferred:
1410
+ | DeferredPromise<WithIndexedContext<T, I> | WithContext<I>>
1411
+ | undefined;
1412
+ let baseRemote:
1413
+ | RemoteQueryOptions<
1414
+ types.AbstractSearchRequest,
1415
+ types.AbstractSearchResult,
1416
+ D
1417
+ >
1418
+ | undefined;
1419
+
1420
+ // Normalize the id key early so listeners can use it
1421
+ let idKey =
1422
+ key instanceof indexerTypes.IdKey ? key : indexerTypes.toId(key);
1381
1423
 
1382
1424
  if (options?.waitFor) {
1383
1425
  // add change listener before query because we might get a concurrent change that matches the query,
@@ -1410,16 +1452,16 @@ export class DocumentIndex<
1410
1452
 
1411
1453
  let timeout = setTimeout(resolveUndefined, options.waitFor);
1412
1454
  this.events.addEventListener("close", resolveUndefined);
1413
- this.documentEvents.addEventListener("change", listener);
1414
- deferred.promise.then(cleanup);
1415
-
1416
- // Prepare remote options without mutating caller options
1417
- const baseRemote =
1418
- options?.remote === false
1419
- ? undefined
1420
- : typeof options?.remote === "object"
1421
- ? { ...options.remote }
1422
- : {};
1455
+ this.documentEvents.addEventListener("change", listener);
1456
+ deferred.promise.then(cleanup);
1457
+
1458
+ // Prepare remote options without mutating caller options
1459
+ baseRemote =
1460
+ options?.remote === false
1461
+ ? undefined
1462
+ : typeof options?.remote === "object"
1463
+ ? { ...options.remote }
1464
+ : {};
1423
1465
  if (baseRemote) {
1424
1466
  const waitPolicy = baseRemote.wait;
1425
1467
  if (
@@ -1455,16 +1497,20 @@ export class DocumentIndex<
1455
1497
  deferred!.resolve(first.value as any);
1456
1498
  }
1457
1499
  },
1458
- });
1500
+ });
1501
+ }
1459
1502
  }
1460
- }
1461
1503
 
1462
- const result = (await this.getDetailed(idKey, options))?.[0]?.results[0];
1504
+ const initialOptions = baseRemote
1505
+ ? ({ ...(options as any), remote: baseRemote } as Options)
1506
+ : options;
1507
+ const result =
1508
+ (await this.getDetailed(idKey, initialOptions))?.[0]?.results[0];
1463
1509
 
1464
- // if no results, and we have remote joining options, we wait for the timout and if there are joining peers we re-query
1465
- if (!result) {
1466
- return deferred?.promise;
1467
- } else if (deferred) {
1510
+ // if no results, and we have remote joining options, we wait for the timout and if there are joining peers we re-query
1511
+ if (!result) {
1512
+ return deferred?.promise;
1513
+ } else if (deferred) {
1468
1514
  deferred.resolve(undefined);
1469
1515
  }
1470
1516
  return result?.value;
@@ -1855,24 +1901,26 @@ export class DocumentIndex<
1855
1901
  const resolveFlag = resolvesDocuments(
1856
1902
  (fromQuery || query) as AnyIterationRequest,
1857
1903
  );
1858
- prevQueued = {
1859
- from,
1860
- queue: [],
1861
- timeout: setTimeout(() => {
1862
- this._resultQueue.delete(query.idString);
1863
- }, 6e4),
1864
- keptInIndex: kept,
1865
- fromQuery: (fromQuery || query) as
1866
- | types.SearchRequest
1867
- | types.SearchRequestIndexed
1868
- | types.IterationRequest,
1869
- resolveResults: resolveFlag,
1870
- };
1871
- if (
1872
- fromQuery instanceof types.IterationRequest &&
1873
- fromQuery.pushUpdates
1874
- ) {
1875
- prevQueued.pushMode = fromQuery.pushUpdates;
1904
+ prevQueued = {
1905
+ from,
1906
+ queue: [],
1907
+ timeout: setTimeout(() => {
1908
+ this._resultQueue.delete(query.idString);
1909
+ }, 6e4),
1910
+ keptInIndex: kept,
1911
+ fromQuery: (fromQuery || query) as
1912
+ | types.SearchRequest
1913
+ | types.SearchRequestIndexed
1914
+ | types.IterationRequest,
1915
+ resolveResults: resolveFlag,
1916
+ };
1917
+ // Don't keep Node alive just to GC old remote iterator state.
1918
+ prevQueued.timeout.unref?.();
1919
+ if (
1920
+ fromQuery instanceof types.IterationRequest &&
1921
+ fromQuery.pushUpdates
1922
+ ) {
1923
+ prevQueued.pushMode = fromQuery.pushUpdates;
1876
1924
  }
1877
1925
  this._resultQueue.set(query.idString, prevQueued);
1878
1926
  }
@@ -1970,17 +2018,19 @@ export class DocumentIndex<
1970
2018
  string,
1971
2019
  ReturnType<typeof setTimeout>
1972
2020
  >());
1973
- const timer = setTimeout(() => {
1974
- timers.delete(idString);
1975
- const queued = this._resultQueue.get(idString);
1976
- if (queued) {
1977
- clearTimeout(queued.timeout);
1978
- this._resultQueue.delete(idString);
1979
- }
1980
- this._resumableIterators.close({ idString });
1981
- }, delay);
1982
- timers.set(idString, timer);
1983
- }
2021
+ const timer = setTimeout(() => {
2022
+ timers.delete(idString);
2023
+ const queued = this._resultQueue.get(idString);
2024
+ if (queued) {
2025
+ clearTimeout(queued.timeout);
2026
+ this._resultQueue.delete(idString);
2027
+ }
2028
+ this._resumableIterators.close({ idString });
2029
+ }, delay);
2030
+ // This is a best-effort cleanup timer; it should not keep Node alive.
2031
+ timer.unref?.();
2032
+ timers.set(idString, timer);
2033
+ }
1984
2034
 
1985
2035
  private cancelIteratorKeepAlive(idString: string) {
1986
2036
  const timers = this.iteratorKeepAliveTimers;
@@ -2213,30 +2263,35 @@ export class DocumentIndex<
2213
2263
  queryRequest: R,
2214
2264
  options?: QueryDetailedOptions<T, I, D, boolean | undefined>,
2215
2265
  fetchFirstForRemote?: Set<string>,
2216
- ): Promise<types.Results<RT>[]> {
2217
- const local = typeof options?.local === "boolean" ? options?.local : true;
2218
- let remote:
2219
- | RemoteQueryOptions<
2220
- types.AbstractSearchRequest,
2221
- types.AbstractSearchResult,
2222
- D
2223
- >
2224
- | undefined = undefined;
2225
- if (typeof options?.remote === "boolean") {
2226
- if (options?.remote) {
2227
- remote = {};
2266
+ ): Promise<types.Results<RT>[]> {
2267
+ const local = typeof options?.local === "boolean" ? options?.local : true;
2268
+ let remote:
2269
+ | RemoteQueryOptions<
2270
+ types.AbstractSearchRequest,
2271
+ types.AbstractSearchResult,
2272
+ D
2273
+ >
2274
+ | undefined = undefined;
2275
+ if (typeof options?.remote === "boolean") {
2276
+ remote = options.remote ? {} : undefined;
2228
2277
  } else {
2229
- remote = undefined;
2278
+ remote = options?.remote || {};
2230
2279
  }
2231
- } else {
2232
- remote = options?.remote || {};
2233
- }
2234
2280
  if (remote && remote.priority == null) {
2235
2281
  // give queries higher priority than other "normal" data activities
2236
2282
  // 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
2283
  // this will lead to bad UX as you usually want to list/expore whats going on before doing any replication work
2238
2284
  remote.priority = 2;
2239
2285
  }
2286
+ if (remote && remote.timeout == null && options?.remote) {
2287
+ const waitPolicy =
2288
+ typeof options.remote === "object" ? options.remote.wait : undefined;
2289
+ const waitTimeout =
2290
+ typeof waitPolicy === "object" ? waitPolicy.timeout : undefined;
2291
+ if (waitTimeout != null) {
2292
+ remote.timeout = waitTimeout;
2293
+ }
2294
+ }
2240
2295
 
2241
2296
  if (!local && !remote) {
2242
2297
  throw new Error(
@@ -2266,19 +2321,76 @@ export class DocumentIndex<
2266
2321
  throw new Error("Unexpected");
2267
2322
  }
2268
2323
 
2269
- const replicatorGroups = options?.remote?.from
2270
- ? options?.remote?.from
2271
- : await this._log.getCover(remote.domain ?? { args: undefined }, {
2272
- roleAge: remote.minAge,
2273
- eager: remote.reach?.eager,
2274
- 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
2275
- signal: options?.signal,
2276
- });
2324
+ const coverProps = remote.domain ?? { args: undefined };
2325
+ const isDefaultDomainArgs =
2326
+ !("range" in coverProps) &&
2327
+ (!("args" in coverProps) || (coverProps as any).args == null);
2328
+
2329
+ let replicatorGroups = options?.remote?.from
2330
+ ? options?.remote?.from
2331
+ : await this._log.getCover(coverProps, {
2332
+ roleAge: remote.minAge,
2333
+ eager: remote.reach?.eager,
2334
+ 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
2335
+ signal: options?.signal,
2336
+ });
2277
2337
 
2278
- if (replicatorGroups) {
2279
- const responseHandler = async (
2280
- results: {
2281
- response: types.AbstractSearchResult;
2338
+ // Cold start: cover can be temporarily empty/self-only while replication metadata
2339
+ // converges. For remote search, it's sometimes better to at least try currently
2340
+ // connected peers, but only if we have evidence that a remote replicator exists.
2341
+ if (!options?.remote?.from && isDefaultDomainArgs) {
2342
+ const selfHash = this.node.identity.publicKey.hashcode();
2343
+ const remoteCount = replicatorGroups.filter((h) => h !== selfHash).length;
2344
+ if (remoteCount === 0) {
2345
+ const waitEnabled = Boolean(remote.wait);
2346
+ const coverIsSelfOnly =
2347
+ replicatorGroups.length === 1 && replicatorGroups[0] === selfHash;
2348
+
2349
+ // If the cover is explicitly empty (no shards), don't override it unless
2350
+ // the caller requested waiting for joins (e.g. get(waitFor)).
2351
+ if (!waitEnabled && !coverIsSelfOnly) {
2352
+ // no-op
2353
+ } else {
2354
+ let hasKnownRemoteReplicator = false;
2355
+ if (!waitEnabled) {
2356
+ try {
2357
+ const replicators = await this._log.getReplicators();
2358
+ for (const hash of replicators.keys()) {
2359
+ if (hash !== selfHash) {
2360
+ hasKnownRemoteReplicator = true;
2361
+ break;
2362
+ }
2363
+ }
2364
+ } catch {
2365
+ // Best-effort only.
2366
+ }
2367
+ }
2368
+
2369
+ if (waitEnabled || hasKnownRemoteReplicator) {
2370
+ const peerMap: Map<string, unknown> | undefined = (this.node.services
2371
+ .pubsub as any)?.peers;
2372
+ if (peerMap?.keys) {
2373
+ const extra: string[] = [];
2374
+ for (const hash of peerMap.keys()) {
2375
+ if (!hash || hash === selfHash) continue;
2376
+ extra.push(hash);
2377
+ if (extra.length >= 8) break;
2378
+ }
2379
+ if (extra.length > 0) {
2380
+ replicatorGroups = [
2381
+ ...new Set([...replicatorGroups, ...extra]),
2382
+ ];
2383
+ }
2384
+ }
2385
+ }
2386
+ }
2387
+ }
2388
+ }
2389
+
2390
+ if (replicatorGroups) {
2391
+ const responseHandler = async (
2392
+ results: {
2393
+ response: types.AbstractSearchResult;
2282
2394
  from?: PublicSignKey;
2283
2395
  }[],
2284
2396
  ) => {
@@ -2464,18 +2576,17 @@ export class DocumentIndex<
2464
2576
  options?: O,
2465
2577
  ): Promise<ValueTypeFromRequest<Resolve, T, I>[]> {
2466
2578
  // Set fetch to search size, or max value (default to max u32 (4294967295))
2467
- const coercedRequest = coerceQuery(
2468
- queryRequest,
2469
- options,
2470
- this.compatibility,
2471
- );
2472
- coercedRequest.fetch = coercedRequest.fetch ?? 0xffffffff;
2579
+ const coercedRequest = coerceQuery(
2580
+ queryRequest,
2581
+ options,
2582
+ this.compatibility,
2583
+ );
2584
+ coercedRequest.fetch = coercedRequest.fetch ?? 0xffffffff;
2473
2585
 
2474
- // So that the iterator is pre-fetching the right amount of entries
2475
- const iterator = this.iterate<Resolve>(coercedRequest, options);
2586
+ // Use an iterator so large results respect message size limits.
2587
+ const iterator = this.iterate<Resolve>(coercedRequest, options);
2476
2588
 
2477
- // So that this call will not do any remote requests
2478
- const allResults: ValueTypeFromRequest<Resolve, T, I>[] = [];
2589
+ const allResults: ValueTypeFromRequest<Resolve, T, I>[] = [];
2479
2590
 
2480
2591
  while (
2481
2592
  iterator.done() !== true &&
@@ -2876,37 +2987,43 @@ export class DocumentIndex<
2876
2987
 
2877
2988
  if (typeof options?.remote === "object") {
2878
2989
  let waitForTime: number | undefined = undefined;
2990
+ const waitPolicy =
2991
+ typeof options.remote.wait === "object"
2992
+ ? options.remote.wait
2993
+ : undefined;
2994
+ const waitBehavior: WaitBehavior = waitPolicy?.behavior ?? "keep-open";
2879
2995
  if (options.remote.wait) {
2880
- let t0 = +new Date();
2881
-
2882
2996
  waitForTime =
2883
2997
  typeof options.remote.wait === "boolean"
2884
2998
  ? DEFAULT_TIMEOUT
2885
2999
  : (options.remote.wait.timeout ?? DEFAULT_TIMEOUT);
2886
- let setDoneIfTimeout = false;
2887
- maybeSetDone = () => {
2888
- if (t0 + waitForTime! < +new Date()) {
2889
- cleanup();
2890
- done = true;
2891
- } else {
2892
- setDoneIfTimeout = true;
2893
- }
2894
- };
2895
- unsetDone = () => {
2896
- setDoneIfTimeout = false;
2897
- done = false;
2898
- };
2899
- let timeout = setTimeout(() => {
2900
- if (setDoneIfTimeout) {
2901
- cleanup();
2902
- done = true;
2903
- }
2904
- }, waitForTime);
3000
+ if (waitBehavior === "keep-open") {
3001
+ let t0 = +new Date();
3002
+ let setDoneIfTimeout = false;
3003
+ maybeSetDone = () => {
3004
+ if (t0 + waitForTime! < +new Date()) {
3005
+ cleanup();
3006
+ done = true;
3007
+ } else {
3008
+ setDoneIfTimeout = true;
3009
+ }
3010
+ };
3011
+ unsetDone = () => {
3012
+ setDoneIfTimeout = false;
3013
+ done = false;
3014
+ };
3015
+ let timeout = setTimeout(() => {
3016
+ if (setDoneIfTimeout) {
3017
+ cleanup();
3018
+ done = true;
3019
+ }
3020
+ }, waitForTime);
2905
3021
 
2906
- cleanup = () => {
2907
- this.clearResultsQueue(queryRequestCoerced);
2908
- clearTimeout(timeout);
2909
- };
3022
+ cleanup = () => {
3023
+ this.clearResultsQueue(queryRequestCoerced);
3024
+ clearTimeout(timeout);
3025
+ };
3026
+ }
2910
3027
  }
2911
3028
 
2912
3029
  if (options.remote.reach?.discover) {
@@ -2933,10 +3050,6 @@ export class DocumentIndex<
2933
3050
  options.remote.reach.eager = true; // include the results from the discovered peer even if it is not mature
2934
3051
  }
2935
3052
 
2936
- const waitPolicy =
2937
- typeof options.remote.wait === "object"
2938
- ? options.remote.wait
2939
- : undefined;
2940
3053
  if (
2941
3054
  waitPolicy?.behavior === "block" &&
2942
3055
  (waitPolicy.until ?? "any") === "any"
@@ -2977,14 +3090,14 @@ export class DocumentIndex<
2977
3090
  queryRequestCoerced.fetch = n;
2978
3091
  await this.queryCommence(
2979
3092
  queryRequestCoerced,
2980
- {
2981
- local: fetchOptions?.from != null ? false : options?.local,
2982
- remote:
2983
- options?.remote !== false && !skipRemoteDueToDiscovery
2984
- ? {
2985
- ...(typeof options?.remote === "object"
2986
- ? options.remote
2987
- : {}),
3093
+ {
3094
+ local: fetchOptions?.from != null ? false : options?.local,
3095
+ remote:
3096
+ options?.remote !== false && !skipRemoteDueToDiscovery
3097
+ ? {
3098
+ ...(typeof options?.remote === "object"
3099
+ ? options.remote
3100
+ : {}),
2988
3101
  from: fetchOptions?.from ?? initialRemoteTargets,
2989
3102
  }
2990
3103
  : false,
@@ -3533,32 +3646,27 @@ export class DocumentIndex<
3533
3646
  done = true;
3534
3647
  };
3535
3648
 
3536
- let close = async () => {
3537
- cleanupAndDone();
3538
-
3539
- // send close to remote
3540
- const closeRequest = new types.CloseIteratorRequest({
3541
- id: queryRequestCoerced.id,
3542
- });
3543
- const promises: Promise<any>[] = [];
3649
+ let close = async () => {
3650
+ cleanupAndDone();
3544
3651
 
3545
- for (const [peer, buffer] of peerBufferMap) {
3546
- if (buffer.kept === 0) {
3547
- peerBufferMap.delete(peer);
3548
- continue;
3549
- }
3550
- if (peer !== this.node.identity.publicKey.hashcode()) {
3551
- // Close remote
3552
- promises.push(
3652
+ // send close to remote (only peers that actually served results / had an active buffer)
3653
+ const closeRequest = new types.CloseIteratorRequest({
3654
+ id: queryRequestCoerced.id,
3655
+ });
3656
+ const selfHash = this.node.identity.publicKey.hashcode();
3657
+ const remotePeers = [...peerBufferMap.entries()]
3658
+ .filter(([peer, buffer]) => peer !== selfHash && buffer.kept > 0)
3659
+ .map(([peer]) => peer);
3660
+ peerBufferMap.clear();
3661
+ await Promise.allSettled(
3662
+ remotePeers.map((peer) =>
3553
3663
  this._query.send(closeRequest, {
3554
3664
  ...options,
3555
3665
  mode: new SilentDelivery({ to: [peer], redundancy: 1 }),
3556
3666
  }),
3557
- );
3558
- }
3559
- }
3560
- await Promise.all(promises);
3561
- };
3667
+ ),
3668
+ );
3669
+ };
3562
3670
  options?.signal && options.signal.addEventListener("abort", close);
3563
3671
 
3564
3672
  let doneFn = () => {
@@ -4144,13 +4252,24 @@ export class DocumentIndex<
4144
4252
  };
4145
4253
  }
4146
4254
 
4147
- if (typeof options?.remote === "object" && options?.remote.wait) {
4255
+ const remoteConfig =
4256
+ options && typeof options.remote === "object" ? options.remote : undefined;
4257
+ const remoteWaitPolicy =
4258
+ remoteConfig && typeof remoteConfig.wait === "object"
4259
+ ? remoteConfig.wait
4260
+ : undefined;
4261
+ const remoteWaitBehavior: WaitBehavior =
4262
+ remoteWaitPolicy?.behavior ?? "keep-open";
4263
+ const keepRemoteWaitOpen =
4264
+ !!remoteConfig?.wait &&
4265
+ remoteWaitBehavior === "keep-open";
4266
+
4267
+ if (keepRemoteWaitOpen) {
4148
4268
  // was used to account for missed results when a peer joins; omitted in this minimal handler
4149
4269
 
4150
4270
  updateDeferred = pDefer<void>();
4151
4271
 
4152
- const waitForTime =
4153
- typeof options.remote.wait === "object" && options.remote.wait.timeout;
4272
+ const waitForTime = remoteWaitPolicy?.timeout;
4154
4273
 
4155
4274
  const prevMaybeSetDone = maybeSetDone;
4156
4275
  maybeSetDone = () => {
@@ -4167,7 +4286,7 @@ export class DocumentIndex<
4167
4286
  fetchedFirstForRemote = new Set<string>();
4168
4287
  joinListener = this.createReplicatorJoinListener({
4169
4288
  signal: ensureController().signal,
4170
- eager: options.remote.reach?.eager,
4289
+ eager: remoteConfig?.reach?.eager,
4171
4290
  onPeer: async (pk) => {
4172
4291
  if (done) return;
4173
4292
  const hash = pk.hashcode();
@@ -4240,8 +4359,7 @@ export class DocumentIndex<
4240
4359
  }
4241
4360
  };
4242
4361
  }
4243
- const remoteWaitActive =
4244
- typeof options?.remote === "object" && !!options.remote.wait;
4362
+ const remoteWaitActive = keepRemoteWaitOpen;
4245
4363
 
4246
4364
  const waitForUpdateAndResetDeferred = async () => {
4247
4365
  if (remoteWaitActive) {