@peerbit/document 9.13.10 → 10.0.0-954957e

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